From d2799b8159cba026ad3208a8a231dfd1658acee0 Mon Sep 17 00:00:00 2001 From: Julia Wegmayr Date: Wed, 23 Oct 2024 09:51:59 +0200 Subject: [PATCH 01/18] add redirection url to entity and input --- demo/api/schema.gql | 3 +++ demo/api/src/db/migrations/Migration20241022144400.ts | 7 +++++++ packages/api/src/brevo-config/dto/brevo-config.input.ts | 7 ++++++- .../brevo-config/entities/brevo-config-entity.factory.ts | 6 ++++++ 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 demo/api/src/db/migrations/Migration20241022144400.ts diff --git a/demo/api/schema.gql b/demo/api/schema.gql index e2ae8814..c03a6a35 100644 --- a/demo/api/schema.gql +++ b/demo/api/schema.gql @@ -507,6 +507,7 @@ type BrevoConfig implements DocumentInterface { senderName: String! doubleOptInTemplateId: Int! folderId: Int! + redirectionUrl: String! createdAt: DateTime! scope: EmailCampaignContentScope! } @@ -1034,6 +1035,7 @@ input BrevoConfigInput { senderName: String! doubleOptInTemplateId: Int! folderId: Int! + redirectionUrl: String! } input BrevoConfigUpdateInput { @@ -1041,4 +1043,5 @@ input BrevoConfigUpdateInput { senderName: String doubleOptInTemplateId: Int folderId: Int + redirectionUrl: String } diff --git a/demo/api/src/db/migrations/Migration20241022144400.ts b/demo/api/src/db/migrations/Migration20241022144400.ts new file mode 100644 index 00000000..6ca540f8 --- /dev/null +++ b/demo/api/src/db/migrations/Migration20241022144400.ts @@ -0,0 +1,7 @@ +import { Migration } from "@mikro-orm/migrations"; + +export class Migration20241022144400 extends Migration { + async up(): Promise { + this.addSql('alter table "BrevoConfig" add column "redirectionUrl" text;'); + } +} diff --git a/packages/api/src/brevo-config/dto/brevo-config.input.ts b/packages/api/src/brevo-config/dto/brevo-config.input.ts index e3ed1657..5adf7fbd 100644 --- a/packages/api/src/brevo-config/dto/brevo-config.input.ts +++ b/packages/api/src/brevo-config/dto/brevo-config.input.ts @@ -1,6 +1,6 @@ import { PartialType } from "@comet/cms-api"; import { Field, InputType, Int } from "@nestjs/graphql"; -import { IsEmail, IsInt, IsNotEmpty, IsString } from "class-validator"; +import { IsEmail, IsInt, IsNotEmpty, IsString, IsUrl } from "class-validator"; @InputType() export class BrevoConfigInput { @@ -22,6 +22,11 @@ export class BrevoConfigInput { @Field(() => Int) @IsInt() folderId: number; + + @IsNotEmpty() + @Field() + @IsUrl({ require_tld: process.env.NODE_ENV === "production" }) + redirectionUrl: string; } @InputType() diff --git a/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts b/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts index 12d59036..6d5b5dec 100644 --- a/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts +++ b/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts @@ -2,6 +2,7 @@ import { DocumentInterface } from "@comet/cms-api"; import { Embedded, Entity, OptionalProps, PrimaryKey, Property } from "@mikro-orm/core"; import { Type } from "@nestjs/common"; import { Field, ID, Int, ObjectType } from "@nestjs/graphql"; +import { IsUrl } from "class-validator"; import { v4 } from "uuid"; import { EmailCampaignScopeInterface } from "../../types"; @@ -47,6 +48,11 @@ export class BrevoConfigEntityFactory { @Field(() => Int) folderId: number; + @Property({ columnType: "text" }) + @Field() + @IsUrl({ require_tld: process.env.NODE_ENV === "production" }) + redirectionUrl: string; + @Property({ columnType: "timestamp with time zone", }) From 54d8fcf28c6e30d3f9b42f07c8f780150462ba19 Mon Sep 17 00:00:00 2001 From: Julia Wegmayr Date: Wed, 18 Dec 2024 15:46:39 +0100 Subject: [PATCH 02/18] add form field for redirection url --- .../brevoConfiguration/BrevoConfigForm.gql.ts | 1 + .../brevoConfiguration/BrevoConfigForm.tsx | 25 ++++++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/admin/src/brevoConfiguration/BrevoConfigForm.gql.ts b/packages/admin/src/brevoConfiguration/BrevoConfigForm.gql.ts index 81e7523c..67489981 100644 --- a/packages/admin/src/brevoConfiguration/BrevoConfigForm.gql.ts +++ b/packages/admin/src/brevoConfiguration/BrevoConfigForm.gql.ts @@ -6,6 +6,7 @@ export const brevoConfigFormFragment = gql` senderName doubleOptInTemplateId folderId + redirectionUrl } `; diff --git a/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx b/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx index a719cacb..d3fcb713 100644 --- a/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx +++ b/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx @@ -8,6 +8,7 @@ import { Loading, MainContent, NumberField, + TextField, Toolbar, ToolbarActions, ToolbarFillSpace, @@ -50,6 +51,7 @@ type FormValues = { sender: Option; doubleOptInTemplate: Option; folderId: number; + redirectionUrl: string; }; interface FormProps { @@ -114,10 +116,13 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { label: `${doubleOptInTemplate?.id}: ${doubleOptInTemplate?.name}`, } : undefined, + // redirectionUrl: data?.brevoConfig?.redirectionUrl ?? "", folderId: data?.brevoConfig?.folderId ?? 1, }; }, [ data?.brevoConfig?.folderId, + // data?.brevoConfig?.redirectionUrl, + data?.brevoConfig?.doubleOptInTemplateId, data?.brevoConfig?.senderMail, data?.brevoConfig?.senderName, @@ -155,7 +160,7 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { const sender = sendersData?.senders?.find((s) => s.email === state.sender.value); - if (!sender || !state.doubleOptInTemplate) { + if (!sender || !state.doubleOptInTemplate || !state.redirectionUrl) { throw new Error("Not all required fields are set"); } @@ -164,6 +169,7 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { senderMail: sender?.email, doubleOptInTemplateId: Number(state.doubleOptInTemplate.value), folderId: state.folderId ?? 1, + redirectionUrl: state?.redirectionUrl ?? "", }; if (mode === "edit") { @@ -177,7 +183,10 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { } else { const { data: mutationResponse } = await client.mutate({ mutation: createBrevoConfigMutation, - variables: { scope, input: output }, + variables: { + scope, + input: output, + }, }); if (!event.navigatingBack) { const id = mutationResponse?.createBrevoConfig.id; @@ -222,7 +231,6 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { fullWidth required /> - option.label} @@ -260,6 +268,17 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { fullWidth required /> + + } + /> ); From 9389a0553ff24c747934209cad7773734cc2b4ac Mon Sep 17 00:00:00 2001 From: Julia Wegmayr Date: Wed, 23 Oct 2024 10:17:52 +0200 Subject: [PATCH 03/18] remove allowedRedirectUrl from demo config --- demo/api/src/app.module.ts | 2 -- demo/api/src/config/config.ts | 1 - demo/api/src/config/environment-variables.ts | 3 --- .../entities/brevo-config-entity.factory.ts | 1 + .../brevo-contact/brevo-contact-import.console.ts | 6 ++++-- .../validator/redirect-url.validator.ts | 12 +++++++++--- packages/api/src/config/brevo-module.config.ts | 1 - 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/demo/api/src/app.module.ts b/demo/api/src/app.module.ts index 91d86107..adbef47e 100644 --- a/demo/api/src/app.module.ts +++ b/demo/api/src/app.module.ts @@ -148,13 +148,11 @@ export class AppModule { if (scope.domain === "main") { return { apiKey: config.brevo.apiKey, - allowedRedirectUrl: config.brevo.allowedRedirectUrl, redirectUrlForImport: config.brevo.redirectUrlForImport, }; } else { return { apiKey: config.brevo.apiKey, - allowedRedirectUrl: config.brevo.allowedRedirectUrl, redirectUrlForImport: config.brevo.redirectUrlForImport, }; } diff --git a/demo/api/src/config/config.ts b/demo/api/src/config/config.ts index 3de2d653..264b4093 100644 --- a/demo/api/src/config/config.ts +++ b/demo/api/src/config/config.ts @@ -57,7 +57,6 @@ export function createConfig(processEnv: NodeJS.ProcessEnv) { }, brevo: { apiKey: envVars.BREVO_API_KEY, - allowedRedirectUrl: envVars.BREVO_ALLOWED_REDIRECT_URL, redirectUrlForImport: envVars.REDIRECT_URL_FOR_IMPORT, }, campaign: { diff --git a/demo/api/src/config/environment-variables.ts b/demo/api/src/config/environment-variables.ts index 96182425..46e55232 100644 --- a/demo/api/src/config/environment-variables.ts +++ b/demo/api/src/config/environment-variables.ts @@ -115,9 +115,6 @@ export class EnvironmentVariables { @IsString() BREVO_API_KEY: string; - @IsString() - BREVO_ALLOWED_REDIRECT_URL: string; - @IsString() REDIRECT_URL_FOR_IMPORT: string; diff --git a/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts b/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts index 6d5b5dec..94316929 100644 --- a/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts +++ b/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts @@ -14,6 +14,7 @@ export interface BrevoConfigInterface { senderMail: string; doubleOptInTemplateId: number; folderId: number; + redirectionUrl: string; createdAt: Date; updatedAt: Date; scope: EmailCampaignScopeInterface; diff --git a/packages/api/src/brevo-contact/brevo-contact-import.console.ts b/packages/api/src/brevo-contact/brevo-contact-import.console.ts index 3b3829b5..2f429db3 100644 --- a/packages/api/src/brevo-contact/brevo-contact-import.console.ts +++ b/packages/api/src/brevo-contact/brevo-contact-import.console.ts @@ -5,6 +5,7 @@ import { isUUID, validateSync } from "class-validator"; import { InvalidOptionArgumentError } from "commander"; import * as fs from "fs"; import { Command, Console } from "nestjs-console"; +import { BrevoConfigInterface } from "src/brevo-config/entities/brevo-config-entity.factory"; import { BrevoContactImportService } from "../brevo-contact/brevo-contact-import.service"; import { BrevoModuleConfig } from "../config/brevo-module.config"; @@ -29,6 +30,7 @@ export function createBrevoContactImportConsole({ Scope }: { Scope: Type, + @InjectRepository("BrevoConfig") private readonly brevoConfigRepository: EntityRepository, ) {} @Command({ @@ -99,13 +101,13 @@ export function createBrevoContactImportConsole({ Scope }: { Scope: Type): Promise { - const configForScope = this.config.brevo.resolveConfig(scope); + const configForScope = await this.brevoConfigRepository.findOneOrFail({ scope }); if (!configForScope) { throw Error("Scope does not exist"); } - if (urlToValidate?.startsWith(configForScope.allowedRedirectUrl)) { + if (urlToValidate?.startsWith(configForScope.redirectionUrl)) { return true; } diff --git a/packages/api/src/brevo-contact/validator/redirect-url.validator.ts b/packages/api/src/brevo-contact/validator/redirect-url.validator.ts index 9acf9363..939ff9c0 100644 --- a/packages/api/src/brevo-contact/validator/redirect-url.validator.ts +++ b/packages/api/src/brevo-contact/validator/redirect-url.validator.ts @@ -1,5 +1,8 @@ +import { EntityRepository } from "@mikro-orm/core"; +import { InjectRepository } from "@mikro-orm/nestjs"; import { Inject, Injectable } from "@nestjs/common"; import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface } from "class-validator"; +import { BrevoConfigInterface } from "src/brevo-config/entities/brevo-config-entity.factory"; import { EmailCampaignScopeInterface } from "src/types"; import { BrevoModuleConfig } from "../../config/brevo-module.config"; @@ -21,17 +24,20 @@ export const IsValidRedirectURL = (scope: EmailCampaignScopeInterface, validatio @ValidatorConstraint({ name: "IsValidRedirectURL", async: true }) @Injectable() export class IsValidRedirectURLConstraint implements ValidatorConstraintInterface { - constructor(@Inject(BREVO_MODULE_CONFIG) private readonly config: BrevoModuleConfig) {} + constructor( + @Inject(BREVO_MODULE_CONFIG) private readonly config: BrevoModuleConfig, + @InjectRepository("BrevoConfig") private readonly brevoConfigRepository: EntityRepository, + ) {} async validate(urlToValidate: string, args: ValidationArguments): Promise { const [scope] = args.constraints; - const configForScope = this.config.brevo.resolveConfig(scope); + const configForScope = await this.brevoConfigRepository.findOneOrFail({ scope }); if (!configForScope) { throw Error("Scope does not exist"); } - if (urlToValidate?.startsWith(configForScope.allowedRedirectUrl)) { + if (urlToValidate?.startsWith(configForScope.redirectionUrl)) { return true; } diff --git a/packages/api/src/config/brevo-module.config.ts b/packages/api/src/config/brevo-module.config.ts index d75144f3..8b1efc10 100644 --- a/packages/api/src/config/brevo-module.config.ts +++ b/packages/api/src/config/brevo-module.config.ts @@ -10,7 +10,6 @@ export interface BrevoModuleConfig { brevo: { resolveConfig: (scope: EmailCampaignScopeInterface) => { apiKey: string; - allowedRedirectUrl: string; redirectUrlForImport: string; }; BrevoContactAttributes?: Type; From 4d6c71a30c0c5096d3abe1671e29be00ed372d95 Mon Sep 17 00:00:00 2001 From: Julia Wegmayr Date: Wed, 23 Oct 2024 10:44:37 +0200 Subject: [PATCH 04/18] add changeset --- .changeset/twelve-games-run.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .changeset/twelve-games-run.md diff --git a/.changeset/twelve-games-run.md b/.changeset/twelve-games-run.md new file mode 100644 index 00000000..c0e6d30f --- /dev/null +++ b/.changeset/twelve-games-run.md @@ -0,0 +1,17 @@ +--- +"@comet/brevo-api": major +"@comet/brevo-admin": minor +--- + +Add a brevo configuration field for `redirectionUrl` +Env vars containing this information can be removed and must be removed from the brevo module configuration. + +```diff +BrevoModule.register({ + brevo: { +- allowedRedirectionUrl: config.brevo.allowedRedirectionUrl, + //... + }, + //.. +}) +``` From bfd38b95f7afc084b5c4cd4639a1d883d089311c Mon Sep 17 00:00:00 2001 From: Julia Wegmayr Date: Wed, 18 Dec 2024 15:51:44 +0100 Subject: [PATCH 05/18] correct migration --- .../api/src/mikro-orm}/migrations/Migration20241022144400.ts | 0 packages/api/src/mikro-orm/migrations/migrations.ts | 2 ++ 2 files changed, 2 insertions(+) rename {demo/api/src/db => packages/api/src/mikro-orm}/migrations/Migration20241022144400.ts (100%) diff --git a/demo/api/src/db/migrations/Migration20241022144400.ts b/packages/api/src/mikro-orm/migrations/Migration20241022144400.ts similarity index 100% rename from demo/api/src/db/migrations/Migration20241022144400.ts rename to packages/api/src/mikro-orm/migrations/Migration20241022144400.ts diff --git a/packages/api/src/mikro-orm/migrations/migrations.ts b/packages/api/src/mikro-orm/migrations/migrations.ts index 0527a326..ebb9a181 100644 --- a/packages/api/src/mikro-orm/migrations/migrations.ts +++ b/packages/api/src/mikro-orm/migrations/migrations.ts @@ -11,6 +11,7 @@ import { Migration20240819214939 } from "./Migration20240819214939"; import { Migration20240830112400 } from "./Migration20240830112400"; import { Migration20241016123307 } from "./Migration20241016123307"; import { Migration20241018110515 } from "./Migration20241018110515"; +import { Migration20241022144400 } from "./Migration20241022144400"; import { Migration20241119101706 } from "./Migration20241119101706"; export const migrationsList: MigrationObject[] = [ @@ -26,4 +27,5 @@ export const migrationsList: MigrationObject[] = [ { name: "Migration20241016123307", class: Migration20241016123307 }, { name: "Migration20241018110515", class: Migration20241018110515 }, { name: "Migration20241119101706", class: Migration20241119101706 }, + { name: "Migration20241022144400", class: Migration20241022144400 }, ]; From f67d6cfedbd3fc223efa0332917d0d818fd203d4 Mon Sep 17 00:00:00 2001 From: Julia Wegmayr Date: Thu, 19 Dec 2024 08:27:50 +0100 Subject: [PATCH 06/18] generate schema --- packages/api/schema.gql | 557 ++++++++++++++++++---------------------- 1 file changed, 254 insertions(+), 303 deletions(-) diff --git a/packages/api/schema.gql b/packages/api/schema.gql index 18493b03..95eb66d6 100644 --- a/packages/api/schema.gql +++ b/packages/api/schema.gql @@ -1,12 +1,12 @@ type User { - id: String! - name: String! - email: String! + id: String! + name: String! + email: String! } type CurrentUserPermission { - permission: String! - contentScopes: [JSONObject!]! + permission: String! + contentScopes: [JSONObject!]! } """ @@ -15,175 +15,153 @@ The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404]( scalar JSONObject @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf") type Dependency { - rootId: String! - rootGraphqlObjectType: String! - rootColumnName: String! - jsonPath: String! - visible: Boolean! - targetGraphqlObjectType: String! - targetId: String! - name: String - secondaryInformation: String + rootId: String! + rootGraphqlObjectType: String! + rootColumnName: String! + jsonPath: String! + visible: Boolean! + targetGraphqlObjectType: String! + targetId: String! + name: String + secondaryInformation: String } type ImageCropArea { - focalPoint: FocalPoint! - width: Float - height: Float - x: Float - y: Float + focalPoint: FocalPoint! + width: Float + height: Float + x: Float + y: Float } enum FocalPoint { - SMART - CENTER - NORTHWEST - NORTHEAST - SOUTHWEST - SOUTHEAST + SMART + CENTER + NORTHWEST + NORTHEAST + SOUTHWEST + SOUTHEAST } type BrevoApiEmailTemplateSender { - id: String - subject: String - email: String! + id: String + subject: String + email: String! } type BrevoApiEmailTemplate { - id: ID! - name: String! - subject: String! - isActive: Boolean! - testSent: Boolean! - replyTo: String! - toField: String! - tag: String! - htmlContent: String! - createdAt: String! - modifiedAt: String! - sender: BrevoApiEmailTemplateSender! + id: ID! + name: String! + subject: String! + isActive: Boolean! + testSent: Boolean! + replyTo: String! + toField: String! + tag: String! + htmlContent: String! + createdAt: String! + modifiedAt: String! + sender: BrevoApiEmailTemplateSender! } type BrevoApiSender { - id: ID! - name: String! - email: String! - active: Boolean! - ips: [BrevoIp!] + id: ID! + name: String! + email: String! + active: Boolean! + ips: [BrevoIp!] } type BrevoIp { - ip: String! - domain: String! - weight: Int! + ip: String! + domain: String! + weight: Int! } type CsvImportInformation { - created: Int! - updated: Int! - failed: Int! - failedColumns: [[String!]!] - errorMessage: String + created: Int! + updated: Int! + failed: Int! + failedColumns: [[String!]!] + errorMessage: String } type BrevoApiCampaignStatistics { - """ - Number of unique clicks for the campaign - """ - uniqueClicks: Int! - - """ - Number of total clicks for the campaign - """ - clickers: Int! - - """ - Number of complaints (Spam reports) for the campaign - """ - complaints: Int! - - """ - Number of delivered emails for the campaign - """ - delivered: Int! - - """ - Number of sent emails for the campaign - """ - sent: Int! - - """ - Number of softbounce for the campaign - """ - softBounces: Int! - - """ - Number of hardbounces for the campaign - """ - hardBounces: Int! - - """ - Number of unique openings for the campaign - """ - uniqueViews: Int! - - """ - Number of unique openings for the campaign - """ - trackableViews: Int! - - """ - Rate of recipients without any privacy protection option enabled in their email client, applied to all delivered emails - """ - estimatedViews: Int! - - """ - Number of unsubscription for the campaign - """ - unsubscriptions: Int! - - """ - Number of openings for the campaign - """ - viewed: Int! + """Number of unique clicks for the campaign""" + uniqueClicks: Int! + + """Number of total clicks for the campaign""" + clickers: Int! + + """Number of complaints (Spam reports) for the campaign""" + complaints: Int! + + """Number of delivered emails for the campaign""" + delivered: Int! + + """Number of sent emails for the campaign""" + sent: Int! + + """Number of softbounce for the campaign""" + softBounces: Int! + + """Number of hardbounces for the campaign""" + hardBounces: Int! + + """Number of unique openings for the campaign""" + uniqueViews: Int! + + """Number of unique openings for the campaign""" + trackableViews: Int! + + """ + Rate of recipients without any privacy protection option enabled in their email client, applied to all delivered emails + """ + estimatedViews: Int! + + """Number of unsubscription for the campaign""" + unsubscriptions: Int! + + """Number of openings for the campaign""" + viewed: Int! } type EmailCampaignContentScope { - thisScopeHasNoFields____: String + thisScopeHasNoFields____: String } type BrevoContact { - id: Int! - createdAt: String! - modifiedAt: String! - email: String! - emailBlacklisted: Boolean! - smsBlacklisted: Boolean! - listIds: [Int!]! - listUnsubscribed: [Int!]! + id: Int! + createdAt: String! + modifiedAt: String! + email: String! + emailBlacklisted: Boolean! + smsBlacklisted: Boolean! + listIds: [Int!]! + listUnsubscribed: [Int!]! } type PaginatedBrevoContacts { - nodes: [BrevoContact!]! - totalCount: Int! + nodes: [BrevoContact!]! + totalCount: Int! } type TargetGroup implements DocumentInterface { - id: ID! - updatedAt: DateTime! - createdAt: DateTime! - title: String! - isMainList: Boolean! - isTestList: Boolean - brevoId: Int! - totalSubscribers: Int! - scope: EmailCampaignContentScope! - assignedContactsTargetGroupBrevoId: Int + id: ID! + updatedAt: DateTime! + createdAt: DateTime! + title: String! + isMainList: Boolean! + isTestList: Boolean! + brevoId: Int! + totalSubscribers: Int! + scope: EmailCampaignContentScope! + assignedContactsTargetGroupBrevoId: Int } interface DocumentInterface { - id: ID! - updatedAt: DateTime! + id: ID! + updatedAt: DateTime! } """ @@ -192,270 +170,243 @@ A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date scalar DateTime type PaginatedTargetGroups { - nodes: [TargetGroup!]! - totalCount: Int! + nodes: [TargetGroup!]! + totalCount: Int! } type EmailCampaign implements DocumentInterface { - id: ID! - updatedAt: DateTime! - createdAt: DateTime! - title: String! - subject: String! - brevoId: Int - scheduledAt: DateTime - targetGroups: [TargetGroup!]! - content: EmailCampaignContentBlockData! - scope: EmailCampaignContentScope! - sendingState: SendingState! -} - -""" -EmailCampaignContent root block data -""" + id: ID! + updatedAt: DateTime! + createdAt: DateTime! + title: String! + subject: String! + brevoId: Int + scheduledAt: DateTime + targetGroups: [TargetGroup!]! + content: EmailCampaignContentBlockData! + scope: EmailCampaignContentScope! + sendingState: SendingState! +} + +"""EmailCampaignContent root block data""" scalar EmailCampaignContentBlockData @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf") enum SendingState { - DRAFT - SENT - SCHEDULED + DRAFT + SENT + SCHEDULED } type PaginatedEmailCampaigns { - nodes: [EmailCampaign!]! - totalCount: Int! + nodes: [EmailCampaign!]! + totalCount: Int! } type BrevoConfig implements DocumentInterface { - id: ID! - updatedAt: DateTime! - senderMail: String! - senderName: String! - doubleOptInTemplateId: Int! - folderId: Int! - createdAt: DateTime! - scope: EmailCampaignContentScope! + id: ID! + updatedAt: DateTime! + senderMail: String! + senderName: String! + doubleOptInTemplateId: Int! + folderId: Int! + redirectionUrl: String! + createdAt: DateTime! + scope: EmailCampaignContentScope! } input EmailCampaignContentScopeInput { - thisScopeHasNoFields____: String + thisScopeHasNoFields____: String } type Query { - brevoContact(id: Int!, scope: EmailCampaignContentScopeInput!): BrevoContact! - brevoContacts( - targetGroupId: ID - email: String - scope: EmailCampaignContentScopeInput! - offset: Int! = 0 - limit: Int! = 25 - ): PaginatedBrevoContacts! - brevoTestContacts( - targetGroupId: ID - email: String - scope: EmailCampaignContentScopeInput! - offset: Int! = 0 - limit: Int! = 25 - ): PaginatedBrevoContacts! - manuallyAssignedBrevoContacts(offset: Int! = 0, limit: Int! = 25, targetGroupId: ID!, email: String): PaginatedBrevoContacts! - targetGroup(id: ID!): TargetGroup! - targetGroups( - scope: EmailCampaignContentScopeInput! - search: String - filter: TargetGroupFilter - sort: [TargetGroupSort!] - offset: Int! = 0 - limit: Int! = 25 - ): PaginatedTargetGroups! - emailCampaign(id: ID!): EmailCampaign! - emailCampaigns( - scope: EmailCampaignContentScopeInput! - search: String - filter: EmailCampaignFilter - sort: [EmailCampaignSort!] - offset: Int! = 0 - limit: Int! = 25 - ): PaginatedEmailCampaigns! - emailCampaignStatistics(id: ID!): BrevoApiCampaignStatistics - senders(scope: EmailCampaignContentScopeInput!): [BrevoApiSender!] - doubleOptInTemplates: [BrevoApiEmailTemplate!] - brevoConfig(scope: EmailCampaignContentScopeInput!): BrevoConfig + brevoContact(id: Int!, scope: EmailCampaignContentScopeInput!): BrevoContact! + brevoContacts(targetGroupId: ID, email: String, scope: EmailCampaignContentScopeInput!, offset: Int! = 0, limit: Int! = 25): PaginatedBrevoContacts! + brevoTestContacts(targetGroupId: ID, email: String, scope: EmailCampaignContentScopeInput!, offset: Int! = 0, limit: Int! = 25): PaginatedBrevoContacts! + manuallyAssignedBrevoContacts(offset: Int! = 0, limit: Int! = 25, targetGroupId: ID!, email: String): PaginatedBrevoContacts! + targetGroup(id: ID!): TargetGroup! + targetGroups(scope: EmailCampaignContentScopeInput!, search: String, filter: TargetGroupFilter, sort: [TargetGroupSort!], offset: Int! = 0, limit: Int! = 25): PaginatedTargetGroups! + emailCampaign(id: ID!): EmailCampaign! + emailCampaigns(scope: EmailCampaignContentScopeInput!, search: String, filter: EmailCampaignFilter, sort: [EmailCampaignSort!], offset: Int! = 0, limit: Int! = 25): PaginatedEmailCampaigns! + emailCampaignStatistics(id: ID!): BrevoApiCampaignStatistics + senders(scope: EmailCampaignContentScopeInput!): [BrevoApiSender!] + doubleOptInTemplates: [BrevoApiEmailTemplate!] + brevoConfig(scope: EmailCampaignContentScopeInput!): BrevoConfig } input TargetGroupFilter { - createdAt: DateTimeFilter - updatedAt: DateTimeFilter - title: StringFilter - isTestList: BooleanFilter - and: [TargetGroupFilter!] - or: [TargetGroupFilter!] + createdAt: DateTimeFilter + updatedAt: DateTimeFilter + title: StringFilter + isTestList: BooleanFilter + and: [TargetGroupFilter!] + or: [TargetGroupFilter!] } input DateTimeFilter { - equal: DateTime - lowerThan: DateTime - greaterThan: DateTime - lowerThanEqual: DateTime - greaterThanEqual: DateTime - notEqual: DateTime + equal: DateTime + lowerThan: DateTime + greaterThan: DateTime + lowerThanEqual: DateTime + greaterThanEqual: DateTime + notEqual: DateTime } input StringFilter { - contains: String - startsWith: String - endsWith: String - equal: String - notEqual: String + contains: String + startsWith: String + endsWith: String + equal: String + notEqual: String } input BooleanFilter { - equal: Boolean + equal: Boolean } input TargetGroupSort { - field: TargetGroupSortField! - direction: SortDirection! = ASC + field: TargetGroupSortField! + direction: SortDirection! = ASC } enum TargetGroupSortField { - createdAt - updatedAt - title + createdAt + updatedAt + title } enum SortDirection { - ASC - DESC + ASC + DESC } input EmailCampaignFilter { - createdAt: DateTimeFilter - updatedAt: DateTimeFilter - title: StringFilter - subject: StringFilter - scheduledAt: DateTimeFilter - and: [EmailCampaignFilter!] - or: [EmailCampaignFilter!] + createdAt: DateTimeFilter + updatedAt: DateTimeFilter + title: StringFilter + subject: StringFilter + scheduledAt: DateTimeFilter + and: [EmailCampaignFilter!] + or: [EmailCampaignFilter!] } input EmailCampaignSort { - field: EmailCampaignSortField! - direction: SortDirection! = ASC + field: EmailCampaignSortField! + direction: SortDirection! = ASC } enum EmailCampaignSortField { - createdAt - updatedAt - title - subject - scheduledAt + createdAt + updatedAt + title + subject + scheduledAt } type Mutation { - updateBrevoContact(id: Int!, scope: EmailCampaignContentScopeInput!, input: BrevoContactUpdateInput!): BrevoContact! - createBrevoContact(scope: EmailCampaignContentScopeInput!, input: BrevoContactInput!): SubscribeResponse! - createBrevoTestContact(scope: EmailCampaignContentScopeInput!, input: BrevoTestContactInput!): SubscribeResponse! - deleteBrevoContact(id: Int!, scope: EmailCampaignContentScopeInput!): Boolean! - deleteBrevoTestContact(id: Int!, scope: EmailCampaignContentScopeInput!): Boolean! - subscribeBrevoContact(input: SubscribeInput!, scope: EmailCampaignContentScopeInput!): SubscribeResponse! - createTargetGroup(scope: EmailCampaignContentScopeInput!, input: TargetGroupInput!): TargetGroup! - addBrevoContactsToTargetGroup(id: ID!, input: AddBrevoContactsInput!): Boolean! - removeBrevoContactFromTargetGroup(id: ID!, input: RemoveBrevoContactInput!): Boolean! - updateTargetGroup(id: ID!, input: TargetGroupUpdateInput!, lastUpdatedAt: DateTime): TargetGroup! - deleteTargetGroup(id: ID!): Boolean! - createEmailCampaign(scope: EmailCampaignContentScopeInput!, input: EmailCampaignInput!): EmailCampaign! - updateEmailCampaign(id: ID!, input: EmailCampaignUpdateInput!, lastUpdatedAt: DateTime): EmailCampaign! - deleteEmailCampaign(id: ID!): Boolean! - sendEmailCampaignNow(id: ID!): Boolean! - sendEmailCampaignToTestEmails(id: ID!, data: SendTestEmailCampaignArgs!): Boolean! - startBrevoContactImport(fileId: ID!, targetGroupIds: [ID!], scope: EmailCampaignContentScopeInput!): CsvImportInformation! - createBrevoConfig(scope: EmailCampaignContentScopeInput!, input: BrevoConfigInput!): BrevoConfig! - updateBrevoConfig(id: ID!, input: BrevoConfigUpdateInput!, lastUpdatedAt: DateTime): BrevoConfig! + updateBrevoContact(id: Int!, scope: EmailCampaignContentScopeInput!, input: BrevoContactUpdateInput!): BrevoContact! + createBrevoContact(scope: EmailCampaignContentScopeInput!, input: BrevoContactInput!): SubscribeResponse! + createBrevoTestContact(scope: EmailCampaignContentScopeInput!, input: BrevoTestContactInput!): SubscribeResponse! + deleteBrevoContact(id: Int!, scope: EmailCampaignContentScopeInput!): Boolean! + deleteBrevoTestContact(id: Int!, scope: EmailCampaignContentScopeInput!): Boolean! + subscribeBrevoContact(input: SubscribeInput!, scope: EmailCampaignContentScopeInput!): SubscribeResponse! + createTargetGroup(scope: EmailCampaignContentScopeInput!, input: TargetGroupInput!): TargetGroup! + addBrevoContactsToTargetGroup(id: ID!, input: AddBrevoContactsInput!): Boolean! + removeBrevoContactFromTargetGroup(id: ID!, input: RemoveBrevoContactInput!): Boolean! + updateTargetGroup(id: ID!, input: TargetGroupUpdateInput!, lastUpdatedAt: DateTime): TargetGroup! + deleteTargetGroup(id: ID!): Boolean! + createEmailCampaign(scope: EmailCampaignContentScopeInput!, input: EmailCampaignInput!): EmailCampaign! + updateEmailCampaign(id: ID!, input: EmailCampaignUpdateInput!, lastUpdatedAt: DateTime): EmailCampaign! + deleteEmailCampaign(id: ID!): Boolean! + sendEmailCampaignNow(id: ID!): Boolean! + sendEmailCampaignToTestEmails(id: ID!, data: SendTestEmailCampaignArgs!): Boolean! + startBrevoContactImport(fileId: ID!, targetGroupIds: [ID!], scope: EmailCampaignContentScopeInput!): CsvImportInformation! + createBrevoConfig(scope: EmailCampaignContentScopeInput!, input: BrevoConfigInput!): BrevoConfig! + updateBrevoConfig(id: ID!, input: BrevoConfigUpdateInput!, lastUpdatedAt: DateTime): BrevoConfig! } input BrevoContactUpdateInput { - blocked: Boolean! + blocked: Boolean! } enum SubscribeResponse { - SUCCESSFUL - ERROR_UNKNOWN - ERROR_CONTAINED_IN_ECG_RTR_LIST + SUCCESSFUL + ERROR_UNKNOWN + ERROR_CONTAINED_IN_ECG_RTR_LIST } input BrevoContactInput { - email: String! - blocked: Boolean! - redirectionUrl: String! + email: String! + blocked: Boolean! + redirectionUrl: String! } input BrevoTestContactInput { - email: String! - blocked: Boolean! + email: String! + blocked: Boolean! } input SubscribeInput { - email: String! - redirectionUrl: String! + email: String! + redirectionUrl: String! } input TargetGroupInput { - title: String! - filters: BrevoContactFilterAttributesInput + title: String! + filters: BrevoContactFilterAttributesInput } input BrevoContactFilterAttributesInput { - thisFilterHasNoFields____: [String!] + thisFilterHasNoFields____: [String!] } input AddBrevoContactsInput { - brevoContactIds: [Int!]! + brevoContactIds: [Int!]! } input RemoveBrevoContactInput { - brevoContactId: Int! + brevoContactId: Int! } input TargetGroupUpdateInput { - title: String - filters: BrevoContactFilterAttributesInput + title: String + filters: BrevoContactFilterAttributesInput } input EmailCampaignInput { - title: String! - subject: String! - scheduledAt: DateTime - targetGroups: [ID!]! - content: EmailCampaignContentBlockInput! + title: String! + subject: String! + scheduledAt: DateTime + targetGroups: [ID!]! + content: EmailCampaignContentBlockInput! } -""" -EmailCampaignContent root block input -""" +"""EmailCampaignContent root block input""" scalar EmailCampaignContentBlockInput @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf") input EmailCampaignUpdateInput { - title: String - subject: String - scheduledAt: DateTime - targetGroups: [ID!] - content: EmailCampaignContentBlockInput + title: String + subject: String + scheduledAt: DateTime + targetGroups: [ID!] + content: EmailCampaignContentBlockInput } input SendTestEmailCampaignArgs { - emails: [String!]! + emails: [String!]! } input BrevoConfigInput { - senderMail: String! - senderName: String! - doubleOptInTemplateId: Int! - folderId: Int! + senderMail: String! + senderName: String! + doubleOptInTemplateId: Int! + folderId: Int! + redirectionUrl: String! } input BrevoConfigUpdateInput { - senderMail: String - senderName: String - doubleOptInTemplateId: Int - folderId: Int + senderMail: String + senderName: String + doubleOptInTemplateId: Int + folderId: Int + redirectionUrl: String } From d8c2b50d47e5285036aaa2c5edbf87339b8b2092 Mon Sep 17 00:00:00 2001 From: Julia Wegmayr Date: Thu, 19 Dec 2024 08:40:02 +0100 Subject: [PATCH 07/18] add redirectionUrl to initialValues --- packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx b/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx index d3fcb713..49b0c058 100644 --- a/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx +++ b/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx @@ -116,12 +116,12 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { label: `${doubleOptInTemplate?.id}: ${doubleOptInTemplate?.name}`, } : undefined, - // redirectionUrl: data?.brevoConfig?.redirectionUrl ?? "", + redirectionUrl: data?.brevoConfig?.redirectionUrl ?? "", folderId: data?.brevoConfig?.folderId ?? 1, }; }, [ data?.brevoConfig?.folderId, - // data?.brevoConfig?.redirectionUrl, + data?.brevoConfig?.redirectionUrl, data?.brevoConfig?.doubleOptInTemplateId, data?.brevoConfig?.senderMail, From e4bf94d42e7f75dd69e38b536e1167f62be77c69 Mon Sep 17 00:00:00 2001 From: Julia Wegmayr Date: Thu, 19 Dec 2024 09:44:39 +0100 Subject: [PATCH 08/18] add validation --- .../admin/src/brevoConfiguration/BrevoConfigForm.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx b/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx index 49b0c058..00c746d3 100644 --- a/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx +++ b/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx @@ -58,6 +58,14 @@ interface FormProps { scope: ContentScopeInterface; } +function validateUrl(value: string): React.ReactNode | undefined { + const urlPattern = /^(https?:\/\/)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(:\d{1,5})?(\/[^\s]*)?$/; + if (!urlPattern.test(value)) { + return ; + } + return undefined; +} + export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { const client = useApolloClient(); const formApiRef = useFormApiRef(); @@ -275,9 +283,10 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { label={ } + validate={validateUrl} /> From ea044ae7d4a941535b98d18b7fe337c1fbf7f516 Mon Sep 17 00:00:00 2001 From: Julia Wegmayr Date: Thu, 19 Dec 2024 11:38:13 +0100 Subject: [PATCH 09/18] rename redirectionUrl to allowedRedirectionUrl --- .changeset/twelve-games-run.md | 2 +- demo/api/schema.gql | 6 +++--- .../brevoConfiguration/BrevoConfigForm.gql.ts | 2 +- .../src/brevoConfiguration/BrevoConfigForm.tsx | 18 ++++++++++-------- packages/api/schema.gql | 6 +++--- .../src/brevo-config/dto/brevo-config.input.ts | 2 +- .../entities/brevo-config-entity.factory.ts | 4 ++-- .../brevo-contact-import.console.ts | 2 +- .../validator/redirect-url.validator.ts | 2 +- .../migrations/Migration20241022144400.ts | 2 +- 10 files changed, 24 insertions(+), 22 deletions(-) diff --git a/.changeset/twelve-games-run.md b/.changeset/twelve-games-run.md index c0e6d30f..846d2bbb 100644 --- a/.changeset/twelve-games-run.md +++ b/.changeset/twelve-games-run.md @@ -3,7 +3,7 @@ "@comet/brevo-admin": minor --- -Add a brevo configuration field for `redirectionUrl` +Add a brevo configuration field for `allowedRedirectionUrl` Env vars containing this information can be removed and must be removed from the brevo module configuration. ```diff diff --git a/demo/api/schema.gql b/demo/api/schema.gql index c03a6a35..81908efb 100644 --- a/demo/api/schema.gql +++ b/demo/api/schema.gql @@ -507,7 +507,7 @@ type BrevoConfig implements DocumentInterface { senderName: String! doubleOptInTemplateId: Int! folderId: Int! - redirectionUrl: String! + allowedRedirectionUrl: String! createdAt: DateTime! scope: EmailCampaignContentScope! } @@ -1035,7 +1035,7 @@ input BrevoConfigInput { senderName: String! doubleOptInTemplateId: Int! folderId: Int! - redirectionUrl: String! + allowedRedirectionUrl: String! } input BrevoConfigUpdateInput { @@ -1043,5 +1043,5 @@ input BrevoConfigUpdateInput { senderName: String doubleOptInTemplateId: Int folderId: Int - redirectionUrl: String + allowedRedirectionUrl: String } diff --git a/packages/admin/src/brevoConfiguration/BrevoConfigForm.gql.ts b/packages/admin/src/brevoConfiguration/BrevoConfigForm.gql.ts index 67489981..80b28b0e 100644 --- a/packages/admin/src/brevoConfiguration/BrevoConfigForm.gql.ts +++ b/packages/admin/src/brevoConfiguration/BrevoConfigForm.gql.ts @@ -6,7 +6,7 @@ export const brevoConfigFormFragment = gql` senderName doubleOptInTemplateId folderId - redirectionUrl + allowedRedirectionUrl } `; diff --git a/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx b/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx index 00c746d3..8bee6de5 100644 --- a/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx +++ b/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx @@ -51,7 +51,7 @@ type FormValues = { sender: Option; doubleOptInTemplate: Option; folderId: number; - redirectionUrl: string; + allowedRedirectionUrl: string; }; interface FormProps { @@ -61,7 +61,9 @@ interface FormProps { function validateUrl(value: string): React.ReactNode | undefined { const urlPattern = /^(https?:\/\/)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(:\d{1,5})?(\/[^\s]*)?$/; if (!urlPattern.test(value)) { - return ; + return ( + + ); } return undefined; } @@ -124,12 +126,12 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { label: `${doubleOptInTemplate?.id}: ${doubleOptInTemplate?.name}`, } : undefined, - redirectionUrl: data?.brevoConfig?.redirectionUrl ?? "", + allowedRedirectionUrl: data?.brevoConfig?.allowedRedirectionUrl ?? "", folderId: data?.brevoConfig?.folderId ?? 1, }; }, [ data?.brevoConfig?.folderId, - data?.brevoConfig?.redirectionUrl, + data?.brevoConfig?.allowedRedirectionUrl, data?.brevoConfig?.doubleOptInTemplateId, data?.brevoConfig?.senderMail, @@ -168,7 +170,7 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { const sender = sendersData?.senders?.find((s) => s.email === state.sender.value); - if (!sender || !state.doubleOptInTemplate || !state.redirectionUrl) { + if (!sender || !state.doubleOptInTemplate || !state.allowedRedirectionUrl) { throw new Error("Not all required fields are set"); } @@ -177,7 +179,7 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { senderMail: sender?.email, doubleOptInTemplateId: Number(state.doubleOptInTemplate.value), folderId: state.folderId ?? 1, - redirectionUrl: state?.redirectionUrl ?? "", + allowedRedirectionUrl: state?.allowedRedirectionUrl ?? "", }; if (mode === "edit") { @@ -279,10 +281,10 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { } diff --git a/packages/api/schema.gql b/packages/api/schema.gql index 95eb66d6..d4e98867 100644 --- a/packages/api/schema.gql +++ b/packages/api/schema.gql @@ -209,7 +209,7 @@ type BrevoConfig implements DocumentInterface { senderName: String! doubleOptInTemplateId: Int! folderId: Int! - redirectionUrl: String! + allowedRedirectionUrl: String! createdAt: DateTime! scope: EmailCampaignContentScope! } @@ -400,7 +400,7 @@ input BrevoConfigInput { senderName: String! doubleOptInTemplateId: Int! folderId: Int! - redirectionUrl: String! + allowedRedirectionUrl: String! } input BrevoConfigUpdateInput { @@ -408,5 +408,5 @@ input BrevoConfigUpdateInput { senderName: String doubleOptInTemplateId: Int folderId: Int - redirectionUrl: String + allowedRedirectionUrl: String } diff --git a/packages/api/src/brevo-config/dto/brevo-config.input.ts b/packages/api/src/brevo-config/dto/brevo-config.input.ts index 5adf7fbd..955dab4f 100644 --- a/packages/api/src/brevo-config/dto/brevo-config.input.ts +++ b/packages/api/src/brevo-config/dto/brevo-config.input.ts @@ -26,7 +26,7 @@ export class BrevoConfigInput { @IsNotEmpty() @Field() @IsUrl({ require_tld: process.env.NODE_ENV === "production" }) - redirectionUrl: string; + allowedRedirectionUrl: string; } @InputType() diff --git a/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts b/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts index 94316929..2615e366 100644 --- a/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts +++ b/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts @@ -14,7 +14,7 @@ export interface BrevoConfigInterface { senderMail: string; doubleOptInTemplateId: number; folderId: number; - redirectionUrl: string; + allowedRedirectionUrl: string; createdAt: Date; updatedAt: Date; scope: EmailCampaignScopeInterface; @@ -52,7 +52,7 @@ export class BrevoConfigEntityFactory { @Property({ columnType: "text" }) @Field() @IsUrl({ require_tld: process.env.NODE_ENV === "production" }) - redirectionUrl: string; + allowedRedirectionUrl: string; @Property({ columnType: "timestamp with time zone", diff --git a/packages/api/src/brevo-contact/brevo-contact-import.console.ts b/packages/api/src/brevo-contact/brevo-contact-import.console.ts index 2f429db3..74cab0db 100644 --- a/packages/api/src/brevo-contact/brevo-contact-import.console.ts +++ b/packages/api/src/brevo-contact/brevo-contact-import.console.ts @@ -107,7 +107,7 @@ export function createBrevoContactImportConsole({ Scope }: { Scope: Type { - this.addSql('alter table "BrevoConfig" add column "redirectionUrl" text;'); + this.addSql('alter table "BrevoConfig" add column "allowedRedirectionUrl" text;'); } } From dbe84757e1dd1997dffbe0dba0ca5f6ba89b2a65 Mon Sep 17 00:00:00 2001 From: Julia Wegmayr Date: Thu, 19 Dec 2024 11:48:16 +0100 Subject: [PATCH 10/18] improve label of allowedRedirectionUrl field --- .../brevoConfiguration/BrevoConfigForm.tsx | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx b/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx index 8bee6de5..e07c6507 100644 --- a/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx +++ b/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx @@ -283,10 +283,23 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { fullWidth name="allowedRedirectionUrl" label={ - + <> + + + } + sx={{ marginLeft: "5px" }} + > + + + } validate={validateUrl} /> From a67c4c20ba7ad73780b7a3e7a409c8377f215eaa Mon Sep 17 00:00:00 2001 From: Julia Wegmayr Date: Thu, 19 Dec 2024 15:43:07 +0100 Subject: [PATCH 11/18] remove validator --- .../src/brevo-config/entities/brevo-config-entity.factory.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts b/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts index 2615e366..c295388d 100644 --- a/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts +++ b/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts @@ -2,7 +2,6 @@ import { DocumentInterface } from "@comet/cms-api"; import { Embedded, Entity, OptionalProps, PrimaryKey, Property } from "@mikro-orm/core"; import { Type } from "@nestjs/common"; import { Field, ID, Int, ObjectType } from "@nestjs/graphql"; -import { IsUrl } from "class-validator"; import { v4 } from "uuid"; import { EmailCampaignScopeInterface } from "../../types"; @@ -51,7 +50,6 @@ export class BrevoConfigEntityFactory { @Property({ columnType: "text" }) @Field() - @IsUrl({ require_tld: process.env.NODE_ENV === "production" }) allowedRedirectionUrl: string; @Property({ From 78d7a55b87b82aadf56a7d727758be75a591b93b Mon Sep 17 00:00:00 2001 From: Julia Wegmayr Date: Thu, 19 Dec 2024 15:45:56 +0100 Subject: [PATCH 12/18] improve info text on form field --- packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx b/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx index e07c6507..6804e50f 100644 --- a/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx +++ b/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx @@ -292,7 +292,7 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { title={ } sx={{ marginLeft: "5px" }} From b14144467896491cc9817aa8118cf370764337ca Mon Sep 17 00:00:00 2001 From: Julia Wegmayr Date: Mon, 30 Dec 2024 11:02:58 +0100 Subject: [PATCH 13/18] add unsubscribePageId to brevo config entity and input --- packages/api/src/brevo-config/dto/brevo-config.input.ts | 7 ++++++- .../brevo-config/entities/brevo-config-entity.factory.ts | 5 +++++ .../src/mikro-orm/migrations/Migration20241024071748.ts | 7 +++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 packages/api/src/mikro-orm/migrations/Migration20241024071748.ts diff --git a/packages/api/src/brevo-config/dto/brevo-config.input.ts b/packages/api/src/brevo-config/dto/brevo-config.input.ts index 955dab4f..a064fd21 100644 --- a/packages/api/src/brevo-config/dto/brevo-config.input.ts +++ b/packages/api/src/brevo-config/dto/brevo-config.input.ts @@ -1,4 +1,4 @@ -import { PartialType } from "@comet/cms-api"; +import { IsUndefinable, PartialType } from "@comet/cms-api"; import { Field, InputType, Int } from "@nestjs/graphql"; import { IsEmail, IsInt, IsNotEmpty, IsString, IsUrl } from "class-validator"; @@ -27,6 +27,11 @@ export class BrevoConfigInput { @Field() @IsUrl({ require_tld: process.env.NODE_ENV === "production" }) allowedRedirectionUrl: string; + + @IsUndefinable() + @IsString() + @Field() + unsubscriptionPageId?: string; } @InputType() diff --git a/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts b/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts index c295388d..a9edbe45 100644 --- a/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts +++ b/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts @@ -14,6 +14,7 @@ export interface BrevoConfigInterface { doubleOptInTemplateId: number; folderId: number; allowedRedirectionUrl: string; + unsubscriptionPageId?: string; createdAt: Date; updatedAt: Date; scope: EmailCampaignScopeInterface; @@ -52,6 +53,10 @@ export class BrevoConfigEntityFactory { @Field() allowedRedirectionUrl: string; + @Property({ columnType: "text", nullable: true }) + @Field(() => String, { nullable: true }) + unsubscriptionPageId?: string; + @Property({ columnType: "timestamp with time zone", }) diff --git a/packages/api/src/mikro-orm/migrations/Migration20241024071748.ts b/packages/api/src/mikro-orm/migrations/Migration20241024071748.ts new file mode 100644 index 00000000..eb93dc22 --- /dev/null +++ b/packages/api/src/mikro-orm/migrations/Migration20241024071748.ts @@ -0,0 +1,7 @@ +import { Migration } from "@mikro-orm/migrations"; + +export class Migration20241024071748 extends Migration { + async up(): Promise { + this.addSql('alter table "BrevoConfig" add column "unsubscriptionPageId" text null;'); + } +} From 14ef8c018113d777e714bc0f34496ff78c9ab2df Mon Sep 17 00:00:00 2001 From: Julia Wegmayr Date: Thu, 24 Oct 2024 12:30:19 +0200 Subject: [PATCH 14/18] add unsubscribePageId field to form --- .../src/brevoConfiguration/BrevoConfigForm.gql.ts | 1 + .../src/brevoConfiguration/BrevoConfigForm.tsx | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/packages/admin/src/brevoConfiguration/BrevoConfigForm.gql.ts b/packages/admin/src/brevoConfiguration/BrevoConfigForm.gql.ts index 80b28b0e..73e7d5c6 100644 --- a/packages/admin/src/brevoConfiguration/BrevoConfigForm.gql.ts +++ b/packages/admin/src/brevoConfiguration/BrevoConfigForm.gql.ts @@ -7,6 +7,7 @@ export const brevoConfigFormFragment = gql` doubleOptInTemplateId folderId allowedRedirectionUrl + unsubscriptionPageId } `; diff --git a/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx b/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx index 6804e50f..75d1ea57 100644 --- a/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx +++ b/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx @@ -52,6 +52,7 @@ type FormValues = { doubleOptInTemplate: Option; folderId: number; allowedRedirectionUrl: string; + unsubscriptionPageId: string; }; interface FormProps { @@ -128,6 +129,7 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { : undefined, allowedRedirectionUrl: data?.brevoConfig?.allowedRedirectionUrl ?? "", folderId: data?.brevoConfig?.folderId ?? 1, + unsubscriptionPageId: data?.brevoConfig?.unsubscriptionPageId ?? "", }; }, [ data?.brevoConfig?.folderId, @@ -137,6 +139,7 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { data?.brevoConfig?.senderMail, data?.brevoConfig?.senderName, doubleOptInTemplatesData?.doubleOptInTemplates, + data?.brevoConfig?.unsubscriptionPageId, sendersData?.senders, ]); @@ -180,6 +183,7 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { doubleOptInTemplateId: Number(state.doubleOptInTemplate.value), folderId: state.folderId ?? 1, allowedRedirectionUrl: state?.allowedRedirectionUrl ?? "", + unsubscriptionPageId: state.unsubscriptionPageId, }; if (mode === "edit") { @@ -303,6 +307,16 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { } validate={validateUrl} /> + + } + /> ); From 428f8eeb2ec8788f5964a4d1009dfbd46129d945 Mon Sep 17 00:00:00 2001 From: Julia Wegmayr Date: Mon, 30 Dec 2024 11:20:32 +0100 Subject: [PATCH 15/18] generate schema --- demo/api/schema.gql | 3 +++ packages/api/schema.gql | 3 +++ 2 files changed, 6 insertions(+) diff --git a/demo/api/schema.gql b/demo/api/schema.gql index 81908efb..d89aaf0c 100644 --- a/demo/api/schema.gql +++ b/demo/api/schema.gql @@ -508,6 +508,7 @@ type BrevoConfig implements DocumentInterface { doubleOptInTemplateId: Int! folderId: Int! allowedRedirectionUrl: String! + unsubscriptionPageId: String createdAt: DateTime! scope: EmailCampaignContentScope! } @@ -1036,6 +1037,7 @@ input BrevoConfigInput { doubleOptInTemplateId: Int! folderId: Int! allowedRedirectionUrl: String! + unsubscriptionPageId: String! } input BrevoConfigUpdateInput { @@ -1044,4 +1046,5 @@ input BrevoConfigUpdateInput { doubleOptInTemplateId: Int folderId: Int allowedRedirectionUrl: String + unsubscriptionPageId: String } diff --git a/packages/api/schema.gql b/packages/api/schema.gql index d4e98867..d72a36be 100644 --- a/packages/api/schema.gql +++ b/packages/api/schema.gql @@ -210,6 +210,7 @@ type BrevoConfig implements DocumentInterface { doubleOptInTemplateId: Int! folderId: Int! allowedRedirectionUrl: String! + unsubscriptionPageId: String createdAt: DateTime! scope: EmailCampaignContentScope! } @@ -401,6 +402,7 @@ input BrevoConfigInput { doubleOptInTemplateId: Int! folderId: Int! allowedRedirectionUrl: String! + unsubscriptionPageId: String! } input BrevoConfigUpdateInput { @@ -409,4 +411,5 @@ input BrevoConfigUpdateInput { doubleOptInTemplateId: Int folderId: Int allowedRedirectionUrl: String + unsubscriptionPageId: String } From aed17646ec63056dc55a02649a3a7c8793f6c1c8 Mon Sep 17 00:00:00 2001 From: Julia Wegmayr Date: Thu, 24 Oct 2024 12:33:13 +0200 Subject: [PATCH 16/18] add unsubscribePageID to email campaign --- packages/api/schema.gql | 16 ++++++++++++++++ .../src/brevo-api/brevo-api-campaigns.service.ts | 3 +++ .../email-campaign/email-campaigns.service.ts | 1 + .../entities/email-campaign-entity.factory.ts | 5 +++++ .../migrations/Migration20241024075236.ts | 7 +++++++ .../api/src/mikro-orm/migrations/migrations.ts | 4 ++++ 6 files changed, 36 insertions(+) create mode 100644 packages/api/src/mikro-orm/migrations/Migration20241024075236.ts diff --git a/packages/api/schema.gql b/packages/api/schema.gql index d72a36be..037672de 100644 --- a/packages/api/schema.gql +++ b/packages/api/schema.gql @@ -180,6 +180,7 @@ type EmailCampaign implements DocumentInterface { createdAt: DateTime! title: String! subject: String! + unsubscriptionPageId: String brevoId: Int scheduledAt: DateTime targetGroups: [TargetGroup!]! @@ -207,9 +208,14 @@ type BrevoConfig implements DocumentInterface { updatedAt: DateTime! senderMail: String! senderName: String! +<<<<<<< HEAD doubleOptInTemplateId: Int! folderId: Int! allowedRedirectionUrl: String! +======= + doiTemplateId: Int + redirectionUrl: String! +>>>>>>> af08396 (add unsubscribePageID to email campaign) unsubscriptionPageId: String createdAt: DateTime! scope: EmailCampaignContentScope! @@ -399,17 +405,27 @@ input SendTestEmailCampaignArgs { input BrevoConfigInput { senderMail: String! senderName: String! +<<<<<<< HEAD doubleOptInTemplateId: Int! folderId: Int! allowedRedirectionUrl: String! +======= + doiTemplateId: Int + redirectionUrl: String! +>>>>>>> af08396 (add unsubscribePageID to email campaign) unsubscriptionPageId: String! } input BrevoConfigUpdateInput { senderMail: String senderName: String +<<<<<<< HEAD doubleOptInTemplateId: Int folderId: Int allowedRedirectionUrl: String +======= + doiTemplateId: Int + redirectionUrl: String +>>>>>>> af08396 (add unsubscribePageID to email campaign) unsubscriptionPageId: String } diff --git a/packages/api/src/brevo-api/brevo-api-campaigns.service.ts b/packages/api/src/brevo-api/brevo-api-campaigns.service.ts index f1d8dab5..062f65ff 100644 --- a/packages/api/src/brevo-api/brevo-api-campaigns.service.ts +++ b/packages/api/src/brevo-api/brevo-api-campaigns.service.ts @@ -59,11 +59,13 @@ export class BrevoApiCampaignsService { htmlContent, sender, scheduledAt, + unsubscriptionPageId, }: { campaign: EmailCampaignInterface; htmlContent: string; sender: { name: string; mail: string }; scheduledAt?: Date; + unsubscriptionPageId?: string; }): Promise { try { const targetGroups = await campaign.targetGroups.loadItems(); @@ -75,6 +77,7 @@ export class BrevoApiCampaignsService { recipients: { listIds: targetGroups.map((targetGroup) => targetGroup.brevoId) }, htmlContent, scheduledAt: scheduledAt?.toISOString(), + unsubscriptionPageId, }; const data = await this.getCampaignsApi(campaign.scope).createEmailCampaign(emailCampaign); diff --git a/packages/api/src/email-campaign/email-campaigns.service.ts b/packages/api/src/email-campaign/email-campaigns.service.ts index d1d870ed..e6246f51 100644 --- a/packages/api/src/email-campaign/email-campaigns.service.ts +++ b/packages/api/src/email-campaign/email-campaigns.service.ts @@ -72,6 +72,7 @@ export class EmailCampaignsService { htmlContent, sender: { name: brevoConfig.senderName, mail: brevoConfig.senderMail }, scheduledAt, + unsubscriptionPageId: brevoConfig.unsubscriptionPageId, }); wrap(campaign).assign({ brevoId }); diff --git a/packages/api/src/email-campaign/entities/email-campaign-entity.factory.ts b/packages/api/src/email-campaign/entities/email-campaign-entity.factory.ts index 4dde972c..869b4ecf 100644 --- a/packages/api/src/email-campaign/entities/email-campaign-entity.factory.ts +++ b/packages/api/src/email-campaign/entities/email-campaign-entity.factory.ts @@ -22,6 +22,7 @@ export interface EmailCampaignInterface { scope: EmailCampaignScopeInterface; sendingState: SendingState; targetGroups: Collection; + unsubscriptionPageId?: string; } export function createEmailCampaignEntity({ @@ -76,6 +77,10 @@ export function createEmailCampaignEntity({ @Field(() => Date, { nullable: true }) scheduledAt?: Date; + @Property({ columnType: "text", nullable: true }) + @Field(() => String, { nullable: true }) + unsubscriptionPageId: string; + @ManyToMany(() => TargetGroup, (targetGroup) => targetGroup.campaigns, { owner: true }) @Field(() => [TargetGroup]) targetGroups = new Collection(this); diff --git a/packages/api/src/mikro-orm/migrations/Migration20241024075236.ts b/packages/api/src/mikro-orm/migrations/Migration20241024075236.ts new file mode 100644 index 00000000..8421240a --- /dev/null +++ b/packages/api/src/mikro-orm/migrations/Migration20241024075236.ts @@ -0,0 +1,7 @@ +import { Migration } from "@mikro-orm/migrations"; + +export class Migration20241024075236 extends Migration { + async up(): Promise { + this.addSql('alter table "EmailCampaign" add column "unsubscriptionPageId" text null;'); + } +} diff --git a/packages/api/src/mikro-orm/migrations/migrations.ts b/packages/api/src/mikro-orm/migrations/migrations.ts index ebb9a181..02bec41c 100644 --- a/packages/api/src/mikro-orm/migrations/migrations.ts +++ b/packages/api/src/mikro-orm/migrations/migrations.ts @@ -12,6 +12,8 @@ import { Migration20240830112400 } from "./Migration20240830112400"; import { Migration20241016123307 } from "./Migration20241016123307"; import { Migration20241018110515 } from "./Migration20241018110515"; import { Migration20241022144400 } from "./Migration20241022144400"; +import { Migration20241024071748 } from "./Migration20241024071748"; +import { Migration20241024075236 } from "./Migration20241024075236"; import { Migration20241119101706 } from "./Migration20241119101706"; export const migrationsList: MigrationObject[] = [ @@ -28,4 +30,6 @@ export const migrationsList: MigrationObject[] = [ { name: "Migration20241018110515", class: Migration20241018110515 }, { name: "Migration20241119101706", class: Migration20241119101706 }, { name: "Migration20241022144400", class: Migration20241022144400 }, + { name: "Migration20241024071748", class: Migration20241024071748 }, + { name: "Migration20241024075236", class: Migration20241024075236 }, ]; From f86f82d3b95eb71e7c75eb8cb8c19dff8c11a7b1 Mon Sep 17 00:00:00 2001 From: Julia Wegmayr Date: Tue, 5 Nov 2024 09:35:46 +0100 Subject: [PATCH 17/18] add validation for UnSubscriptionPageId --- .../src/brevoConfiguration/BrevoConfigForm.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx b/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx index 75d1ea57..0a2be628 100644 --- a/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx +++ b/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx @@ -219,6 +219,19 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { return ; } + const validateUnsubscriptionPageId = (value: string) => { + const validUnsubscriptionPageId = /^[a-zA-Z0-9]{24}$/; + if (value && !validUnsubscriptionPageId.test(value)) { + return ( + + ); + } + return undefined; + }; + return ( apiRef={formApiRef} onSubmit={handleSubmit} mode={mode} initialValues={initialValues}> {({ values }) => { @@ -316,6 +329,7 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { defaultMessage="Unsubscription Page ID" /> } + validate={validateUnsubscriptionPageId} /> From b7bb3d9bb3ea90c15b41ef1e60dd49ce4af6352d Mon Sep 17 00:00:00 2001 From: Julia Wegmayr Date: Tue, 5 Nov 2024 09:52:18 +0100 Subject: [PATCH 18/18] set unsubscription page id field to required --- demo/api/schema.gql | 3 +- .../brevoConfiguration/BrevoConfigForm.tsx | 5 +- packages/api/schema.gql | 583 ++++++++++-------- .../brevo-config/dto/brevo-config.input.ts | 5 +- .../entities/brevo-config-entity.factory.ts | 8 +- 5 files changed, 321 insertions(+), 283 deletions(-) diff --git a/demo/api/schema.gql b/demo/api/schema.gql index d89aaf0c..79607f5b 100644 --- a/demo/api/schema.gql +++ b/demo/api/schema.gql @@ -421,6 +421,7 @@ type EmailCampaign implements DocumentInterface { subject: String! brevoId: Int scheduledAt: DateTime + unsubscriptionPageId: String targetGroups: [TargetGroup!]! content: EmailCampaignContentBlockData! scope: EmailCampaignContentScope! @@ -508,7 +509,7 @@ type BrevoConfig implements DocumentInterface { doubleOptInTemplateId: Int! folderId: Int! allowedRedirectionUrl: String! - unsubscriptionPageId: String + unsubscriptionPageId: String! createdAt: DateTime! scope: EmailCampaignContentScope! } diff --git a/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx b/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx index 0a2be628..52383908 100644 --- a/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx +++ b/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx @@ -173,7 +173,7 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { const sender = sendersData?.senders?.find((s) => s.email === state.sender.value); - if (!sender || !state.doubleOptInTemplate || !state.allowedRedirectionUrl) { + if (!sender || !state.doubleOptInTemplate || !state.allowedRedirectionUrl || !state.unsubscriptionPageId) { throw new Error("Not all required fields are set"); } @@ -221,7 +221,7 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement { const validateUnsubscriptionPageId = (value: string) => { const validUnsubscriptionPageId = /^[a-zA-Z0-9]{24}$/; - if (value && !validUnsubscriptionPageId.test(value)) { + if (!validUnsubscriptionPageId.test(value)) { return ( >>>>>> af08396 (add unsubscribePageID to email campaign) - unsubscriptionPageId: String - createdAt: DateTime! - scope: EmailCampaignContentScope! + id: ID! + updatedAt: DateTime! + senderMail: String! + senderName: String! + doubleOptInTemplateId: Int! + folderId: Int! + allowedRedirectionUrl: String! + unsubscriptionPageId: String + createdAt: DateTime! + scope: EmailCampaignContentScope! } input EmailCampaignContentScopeInput { - thisScopeHasNoFields____: String + thisScopeHasNoFields____: String } type Query { - brevoContact(id: Int!, scope: EmailCampaignContentScopeInput!): BrevoContact! - brevoContacts(targetGroupId: ID, email: String, scope: EmailCampaignContentScopeInput!, offset: Int! = 0, limit: Int! = 25): PaginatedBrevoContacts! - brevoTestContacts(targetGroupId: ID, email: String, scope: EmailCampaignContentScopeInput!, offset: Int! = 0, limit: Int! = 25): PaginatedBrevoContacts! - manuallyAssignedBrevoContacts(offset: Int! = 0, limit: Int! = 25, targetGroupId: ID!, email: String): PaginatedBrevoContacts! - targetGroup(id: ID!): TargetGroup! - targetGroups(scope: EmailCampaignContentScopeInput!, search: String, filter: TargetGroupFilter, sort: [TargetGroupSort!], offset: Int! = 0, limit: Int! = 25): PaginatedTargetGroups! - emailCampaign(id: ID!): EmailCampaign! - emailCampaigns(scope: EmailCampaignContentScopeInput!, search: String, filter: EmailCampaignFilter, sort: [EmailCampaignSort!], offset: Int! = 0, limit: Int! = 25): PaginatedEmailCampaigns! - emailCampaignStatistics(id: ID!): BrevoApiCampaignStatistics - senders(scope: EmailCampaignContentScopeInput!): [BrevoApiSender!] - doubleOptInTemplates: [BrevoApiEmailTemplate!] - brevoConfig(scope: EmailCampaignContentScopeInput!): BrevoConfig + brevoContact(id: Int!, scope: EmailCampaignContentScopeInput!): BrevoContact! + brevoContacts( + targetGroupId: ID + email: String + scope: EmailCampaignContentScopeInput! + offset: Int! = 0 + limit: Int! = 25 + ): PaginatedBrevoContacts! + brevoTestContacts( + targetGroupId: ID + email: String + scope: EmailCampaignContentScopeInput! + offset: Int! = 0 + limit: Int! = 25 + ): PaginatedBrevoContacts! + manuallyAssignedBrevoContacts(offset: Int! = 0, limit: Int! = 25, targetGroupId: ID!, email: String): PaginatedBrevoContacts! + targetGroup(id: ID!): TargetGroup! + targetGroups( + scope: EmailCampaignContentScopeInput! + search: String + filter: TargetGroupFilter + sort: [TargetGroupSort!] + offset: Int! = 0 + limit: Int! = 25 + ): PaginatedTargetGroups! + emailCampaign(id: ID!): EmailCampaign! + emailCampaigns( + scope: EmailCampaignContentScopeInput! + search: String + filter: EmailCampaignFilter + sort: [EmailCampaignSort!] + offset: Int! = 0 + limit: Int! = 25 + ): PaginatedEmailCampaigns! + emailCampaignStatistics(id: ID!): BrevoApiCampaignStatistics + senders(scope: EmailCampaignContentScopeInput!): [BrevoApiSender!] + doubleOptInTemplates: [BrevoApiEmailTemplate!] + brevoConfig(scope: EmailCampaignContentScopeInput!): BrevoConfig } input TargetGroupFilter { - createdAt: DateTimeFilter - updatedAt: DateTimeFilter - title: StringFilter - isTestList: BooleanFilter - and: [TargetGroupFilter!] - or: [TargetGroupFilter!] + createdAt: DateTimeFilter + updatedAt: DateTimeFilter + title: StringFilter + isTestList: BooleanFilter + and: [TargetGroupFilter!] + or: [TargetGroupFilter!] } input DateTimeFilter { - equal: DateTime - lowerThan: DateTime - greaterThan: DateTime - lowerThanEqual: DateTime - greaterThanEqual: DateTime - notEqual: DateTime + equal: DateTime + lowerThan: DateTime + greaterThan: DateTime + lowerThanEqual: DateTime + greaterThanEqual: DateTime + notEqual: DateTime } input StringFilter { - contains: String - startsWith: String - endsWith: String - equal: String - notEqual: String + contains: String + startsWith: String + endsWith: String + equal: String + notEqual: String } input BooleanFilter { - equal: Boolean + equal: Boolean } input TargetGroupSort { - field: TargetGroupSortField! - direction: SortDirection! = ASC + field: TargetGroupSortField! + direction: SortDirection! = ASC } enum TargetGroupSortField { - createdAt - updatedAt - title + createdAt + updatedAt + title } enum SortDirection { - ASC - DESC + ASC + DESC } input EmailCampaignFilter { - createdAt: DateTimeFilter - updatedAt: DateTimeFilter - title: StringFilter - subject: StringFilter - scheduledAt: DateTimeFilter - and: [EmailCampaignFilter!] - or: [EmailCampaignFilter!] + createdAt: DateTimeFilter + updatedAt: DateTimeFilter + title: StringFilter + subject: StringFilter + scheduledAt: DateTimeFilter + and: [EmailCampaignFilter!] + or: [EmailCampaignFilter!] } input EmailCampaignSort { - field: EmailCampaignSortField! - direction: SortDirection! = ASC + field: EmailCampaignSortField! + direction: SortDirection! = ASC } enum EmailCampaignSortField { - createdAt - updatedAt - title - subject - scheduledAt + createdAt + updatedAt + title + subject + scheduledAt } type Mutation { - updateBrevoContact(id: Int!, scope: EmailCampaignContentScopeInput!, input: BrevoContactUpdateInput!): BrevoContact! - createBrevoContact(scope: EmailCampaignContentScopeInput!, input: BrevoContactInput!): SubscribeResponse! - createBrevoTestContact(scope: EmailCampaignContentScopeInput!, input: BrevoTestContactInput!): SubscribeResponse! - deleteBrevoContact(id: Int!, scope: EmailCampaignContentScopeInput!): Boolean! - deleteBrevoTestContact(id: Int!, scope: EmailCampaignContentScopeInput!): Boolean! - subscribeBrevoContact(input: SubscribeInput!, scope: EmailCampaignContentScopeInput!): SubscribeResponse! - createTargetGroup(scope: EmailCampaignContentScopeInput!, input: TargetGroupInput!): TargetGroup! - addBrevoContactsToTargetGroup(id: ID!, input: AddBrevoContactsInput!): Boolean! - removeBrevoContactFromTargetGroup(id: ID!, input: RemoveBrevoContactInput!): Boolean! - updateTargetGroup(id: ID!, input: TargetGroupUpdateInput!, lastUpdatedAt: DateTime): TargetGroup! - deleteTargetGroup(id: ID!): Boolean! - createEmailCampaign(scope: EmailCampaignContentScopeInput!, input: EmailCampaignInput!): EmailCampaign! - updateEmailCampaign(id: ID!, input: EmailCampaignUpdateInput!, lastUpdatedAt: DateTime): EmailCampaign! - deleteEmailCampaign(id: ID!): Boolean! - sendEmailCampaignNow(id: ID!): Boolean! - sendEmailCampaignToTestEmails(id: ID!, data: SendTestEmailCampaignArgs!): Boolean! - startBrevoContactImport(fileId: ID!, targetGroupIds: [ID!], scope: EmailCampaignContentScopeInput!): CsvImportInformation! - createBrevoConfig(scope: EmailCampaignContentScopeInput!, input: BrevoConfigInput!): BrevoConfig! - updateBrevoConfig(id: ID!, input: BrevoConfigUpdateInput!, lastUpdatedAt: DateTime): BrevoConfig! + updateBrevoContact(id: Int!, scope: EmailCampaignContentScopeInput!, input: BrevoContactUpdateInput!): BrevoContact! + createBrevoContact(scope: EmailCampaignContentScopeInput!, input: BrevoContactInput!): SubscribeResponse! + createBrevoTestContact(scope: EmailCampaignContentScopeInput!, input: BrevoTestContactInput!): SubscribeResponse! + deleteBrevoContact(id: Int!, scope: EmailCampaignContentScopeInput!): Boolean! + deleteBrevoTestContact(id: Int!, scope: EmailCampaignContentScopeInput!): Boolean! + subscribeBrevoContact(input: SubscribeInput!, scope: EmailCampaignContentScopeInput!): SubscribeResponse! + createTargetGroup(scope: EmailCampaignContentScopeInput!, input: TargetGroupInput!): TargetGroup! + addBrevoContactsToTargetGroup(id: ID!, input: AddBrevoContactsInput!): Boolean! + removeBrevoContactFromTargetGroup(id: ID!, input: RemoveBrevoContactInput!): Boolean! + updateTargetGroup(id: ID!, input: TargetGroupUpdateInput!, lastUpdatedAt: DateTime): TargetGroup! + deleteTargetGroup(id: ID!): Boolean! + createEmailCampaign(scope: EmailCampaignContentScopeInput!, input: EmailCampaignInput!): EmailCampaign! + updateEmailCampaign(id: ID!, input: EmailCampaignUpdateInput!, lastUpdatedAt: DateTime): EmailCampaign! + deleteEmailCampaign(id: ID!): Boolean! + sendEmailCampaignNow(id: ID!): Boolean! + sendEmailCampaignToTestEmails(id: ID!, data: SendTestEmailCampaignArgs!): Boolean! + startBrevoContactImport(fileId: ID!, targetGroupIds: [ID!], scope: EmailCampaignContentScopeInput!): CsvImportInformation! + createBrevoConfig(scope: EmailCampaignContentScopeInput!, input: BrevoConfigInput!): BrevoConfig! + updateBrevoConfig(id: ID!, input: BrevoConfigUpdateInput!, lastUpdatedAt: DateTime): BrevoConfig! } input BrevoContactUpdateInput { - blocked: Boolean! + blocked: Boolean! } enum SubscribeResponse { - SUCCESSFUL - ERROR_UNKNOWN - ERROR_CONTAINED_IN_ECG_RTR_LIST + SUCCESSFUL + ERROR_UNKNOWN + ERROR_CONTAINED_IN_ECG_RTR_LIST } input BrevoContactInput { - email: String! - blocked: Boolean! - redirectionUrl: String! + email: String! + blocked: Boolean! + redirectionUrl: String! } input BrevoTestContactInput { - email: String! - blocked: Boolean! + email: String! + blocked: Boolean! } input SubscribeInput { - email: String! - redirectionUrl: String! + email: String! + redirectionUrl: String! } input TargetGroupInput { - title: String! - filters: BrevoContactFilterAttributesInput + title: String! + filters: BrevoContactFilterAttributesInput } input BrevoContactFilterAttributesInput { - thisFilterHasNoFields____: [String!] + thisFilterHasNoFields____: [String!] } input AddBrevoContactsInput { - brevoContactIds: [Int!]! + brevoContactIds: [Int!]! } input RemoveBrevoContactInput { - brevoContactId: Int! + brevoContactId: Int! } input TargetGroupUpdateInput { - title: String - filters: BrevoContactFilterAttributesInput + title: String + filters: BrevoContactFilterAttributesInput } input EmailCampaignInput { - title: String! - subject: String! - scheduledAt: DateTime - targetGroups: [ID!]! - content: EmailCampaignContentBlockInput! + title: String! + subject: String! + scheduledAt: DateTime + targetGroups: [ID!]! + content: EmailCampaignContentBlockInput! } -"""EmailCampaignContent root block input""" +""" +EmailCampaignContent root block input +""" scalar EmailCampaignContentBlockInput @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf") input EmailCampaignUpdateInput { - title: String - subject: String - scheduledAt: DateTime - targetGroups: [ID!] - content: EmailCampaignContentBlockInput + title: String + subject: String + scheduledAt: DateTime + targetGroups: [ID!] + content: EmailCampaignContentBlockInput } input SendTestEmailCampaignArgs { - emails: [String!]! + emails: [String!]! } input BrevoConfigInput { - senderMail: String! - senderName: String! -<<<<<<< HEAD - doubleOptInTemplateId: Int! - folderId: Int! - allowedRedirectionUrl: String! -======= - doiTemplateId: Int - redirectionUrl: String! ->>>>>>> af08396 (add unsubscribePageID to email campaign) - unsubscriptionPageId: String! + senderMail: String! + senderName: String! + doubleOptInTemplateId: Int! + folderId: Int! + allowedRedirectionUrl: String! + unsubscriptionPageId: String! } input BrevoConfigUpdateInput { - senderMail: String - senderName: String -<<<<<<< HEAD - doubleOptInTemplateId: Int - folderId: Int - allowedRedirectionUrl: String -======= - doiTemplateId: Int - redirectionUrl: String ->>>>>>> af08396 (add unsubscribePageID to email campaign) - unsubscriptionPageId: String + senderMail: String + senderName: String + doubleOptInTemplateId: Int + folderId: Int + allowedRedirectionUrl: String + unsubscriptionPageId: String } diff --git a/packages/api/src/brevo-config/dto/brevo-config.input.ts b/packages/api/src/brevo-config/dto/brevo-config.input.ts index a064fd21..da2880ec 100644 --- a/packages/api/src/brevo-config/dto/brevo-config.input.ts +++ b/packages/api/src/brevo-config/dto/brevo-config.input.ts @@ -1,4 +1,4 @@ -import { IsUndefinable, PartialType } from "@comet/cms-api"; +import { PartialType } from "@comet/cms-api"; import { Field, InputType, Int } from "@nestjs/graphql"; import { IsEmail, IsInt, IsNotEmpty, IsString, IsUrl } from "class-validator"; @@ -28,10 +28,9 @@ export class BrevoConfigInput { @IsUrl({ require_tld: process.env.NODE_ENV === "production" }) allowedRedirectionUrl: string; - @IsUndefinable() @IsString() @Field() - unsubscriptionPageId?: string; + unsubscriptionPageId: string; } @InputType() diff --git a/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts b/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts index a9edbe45..2c606bc2 100644 --- a/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts +++ b/packages/api/src/brevo-config/entities/brevo-config-entity.factory.ts @@ -14,7 +14,7 @@ export interface BrevoConfigInterface { doubleOptInTemplateId: number; folderId: number; allowedRedirectionUrl: string; - unsubscriptionPageId?: string; + unsubscriptionPageId: string; createdAt: Date; updatedAt: Date; scope: EmailCampaignScopeInterface; @@ -53,9 +53,9 @@ export class BrevoConfigEntityFactory { @Field() allowedRedirectionUrl: string; - @Property({ columnType: "text", nullable: true }) - @Field(() => String, { nullable: true }) - unsubscriptionPageId?: string; + @Property({ columnType: "text" }) + @Field(() => String) + unsubscriptionPageId: string; @Property({ columnType: "timestamp with time zone",