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