@develit-services/notification 0.0.12 → 0.0.14

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.
@@ -0,0 +1,7 @@
1
+ export const AUDIT_LOG_EVENT_TYPES = [
2
+ 'EMAIL',
3
+ 'SMS',
4
+ 'PUSH_NOTIFICATION',
5
+ 'SLACK',
6
+ ] as const
7
+ export type AuditLogEventType = (typeof AUDIT_LOG_EVENT_TYPES)[number]
@@ -0,0 +1 @@
1
+ export * from './audit-log.consts'
@@ -0,0 +1,7 @@
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> {}
@@ -0,0 +1 @@
1
+ export * from './audit-log.types'
@@ -0,0 +1,21 @@
1
+ import type { IContact, IEmail } from '../../@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
+ }
@@ -0,0 +1,25 @@
1
+ import { z } from 'zod'
2
+
3
+ export const iContactSchema = z.union([
4
+ z.string(),
5
+ z.object({
6
+ email: z.string(),
7
+ name: z.union([z.string(), z.undefined()]),
8
+ }),
9
+ ])
10
+
11
+ export const iEmailSchema = z.object({
12
+ to: z.union([iContactSchema, z.array(iContactSchema)]),
13
+ replyTo: z.union([iContactSchema, z.array(iContactSchema)]).optional(),
14
+ cc: z.union([iContactSchema, z.array(iContactSchema)]).optional(),
15
+ bcc: z.union([iContactSchema, z.array(iContactSchema)]).optional(),
16
+ from: iContactSchema.optional(),
17
+ subject: z.string(),
18
+ text: z.string().optional(),
19
+ html: z.string().optional(),
20
+ templateId: z.number().optional(),
21
+ templateVariables: z.record(z.string(), z.string()).optional(),
22
+ })
23
+
24
+ export type IContact = z.infer<typeof iContactSchema>
25
+ export type IEmail = z.infer<typeof iEmailSchema>
@@ -0,0 +1,139 @@
1
+ import { useResult } from '@develit-io/backend-sdk'
2
+ import {
3
+ type EMAttachment,
4
+ type EMContact,
5
+ type EMEmail,
6
+ type IContact,
7
+ type IEmail,
8
+ IEmailConnector,
9
+ } from '../../email'
10
+
11
+ export class EcomailConnector extends IEmailConnector {
12
+ static providerName = 'ecomail'
13
+
14
+ constructor({
15
+ API_KEY,
16
+ SMTP_HOST,
17
+ SENDER,
18
+ }: { API_KEY: string; SMTP_HOST: string; SENDER: IContact }) {
19
+ super({ API_KEY, SMTP_HOST, SENDER })
20
+ }
21
+
22
+ async sendEmail(email: IEmail): Promise<Response | null> {
23
+ // This is needed only for ecomail connector because when template variables includes word 'localhost' it does not send email
24
+ if (email.templateVariables) {
25
+ for (const [key, value] of Object.entries(email.templateVariables)) {
26
+ if (
27
+ typeof value === 'string' &&
28
+ String(value).includes('localhost') &&
29
+ key in email.templateVariables
30
+ ) {
31
+ email.templateVariables[key as keyof typeof email.templateVariables] =
32
+ String(value).replace('localhost', 'origin')
33
+ }
34
+ }
35
+ }
36
+
37
+ const emEmail: EMEmail = this.convertEmail(email)
38
+ const uri = emEmail.message.template_id
39
+ ? 'https://api2.ecomailapp.cz/transactional/send-template'
40
+ : 'https://api2.ecomailapp.cz/transactional/send-message'
41
+
42
+ const [data, error] = await useResult(
43
+ fetch(uri, {
44
+ method: 'POST',
45
+ headers: {
46
+ 'Content-Type': 'application/json',
47
+ key: this.API_KEY,
48
+ },
49
+ body: JSON.stringify(emEmail),
50
+ }),
51
+ )
52
+ if (error) throw error
53
+
54
+ return data
55
+ }
56
+
57
+ protected convertEmail(email: IEmail): EMEmail {
58
+ const toContacts: EMContact[] = this.convertContacts(email.to)
59
+ const ccContacts: EMContact[] = email.cc
60
+ ? this.convertContacts(email.cc)
61
+ : []
62
+ const bccContacts: EMContact[] = email.bcc
63
+ ? this.convertContacts(email.bcc)
64
+ : []
65
+
66
+ const replyTo: EMContact | undefined = email.replyTo
67
+ ? this.convertContacts(email.replyTo)[0]
68
+ : undefined
69
+
70
+ const from: EMContact = this.convertContact(email.from || this.SENDER)
71
+ const subject: string = email.subject
72
+
73
+ const textAttachments: EMAttachment[] = email.text
74
+ ? [{ name: 'plain', type: 'text/plain', content: email.text }]
75
+ : []
76
+
77
+ const htmlAttachments: EMAttachment[] = email.html
78
+ ? [{ type: 'text/html', content: email.html, name: 'email_body.html' }]
79
+ : []
80
+
81
+ const attachments: EMAttachment[] = [...textAttachments, ...htmlAttachments]
82
+
83
+ const contacts: EMContact[] = toContacts.flatMap((to) => {
84
+ const entries: EMContact[] = []
85
+ if (ccContacts.length > 0) {
86
+ ccContacts.forEach((cc) => {
87
+ entries.push({ email: cc?.email, name: cc?.name, cc: cc?.email })
88
+ })
89
+ }
90
+ if (bccContacts.length > 0) {
91
+ bccContacts.forEach((bcc) => {
92
+ entries.push({ email: bcc?.email, name: bcc?.name, bcc: bcc?.email })
93
+ })
94
+ }
95
+ return entries.length > 0 ? entries : [to]
96
+ })
97
+
98
+ return {
99
+ message: {
100
+ from_email: from?.email,
101
+ from_name: from.name || '',
102
+ subject,
103
+ attachments,
104
+ to: contacts,
105
+ text: email.text,
106
+ html: email.html,
107
+ reply_to: replyTo?.email,
108
+ template_id: email.templateId,
109
+ global_merge_vars: email.templateVariables
110
+ ? Object.keys(email.templateVariables).map((key) => ({
111
+ name: key,
112
+ content:
113
+ email.templateVariables![
114
+ key as keyof typeof email.templateVariables
115
+ ],
116
+ }))
117
+ : null,
118
+ },
119
+ }
120
+ }
121
+
122
+ protected convertContacts(contacts: IContact | IContact[]): EMContact[] {
123
+ if (!contacts) {
124
+ return []
125
+ }
126
+ const contactArray: IContact[] = Array.isArray(contacts)
127
+ ? contacts
128
+ : [contacts]
129
+ return contactArray.map(this.convertContact)
130
+ }
131
+
132
+ protected convertContact(contact: IContact): EMContact {
133
+ if (typeof contact === 'string') {
134
+ return { email: contact, name: undefined }
135
+ }
136
+
137
+ return { email: contact?.email, name: contact?.name }
138
+ }
139
+ }
@@ -0,0 +1,27 @@
1
+ export interface EMContact {
2
+ email: string
3
+ name?: string
4
+ cc?: string
5
+ bcc?: string
6
+ }
7
+
8
+ export interface EMAttachment {
9
+ type: string
10
+ name: string
11
+ content: string
12
+ }
13
+
14
+ export interface EMEmail {
15
+ message: {
16
+ template_id?: number
17
+ subject: string
18
+ from_name: string
19
+ from_email: string
20
+ reply_to?: string
21
+ to: EMContact[]
22
+ attachments: EMAttachment[]
23
+ text?: string
24
+ html?: string
25
+ global_merge_vars: object | null
26
+ }
27
+ }
@@ -0,0 +1,2 @@
1
+ export * from './ecomail.connector'
2
+ export * from './ecomail.types'
@@ -0,0 +1,3 @@
1
+ export * from './IEmail.types'
2
+ export * from './IEmail.connector'
3
+ export * from './ecomail'
@@ -0,0 +1,9 @@
1
+ export * from './email'
2
+ export * from './io'
3
+ export * from './pushNotification'
4
+ export * from './queue'
5
+ export * from './sms'
6
+ export * from './slack'
7
+ export * from './database'
8
+ export * from './consts'
9
+ export * from './service'
@@ -0,0 +1,3 @@
1
+ export * from './sendEmail'
2
+ export * from './sendSlack'
3
+ export * from './sendSms'
@@ -0,0 +1,18 @@
1
+ import { iEmailSchema } from '../'
2
+ import z from 'zod'
3
+
4
+ export const sendEmailInputSchema = z.object({
5
+ email: iEmailSchema,
6
+ metadata: z.object({
7
+ userAgent: z.string().optional(),
8
+ ip: z.ipv4().or(z.ipv6()).optional(),
9
+ initiator: z.object({
10
+ service: z.string(),
11
+ userId: z.string().optional(),
12
+ }),
13
+ }),
14
+ })
15
+
16
+ export interface SendEmailInput extends z.infer<typeof sendEmailInputSchema> {}
17
+
18
+ export interface SendEmailOutput {}
@@ -0,0 +1,19 @@
1
+ import { z } from 'zod/v4'
2
+
3
+ export const sendSlackInputSchema = z.object({
4
+ slack: z.object({
5
+ message: z.string(),
6
+ }),
7
+ metadata: z.object({
8
+ userAgent: z.string().optional(),
9
+ ip: z.ipv4().or(z.ipv6()).optional(),
10
+ initiator: z.object({
11
+ service: z.string(),
12
+ userId: z.string().optional(),
13
+ }),
14
+ }),
15
+ })
16
+
17
+ export interface SendSlackInput extends z.infer<typeof sendSlackInputSchema> {}
18
+
19
+ export interface SendSlackOutput {}
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod'
2
+
3
+ export const sendSmsInputSchema = z.object({
4
+ sms: z.object({
5
+ message: z.string(),
6
+ to: z.string(),
7
+ }),
8
+ metadata: z.object({
9
+ userAgent: z.string().optional(),
10
+ ip: z.ipv4().or(z.ipv6()).optional(),
11
+ initiator: z.object({
12
+ service: z.string(),
13
+ userId: z.string().optional(),
14
+ }),
15
+ }),
16
+ })
17
+
18
+ export interface SendSmsInput extends z.infer<typeof sendSmsInputSchema> {}
19
+
20
+ export interface SendSmsOutput {}
@@ -0,0 +1 @@
1
+ export interface IPushNotification {}
@@ -0,0 +1 @@
1
+ export * from './IPushNotification'
@@ -0,0 +1,19 @@
1
+ import type { IEmail, IPushNotification, ISlack, ISms } from './'
2
+
3
+ export interface NotificationQueueMessage {
4
+ type: 'email' | 'sms' | 'pushNotification' | 'slack'
5
+ metadata: {
6
+ userAgent?: string
7
+ ip?: string
8
+ initiator: {
9
+ service: string
10
+ userId?: string
11
+ }
12
+ }
13
+ payload: {
14
+ email?: IEmail
15
+ sms?: ISms
16
+ pushNotification?: IPushNotification
17
+ slack?: ISlack
18
+ }
19
+ }
@@ -0,0 +1,30 @@
1
+ export interface NotificationServiceEnvironmentConfig {
2
+ queue?: {
3
+ max_batch_size?: number
4
+ max_batch_timeout?: number
5
+ }
6
+ d1: {
7
+ id: string
8
+ }
9
+ vars: {
10
+ EMAIL_PROVIDER: 'ecomail'
11
+ EMAIL_SENDER: { name: string; email: string }
12
+ EMAIL_SMTP_HOST: string
13
+ EMAIL_API_KEY: string
14
+ SMS_PROVIDER: 'twilio'
15
+ SMS_ACCOUNT_ID: string
16
+ SMS_AUTH_TOKEN: string
17
+ SMS_SERVICE_ID: string | number
18
+ SLACK_WEBHOOKS: [string]
19
+ }
20
+ }
21
+
22
+ export interface NotificationServiceWranglerConfig {
23
+ name: string
24
+ envs: {
25
+ local: NotificationServiceEnvironmentConfig
26
+ [key: string]: NotificationServiceEnvironmentConfig
27
+ }
28
+ }
29
+
30
+ export interface NotificationServiceEnv extends NotificationEnv {}
@@ -0,0 +1,3 @@
1
+ export interface ISlack {
2
+ message: string
3
+ }
@@ -0,0 +1 @@
1
+ export * from './ISlack.types'
@@ -0,0 +1,27 @@
1
+ export class SlackConnector {
2
+ private webhooks: string[]
3
+
4
+ constructor(webhooks: string[]) {
5
+ this.webhooks = webhooks
6
+ }
7
+
8
+ async sendNotificationToAllSlack(message: string) {
9
+ const controller = new AbortController()
10
+ const timeoutId = setTimeout(() => controller.abort(), 3000)
11
+
12
+ for (const webhook of this.webhooks) {
13
+ let response = await fetch(webhook, {
14
+ method: 'POST',
15
+ body: JSON.stringify({ text: message }),
16
+ headers: { 'Content-Type': 'application/json' },
17
+ signal: controller.signal,
18
+ })
19
+
20
+ clearTimeout(timeoutId)
21
+
22
+ if (!response.ok) {
23
+ throw new Error('Failed sending Slack notification to ' + message)
24
+ }
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,22 @@
1
+ import type { InternalError } from '@develit-io/backend-sdk'
2
+ import type { ISms } from '../'
3
+
4
+ export abstract class ISmsConnector {
5
+ static providerName: string
6
+
7
+ public ACCOUNT_ID: string
8
+ public AUTH_TOKEN: string
9
+ public SERVICE_ID: string
10
+
11
+ protected constructor({
12
+ ACCOUNT_ID,
13
+ AUTH_TOKEN,
14
+ SERVICE_ID,
15
+ }: { ACCOUNT_ID: string; AUTH_TOKEN: string; SERVICE_ID: string }) {
16
+ this.ACCOUNT_ID = ACCOUNT_ID
17
+ this.AUTH_TOKEN = AUTH_TOKEN
18
+ this.SERVICE_ID = SERVICE_ID
19
+ }
20
+
21
+ abstract sendSms(sms: ISms): Promise<InternalError | void>
22
+ }
@@ -0,0 +1,4 @@
1
+ export interface ISms {
2
+ message: string
3
+ to: string
4
+ }
@@ -0,0 +1,3 @@
1
+ export * from './ISms.types'
2
+ export * from './ISms.connector'
3
+ export * from './twilio'
@@ -0,0 +1 @@
1
+ export * from './twilio.connector'
@@ -0,0 +1,35 @@
1
+ import {
2
+ type InternalError,
3
+ createInternalError,
4
+ } from '@develit-io/backend-sdk'
5
+ import { type ISms, ISmsConnector } from '../'
6
+ import twilio from 'twilio'
7
+
8
+ export class TwilioConnector extends ISmsConnector {
9
+ static providerName = 'twilio'
10
+
11
+ readonly twilioClient: twilio.Twilio
12
+
13
+ constructor({
14
+ ACCOUNT_ID,
15
+ AUTH_TOKEN,
16
+ SERVICE_ID,
17
+ }: { ACCOUNT_ID: string; AUTH_TOKEN: string; SERVICE_ID: string }) {
18
+ super({ ACCOUNT_ID, AUTH_TOKEN, SERVICE_ID })
19
+ this.twilioClient = twilio(ACCOUNT_ID, AUTH_TOKEN)
20
+ }
21
+
22
+ async sendSms(sms: ISms): Promise<InternalError | void> {
23
+ const message = await this.twilioClient.messages.create({
24
+ body: sms.message,
25
+ messagingServiceSid: this.SERVICE_ID,
26
+ to: sms.to,
27
+ })
28
+
29
+ if (message.errorMessage)
30
+ return createInternalError(null, {
31
+ message: message.errorMessage,
32
+ status: message.errorCode,
33
+ })
34
+ }
35
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@develit-services/notification",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
4
4
  "author": "Develit.io s.r.o.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -15,13 +15,13 @@
15
15
  "require": "./dist/export/worker.cjs"
16
16
  },
17
17
  "./db-schema": {
18
- "types": "./src/database/schema/index.d.ts",
19
18
  "import": "./src/database/schema/index.ts"
20
19
  },
21
20
  "./package.json": "./package.json"
22
21
  },
23
22
  "files": [
24
23
  "./dist",
24
+ "./@types",
25
25
  "./src/database/schema"
26
26
  ],
27
27
  "scripts": {