@develit-services/notification 0.0.3 → 0.0.4
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/dist/src.cjs +636 -0
- package/dist/src.d.cts +491 -0
- package/dist/src.d.mts +489 -0
- package/dist/src.d.ts +491 -0
- package/dist/src.mjs +625 -0
- package/package.json +6 -1
- package/@types/consts/audit-log.consts.ts +0 -7
- package/@types/consts/index.ts +0 -1
- package/@types/database/audit-log.types.ts +0 -7
- package/@types/database/index.ts +0 -1
- package/@types/email/IEmail.connector.ts +0 -21
- package/@types/email/IEmail.types.ts +0 -25
- package/@types/email/ecomail/ecomail.connector.ts +0 -139
- package/@types/email/ecomail/ecomail.types.ts +0 -27
- package/@types/email/ecomail/index.ts +0 -2
- package/@types/email/index.ts +0 -3
- package/@types/index.ts +0 -9
- package/@types/io/index.ts +0 -3
- package/@types/io/sendEmail.ts +0 -18
- package/@types/io/sendSlack.ts +0 -19
- package/@types/io/sendSms.ts +0 -20
- package/@types/pushNotification/IPushNotification.ts +0 -1
- package/@types/pushNotification/index.ts +0 -1
- package/@types/queue.ts +0 -19
- package/@types/service.ts +0 -30
- package/@types/slack/ISlack.types.ts +0 -3
- package/@types/slack/index.ts +0 -1
- package/@types/slack/slack.connector.ts +0 -27
- package/@types/sms/ISms.connector.ts +0 -22
- package/@types/sms/ISms.types.ts +0 -4
- package/@types/sms/index.ts +0 -3
- package/@types/sms/twilio/index.ts +0 -1
- package/@types/sms/twilio/twilio.connector.ts +0 -35
- package/CHANGELOG.md +0 -32
- package/build.config.ts +0 -29
- package/drizzle.config.ts +0 -3
- package/src/database/drizzle.ts +0 -6
- package/src/database/migrations/0000_funny_beast.sql +0 -12
- package/src/database/migrations/meta/0000_snapshot.json +0 -101
- package/src/database/migrations/meta/_journal.json +0 -13
- package/src/database/schema/audit-log.schema.ts +0 -13
- package/src/database/schema/index.ts +0 -1
- package/src/defineNotificationService.ts +0 -328
- package/src/defineNotificationWrangler.ts +0 -78
- package/src/index.ts +0 -12
- package/src/utils/connectors.ts +0 -1
- package/src/utils/database/command/create-audit-log.command.ts +0 -34
- package/src/utils/database/command/index.ts +0 -1
- package/src/utils/database/index.ts +0 -1
- package/src/utils/email.ts +0 -25
- package/src/utils/index.ts +0 -3
- package/src/utils/sms.ts +0 -25
- package/test/env.d.ts +0 -7
- package/test/integration/sendEmail.test.ts +0 -88
- package/test/setup/migrations.ts +0 -3
- package/test/unit/connectors/ecomail.connector.ts +0 -715
- package/test/unit/email.test.ts +0 -55
- package/tsconfig.json +0 -3
- package/vitest.config.ts +0 -31
- package/worker-configuration.d.ts +0 -25
- package/wrangler.jsonc +0 -209
- package/wrangler.ts +0 -106
package/dist/src.cjs
ADDED
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
const backendSdk = require('@develit-io/backend-sdk');
|
|
6
|
+
const z = require('zod');
|
|
7
|
+
const v4 = require('zod/v4');
|
|
8
|
+
const twilio = require('twilio');
|
|
9
|
+
const sqliteCore = require('drizzle-orm/sqlite-core');
|
|
10
|
+
require('drizzle-orm');
|
|
11
|
+
const cloudflare_workers = require('cloudflare:workers');
|
|
12
|
+
const d1 = require('drizzle-orm/d1');
|
|
13
|
+
|
|
14
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
15
|
+
|
|
16
|
+
const z__default = /*#__PURE__*/_interopDefaultCompat(z);
|
|
17
|
+
const twilio__default = /*#__PURE__*/_interopDefaultCompat(twilio);
|
|
18
|
+
|
|
19
|
+
const iContactSchema = z.z.union([
|
|
20
|
+
z.z.string(),
|
|
21
|
+
z.z.object({
|
|
22
|
+
email: z.z.string(),
|
|
23
|
+
name: z.z.union([z.z.string(), z.z.undefined()])
|
|
24
|
+
})
|
|
25
|
+
]);
|
|
26
|
+
const iEmailSchema = z.z.object({
|
|
27
|
+
to: z.z.union([iContactSchema, z.z.array(iContactSchema)]),
|
|
28
|
+
replyTo: z.z.union([iContactSchema, z.z.array(iContactSchema)]).optional(),
|
|
29
|
+
cc: z.z.union([iContactSchema, z.z.array(iContactSchema)]).optional(),
|
|
30
|
+
bcc: z.z.union([iContactSchema, z.z.array(iContactSchema)]).optional(),
|
|
31
|
+
from: iContactSchema.optional(),
|
|
32
|
+
subject: z.z.string(),
|
|
33
|
+
text: z.z.string().optional(),
|
|
34
|
+
html: z.z.string().optional(),
|
|
35
|
+
templateId: z.z.number().optional(),
|
|
36
|
+
templateVariables: z.z.record(z.z.string(), z.z.string()).optional()
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
class IEmailConnector {
|
|
40
|
+
static providerName;
|
|
41
|
+
API_KEY;
|
|
42
|
+
SMTP_HOST;
|
|
43
|
+
SENDER;
|
|
44
|
+
constructor({
|
|
45
|
+
API_KEY,
|
|
46
|
+
SMTP_HOST,
|
|
47
|
+
SENDER
|
|
48
|
+
}) {
|
|
49
|
+
this.API_KEY = API_KEY;
|
|
50
|
+
this.SMTP_HOST = SMTP_HOST;
|
|
51
|
+
this.SENDER = SENDER;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
class EcomailConnector extends IEmailConnector {
|
|
56
|
+
static providerName = "ecomail";
|
|
57
|
+
constructor({
|
|
58
|
+
API_KEY,
|
|
59
|
+
SMTP_HOST,
|
|
60
|
+
SENDER
|
|
61
|
+
}) {
|
|
62
|
+
super({ API_KEY, SMTP_HOST, SENDER });
|
|
63
|
+
}
|
|
64
|
+
async sendEmail(email) {
|
|
65
|
+
if (email.templateVariables) {
|
|
66
|
+
for (const [key, value] of Object.entries(email.templateVariables)) {
|
|
67
|
+
if (typeof value === "string" && String(value).includes("localhost") && key in email.templateVariables) {
|
|
68
|
+
email.templateVariables[key] = String(value).replace("localhost", "origin");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const emEmail = this.convertEmail(email);
|
|
73
|
+
const uri = emEmail.message.template_id ? "https://api2.ecomailapp.cz/transactional/send-template" : "https://api2.ecomailapp.cz/transactional/send-message";
|
|
74
|
+
const [data, error] = await backendSdk.useResult(
|
|
75
|
+
fetch(uri, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: {
|
|
78
|
+
"Content-Type": "application/json",
|
|
79
|
+
key: this.API_KEY
|
|
80
|
+
},
|
|
81
|
+
body: JSON.stringify(emEmail)
|
|
82
|
+
})
|
|
83
|
+
);
|
|
84
|
+
if (error) throw error;
|
|
85
|
+
return data;
|
|
86
|
+
}
|
|
87
|
+
convertEmail(email) {
|
|
88
|
+
const toContacts = this.convertContacts(email.to);
|
|
89
|
+
const ccContacts = email.cc ? this.convertContacts(email.cc) : [];
|
|
90
|
+
const bccContacts = email.bcc ? this.convertContacts(email.bcc) : [];
|
|
91
|
+
const replyTo = email.replyTo ? this.convertContacts(email.replyTo)[0] : void 0;
|
|
92
|
+
const from = this.convertContact(email.from || this.SENDER);
|
|
93
|
+
const subject = email.subject;
|
|
94
|
+
const textAttachments = email.text ? [{ name: "plain", type: "text/plain", content: email.text }] : [];
|
|
95
|
+
const htmlAttachments = email.html ? [{ type: "text/html", content: email.html, name: "email_body.html" }] : [];
|
|
96
|
+
const attachments = [...textAttachments, ...htmlAttachments];
|
|
97
|
+
const contacts = toContacts.flatMap((to) => {
|
|
98
|
+
const entries = [];
|
|
99
|
+
if (ccContacts.length > 0) {
|
|
100
|
+
ccContacts.forEach((cc) => {
|
|
101
|
+
entries.push({ email: cc?.email, name: cc?.name, cc: cc?.email });
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
if (bccContacts.length > 0) {
|
|
105
|
+
bccContacts.forEach((bcc) => {
|
|
106
|
+
entries.push({ email: bcc?.email, name: bcc?.name, bcc: bcc?.email });
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return entries.length > 0 ? entries : [to];
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
message: {
|
|
113
|
+
from_email: from?.email,
|
|
114
|
+
from_name: from.name || "",
|
|
115
|
+
subject,
|
|
116
|
+
attachments,
|
|
117
|
+
to: contacts,
|
|
118
|
+
text: email.text,
|
|
119
|
+
html: email.html,
|
|
120
|
+
reply_to: replyTo?.email,
|
|
121
|
+
template_id: email.templateId,
|
|
122
|
+
global_merge_vars: email.templateVariables ? Object.keys(email.templateVariables).map((key) => ({
|
|
123
|
+
name: key,
|
|
124
|
+
content: email.templateVariables[key]
|
|
125
|
+
})) : null
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
convertContacts(contacts) {
|
|
130
|
+
if (!contacts) {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
const contactArray = Array.isArray(contacts) ? contacts : [contacts];
|
|
134
|
+
return contactArray.map(this.convertContact);
|
|
135
|
+
}
|
|
136
|
+
convertContact(contact) {
|
|
137
|
+
if (typeof contact === "string") {
|
|
138
|
+
return { email: contact, name: void 0 };
|
|
139
|
+
}
|
|
140
|
+
return { email: contact?.email, name: contact?.name };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const sendEmailInputSchema = z__default.object({
|
|
145
|
+
email: iEmailSchema,
|
|
146
|
+
metadata: z__default.object({
|
|
147
|
+
userAgent: z__default.string().optional(),
|
|
148
|
+
ip: z__default.ipv4().or(z__default.ipv6()).optional(),
|
|
149
|
+
initiator: z__default.object({
|
|
150
|
+
service: z__default.string(),
|
|
151
|
+
userId: z__default.string().optional()
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const sendSlackInputSchema = v4.z.object({
|
|
157
|
+
slack: v4.z.object({
|
|
158
|
+
message: v4.z.string()
|
|
159
|
+
}),
|
|
160
|
+
metadata: v4.z.object({
|
|
161
|
+
userAgent: v4.z.string().optional(),
|
|
162
|
+
ip: v4.z.ipv4().or(v4.z.ipv6()).optional(),
|
|
163
|
+
initiator: v4.z.object({
|
|
164
|
+
service: v4.z.string(),
|
|
165
|
+
userId: v4.z.string().optional()
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const sendSmsInputSchema = z.z.object({
|
|
171
|
+
sms: z.z.object({
|
|
172
|
+
message: z.z.string(),
|
|
173
|
+
to: z.z.string()
|
|
174
|
+
}),
|
|
175
|
+
metadata: z.z.object({
|
|
176
|
+
userAgent: z.z.string().optional(),
|
|
177
|
+
ip: z.z.ipv4().or(z.z.ipv6()).optional(),
|
|
178
|
+
initiator: z.z.object({
|
|
179
|
+
service: z.z.string(),
|
|
180
|
+
userId: z.z.string().optional()
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
class ISmsConnector {
|
|
186
|
+
static providerName;
|
|
187
|
+
ACCOUNT_ID;
|
|
188
|
+
AUTH_TOKEN;
|
|
189
|
+
SERVICE_ID;
|
|
190
|
+
constructor({
|
|
191
|
+
ACCOUNT_ID,
|
|
192
|
+
AUTH_TOKEN,
|
|
193
|
+
SERVICE_ID
|
|
194
|
+
}) {
|
|
195
|
+
this.ACCOUNT_ID = ACCOUNT_ID;
|
|
196
|
+
this.AUTH_TOKEN = AUTH_TOKEN;
|
|
197
|
+
this.SERVICE_ID = SERVICE_ID;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
class TwilioConnector extends ISmsConnector {
|
|
202
|
+
static providerName = "twilio";
|
|
203
|
+
twilioClient;
|
|
204
|
+
constructor({
|
|
205
|
+
ACCOUNT_ID,
|
|
206
|
+
AUTH_TOKEN,
|
|
207
|
+
SERVICE_ID
|
|
208
|
+
}) {
|
|
209
|
+
super({ ACCOUNT_ID, AUTH_TOKEN, SERVICE_ID });
|
|
210
|
+
this.twilioClient = twilio__default(ACCOUNT_ID, AUTH_TOKEN);
|
|
211
|
+
}
|
|
212
|
+
async sendSms(sms) {
|
|
213
|
+
const message = await this.twilioClient.messages.create({
|
|
214
|
+
body: sms.message,
|
|
215
|
+
messagingServiceSid: this.SERVICE_ID,
|
|
216
|
+
to: sms.to
|
|
217
|
+
});
|
|
218
|
+
if (message.errorMessage)
|
|
219
|
+
return backendSdk.createInternalError(null, {
|
|
220
|
+
message: message.errorMessage,
|
|
221
|
+
status: message.errorCode
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const auditLog = sqliteCore.sqliteTable("audit_log", {
|
|
227
|
+
...backendSdk.base,
|
|
228
|
+
event: sqliteCore.text("event").$type().notNull(),
|
|
229
|
+
ip: sqliteCore.text("ip"),
|
|
230
|
+
userAgent: sqliteCore.text("user_agent"),
|
|
231
|
+
description: sqliteCore.text("description"),
|
|
232
|
+
initiatorService: sqliteCore.text("initiator_service").notNull(),
|
|
233
|
+
initiatorUserId: sqliteCore.text("initiator_user_id")
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const schema = {
|
|
237
|
+
__proto__: null,
|
|
238
|
+
auditLog: auditLog
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const tables = schema;
|
|
242
|
+
|
|
243
|
+
const initiateEmailConnector = async (provider, apiKey, smtpHost, sender) => {
|
|
244
|
+
const connector = [EcomailConnector].find(
|
|
245
|
+
(conn) => conn.providerName === provider
|
|
246
|
+
);
|
|
247
|
+
if (!connector)
|
|
248
|
+
throw backendSdk.createInternalError(null, {
|
|
249
|
+
message: "Unsupported email provider",
|
|
250
|
+
status: 404
|
|
251
|
+
});
|
|
252
|
+
return new connector({ API_KEY: apiKey, SMTP_HOST: smtpHost, SENDER: sender });
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const createAuditLogCommand = async ({
|
|
256
|
+
db,
|
|
257
|
+
auditLog: {
|
|
258
|
+
event,
|
|
259
|
+
description,
|
|
260
|
+
initiatorService,
|
|
261
|
+
initiatorUserId,
|
|
262
|
+
ip,
|
|
263
|
+
userAgent
|
|
264
|
+
}
|
|
265
|
+
}) => {
|
|
266
|
+
const command = db.insert(tables.auditLog).values({
|
|
267
|
+
id: backendSdk.uuidv4(),
|
|
268
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
269
|
+
event,
|
|
270
|
+
description,
|
|
271
|
+
ip,
|
|
272
|
+
userAgent,
|
|
273
|
+
initiatorService,
|
|
274
|
+
initiatorUserId
|
|
275
|
+
});
|
|
276
|
+
return {
|
|
277
|
+
command
|
|
278
|
+
};
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const initiateSmsConnector = async (provider, accountId, authToken, serviceId) => {
|
|
282
|
+
const connector = [TwilioConnector].find(
|
|
283
|
+
(conn) => conn.providerName === provider
|
|
284
|
+
);
|
|
285
|
+
if (!connector)
|
|
286
|
+
throw backendSdk.createInternalError(null, {
|
|
287
|
+
message: "Unsupported sms provider",
|
|
288
|
+
status: 404
|
|
289
|
+
});
|
|
290
|
+
return new connector({
|
|
291
|
+
ACCOUNT_ID: accountId,
|
|
292
|
+
AUTH_TOKEN: authToken,
|
|
293
|
+
SERVICE_ID: serviceId
|
|
294
|
+
});
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
class SlackConnector {
|
|
298
|
+
webhooks;
|
|
299
|
+
constructor(webhooks) {
|
|
300
|
+
this.webhooks = webhooks;
|
|
301
|
+
}
|
|
302
|
+
async sendNotificationToAllSlack(message) {
|
|
303
|
+
const controller = new AbortController();
|
|
304
|
+
const timeoutId = setTimeout(() => controller.abort(), 3e3);
|
|
305
|
+
for (const webhook of this.webhooks) {
|
|
306
|
+
let response = await fetch(webhook, {
|
|
307
|
+
method: "POST",
|
|
308
|
+
body: JSON.stringify({ text: message }),
|
|
309
|
+
headers: { "Content-Type": "application/json" },
|
|
310
|
+
signal: controller.signal
|
|
311
|
+
});
|
|
312
|
+
clearTimeout(timeoutId);
|
|
313
|
+
if (!response.ok) {
|
|
314
|
+
throw new Error("Failed sending Slack notification to " + message);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
class NotificationServiceBase extends backendSdk.develitWorker(
|
|
321
|
+
cloudflare_workers.WorkerEntrypoint
|
|
322
|
+
) {
|
|
323
|
+
name;
|
|
324
|
+
slackConnector = new SlackConnector(this.env.SLACK_WEBHOOKS);
|
|
325
|
+
emailConnector;
|
|
326
|
+
smsConnector;
|
|
327
|
+
db;
|
|
328
|
+
constructor(ctx, env) {
|
|
329
|
+
super(ctx, env);
|
|
330
|
+
this.name = "notification-service";
|
|
331
|
+
this.db = d1.drizzle(this.env.NOTIFICATION_D1, { schema: tables });
|
|
332
|
+
}
|
|
333
|
+
@backendSdk.cloudflareQueue({ baseDelay: 60 })
|
|
334
|
+
async queue(batch) {
|
|
335
|
+
for (const message of batch.messages) {
|
|
336
|
+
this.logInput({ message });
|
|
337
|
+
let notificationAction;
|
|
338
|
+
const { type, metadata, payload } = message.body;
|
|
339
|
+
if (type === "email") {
|
|
340
|
+
const [emailConnector, error2] = await backendSdk.useResult(
|
|
341
|
+
initiateEmailConnector(
|
|
342
|
+
this.env.EMAIL_PROVIDER,
|
|
343
|
+
this.env.EMAIL_API_KEY,
|
|
344
|
+
this.env.EMAIL_SMTP_HOST,
|
|
345
|
+
this.env.EMAIL_SENDER
|
|
346
|
+
)
|
|
347
|
+
);
|
|
348
|
+
if (error2) {
|
|
349
|
+
this.logError({ error: error2 });
|
|
350
|
+
message.retry();
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
this.emailConnector = emailConnector;
|
|
354
|
+
}
|
|
355
|
+
if (type === "sms") {
|
|
356
|
+
const [smsConnector, error2] = await backendSdk.useResult(
|
|
357
|
+
initiateSmsConnector(
|
|
358
|
+
this.env.SMS_PROVIDER,
|
|
359
|
+
this.env.SMS_ACCOUNT_ID,
|
|
360
|
+
this.env.SMS_AUTH_TOKEN,
|
|
361
|
+
this.env.SMS_SERVICE_ID
|
|
362
|
+
)
|
|
363
|
+
);
|
|
364
|
+
if (error2) {
|
|
365
|
+
this.logError({ error: error2 });
|
|
366
|
+
message.retry();
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
this.smsConnector = smsConnector;
|
|
370
|
+
}
|
|
371
|
+
switch (type) {
|
|
372
|
+
case "email":
|
|
373
|
+
notificationAction = async () => this._sendEmail({
|
|
374
|
+
email: payload.email,
|
|
375
|
+
metadata
|
|
376
|
+
});
|
|
377
|
+
break;
|
|
378
|
+
case "sms":
|
|
379
|
+
notificationAction = async () => this._sendSms({
|
|
380
|
+
sms: payload.sms,
|
|
381
|
+
metadata
|
|
382
|
+
});
|
|
383
|
+
break;
|
|
384
|
+
case "pushNotification":
|
|
385
|
+
notificationAction = async () => this._sendPushNotification();
|
|
386
|
+
break;
|
|
387
|
+
case "slack":
|
|
388
|
+
notificationAction = async () => this.sendSlackNotification({
|
|
389
|
+
slack: message.body.payload.slack,
|
|
390
|
+
metadata
|
|
391
|
+
});
|
|
392
|
+
break;
|
|
393
|
+
default:
|
|
394
|
+
this.logError({ error: `Unknown notification type: ${type}` });
|
|
395
|
+
message.retry();
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
const { error, message: errorMessage } = await notificationAction();
|
|
399
|
+
if (error) {
|
|
400
|
+
this.logError({ error: errorMessage });
|
|
401
|
+
message.retry();
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
message.ack();
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
@backendSdk.action("private-send-email")
|
|
408
|
+
async _sendEmail(input) {
|
|
409
|
+
return this.handleAction(
|
|
410
|
+
{ data: input, schema: sendEmailInputSchema },
|
|
411
|
+
{ successMessage: "Email sent." },
|
|
412
|
+
async (params) => {
|
|
413
|
+
const {
|
|
414
|
+
email,
|
|
415
|
+
metadata: {
|
|
416
|
+
ip,
|
|
417
|
+
userAgent,
|
|
418
|
+
initiator: { service, userId }
|
|
419
|
+
}
|
|
420
|
+
} = params;
|
|
421
|
+
if (!this.emailConnector)
|
|
422
|
+
this.emailConnector = await initiateEmailConnector(
|
|
423
|
+
this.env.EMAIL_PROVIDER,
|
|
424
|
+
this.env.EMAIL_API_KEY,
|
|
425
|
+
this.env.EMAIL_SMTP_HOST,
|
|
426
|
+
this.env.EMAIL_SENDER
|
|
427
|
+
);
|
|
428
|
+
const response = await this.emailConnector.sendEmail(email);
|
|
429
|
+
if (!response?.ok) {
|
|
430
|
+
throw backendSdk.createInternalError(null, {
|
|
431
|
+
message: `Could not send email: ${JSON.stringify(await response?.json())}`,
|
|
432
|
+
status: 500
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
const { command } = await createAuditLogCommand({
|
|
436
|
+
db: this.db,
|
|
437
|
+
auditLog: {
|
|
438
|
+
id: backendSdk.uuidv4(),
|
|
439
|
+
event: "EMAIL",
|
|
440
|
+
ip,
|
|
441
|
+
initiatorService: service,
|
|
442
|
+
initiatorUserId: userId,
|
|
443
|
+
userAgent,
|
|
444
|
+
description: JSON.stringify(input)
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
await this.db.batch([command]);
|
|
448
|
+
return {};
|
|
449
|
+
}
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
@backendSdk.action("private-send-sms")
|
|
453
|
+
async _sendSms(input) {
|
|
454
|
+
return this.handleAction(
|
|
455
|
+
{ data: input, schema: sendSmsInputSchema },
|
|
456
|
+
{ successMessage: "Sms sent." },
|
|
457
|
+
async (params) => {
|
|
458
|
+
const {
|
|
459
|
+
sms: { message, to },
|
|
460
|
+
metadata: {
|
|
461
|
+
ip,
|
|
462
|
+
userAgent,
|
|
463
|
+
initiator: { service, userId }
|
|
464
|
+
}
|
|
465
|
+
} = params;
|
|
466
|
+
await this.smsConnector.sendSms({ message, to });
|
|
467
|
+
const { command } = await createAuditLogCommand({
|
|
468
|
+
db: this.db,
|
|
469
|
+
auditLog: {
|
|
470
|
+
id: backendSdk.uuidv4(),
|
|
471
|
+
event: "SMS",
|
|
472
|
+
ip,
|
|
473
|
+
userAgent,
|
|
474
|
+
initiatorService: service,
|
|
475
|
+
initiatorUserId: userId,
|
|
476
|
+
description: JSON.stringify(input)
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
await this.db.batch([command]);
|
|
480
|
+
return {};
|
|
481
|
+
}
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
@backendSdk.action("public-send-email")
|
|
485
|
+
async sendEmail(input) {
|
|
486
|
+
return this.handleAction(
|
|
487
|
+
{ data: input, schema: sendEmailInputSchema },
|
|
488
|
+
{ successMessage: "Email sent." },
|
|
489
|
+
async (params) => {
|
|
490
|
+
const { email, metadata } = params;
|
|
491
|
+
await this.pushToQueue(
|
|
492
|
+
this.env.NOTIFICATIONS_QUEUE,
|
|
493
|
+
{
|
|
494
|
+
type: "email",
|
|
495
|
+
payload: {
|
|
496
|
+
email
|
|
497
|
+
},
|
|
498
|
+
metadata
|
|
499
|
+
}
|
|
500
|
+
);
|
|
501
|
+
return {};
|
|
502
|
+
}
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
@backendSdk.action("public-send-sms")
|
|
506
|
+
async sendSms(input) {
|
|
507
|
+
return this.handleAction(
|
|
508
|
+
{ data: input, schema: sendSmsInputSchema },
|
|
509
|
+
{ successMessage: "SMS sent." },
|
|
510
|
+
async (params) => {
|
|
511
|
+
const {
|
|
512
|
+
sms: { message, to },
|
|
513
|
+
metadata
|
|
514
|
+
} = params;
|
|
515
|
+
await this.pushToQueue(
|
|
516
|
+
this.env.NOTIFICATIONS_QUEUE,
|
|
517
|
+
{
|
|
518
|
+
type: "sms",
|
|
519
|
+
payload: {
|
|
520
|
+
sms: {
|
|
521
|
+
message,
|
|
522
|
+
to
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
metadata
|
|
526
|
+
}
|
|
527
|
+
);
|
|
528
|
+
return {};
|
|
529
|
+
}
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
@backendSdk.action("send-push-notification")
|
|
533
|
+
async _sendPushNotification() {
|
|
534
|
+
this.logInput({});
|
|
535
|
+
this.logError({ error: "Method not implemented." });
|
|
536
|
+
throw new Error("Method not implemented.");
|
|
537
|
+
}
|
|
538
|
+
@backendSdk.action("send-slack-notification")
|
|
539
|
+
async sendSlackNotification(input) {
|
|
540
|
+
return this.handleAction(
|
|
541
|
+
{
|
|
542
|
+
data: input,
|
|
543
|
+
schema: sendSlackInputSchema
|
|
544
|
+
},
|
|
545
|
+
{ successMessage: "Slack sent." },
|
|
546
|
+
async (params) => {
|
|
547
|
+
const { slack } = params;
|
|
548
|
+
await this.slackConnector.sendNotificationToAllSlack(slack.message);
|
|
549
|
+
return {};
|
|
550
|
+
}
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function defineNotificationService() {
|
|
555
|
+
return class NotificationService extends NotificationServiceBase {
|
|
556
|
+
constructor(ctx, env) {
|
|
557
|
+
super(ctx, env);
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function defineNotificationServiceWrangler(config) {
|
|
563
|
+
const { name, envs } = config;
|
|
564
|
+
const base = {
|
|
565
|
+
name,
|
|
566
|
+
main: "./src/index.ts",
|
|
567
|
+
compatibility_date: "2025-06-04",
|
|
568
|
+
compatibility_flags: ["nodejs_compat"],
|
|
569
|
+
d1_databases: [
|
|
570
|
+
{
|
|
571
|
+
binding: "NOTIFICATION_D1",
|
|
572
|
+
database_name: name,
|
|
573
|
+
database_id: envs.local.d1.id,
|
|
574
|
+
migrations_dir: "./src/database/migrations"
|
|
575
|
+
}
|
|
576
|
+
],
|
|
577
|
+
queues: {
|
|
578
|
+
producers: [
|
|
579
|
+
{
|
|
580
|
+
binding: "NOTIFICATIONS_QUEUE",
|
|
581
|
+
queue: `${name}`
|
|
582
|
+
}
|
|
583
|
+
],
|
|
584
|
+
consumers: [
|
|
585
|
+
{
|
|
586
|
+
queue: `${name}`,
|
|
587
|
+
max_batch_size: 1,
|
|
588
|
+
max_batch_timeout: 5,
|
|
589
|
+
dead_letter_queue: `${name}-dlq`
|
|
590
|
+
}
|
|
591
|
+
]
|
|
592
|
+
},
|
|
593
|
+
env: {}
|
|
594
|
+
};
|
|
595
|
+
for (const [envName, envCfg] of Object.entries(envs).filter(
|
|
596
|
+
([key]) => key !== "local"
|
|
597
|
+
)) {
|
|
598
|
+
base.env[envName] = {
|
|
599
|
+
vars: {
|
|
600
|
+
...envCfg.vars,
|
|
601
|
+
ENVIRONMENT: envName
|
|
602
|
+
},
|
|
603
|
+
d1_databases: [
|
|
604
|
+
{
|
|
605
|
+
binding: "NOTIFICATION_D1",
|
|
606
|
+
database_name: `${name}-${envName}`,
|
|
607
|
+
database_id: envCfg.d1.id,
|
|
608
|
+
migrations_dir: "./src/database/migrations"
|
|
609
|
+
}
|
|
610
|
+
],
|
|
611
|
+
queues: {
|
|
612
|
+
producers: [
|
|
613
|
+
{
|
|
614
|
+
binding: "NOTIFICATIONS_QUEUE",
|
|
615
|
+
queue: `${name}-${envName}`
|
|
616
|
+
}
|
|
617
|
+
],
|
|
618
|
+
consumers: [
|
|
619
|
+
{
|
|
620
|
+
queue: `${name}-${envName}`,
|
|
621
|
+
max_batch_size: envCfg.queue?.max_batch_size ?? 1,
|
|
622
|
+
max_batch_timeout: envCfg.queue?.max_batch_timeout ?? 5,
|
|
623
|
+
dead_letter_queue: `${name}-dlq-${envName}`
|
|
624
|
+
}
|
|
625
|
+
]
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
return base;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const NotificationService = defineNotificationService();
|
|
633
|
+
|
|
634
|
+
exports.default = NotificationService;
|
|
635
|
+
exports.defineNotificationService = defineNotificationService;
|
|
636
|
+
exports.defineNotificationServiceWrangler = defineNotificationServiceWrangler;
|