diff --git a/.changeset/twelve-games-run.md b/.changeset/twelve-games-run.md
new file mode 100644
index 00000000..846d2bbb
--- /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 `allowedRedirectionUrl`
+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,
+ //...
+ },
+ //..
+})
+```
diff --git a/demo/api/schema.gql b/demo/api/schema.gql
index e2ae8814..81908efb 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!
+ allowedRedirectionUrl: String!
createdAt: DateTime!
scope: EmailCampaignContentScope!
}
@@ -1034,6 +1035,7 @@ input BrevoConfigInput {
senderName: String!
doubleOptInTemplateId: Int!
folderId: Int!
+ allowedRedirectionUrl: String!
}
input BrevoConfigUpdateInput {
@@ -1041,4 +1043,5 @@ input BrevoConfigUpdateInput {
senderName: String
doubleOptInTemplateId: Int
folderId: Int
+ allowedRedirectionUrl: String
}
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/admin/src/brevoConfiguration/BrevoConfigForm.gql.ts b/packages/admin/src/brevoConfiguration/BrevoConfigForm.gql.ts
index 81e7523c..80b28b0e 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
+ allowedRedirectionUrl
}
`;
diff --git a/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx b/packages/admin/src/brevoConfiguration/BrevoConfigForm.tsx
index a719cacb..6804e50f 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,12 +51,23 @@ type FormValues = {
sender: Option;
doubleOptInTemplate: Option;
folderId: number;
+ allowedRedirectionUrl: string;
};
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();
@@ -114,10 +126,13 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement {
label: `${doubleOptInTemplate?.id}: ${doubleOptInTemplate?.name}`,
}
: undefined,
+ allowedRedirectionUrl: data?.brevoConfig?.allowedRedirectionUrl ?? "",
folderId: data?.brevoConfig?.folderId ?? 1,
};
}, [
data?.brevoConfig?.folderId,
+ data?.brevoConfig?.allowedRedirectionUrl,
+
data?.brevoConfig?.doubleOptInTemplateId,
data?.brevoConfig?.senderMail,
data?.brevoConfig?.senderName,
@@ -155,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) {
+ if (!sender || !state.doubleOptInTemplate || !state.allowedRedirectionUrl) {
throw new Error("Not all required fields are set");
}
@@ -164,6 +179,7 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement {
senderMail: sender?.email,
doubleOptInTemplateId: Number(state.doubleOptInTemplate.value),
folderId: state.folderId ?? 1,
+ allowedRedirectionUrl: state?.allowedRedirectionUrl ?? "",
};
if (mode === "edit") {
@@ -177,7 +193,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 +241,6 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement {
fullWidth
required
/>
-
option.label}
@@ -260,6 +278,31 @@ export function BrevoConfigForm({ scope }: FormProps): React.ReactElement {
fullWidth
required
/>
+
+
+
+ }
+ sx={{ marginLeft: "5px" }}
+ >
+
+
+ >
+ }
+ validate={validateUrl}
+ />
>
);
diff --git a/packages/api/schema.gql b/packages/api/schema.gql
index 18493b03..d4e98867 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!
+ allowedRedirectionUrl: 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!
+ allowedRedirectionUrl: String!
}
input BrevoConfigUpdateInput {
- senderMail: String
- senderName: String
- doubleOptInTemplateId: Int
- folderId: Int
+ senderMail: String
+ senderName: String
+ doubleOptInTemplateId: Int
+ folderId: Int
+ 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 e3ed1657..955dab4f 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" })
+ 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 12d59036..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
@@ -13,6 +13,7 @@ export interface BrevoConfigInterface {
senderMail: string;
doubleOptInTemplateId: number;
folderId: number;
+ allowedRedirectionUrl: string;
createdAt: Date;
updatedAt: Date;
scope: EmailCampaignScopeInterface;
@@ -47,6 +48,10 @@ export class BrevoConfigEntityFactory {
@Field(() => Int)
folderId: number;
+ @Property({ columnType: "text" })
+ @Field()
+ 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 3b3829b5..74cab0db 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.allowedRedirectionUrl)) {
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..1f20cb92 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.allowedRedirectionUrl)) {
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;
diff --git a/packages/api/src/mikro-orm/migrations/Migration20241022144400.ts b/packages/api/src/mikro-orm/migrations/Migration20241022144400.ts
new file mode 100644
index 00000000..47bad202
--- /dev/null
+++ b/packages/api/src/mikro-orm/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 "allowedRedirectionUrl" text;');
+ }
+}
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 },
];