-
Notifications
You must be signed in to change notification settings - Fork 199
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: email plugins support #2611
Changes from 14 commits
c5b9088
47b0bf5
99d478f
bdad181
d002693
af7e237
3cc3767
35d4fff
8fcd3bf
b0a9820
d71cdfd
752607c
1e1355e
c57c8b9
1cf619b
b99febb
e40137e
07f2aa4
00c6a78
c9ee508
ec83773
af79dcb
15a3d6d
0627531
da21a97
5f42f5b
2ea4724
64ed794
06737df
aea77f3
e1bc5f1
7ab309a
7a98b7c
1ef0964
e726c98
71f2fe1
6928ac4
2e04b91
d5eb09b
90f066d
109217b
6a6f74e
b8085ff
933ed5f
9eaa524
cd950b7
69a4009
d03f0c9
7da3a9d
22871ec
baad4cf
59d7d2a
59e0b85
b9eac99
88091e6
f7f61d7
035fd7b
e89c31b
93ad5df
461e1bd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -59,23 +59,23 @@ export class ApiPlugin { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const urlWithoutPlaceholders = await this.replaceValuePlaceholders(this.url, context); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const _url = await this._getPluginUrl(context); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.log('API Plugin - Sending API request', { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
url: urlWithoutPlaceholders, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
url: _url, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
method: this.method, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const apiResponse = await this.makeApiRequest( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
urlWithoutPlaceholders, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_url, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.method, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
requestPayload, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
await this.composeRequestHeaders(this.headers!, context), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.log('API Plugin - Received response', { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
status: apiResponse.statusText, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
url: urlWithoutPlaceholders, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
url: _url, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (apiResponse.ok) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -115,6 +115,10 @@ export class ApiPlugin { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
protected async _getPluginUrl(_: AnyRecord) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return this.url; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
returnSuccessResponse(callbackAction: string, responseBody: AnyRecord) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return { callbackAction, responseBody }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -142,11 +146,7 @@ export class ApiPlugin { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (payload) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for (const key of Object.keys(payload)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (typeof payload[key] === 'string') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
payload[key] = await this.replaceValuePlaceholders(payload[key] as string, payload); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
payload = await this._onPreparePayload(payload); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// @TODO: Use an enum over string literals for HTTP methods | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (this.method.toUpperCase() !== 'GET') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -172,6 +172,18 @@ export class ApiPlugin { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return res; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private async _onPreparePayload(_payload: AnyRecord) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const returnObj = JSON.parse(JSON.stringify(_payload)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for (const key of Object.keys(returnObj)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (typeof returnObj[key] === 'string') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
returnObj[key] = await this.replaceAllVariables(returnObj[key] as string, returnObj); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return returnObj; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optimize The implementation is correct, but the function can be optimized by using a more functional approach. - for (const key of Object.keys(returnObj)) {
- if (typeof returnObj[key] === 'string') {
- returnObj[key] = await this.replaceAllVariables(returnObj[key] as string, returnObj);
- }
- }
+ await Promise.all(Object.keys(returnObj).map(async key => {
+ if (typeof returnObj[key] === 'string') {
+ returnObj[key] = await this.replaceAllVariables(returnObj[key] as string, returnObj);
+ }
+ })); Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
async transformData(transformers: Transformers | undefined, record: AnyRecord) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let mutatedRecord = record; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -218,14 +230,36 @@ export class ApiPlugin { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Object.entries(headers).map(async header => [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
header[0], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
await this.replaceValuePlaceholders(header[1], context), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
await this.replaceAllVariables(header[1], context), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
]), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return Object.fromEntries(headersEntries); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
async replaceValuePlaceholders(content: string, context: TContext) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
async _onReplaceVariable(variableKey: string, replacedContent: string, placeholder: string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let replacedSecrets = await this.replaceSecretsByProvider( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'customer', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
variableKey, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
replacedContent, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
placeholder, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// TODO: Remove this line when migrate to new ballerine plugins | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
replacedSecrets = await this.replaceBallerineVariable( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
variableKey, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
replacedContent, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
placeholder, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return replacedSecrets; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Simplify variable replacement in The Apply this diff to streamline the variable replacement: async _onReplaceVariable(variableKey: string, replacedContent: string, placeholder: string) {
- let replacedSecrets = await this.replaceSecretsByProvider(
- 'customer',
- variableKey,
- replacedContent,
- placeholder,
- );
-
- // TODO: Remove this line when migrate to new ballerine plugins
- replacedSecrets = await this.replaceBallerineVariable(
- variableKey,
- replacedContent,
- placeholder,
- );
+ const providers: Array<'customer' | 'ballerine'> = ['customer', 'ballerine'];
+ let replacedSecrets = replacedContent;
+
+ for (const provider of providers) {
+ replacedSecrets = await this.replaceSecretsByProvider(
+ provider,
+ variableKey,
+ replacedSecrets,
+ placeholder,
+ );
+ }
return replacedSecrets;
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
async replaceBallerineVariable(variableKey: string, content: string, placeholder: string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return await this.replaceSecretsByProvider('ballerine', variableKey, content, placeholder); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
async replaceAllVariables(content: string, context: TContext) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const placeholders = content.match(/{(.*?)}/g); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!placeholders) return content; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -235,24 +269,36 @@ export class ApiPlugin { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for (const placeholder of placeholders) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const variableKey = placeholder.replace(/{|}/g, ''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const isSystemSecret = variableKey.includes('secret.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const isSecret = variableKey.includes('secrets.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
replacedContent = await this._onReplaceVariable(variableKey, replacedContent, placeholder); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (isSystemSecret) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const secretKey = variableKey.replace('secret.', ''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const secretValue = `${this.getSystemSecret(secretKey)}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const placeholderValue = `${this.fetchObjectPlaceholderValue(context, variableKey)}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
replacedContent = replacedContent.replace(placeholder, secretValue); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else if (isSecret) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const secretKey = variableKey.replace('secrets.', ''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const secretValue = `${await this.fetchSecret(secretKey)}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
replacedContent = replacedContent.replace(placeholder, placeholderValue); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
replacedContent = replacedContent.replace(placeholder, secretValue); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const placeholderValue = `${this.fetchObjectPlaceholderValue(context, variableKey)}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return replacedContent; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
replacedContent = replacedContent.replace(placeholder, placeholderValue); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
async replaceSecretsByProvider( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
provider: 'ballerine' | 'customer', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
variableKey: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
content: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
placeholder: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const variableName = provider === 'ballerine' ? 'secret.' : 'secrets.'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let replacedContent = content; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (provider === 'ballerine' && variableKey.includes(variableName)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const secretKey = variableKey.replace(variableName, ''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const secretValue = `${this.getSystemSecret(secretKey)}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
replacedContent = content.replace(placeholder, secretValue); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else if (provider === 'customer' && variableKey.includes(variableName)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const secretKey = variableKey.replace('secrets.', ''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const secretValue = `${await this.fetchSecret(secretKey)}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
replacedContent = content.replace(placeholder, secretValue); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+336
to
+355
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Refine The Here's a suggested refactor: async replaceSecretsByProvider(
provider: 'ballerine' | 'customer',
variableKey: string,
content: string,
placeholder: string,
) {
- const variableName = provider === 'ballerine' ? 'secret.' : 'secrets.';
+ const variableName = provider === 'ballerine' ? 'secret.' : 'secret.';
let replacedContent = content;
- if (provider === 'ballerine' && variableKey.includes(variableName)) {
+ if (variableKey.startsWith(variableName)) {
const secretKey = variableKey.replace(variableName, '');
- const secretValue = `${this.getSystemSecret(secretKey)}`;
+ const secretValue =
+ provider === 'ballerine'
+ ? `${this.getSystemSecret(secretKey)}`
+ : `${await this.fetchSecret(secretKey)}`;
replacedContent = content.replace(placeholder, secretValue);
}
- } else if (provider === 'customer' && variableKey.includes(variableName)) {
- const secretKey = variableKey.replace('secrets.', '');
- const secretValue = `${await this.fetchSecret(secretKey)}`;
- replacedContent = content.replace(placeholder, secretValue);
- }
return replacedContent;
} Note: Ensure that the 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return replacedContent; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,95 @@ | ||||||||||||||||||
import { AnyRecord } from '@ballerine/common'; | ||||||||||||||||||
import { logger } from '../../logger'; | ||||||||||||||||||
import { IApiPluginParams } from './types'; | ||||||||||||||||||
import { ApiPlugin } from './api-plugin'; | ||||||||||||||||||
import { ApiEmailTemplates } from './vendor-consts'; | ||||||||||||||||||
import { BallerineApiPlugin, IBallerineApiPluginParams } from './ballerine-plugin'; | ||||||||||||||||||
|
||||||||||||||||||
export interface IBallerineEmailPluginParams { | ||||||||||||||||||
pluginKind: 'template-email'; | ||||||||||||||||||
template: ApiEmailTemplates; | ||||||||||||||||||
displayName: string | undefined; | ||||||||||||||||||
stateNames: string[]; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
export class BallerineEmailPlugin extends BallerineApiPlugin { | ||||||||||||||||||
public static pluginType = 'http'; | ||||||||||||||||||
|
||||||||||||||||||
constructor(params: IBallerineEmailPluginParams & IBallerineApiPluginParams & IApiPluginParams) { | ||||||||||||||||||
super(params); | ||||||||||||||||||
liorzam marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
} | ||||||||||||||||||
liorzam marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+15
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove unnecessary constructor. The current constructor doesn't add any functionality beyond calling the super constructor. It can be safely removed to simplify the code. Apply this diff to remove the unnecessary constructor: export class BallerineEmailPlugin extends BallerineApiPlugin {
public static pluginType = 'http';
- constructor(params: IBallerineEmailPluginParams & IBallerineApiPluginParams & IApiPluginParams) {
- super(params);
- } 📝 Committable suggestion
Suggested change
🧰 Tools🪛 Biome
|
||||||||||||||||||
|
||||||||||||||||||
async makeApiRequest( | ||||||||||||||||||
url: string, | ||||||||||||||||||
method: ApiPlugin['method'], | ||||||||||||||||||
payload: AnyRecord, | ||||||||||||||||||
headers: HeadersInit, | ||||||||||||||||||
liorzam marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
) { | ||||||||||||||||||
Comment on lines
+22
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider adding type safety for payload properties. The Would you like assistance in creating a more specific interface for the payload? |
||||||||||||||||||
const from = { from: { email: payload.from, ...(payload.name ? { name: payload.name } : {}) } }; | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add validation for 'payload.from' to prevent potential errors The Apply this diff to add validation: const from = { from: { email: payload.from, ...(payload.name ? { name: payload.name } : {}) } };
+ if (!payload.from) {
+ throw new Error('payload.from is required and must be a valid email address');
+ }
|
||||||||||||||||||
const subject = payload.subject | ||||||||||||||||||
? { | ||||||||||||||||||
subject: await (this as unknown as ApiPlugin).replaceAllVariables( | ||||||||||||||||||
payload.subject as string, | ||||||||||||||||||
payload, | ||||||||||||||||||
), | ||||||||||||||||||
} | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Simplify method calls by removing unnecessary type casting The current implementation casts Apply this diff to simplify the method calls: - subject: await (this as unknown as ApiPlugin).replaceAllVariables(
+ subject: await this.replaceAllVariables( Similarly, update other instances where the casting is used: - preheader: await (this as unknown as ApiPlugin).replaceAllVariables(
+ preheader: await this.replaceAllVariables( And in the - payload[key] = await (this as unknown as ApiPlugin).replaceAllVariables(
+ payload[key] = await this.replaceAllVariables( Ensure that the Also applies to: 39-43, 84-88 |
||||||||||||||||||
: {}; | ||||||||||||||||||
const preheader = payload.preheader | ||||||||||||||||||
? { | ||||||||||||||||||
preheader: await (this as unknown as ApiPlugin).replaceAllVariables( | ||||||||||||||||||
payload.preheader as string, | ||||||||||||||||||
payload, | ||||||||||||||||||
), | ||||||||||||||||||
} | ||||||||||||||||||
: {}; | ||||||||||||||||||
const receivers = (payload.receivers as string[]).map(receiver => { | ||||||||||||||||||
return { email: receiver }; | ||||||||||||||||||
}); | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for undefined or invalid 'payload.receivers' The code assumes that Apply this diff to add validation: const receivers = (payload.receivers as string[]).map(receiver => {
+ if (!Array.isArray(payload.receivers)) {
+ throw new Error('payload.receivers must be an array of strings');
+ }
return { email: receiver };
}); Alternatively, provide a default or handle the case when
|
||||||||||||||||||
const to = { to: receivers }; | ||||||||||||||||||
const templateId = { template_id: payload.templateId }; | ||||||||||||||||||
|
||||||||||||||||||
await this.replaceVariablesInPayload(payload); | ||||||||||||||||||
|
||||||||||||||||||
const emailPayload = { | ||||||||||||||||||
...from, | ||||||||||||||||||
personalizations: [ | ||||||||||||||||||
{ | ||||||||||||||||||
...preheader, | ||||||||||||||||||
...subject, | ||||||||||||||||||
...to, | ||||||||||||||||||
...{ dynamic_template_data: payload }, | ||||||||||||||||||
}, | ||||||||||||||||||
], | ||||||||||||||||||
...templateId, | ||||||||||||||||||
}; | ||||||||||||||||||
|
||||||||||||||||||
payload.adapter ??= 'sendgrid'; | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Avoid mutating the 'payload' parameter directly The line Apply this diff to create a copy of - payload.adapter ??= 'sendgrid';
+ const emailPayload = { ...payload };
+ emailPayload.adapter ??= 'sendgrid'; Then use 📝 Committable suggestion
Suggested change
|
||||||||||||||||||
|
||||||||||||||||||
if (payload.adapter === 'log') { | ||||||||||||||||||
logger.warn('No email provider', { emailPayload }); | ||||||||||||||||||
liorzam marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
|
||||||||||||||||||
return { | ||||||||||||||||||
ok: true, | ||||||||||||||||||
json: () => Promise.resolve({}), | ||||||||||||||||||
statusText: 'OK', | ||||||||||||||||||
}; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
return await super.makeApiRequest(url, method, emailPayload, headers); | ||||||||||||||||||
liorzam marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
private async replaceVariablesInPayload(payload: AnyRecord) { | ||||||||||||||||||
for (const key of Object.keys(payload)) { | ||||||||||||||||||
if (typeof payload[key] === 'string') { | ||||||||||||||||||
payload[key] = await (this as unknown as ApiPlugin).replaceAllVariables( | ||||||||||||||||||
payload[key] as string, | ||||||||||||||||||
payload, | ||||||||||||||||||
); | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
returnSuccessResponse(callbackAction: string, responseBody: AnyRecord) { | ||||||||||||||||||
return super.returnSuccessResponse(callbackAction, {}); | ||||||||||||||||||
} | ||||||||||||||||||
Comment on lines
+74
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clarify the purpose of The current implementation ignores the If the empty response is intentional, consider this change: returnSuccessResponse(callbackAction: string) {
// Always return an empty object as the response body for email operations
return super.returnSuccessResponse(callbackAction, {});
} If not, consider passing the returnSuccessResponse(callbackAction: string, responseBody: AnyRecord) {
return super.returnSuccessResponse(callbackAction, responseBody);
} |
||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,91 @@ | ||||||||||||||
import { AnyRecord } from '@ballerine/common'; | ||||||||||||||
import { WorkflowRunner } from '../../workflow-runner'; | ||||||||||||||
import { IBallerineApiPluginParams } from './ballerine-plugin'; | ||||||||||||||
import { ApiPlugin, IApiPluginParams } from '.'; | ||||||||||||||
import { ApiBallerinePlugins, BALLERINE_API_PLUGIN_FACTORY } from './vendor-consts'; | ||||||||||||||
|
||||||||||||||
export interface IBallerineApiPluginParams { | ||||||||||||||
pluginKind: ApiBallerinePlugins; | ||||||||||||||
vendor?: string; | ||||||||||||||
displayName: string | undefined; | ||||||||||||||
stateNames: string[]; | ||||||||||||||
} | ||||||||||||||
Comment on lines
+5
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix redeclaration issue for The - export interface IBallerineApiPluginParams {
- pluginKind: ApiBallerinePlugins;
- vendor?: string;
- displayName: string | undefined;
- stateNames: string[];
- } Committable suggestion
Suggested change
ToolsBiome
|
||||||||||||||
|
||||||||||||||
const _getPluginOptions = (params: IBallerineApiPluginParams & IApiPluginParams) => { | ||||||||||||||
let optionsFactoryFn: any = BALLERINE_API_PLUGIN_FACTORY[params.pluginKind]; | ||||||||||||||
|
||||||||||||||
if (!optionsFactoryFn) { | ||||||||||||||
throw new Error(`Unknown plugin kind: ${params.pluginKind}`); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
const pluginOptionFactoryFn = BALLERINE_API_PLUGIN_FACTORY[params.pluginKind] as any; | ||||||||||||||
|
||||||||||||||
if (params.pluginKind === 'registry-information') { | ||||||||||||||
// Currently: only asia verify | ||||||||||||||
optionsFactoryFn = pluginOptionFactoryFn; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
if ( | ||||||||||||||
['individual-sanctions', 'company-sanctions', 'ubo', 'merchant-monitoring'].includes( | ||||||||||||||
params.pluginKind, | ||||||||||||||
) | ||||||||||||||
) { | ||||||||||||||
if (!params.vendor) { | ||||||||||||||
throw new Error(`Missed vendor for: ${params.pluginKind}`); | ||||||||||||||
} | ||||||||||||||
optionsFactoryFn = pluginOptionFactoryFn[params.vendor]; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
if (params.pluginKind === 'template-email') { | ||||||||||||||
if (!params.template) { | ||||||||||||||
throw new Error(`Missed templateName for: ${params.pluginKind}`); | ||||||||||||||
} | ||||||||||||||
optionsFactoryFn = pluginOptionFactoryFn[params.template]; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
if (!optionsFactoryFn) { | ||||||||||||||
throw new Error(`Unknown plugin kind: ${params.pluginKind}, params: ${JSON.stringify(params)}`); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
return optionsFactoryFn(params as any); | ||||||||||||||
}; | ||||||||||||||
|
||||||||||||||
export class BallerineApiPlugin extends ApiPlugin { | ||||||||||||||
public static pluginType = 'http'; | ||||||||||||||
|
||||||||||||||
constructor(params: IBallerineApiPluginParams & IApiPluginParams) { | ||||||||||||||
let options = _getPluginOptions(params); | ||||||||||||||
|
||||||||||||||
let { requestTransformer, requestValidator, responseTransformer, responseValidator } = | ||||||||||||||
WorkflowRunner.reqResTransformersObj({ | ||||||||||||||
params, | ||||||||||||||
...options, | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
super({ | ||||||||||||||
persistResponseDestination: undefined, | ||||||||||||||
...params, | ||||||||||||||
...options, | ||||||||||||||
request: { transformers: requestTransformer, schemaValidator: requestValidator } as any, | ||||||||||||||
response: { transformers: responseTransformer, schemaValidator: responseValidator } as any, | ||||||||||||||
}); | ||||||||||||||
} | ||||||||||||||
liorzam marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
|
||||||||||||||
protected async _getPluginUrl(context: AnyRecord) { | ||||||||||||||
return await this.replaceAllVariables(this.url, context); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// TODO: remove # after refactoring plugins | ||||||||||||||
async #_onReplaceVariable(variableKey: string, content: string, placeholder: string) { | ||||||||||||||
let replacedSecrets = await this.replaceSecretsByProvider( | ||||||||||||||
'ballerine', | ||||||||||||||
variableKey, | ||||||||||||||
content, | ||||||||||||||
placeholder, | ||||||||||||||
); | ||||||||||||||
|
||||||||||||||
replacedSecrets = await super._onReplaceVariable(variableKey, replacedSecrets, placeholder); | ||||||||||||||
|
||||||||||||||
return replacedSecrets; | ||||||||||||||
} | ||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider simplifying
_getPluginUrl
methodThe
_getPluginUrl
method currently returnsthis.url
without additional logic. If there's no plan to extend or override this method in subclasses, you might consider usingthis.url
directly or converting it to a getter property to simplify the code.