strapi-plugin-brevo-template-sender 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -0
- package/dist/_chunks/ConfigPage-BPJ_07lq.js +2697 -0
- package/dist/_chunks/ConfigPage-DLf1Pz1x.mjs +2695 -0
- package/dist/_chunks/en-BWeSd96-.mjs +108 -0
- package/dist/_chunks/en-DHu9evKQ.js +108 -0
- package/dist/_chunks/fr-BYXX6P6j.js +109 -0
- package/dist/_chunks/fr-CcwfVp1l.mjs +109 -0
- package/dist/admin/index.js +58 -0
- package/dist/admin/index.mjs +59 -0
- package/dist/admin/src/components/BrevoHtmlTemplate.d.ts +19 -0
- package/dist/admin/src/components/BrevoHtmlTemplateIcon.d.ts +1 -0
- package/dist/admin/src/components/BrevoLogo.d.ts +1 -0
- package/dist/admin/src/index.d.ts +10 -0
- package/dist/admin/src/pages/ActiveTemplatesCard.d.ts +9 -0
- package/dist/admin/src/pages/ConfigCard.d.ts +15 -0
- package/dist/admin/src/pages/ConfigPage.d.ts +1 -0
- package/dist/admin/src/pages/ManageContentTypesModal.d.ts +12 -0
- package/dist/admin/src/pages/SendEmailApiCard.d.ts +5 -0
- package/dist/admin/src/pages/SupportPluginCard.d.ts +6 -0
- package/dist/admin/src/pages/TemplateModal.d.ts +34 -0
- package/dist/admin/src/pluginId.d.ts +1 -0
- package/dist/admin/src/types.d.ts +39 -0
- package/dist/server/index.js +905 -0
- package/dist/server/index.mjs +906 -0
- package/doc/SCREENSHOTS.md +8 -0
- package/doc/demo.gif +0 -0
- package/doc/logo.webp +0 -0
- package/package.json +82 -0
|
@@ -0,0 +1,905 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const config = {
|
|
3
|
+
default: () => ({
|
|
4
|
+
templateContentType: "plugin::brevo-template-sender.email-template",
|
|
5
|
+
logoUrl: process.env?.STRAPI_LOGO_URL ?? ""
|
|
6
|
+
}),
|
|
7
|
+
validator(config2) {
|
|
8
|
+
if (config2 && typeof config2 === "object" && "templateContentType" in config2) {
|
|
9
|
+
const uid = config2.templateContentType;
|
|
10
|
+
if (uid !== void 0 && (typeof uid !== "string" || !uid)) {
|
|
11
|
+
throw new Error("brevo-template-sender.templateContentType must be a non-empty string when provided");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
const kind = "collectionType";
|
|
17
|
+
const collectionName = "brevo_email_templates";
|
|
18
|
+
const info = {
|
|
19
|
+
singularName: "email-template",
|
|
20
|
+
pluralName: "email-templates",
|
|
21
|
+
displayName: "Email Template (Brevo)",
|
|
22
|
+
description: "Templates HTML pour envoi d'emails via Brevo. Utilisez {{variable}} dans le sujet et le HTML."
|
|
23
|
+
};
|
|
24
|
+
const options = {
|
|
25
|
+
draftAndPublish: true
|
|
26
|
+
};
|
|
27
|
+
const pluginOptions = {
|
|
28
|
+
"content-manager": {
|
|
29
|
+
visible: false
|
|
30
|
+
},
|
|
31
|
+
"content-type-builder": {
|
|
32
|
+
visible: true
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const attributes = {
|
|
36
|
+
name: {
|
|
37
|
+
type: "string",
|
|
38
|
+
required: true
|
|
39
|
+
},
|
|
40
|
+
code: {
|
|
41
|
+
type: "string",
|
|
42
|
+
required: true,
|
|
43
|
+
unique: true
|
|
44
|
+
},
|
|
45
|
+
subject: {
|
|
46
|
+
type: "string",
|
|
47
|
+
required: true
|
|
48
|
+
},
|
|
49
|
+
html: {
|
|
50
|
+
type: "text",
|
|
51
|
+
required: true
|
|
52
|
+
},
|
|
53
|
+
templateMode: {
|
|
54
|
+
type: "enumeration",
|
|
55
|
+
"enum": [
|
|
56
|
+
"html",
|
|
57
|
+
"brevo"
|
|
58
|
+
],
|
|
59
|
+
"default": "html"
|
|
60
|
+
},
|
|
61
|
+
brevoTemplateId: {
|
|
62
|
+
type: "integer"
|
|
63
|
+
},
|
|
64
|
+
recipients: {
|
|
65
|
+
type: "text"
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const schema = {
|
|
69
|
+
kind,
|
|
70
|
+
collectionName,
|
|
71
|
+
info,
|
|
72
|
+
options,
|
|
73
|
+
pluginOptions,
|
|
74
|
+
attributes
|
|
75
|
+
};
|
|
76
|
+
const emailTemplate = { schema };
|
|
77
|
+
const contentTypes$1 = {
|
|
78
|
+
"email-template": emailTemplate
|
|
79
|
+
};
|
|
80
|
+
const send = {
|
|
81
|
+
/**
|
|
82
|
+
* POST /api/brevo-template-sender/send
|
|
83
|
+
* Body: { params: { ... } } uniquement. Template et destinataires viennent de la config Strapi (template par défaut).
|
|
84
|
+
*/
|
|
85
|
+
async send(ctx) {
|
|
86
|
+
try {
|
|
87
|
+
const body = ctx.request.body || {};
|
|
88
|
+
const params2 = typeof body.params === "object" && body.params !== null ? body.params : {};
|
|
89
|
+
const settingsService = strapi.service("plugin::brevo-template-sender.settings");
|
|
90
|
+
const settings2 = await settingsService.getSettings();
|
|
91
|
+
const templateCode = settings2.sendEmailTemplateCode || "contact";
|
|
92
|
+
const config2 = strapi.config.get("plugin::brevo-template-sender");
|
|
93
|
+
const contentTypeUid = config2?.templateContentType || "plugin::brevo-template-sender.email-template";
|
|
94
|
+
const entries = await strapi.documents(contentTypeUid).findMany({
|
|
95
|
+
filters: { code: templateCode },
|
|
96
|
+
status: "published"
|
|
97
|
+
});
|
|
98
|
+
const doc = Array.isArray(entries) ? entries[0] : entries;
|
|
99
|
+
const templateRecipients = doc?.recipients ?? "";
|
|
100
|
+
const recipients = templateRecipients.split(",").map((r) => r.trim()).filter(Boolean);
|
|
101
|
+
if (!recipients.length) {
|
|
102
|
+
ctx.throw(
|
|
103
|
+
400,
|
|
104
|
+
"No recipient configured. Set recipients in the plugin default template (Brevo Template Sender → Configure template)."
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
const senderService = strapi.service("plugin::brevo-template-sender.sender");
|
|
108
|
+
for (const recipient of recipients) {
|
|
109
|
+
await senderService.sendWithTemplateCode({ templateCode, to: recipient, params: params2 });
|
|
110
|
+
}
|
|
111
|
+
ctx.body = { success: true, message: "Email sent" };
|
|
112
|
+
} catch (error) {
|
|
113
|
+
ctx.throw(error.status || 500, error.message || "Failed to send email");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const settings$1 = {
|
|
118
|
+
/** Retourne les paramètres pour l’admin (clé API masquée) et pour la prévisualisation (logoUrl). */
|
|
119
|
+
async getSettings(ctx) {
|
|
120
|
+
try {
|
|
121
|
+
const service = strapi.service("plugin::brevo-template-sender.settings");
|
|
122
|
+
ctx.body = await service.getSettings();
|
|
123
|
+
} catch (e) {
|
|
124
|
+
ctx.status = 500;
|
|
125
|
+
ctx.body = { error: e?.message ?? "Failed to load settings" };
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
/** Enregistre les paramètres depuis la page de configuration (admin). */
|
|
129
|
+
async setSettings(ctx) {
|
|
130
|
+
try {
|
|
131
|
+
const raw = ctx.request?.body ?? {};
|
|
132
|
+
const body = typeof raw?.body === "object" && raw.body !== null ? raw.body : raw;
|
|
133
|
+
const service = strapi.service("plugin::brevo-template-sender.settings");
|
|
134
|
+
const partial = {};
|
|
135
|
+
if (typeof body.apiKey === "string") partial.apiKey = body.apiKey.length > 0 ? body.apiKey : void 0;
|
|
136
|
+
if (body.senderEmail !== void 0) partial.senderEmail = body.senderEmail;
|
|
137
|
+
if (body.senderName !== void 0) partial.senderName = body.senderName;
|
|
138
|
+
if (body.logoUrl !== void 0) partial.logoUrl = body.logoUrl;
|
|
139
|
+
if (body.sendEmailTemplateCode !== void 0) partial.sendEmailTemplateCode = body.sendEmailTemplateCode;
|
|
140
|
+
if (body.openaiApiKey !== void 0) partial.openaiApiKey = typeof body.openaiApiKey === "string" ? body.openaiApiKey : void 0;
|
|
141
|
+
await service.setSettings(partial);
|
|
142
|
+
ctx.body = { ok: true };
|
|
143
|
+
} catch (e) {
|
|
144
|
+
ctx.status = 500;
|
|
145
|
+
ctx.body = { error: e?.message ?? "Failed to save settings" };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
const triggers$1 = {
|
|
150
|
+
async getTriggers(ctx) {
|
|
151
|
+
const triggers2 = await strapi.service("plugin::brevo-template-sender.triggers").getTriggers();
|
|
152
|
+
ctx.body = triggers2;
|
|
153
|
+
},
|
|
154
|
+
async setTriggers(ctx) {
|
|
155
|
+
const raw = ctx.request?.body ?? {};
|
|
156
|
+
const body = typeof raw?.body === "object" && raw.body !== null ? raw.body : raw;
|
|
157
|
+
const triggers2 = Array.isArray(body?.triggers) ? body.triggers : [];
|
|
158
|
+
await strapi.service("plugin::brevo-template-sender.triggers").setTriggers(triggers2);
|
|
159
|
+
ctx.body = { ok: true };
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
const contentTypes = {
|
|
163
|
+
async list(ctx) {
|
|
164
|
+
const paramsService = strapi.service("plugin::brevo-template-sender.params");
|
|
165
|
+
const contentTypes2 = strapi.contentTypes;
|
|
166
|
+
const list = Object.entries(contentTypes2).filter(([uid]) => uid.startsWith("api::") && !uid.includes("email-template")).map(([uid, ct]) => ({
|
|
167
|
+
uid,
|
|
168
|
+
displayName: ct?.info?.displayName ?? uid,
|
|
169
|
+
attributes: paramsService.getAttributesMeta(uid)
|
|
170
|
+
})).sort((a, b) => (a.displayName || a.uid).localeCompare(b.displayName || b.uid));
|
|
171
|
+
ctx.body = list;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const depthAttributes = {
|
|
175
|
+
async get(ctx) {
|
|
176
|
+
const contentTypeUid = ctx.request?.query?.contentTypeUid;
|
|
177
|
+
const attributeName = ctx.request?.query?.attributeName;
|
|
178
|
+
if (!contentTypeUid || !attributeName) {
|
|
179
|
+
ctx.status = 400;
|
|
180
|
+
ctx.body = { error: "contentTypeUid and attributeName are required" };
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const paramsService = strapi.service("plugin::brevo-template-sender.params");
|
|
184
|
+
const attributes2 = paramsService.getDepthAttributes(contentTypeUid, attributeName);
|
|
185
|
+
ctx.body = { attributes: attributes2 };
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
const SYSTEM_PROMPT = `Tu es un expert en emails HTML transactionnels. Tu génères uniquement du HTML valide pour le corps d'un email (sans <!DOCTYPE> ni <html>), compatible avec les clients email.
|
|
189
|
+
Règles :
|
|
190
|
+
- Utilise des tables pour la mise en page si besoin (compatibilité email).
|
|
191
|
+
- Utilise des styles inline.
|
|
192
|
+
- Pour les variables dynamiques, utilise la syntaxe {{nom_variable}} (ex: {{firstname}}, {{email}}, {{logo_url}}).
|
|
193
|
+
- Ne renvoie que le HTML, sans markdown ni explication.`;
|
|
194
|
+
const generateHtml = {
|
|
195
|
+
async generate(ctx) {
|
|
196
|
+
try {
|
|
197
|
+
const { prompt, currentHtml } = ctx.request?.body ?? {};
|
|
198
|
+
if (!prompt || typeof prompt !== "string" || !prompt.trim()) {
|
|
199
|
+
ctx.throw(400, "prompt is required");
|
|
200
|
+
}
|
|
201
|
+
const settingsService = strapi.service("plugin::brevo-template-sender.settings");
|
|
202
|
+
const openaiKey = await settingsService.getOpenAiApiKey();
|
|
203
|
+
if (!openaiKey || typeof openaiKey !== "string" || !openaiKey.trim()) {
|
|
204
|
+
ctx.throw(400, "OpenAI API key is not configured. Add it in the plugin settings (Config) or set OPENAI_API_KEY.");
|
|
205
|
+
}
|
|
206
|
+
const userContent = currentHtml && currentHtml.trim() ? `Contexte : le template actuel est :
|
|
207
|
+
|
|
208
|
+
${currentHtml.trim()}
|
|
209
|
+
|
|
210
|
+
Instruction : ${prompt.trim()}
|
|
211
|
+
|
|
212
|
+
Génère le HTML mis à jour (uniquement le HTML).` : `Instruction : ${prompt.trim()}
|
|
213
|
+
|
|
214
|
+
Génère un template HTML d'email (uniquement le HTML).`;
|
|
215
|
+
const res = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
216
|
+
method: "POST",
|
|
217
|
+
headers: {
|
|
218
|
+
"Content-Type": "application/json",
|
|
219
|
+
Authorization: `Bearer ${openaiKey.trim()}`
|
|
220
|
+
},
|
|
221
|
+
body: JSON.stringify({
|
|
222
|
+
model: "gpt-4o-mini",
|
|
223
|
+
messages: [
|
|
224
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
225
|
+
{ role: "user", content: userContent }
|
|
226
|
+
],
|
|
227
|
+
temperature: 0.3,
|
|
228
|
+
max_tokens: 4096
|
|
229
|
+
})
|
|
230
|
+
});
|
|
231
|
+
const data = await res.json().catch(() => ({}));
|
|
232
|
+
if (!res.ok) {
|
|
233
|
+
const errMsg = data?.error?.message ?? data?.message ?? `OpenAI API error ${res.status}`;
|
|
234
|
+
ctx.throw(res.status >= 400 && res.status < 500 ? res.status : 502, errMsg);
|
|
235
|
+
}
|
|
236
|
+
const content = data?.choices?.[0]?.message?.content;
|
|
237
|
+
if (!content || typeof content !== "string") {
|
|
238
|
+
ctx.throw(502, "Invalid response from OpenAI");
|
|
239
|
+
}
|
|
240
|
+
let html = content.trim();
|
|
241
|
+
const codeBlock = /^```(?:html)?\s*([\s\S]*?)```\s*$/i;
|
|
242
|
+
const match = html.match(codeBlock);
|
|
243
|
+
if (match) html = match[1].trim();
|
|
244
|
+
ctx.body = { html };
|
|
245
|
+
} catch (error) {
|
|
246
|
+
const err = error;
|
|
247
|
+
ctx.throw(err?.status || 500, err?.message || "Failed to generate HTML");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
function getContentTypeUid() {
|
|
252
|
+
const config2 = strapi.config.get("plugin::brevo-template-sender");
|
|
253
|
+
return config2?.templateContentType || "plugin::brevo-template-sender.email-template";
|
|
254
|
+
}
|
|
255
|
+
const defaultTemplate = {
|
|
256
|
+
async get(ctx) {
|
|
257
|
+
const code = ctx.request?.query?.code ?? ctx.request?.query;
|
|
258
|
+
const codeStr = typeof code === "string" ? code.trim() : "";
|
|
259
|
+
if (!codeStr) {
|
|
260
|
+
ctx.status = 400;
|
|
261
|
+
ctx.body = { error: "code is required" };
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const uid = getContentTypeUid();
|
|
265
|
+
const raw = await strapi.documents(uid).findMany({
|
|
266
|
+
filters: { code: codeStr },
|
|
267
|
+
status: "published"
|
|
268
|
+
});
|
|
269
|
+
const list = Array.isArray(raw) ? raw : raw && typeof raw === "object" && Array.isArray(raw.results) ? raw.results : [];
|
|
270
|
+
const doc = list[0];
|
|
271
|
+
if (!doc) {
|
|
272
|
+
ctx.body = { template: null };
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const documentId = doc.documentId ?? (doc.id != null ? String(doc.id) : void 0);
|
|
276
|
+
ctx.body = {
|
|
277
|
+
template: {
|
|
278
|
+
documentId,
|
|
279
|
+
code: doc.code,
|
|
280
|
+
name: doc.name ?? "",
|
|
281
|
+
subject: doc.subject ?? "",
|
|
282
|
+
html: doc.html ?? "",
|
|
283
|
+
templateMode: doc.templateMode ?? "html",
|
|
284
|
+
brevoTemplateId: doc.brevoTemplateId ?? void 0,
|
|
285
|
+
recipients: doc.recipients ?? ""
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
},
|
|
289
|
+
async update(ctx) {
|
|
290
|
+
const body = ctx.request?.body ?? {};
|
|
291
|
+
const rawBody = typeof body?.body === "object" && body.body !== null ? body.body : body;
|
|
292
|
+
const { code, name, subject, html, templateMode, brevoTemplateId, recipients } = rawBody;
|
|
293
|
+
const codeStr = typeof code === "string" ? code.trim() : "";
|
|
294
|
+
if (!codeStr) {
|
|
295
|
+
ctx.status = 400;
|
|
296
|
+
ctx.body = { error: "code is required" };
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const uid = getContentTypeUid();
|
|
300
|
+
const toList = (r) => Array.isArray(r) ? r : r && typeof r === "object" && Array.isArray(r.results) ? r.results : [];
|
|
301
|
+
let raw = await strapi.documents(uid).findMany({
|
|
302
|
+
filters: { code: codeStr },
|
|
303
|
+
status: "published"
|
|
304
|
+
});
|
|
305
|
+
let existing = toList(raw)[0];
|
|
306
|
+
if (!existing) {
|
|
307
|
+
raw = await strapi.documents(uid).findMany({
|
|
308
|
+
filters: { code: codeStr },
|
|
309
|
+
status: "draft"
|
|
310
|
+
});
|
|
311
|
+
existing = toList(raw)[0];
|
|
312
|
+
}
|
|
313
|
+
const documentId = existing ? existing.documentId ?? (existing.id != null ? String(existing.id) : void 0) : void 0;
|
|
314
|
+
if (existing && documentId) {
|
|
315
|
+
await strapi.documents(uid).update({
|
|
316
|
+
documentId,
|
|
317
|
+
status: "draft",
|
|
318
|
+
data: {
|
|
319
|
+
...typeof name === "string" && {
|
|
320
|
+
name: name.trim() || existing.name
|
|
321
|
+
},
|
|
322
|
+
...typeof subject === "string" && { subject: subject.trim() },
|
|
323
|
+
...typeof html === "string" && { html: html.trim() },
|
|
324
|
+
...(templateMode === "html" || templateMode === "brevo") && {
|
|
325
|
+
templateMode
|
|
326
|
+
},
|
|
327
|
+
...typeof brevoTemplateId === "number" ? { brevoTemplateId } : typeof brevoTemplateId === "string" ? { brevoTemplateId: parseInt(brevoTemplateId, 10) } : {},
|
|
328
|
+
...typeof recipients === "string" && { recipients: recipients.trim() }
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
await strapi.documents(uid).publish({ documentId });
|
|
332
|
+
} else {
|
|
333
|
+
const created = await strapi.documents(uid).create({
|
|
334
|
+
data: {
|
|
335
|
+
code: codeStr,
|
|
336
|
+
name: typeof name === "string" && name.trim() ? name.trim() : "Send Email Default",
|
|
337
|
+
subject: typeof subject === "string" ? subject.trim() : "Message",
|
|
338
|
+
html: typeof html === "string" ? html.trim() : "<p>Hello {{firstname}}</p>",
|
|
339
|
+
templateMode: templateMode === "brevo" ? "brevo" : "html",
|
|
340
|
+
brevoTemplateId: typeof brevoTemplateId === "number" ? brevoTemplateId : typeof brevoTemplateId === "string" ? parseInt(brevoTemplateId, 10) : void 0,
|
|
341
|
+
recipients: typeof recipients === "string" ? recipients.trim() : void 0
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
const createdId = created?.documentId ?? (created?.id != null ? String(created.id) : void 0);
|
|
345
|
+
if (createdId) {
|
|
346
|
+
await strapi.documents(uid).publish({ documentId: createdId });
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
ctx.body = { ok: true };
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
const controllers = {
|
|
353
|
+
send,
|
|
354
|
+
settings: settings$1,
|
|
355
|
+
triggers: triggers$1,
|
|
356
|
+
"content-types": contentTypes,
|
|
357
|
+
"depth-attributes": depthAttributes,
|
|
358
|
+
"generate-html": generateHtml,
|
|
359
|
+
"default-template": defaultTemplate
|
|
360
|
+
};
|
|
361
|
+
const admin = {
|
|
362
|
+
type: "admin",
|
|
363
|
+
routes: [
|
|
364
|
+
{
|
|
365
|
+
method: "GET",
|
|
366
|
+
path: "/settings",
|
|
367
|
+
handler: "settings.getSettings",
|
|
368
|
+
config: { policies: [] }
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
method: "PUT",
|
|
372
|
+
path: "/settings",
|
|
373
|
+
handler: "settings.setSettings",
|
|
374
|
+
config: { policies: [] }
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
method: "GET",
|
|
378
|
+
path: "/triggers",
|
|
379
|
+
handler: "triggers.getTriggers",
|
|
380
|
+
config: { policies: [] }
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
method: "PUT",
|
|
384
|
+
path: "/triggers",
|
|
385
|
+
handler: "triggers.setTriggers",
|
|
386
|
+
config: { policies: [] }
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
method: "GET",
|
|
390
|
+
path: "/content-types",
|
|
391
|
+
handler: "content-types.list",
|
|
392
|
+
config: { policies: [] }
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
method: "GET",
|
|
396
|
+
path: "/depth-attributes",
|
|
397
|
+
handler: "depth-attributes.get",
|
|
398
|
+
config: { policies: [] }
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
method: "POST",
|
|
402
|
+
path: "/generate-html",
|
|
403
|
+
handler: "generate-html.generate",
|
|
404
|
+
config: { policies: [] }
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
method: "GET",
|
|
408
|
+
path: "/default-template",
|
|
409
|
+
handler: "default-template.get",
|
|
410
|
+
config: { policies: [] }
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
method: "PUT",
|
|
414
|
+
path: "/default-template",
|
|
415
|
+
handler: "default-template.update",
|
|
416
|
+
config: { policies: [] }
|
|
417
|
+
}
|
|
418
|
+
]
|
|
419
|
+
};
|
|
420
|
+
const contentApi = {
|
|
421
|
+
type: "content-api",
|
|
422
|
+
routes: [
|
|
423
|
+
{
|
|
424
|
+
method: "POST",
|
|
425
|
+
path: "/send",
|
|
426
|
+
handler: "send.send",
|
|
427
|
+
config: {
|
|
428
|
+
auth: false
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
]
|
|
432
|
+
};
|
|
433
|
+
const routes = {
|
|
434
|
+
admin,
|
|
435
|
+
"content-api": contentApi
|
|
436
|
+
};
|
|
437
|
+
function getByPath$1(obj, path) {
|
|
438
|
+
if (path === "") return obj;
|
|
439
|
+
const parts = path.split(".").filter(Boolean);
|
|
440
|
+
let current = obj;
|
|
441
|
+
for (const p of parts) {
|
|
442
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
443
|
+
current = current[p];
|
|
444
|
+
}
|
|
445
|
+
return current;
|
|
446
|
+
}
|
|
447
|
+
function resolveRecipient(value, params2) {
|
|
448
|
+
const match = value.trim().match(/^\{\{\s*([^}]+)\s*\}\}$/);
|
|
449
|
+
if (!match) return value;
|
|
450
|
+
const path = match[1].trim();
|
|
451
|
+
const v = getByPath$1(params2, path);
|
|
452
|
+
if (v == null) return "";
|
|
453
|
+
if (typeof v === "object" && !Array.isArray(v) && v !== null && "url" in v) {
|
|
454
|
+
return String(v.url ?? "");
|
|
455
|
+
}
|
|
456
|
+
if (Array.isArray(v)) {
|
|
457
|
+
return v.map((x) => x && typeof x === "object" && "url" in x ? x.url : String(x)).join(", ");
|
|
458
|
+
}
|
|
459
|
+
return String(v);
|
|
460
|
+
}
|
|
461
|
+
const register = ({ strapi: strapi2 }) => {
|
|
462
|
+
strapi2.documents.use(async (context, next) => {
|
|
463
|
+
const result = await next();
|
|
464
|
+
const { uid, action } = context;
|
|
465
|
+
const event = action === "create" ? "create" : action === "update" ? "update" : action === "delete" ? "delete" : action === "publish" ? "publish" : action === "unpublish" ? "unpublish" : null;
|
|
466
|
+
if (!event) return result;
|
|
467
|
+
const triggerService = strapi2.service("plugin::brevo-template-sender.triggers");
|
|
468
|
+
const trigger = await triggerService.getTrigger(uid, event);
|
|
469
|
+
if (!trigger) {
|
|
470
|
+
strapi2.log.debug(`[brevo-template-sender] Pas de trigger configuré pour ${uid} / ${event}`);
|
|
471
|
+
return result;
|
|
472
|
+
}
|
|
473
|
+
strapi2.log.info(`[brevo-template-sender] Trigger trouvé pour ${uid} / ${event} (mode=${trigger.templateMode})`);
|
|
474
|
+
const canSendBrevo = trigger.templateMode === "brevo" && trigger.brevoTemplateId != null && trigger.brevoTemplateId > 0;
|
|
475
|
+
const canSendHtml = trigger.subject && trigger.html;
|
|
476
|
+
if (!canSendBrevo && !canSendHtml) {
|
|
477
|
+
strapi2.log.warn(`[brevo-template-sender] Trigger sans template valide pour ${uid} / ${event} — mode=${trigger.templateMode}, subject=${!!trigger.subject}, html=${!!trigger.html}, brevoTemplateId=${trigger.brevoTemplateId}`);
|
|
478
|
+
return result;
|
|
479
|
+
}
|
|
480
|
+
const doc = (result && typeof result === "object" ? result : null) ?? context.params?.data ?? null;
|
|
481
|
+
let params2 = doc && typeof doc === "object" ? { ...doc } : {};
|
|
482
|
+
const documentId = doc?.documentId ?? doc?.id;
|
|
483
|
+
if (documentId && typeof documentId === "string") {
|
|
484
|
+
const paramsService = strapi2.service("plugin::brevo-template-sender.params");
|
|
485
|
+
const withDepth = await paramsService.getDocumentWithDepth(uid, documentId, 2);
|
|
486
|
+
if (withDepth && typeof withDepth === "object") params2 = withDepth;
|
|
487
|
+
}
|
|
488
|
+
const fromDoc = trigger.sendToField && doc?.[trigger.sendToField] ? String(doc[trigger.sendToField]).trim() : "";
|
|
489
|
+
const rawRecipients = (trigger.recipients || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
490
|
+
const resolved = rawRecipients.flatMap((addr) => {
|
|
491
|
+
const v = resolveRecipient(addr, params2);
|
|
492
|
+
return v ? v.split(",").map((e) => e.trim()).filter(Boolean) : [];
|
|
493
|
+
});
|
|
494
|
+
const sendToList = [fromDoc, ...resolved].filter(Boolean);
|
|
495
|
+
const sendTo = sendToList.length ? sendToList : process.env.BREVO_SENDER ? [process.env.BREVO_SENDER] : [];
|
|
496
|
+
if (!sendTo.length) {
|
|
497
|
+
strapi2.log.warn(`[brevo-template-sender] Aucun destinataire pour ${uid} / ${event} — configurez « Recipients » dans le trigger ou la variable d'env BREVO_SENDER`);
|
|
498
|
+
return result;
|
|
499
|
+
}
|
|
500
|
+
try {
|
|
501
|
+
strapi2.log.info(`[brevo-template-sender] Envoi email pour ${uid} événement ${event} vers ${sendTo.join(", ")}`);
|
|
502
|
+
const sender2 = strapi2.service("plugin::brevo-template-sender.sender");
|
|
503
|
+
for (const to of sendTo) {
|
|
504
|
+
await sender2.sendFromTrigger({
|
|
505
|
+
subject: trigger.subject,
|
|
506
|
+
html: trigger.html,
|
|
507
|
+
params: params2,
|
|
508
|
+
sendTo: to,
|
|
509
|
+
templateMode: trigger.templateMode,
|
|
510
|
+
brevoTemplateId: trigger.brevoTemplateId
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
} catch (e) {
|
|
514
|
+
strapi2.log?.error?.("brevo-template-sender trigger", e);
|
|
515
|
+
}
|
|
516
|
+
return result;
|
|
517
|
+
});
|
|
518
|
+
};
|
|
519
|
+
const STORE_KEY$1 = "triggers";
|
|
520
|
+
const triggers = ({ strapi: strapi2 }) => ({
|
|
521
|
+
async getTriggers() {
|
|
522
|
+
const store = strapi2.store({ type: "plugin", name: "brevo-template-sender" });
|
|
523
|
+
const value = await store.get({ key: STORE_KEY$1 });
|
|
524
|
+
return Array.isArray(value) ? value : [];
|
|
525
|
+
},
|
|
526
|
+
async setTriggers(triggers2) {
|
|
527
|
+
const store = strapi2.store({ type: "plugin", name: "brevo-template-sender" });
|
|
528
|
+
await store.set({ key: STORE_KEY$1, value: triggers2 });
|
|
529
|
+
},
|
|
530
|
+
async getTrigger(contentTypeUid, event) {
|
|
531
|
+
const triggers2 = await this.getTriggers();
|
|
532
|
+
return triggers2.find((t) => t.contentTypeUid === contentTypeUid && t.event === event) ?? null;
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
const brevo = require("@getbrevo/brevo");
|
|
536
|
+
function getByPath(obj, path) {
|
|
537
|
+
if (path === "") return obj;
|
|
538
|
+
const parts = path.split(".").filter(Boolean);
|
|
539
|
+
let current = obj;
|
|
540
|
+
for (const p of parts) {
|
|
541
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
542
|
+
current = current[p];
|
|
543
|
+
}
|
|
544
|
+
return current;
|
|
545
|
+
}
|
|
546
|
+
function replacePlaceholders(template, params2) {
|
|
547
|
+
if (!template || typeof template !== "string") return template;
|
|
548
|
+
const regex = /\{\{\s*([a-zA-Z0-9_.]+)\s*\}\}/g;
|
|
549
|
+
return template.replace(regex, (_, path) => {
|
|
550
|
+
const value = getByPath(params2, path.trim());
|
|
551
|
+
if (value != null && typeof value === "object" && !Array.isArray(value) && "url" in value) {
|
|
552
|
+
return String(value.url ?? "");
|
|
553
|
+
}
|
|
554
|
+
if (Array.isArray(value)) return value.map((v) => v && typeof v === "object" && "url" in v ? v.url : String(v)).join(", ");
|
|
555
|
+
return String(value ?? "");
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
const sender = ({ strapi: strapi2 }) => ({
|
|
559
|
+
async sendHtml({
|
|
560
|
+
htmlContent,
|
|
561
|
+
subject,
|
|
562
|
+
sendTo,
|
|
563
|
+
replyTo,
|
|
564
|
+
attachment
|
|
565
|
+
}) {
|
|
566
|
+
const settingsService = strapi2.service("plugin::brevo-template-sender.settings");
|
|
567
|
+
const { apiKey, senderEmail, senderName } = await settingsService.getSettingsForSend();
|
|
568
|
+
if (!apiKey) {
|
|
569
|
+
throw new Error("Brevo API key is not configured. Set it in the plugin settings or BREVO_API_KEY env.");
|
|
570
|
+
}
|
|
571
|
+
if (!senderEmail) {
|
|
572
|
+
throw new Error("Brevo sender email is not configured. Set it in the plugin settings (Sender Email) or BREVO_SENDER env.");
|
|
573
|
+
}
|
|
574
|
+
const apiInstance = new brevo.TransactionalEmailsApi();
|
|
575
|
+
const apiKeyAuth = apiInstance.authentications["apiKey"];
|
|
576
|
+
apiKeyAuth.apiKey = apiKey;
|
|
577
|
+
const sendSmtpEmail = new brevo.SendSmtpEmail();
|
|
578
|
+
sendSmtpEmail.htmlContent = htmlContent;
|
|
579
|
+
sendSmtpEmail.subject = subject;
|
|
580
|
+
sendSmtpEmail.sender = new brevo.SendSmtpEmailSender();
|
|
581
|
+
sendSmtpEmail.sender.email = senderEmail;
|
|
582
|
+
sendSmtpEmail.sender.name = senderName || void 0;
|
|
583
|
+
const replyEmail = replyTo || senderEmail;
|
|
584
|
+
sendSmtpEmail.replyTo = replyEmail ? { email: replyEmail } : void 0;
|
|
585
|
+
sendSmtpEmail.to = [{ email: sendTo }];
|
|
586
|
+
if (attachment && attachment.length > 0) {
|
|
587
|
+
sendSmtpEmail.attachment = attachment;
|
|
588
|
+
}
|
|
589
|
+
strapi2.log.info(`[brevo-template-sender] sendHtml from=${senderEmail} to=${sendTo} subject="${subject}"`);
|
|
590
|
+
await apiInstance.sendTransacEmail(sendSmtpEmail);
|
|
591
|
+
strapi2.log.info(`[brevo-template-sender] sendHtml OK → ${sendTo}`);
|
|
592
|
+
},
|
|
593
|
+
/**
|
|
594
|
+
* Envoi via un template Brevo (templateId). Les params sont passés à Brevo pour les variables du template.
|
|
595
|
+
*/
|
|
596
|
+
async sendWithBrevoTemplateId({
|
|
597
|
+
templateId,
|
|
598
|
+
sendTo,
|
|
599
|
+
params: params2 = {}
|
|
600
|
+
}) {
|
|
601
|
+
const settingsService = strapi2.service("plugin::brevo-template-sender.settings");
|
|
602
|
+
const { apiKey, senderEmail, senderName } = await settingsService.getSettingsForSend();
|
|
603
|
+
if (!apiKey) {
|
|
604
|
+
throw new Error("Brevo API key is not configured. Set it in the plugin settings or BREVO_API_KEY env.");
|
|
605
|
+
}
|
|
606
|
+
if (!senderEmail) {
|
|
607
|
+
throw new Error("Brevo sender email is not configured. Set it in the plugin settings (Sender Email) or BREVO_SENDER env.");
|
|
608
|
+
}
|
|
609
|
+
const apiInstance = new brevo.TransactionalEmailsApi();
|
|
610
|
+
const apiKeyAuth = apiInstance.authentications["apiKey"];
|
|
611
|
+
apiKeyAuth.apiKey = apiKey;
|
|
612
|
+
const sendSmtpEmail = new brevo.SendSmtpEmail();
|
|
613
|
+
sendSmtpEmail.templateId = templateId;
|
|
614
|
+
const stringParams = {};
|
|
615
|
+
for (const [k, v] of Object.entries(params2)) {
|
|
616
|
+
if (v != null && typeof v === "object" && "url" in v) {
|
|
617
|
+
stringParams[k] = String(v.url ?? "");
|
|
618
|
+
} else {
|
|
619
|
+
stringParams[k] = typeof v === "string" ? v : String(v ?? "");
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
sendSmtpEmail.params = stringParams;
|
|
623
|
+
sendSmtpEmail.sender = new brevo.SendSmtpEmailSender();
|
|
624
|
+
sendSmtpEmail.sender.email = senderEmail;
|
|
625
|
+
sendSmtpEmail.sender.name = senderName || void 0;
|
|
626
|
+
sendSmtpEmail.to = [{ email: sendTo }];
|
|
627
|
+
strapi2.log.info(`[brevo-template-sender] sendWithBrevoTemplateId templateId=${templateId} from=${senderEmail} to=${sendTo}`);
|
|
628
|
+
await apiInstance.sendTransacEmail(sendSmtpEmail);
|
|
629
|
+
strapi2.log.info(`[brevo-template-sender] sendWithBrevoTemplateId OK → ${sendTo}`);
|
|
630
|
+
},
|
|
631
|
+
/**
|
|
632
|
+
* Charge un template par code, remplace les placeholders et envoie l'email.
|
|
633
|
+
* Utilisé par les routes /contact, /brevo/inquiry et les lifecycles inquiry-request.
|
|
634
|
+
*/
|
|
635
|
+
async sendWithTemplateCode({
|
|
636
|
+
templateCode,
|
|
637
|
+
to,
|
|
638
|
+
params: params2 = {},
|
|
639
|
+
attachment
|
|
640
|
+
}) {
|
|
641
|
+
const settingsService = strapi2.service("plugin::brevo-template-sender.settings");
|
|
642
|
+
const { logoUrl } = await settingsService.getSettingsForSend();
|
|
643
|
+
const config2 = strapi2.config.get("plugin::brevo-template-sender");
|
|
644
|
+
const contentTypeUid = config2?.templateContentType || "plugin::brevo-template-sender.email-template";
|
|
645
|
+
const mergedParams = {
|
|
646
|
+
...params2,
|
|
647
|
+
logo_url: logoUrl || ""
|
|
648
|
+
};
|
|
649
|
+
const entries = await strapi2.documents(contentTypeUid).findMany({
|
|
650
|
+
filters: { code: templateCode },
|
|
651
|
+
status: "published"
|
|
652
|
+
});
|
|
653
|
+
const doc = Array.isArray(entries) ? entries[0] : entries;
|
|
654
|
+
if (!doc) {
|
|
655
|
+
throw new Error(`Email template not found: ${templateCode}`);
|
|
656
|
+
}
|
|
657
|
+
const templateMode = doc.templateMode ?? "html";
|
|
658
|
+
const brevoTemplateId = doc.brevoTemplateId != null ? Number(doc.brevoTemplateId) : void 0;
|
|
659
|
+
if (templateMode === "brevo" && brevoTemplateId != null && brevoTemplateId > 0) {
|
|
660
|
+
await this.sendWithBrevoTemplateId({
|
|
661
|
+
templateId: brevoTemplateId,
|
|
662
|
+
sendTo: to,
|
|
663
|
+
params: mergedParams
|
|
664
|
+
});
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
const subject = replacePlaceholders(doc.subject || "", mergedParams);
|
|
668
|
+
const htmlContent = replacePlaceholders(doc.html || "", mergedParams);
|
|
669
|
+
if (!htmlContent) {
|
|
670
|
+
throw new Error(`Email template "${templateCode}" has no HTML content`);
|
|
671
|
+
}
|
|
672
|
+
const sender2 = strapi2.service("plugin::brevo-template-sender.sender");
|
|
673
|
+
const { senderEmail } = await settingsService.getSettingsForSend();
|
|
674
|
+
await sender2.sendHtml({
|
|
675
|
+
htmlContent,
|
|
676
|
+
subject,
|
|
677
|
+
sendTo: to,
|
|
678
|
+
replyTo: senderEmail,
|
|
679
|
+
attachment
|
|
680
|
+
});
|
|
681
|
+
},
|
|
682
|
+
/**
|
|
683
|
+
* Envoi à partir d'une config trigger : mode HTML (subject + html) ou mode template Brevo (templateId).
|
|
684
|
+
*/
|
|
685
|
+
async sendFromTrigger({
|
|
686
|
+
subject,
|
|
687
|
+
html,
|
|
688
|
+
params: params2,
|
|
689
|
+
sendTo,
|
|
690
|
+
templateMode,
|
|
691
|
+
brevoTemplateId
|
|
692
|
+
}) {
|
|
693
|
+
if (templateMode === "brevo" && brevoTemplateId != null && brevoTemplateId > 0) {
|
|
694
|
+
const settingsService2 = strapi2.service("plugin::brevo-template-sender.settings");
|
|
695
|
+
const { logoUrl: logoUrl2 } = await settingsService2.getSettingsForSend();
|
|
696
|
+
const mergedParams2 = { ...params2, logo_url: logoUrl2 || "" };
|
|
697
|
+
await this.sendWithBrevoTemplateId({
|
|
698
|
+
templateId: brevoTemplateId,
|
|
699
|
+
sendTo,
|
|
700
|
+
params: mergedParams2
|
|
701
|
+
});
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
const settingsService = strapi2.service("plugin::brevo-template-sender.settings");
|
|
705
|
+
const { logoUrl, senderEmail } = await settingsService.getSettingsForSend();
|
|
706
|
+
const mergedParams = {
|
|
707
|
+
...params2,
|
|
708
|
+
logo_url: logoUrl || ""
|
|
709
|
+
};
|
|
710
|
+
const finalSubject = replacePlaceholders(subject, mergedParams);
|
|
711
|
+
const htmlContent = replacePlaceholders(html, mergedParams);
|
|
712
|
+
await this.sendHtml({
|
|
713
|
+
htmlContent,
|
|
714
|
+
subject: finalSubject,
|
|
715
|
+
sendTo,
|
|
716
|
+
replyTo: senderEmail
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
function getAttributeType(attr) {
|
|
721
|
+
if (!attr || typeof attr !== "object") return "unknown";
|
|
722
|
+
const a = attr;
|
|
723
|
+
return a.type ?? a.model ?? "unknown";
|
|
724
|
+
}
|
|
725
|
+
function getAttributeKind(type) {
|
|
726
|
+
if (type === "relation") return "relation";
|
|
727
|
+
if (type === "media") return "media";
|
|
728
|
+
if (type === "component") return "component";
|
|
729
|
+
if (type === "dynamiczone") return "dynamiczone";
|
|
730
|
+
return "scalar";
|
|
731
|
+
}
|
|
732
|
+
const params = ({ strapi: strapi2 }) => ({
|
|
733
|
+
/**
|
|
734
|
+
* Retourne les métadonnées des attributs d'un content type (nom, type, kind, target).
|
|
735
|
+
* Permet à l'admin de distinguer champs simples (texte) vs champs à profondeur (relation, media).
|
|
736
|
+
*/
|
|
737
|
+
getAttributesMeta(uid) {
|
|
738
|
+
const ct = strapi2.contentTypes?.[uid];
|
|
739
|
+
if (!ct?.attributes) return [];
|
|
740
|
+
return Object.entries(ct.attributes).map(([name, attr]) => {
|
|
741
|
+
const type = getAttributeType(attr);
|
|
742
|
+
const kind2 = getAttributeKind(type);
|
|
743
|
+
const target = attr?.target;
|
|
744
|
+
return { name, type, kind: kind2, target };
|
|
745
|
+
});
|
|
746
|
+
},
|
|
747
|
+
/**
|
|
748
|
+
* Returns the list of depth attributes for a relation/media/component attribute
|
|
749
|
+
* (e.g. for "thumbnail" media: id, url, name, ...; for relation: target content type attributes).
|
|
750
|
+
*/
|
|
751
|
+
getDepthAttributes(uid, attributeName) {
|
|
752
|
+
const meta = this.getAttributesMeta(uid);
|
|
753
|
+
const attr = meta.find((m) => m.name === attributeName);
|
|
754
|
+
if (!attr) return [];
|
|
755
|
+
if (attr.kind === "media") {
|
|
756
|
+
return [
|
|
757
|
+
{ name: "id" },
|
|
758
|
+
{ name: "url" },
|
|
759
|
+
{ name: "name" },
|
|
760
|
+
{ name: "alternativeText" },
|
|
761
|
+
{ name: "caption" },
|
|
762
|
+
{ name: "width" },
|
|
763
|
+
{ name: "height" }
|
|
764
|
+
];
|
|
765
|
+
}
|
|
766
|
+
if (attr.kind === "relation" && attr.target) {
|
|
767
|
+
return this.getAttributesMeta(attr.target).map((m) => ({ name: m.name }));
|
|
768
|
+
}
|
|
769
|
+
if (attr.kind === "component" && attr.target) {
|
|
770
|
+
const comp = strapi2.components?.[attr.target];
|
|
771
|
+
if (comp?.attributes) {
|
|
772
|
+
return Object.keys(comp.attributes).map((name) => ({ name }));
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return [];
|
|
776
|
+
},
|
|
777
|
+
/**
|
|
778
|
+
* Construit un objet populate pour strapi.documents(uid).findOne()
|
|
779
|
+
* en incluant les relations et media jusqu'à la profondeur demandée.
|
|
780
|
+
*/
|
|
781
|
+
buildPopulate(uid, depth) {
|
|
782
|
+
if (depth <= 0) return {};
|
|
783
|
+
const meta = this.getAttributesMeta(uid);
|
|
784
|
+
const relationAndMedia = meta.filter((m) => m.kind === "relation" || m.kind === "media" || m.kind === "component");
|
|
785
|
+
if (relationAndMedia.length === 0) return {};
|
|
786
|
+
if (depth === 1) return relationAndMedia.map((m) => m.name);
|
|
787
|
+
const populate = {};
|
|
788
|
+
for (const m of relationAndMedia) {
|
|
789
|
+
if (m.kind === "relation" && m.target && depth > 1) {
|
|
790
|
+
const nested = this.buildPopulate(m.target, depth - 1);
|
|
791
|
+
populate[m.name] = Array.isArray(nested) ? { populate: nested } : { populate: nested };
|
|
792
|
+
} else {
|
|
793
|
+
populate[m.name] = true;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return populate;
|
|
797
|
+
},
|
|
798
|
+
/**
|
|
799
|
+
* Récupère le document avec les relations/media peuplés jusqu'à la profondeur donnée.
|
|
800
|
+
* Pour les media, on normalise pour avoir .url accessible ; pour les relations, l'objet peuplé.
|
|
801
|
+
*/
|
|
802
|
+
async getDocumentWithDepth(uid, documentId, depth = 2) {
|
|
803
|
+
try {
|
|
804
|
+
const populate = this.buildPopulate(uid, depth);
|
|
805
|
+
const doc = await strapi2.documents(uid).findOne({
|
|
806
|
+
documentId,
|
|
807
|
+
populate: Object.keys(populate).length ? populate : void 0
|
|
808
|
+
});
|
|
809
|
+
if (!doc || typeof doc !== "object") return null;
|
|
810
|
+
return this.normalizeForTemplate(doc, uid, depth);
|
|
811
|
+
} catch (e) {
|
|
812
|
+
strapi2.log?.warn?.("brevo-template-sender getDocumentWithDepth", e);
|
|
813
|
+
return null;
|
|
814
|
+
}
|
|
815
|
+
},
|
|
816
|
+
/**
|
|
817
|
+
* Normalise le document pour le template : media → { url, ... }, relations déjà en objet.
|
|
818
|
+
*/
|
|
819
|
+
normalizeForTemplate(doc, uid, _depth) {
|
|
820
|
+
const meta = this.getAttributesMeta(uid);
|
|
821
|
+
const out = { ...doc };
|
|
822
|
+
for (const m of meta) {
|
|
823
|
+
const val = out[m.name];
|
|
824
|
+
if (val == null) continue;
|
|
825
|
+
if (m.kind === "media") {
|
|
826
|
+
if (Array.isArray(val)) {
|
|
827
|
+
out[m.name] = val.map((v) => v && typeof v === "object" && v.url ? v : { url: "" });
|
|
828
|
+
} else if (typeof val === "object" && val !== null && "url" in val) {
|
|
829
|
+
out[m.name] = val;
|
|
830
|
+
} else {
|
|
831
|
+
out[m.name] = { url: String(val) };
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
if (m.kind === "relation" && typeof val === "object" && val !== null && !Array.isArray(val)) {
|
|
835
|
+
out[m.name] = val;
|
|
836
|
+
}
|
|
837
|
+
if (m.kind === "relation" && Array.isArray(val)) {
|
|
838
|
+
out[m.name] = val;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
return out;
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
const STORE_KEY = "brevo-settings";
|
|
845
|
+
const settings = ({ strapi: strapi2 }) => {
|
|
846
|
+
const store = strapi2.store({ type: "plugin", name: "brevo-template-sender" });
|
|
847
|
+
return {
|
|
848
|
+
/** Pour l’admin : retourne les paramètres avec la clé API masquée (présente ou non). */
|
|
849
|
+
async getSettings() {
|
|
850
|
+
const saved = await store.get({ key: STORE_KEY });
|
|
851
|
+
const config2 = strapi2.config.get("plugin::brevo-template-sender");
|
|
852
|
+
return {
|
|
853
|
+
apiKeySet: Boolean(saved?.apiKey || process.env.BREVO_API_KEY),
|
|
854
|
+
senderEmail: saved?.senderEmail ?? process.env.BREVO_SENDER ?? "",
|
|
855
|
+
senderName: saved?.senderName ?? process.env.BREVO_SENDER_NAME ?? "",
|
|
856
|
+
logoUrl: saved?.logoUrl ?? config2?.logoUrl ?? process.env.STRAPI_LOGO_URL ?? "",
|
|
857
|
+
sendEmailTemplateCode: saved?.sendEmailTemplateCode ?? "contact",
|
|
858
|
+
openaiApiKeySet: Boolean(saved?.openaiApiKey),
|
|
859
|
+
isDevelopment: process.env.NODE_ENV === "development",
|
|
860
|
+
supportRepoUrl: process.env.BREVO_TEMPLATE_SENDER_REPO_URL || "https://github.com/calistock/strapi-plugin-brevo-template-sender",
|
|
861
|
+
supportBmcUrl: process.env.BREVO_TEMPLATE_SENDER_BMC_URL || "https://buymeacoffee.com/dupflo"
|
|
862
|
+
};
|
|
863
|
+
},
|
|
864
|
+
/** Enregistre les paramètres (seules les clés fournies sont mises à jour). */
|
|
865
|
+
async setSettings(partial) {
|
|
866
|
+
const current = await store.get({ key: STORE_KEY });
|
|
867
|
+
const next = { ...current, ...partial };
|
|
868
|
+
if (partial.apiKey === "") delete next.apiKey;
|
|
869
|
+
else if (partial.apiKey != null) next.apiKey = partial.apiKey;
|
|
870
|
+
if (partial.openaiApiKey === "") delete next.openaiApiKey;
|
|
871
|
+
else if (partial.openaiApiKey != null) next.openaiApiKey = partial.openaiApiKey;
|
|
872
|
+
await store.set({ key: STORE_KEY, value: next });
|
|
873
|
+
},
|
|
874
|
+
/** Pour l’envoi côté serveur uniquement : retourne la clé API et l’expéditeur (store puis env). */
|
|
875
|
+
async getOpenAiApiKey() {
|
|
876
|
+
const saved = await store.get({ key: STORE_KEY });
|
|
877
|
+
return saved?.openaiApiKey ?? process.env.OPENAI_API_KEY ?? "";
|
|
878
|
+
},
|
|
879
|
+
async getSettingsForSend() {
|
|
880
|
+
const saved = await store.get({ key: STORE_KEY });
|
|
881
|
+
const config2 = strapi2.config.get("plugin::brevo-template-sender");
|
|
882
|
+
return {
|
|
883
|
+
apiKey: saved?.apiKey ?? process.env.BREVO_API_KEY ?? "",
|
|
884
|
+
senderEmail: saved?.senderEmail ?? process.env.BREVO_SENDER ?? "",
|
|
885
|
+
senderName: saved?.senderName ?? process.env.BREVO_SENDER_NAME ?? "",
|
|
886
|
+
logoUrl: saved?.logoUrl ?? config2?.logoUrl ?? process.env.STRAPI_LOGO_URL ?? ""
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
};
|
|
891
|
+
const services = {
|
|
892
|
+
triggers,
|
|
893
|
+
sender,
|
|
894
|
+
params,
|
|
895
|
+
settings
|
|
896
|
+
};
|
|
897
|
+
const index = () => ({
|
|
898
|
+
register,
|
|
899
|
+
config,
|
|
900
|
+
contentTypes: contentTypes$1,
|
|
901
|
+
controllers,
|
|
902
|
+
routes,
|
|
903
|
+
services
|
|
904
|
+
});
|
|
905
|
+
module.exports = index;
|