@internetderdinge/api 1.224.2

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.
Files changed (102) hide show
  1. package/.github/copilot-instructions.md +77 -0
  2. package/CHANGELOG.md +11 -0
  3. package/README.md +52 -0
  4. package/package.json +112 -0
  5. package/src/accounts/accounts.controller.ts +166 -0
  6. package/src/accounts/accounts.route.ts +107 -0
  7. package/src/accounts/accounts.schemas.ts +16 -0
  8. package/src/accounts/accounts.service.ts +85 -0
  9. package/src/accounts/accounts.validation.ts +118 -0
  10. package/src/accounts/auth0.service.ts +226 -0
  11. package/src/config/config.ts +49 -0
  12. package/src/config/logger.ts +33 -0
  13. package/src/config/morgan.ts +22 -0
  14. package/src/config/passport.cjs +30 -0
  15. package/src/config/roles.ts +13 -0
  16. package/src/config/tokens.cjs +10 -0
  17. package/src/devices/devices.controller.ts +276 -0
  18. package/src/devices/devices.model.ts +126 -0
  19. package/src/devices/devices.route.ts +198 -0
  20. package/src/devices/devices.schemas.ts +94 -0
  21. package/src/devices/devices.service.ts +320 -0
  22. package/src/devices/devices.validation.ts +221 -0
  23. package/src/devicesNotifications/devicesNotifications.controller.ts +72 -0
  24. package/src/devicesNotifications/devicesNotifications.model.ts +67 -0
  25. package/src/devicesNotifications/devicesNotifications.route.ts +150 -0
  26. package/src/devicesNotifications/devicesNotifications.schemas.ts +11 -0
  27. package/src/devicesNotifications/devicesNotifications.service.ts +222 -0
  28. package/src/devicesNotifications/devicesNotifications.validation.ts +56 -0
  29. package/src/email/email.service.ts +609 -0
  30. package/src/files/upload.service.ts +145 -0
  31. package/src/i18n/i18n.ts +51 -0
  32. package/src/i18n/saveMissingLocalJsonBackend.ts +92 -0
  33. package/src/index.ts +7 -0
  34. package/src/iotdevice/iotdevice.controller.ts +136 -0
  35. package/src/iotdevice/iotdevice.model.ts +32 -0
  36. package/src/iotdevice/iotdevice.route.ts +181 -0
  37. package/src/iotdevice/iotdevice.schemas.ts +79 -0
  38. package/src/iotdevice/iotdevice.service.ts +732 -0
  39. package/src/iotdevice/iotdevice.validation.ts +61 -0
  40. package/src/middlewares/auth.ts +110 -0
  41. package/src/middlewares/checkJwt.cjs +19 -0
  42. package/src/middlewares/error.js.legacy +44 -0
  43. package/src/middlewares/error.ts +41 -0
  44. package/src/middlewares/mongooseValidations/ensureSameOrganization.ts +15 -0
  45. package/src/middlewares/rateLimiter.ts +10 -0
  46. package/src/middlewares/validate.ts +25 -0
  47. package/src/middlewares/validateAction.ts +41 -0
  48. package/src/middlewares/validateAdmin.ts +21 -0
  49. package/src/middlewares/validateAi.ts +24 -0
  50. package/src/middlewares/validateCurrentAuthUser.ts +23 -0
  51. package/src/middlewares/validateCurrentUser.ts +35 -0
  52. package/src/middlewares/validateDevice.ts +191 -0
  53. package/src/middlewares/validateDeviceUserOrganization.ts +54 -0
  54. package/src/middlewares/validateOrganization.ts +109 -0
  55. package/src/middlewares/validateQuerySearchUserAndOrganization.ts +75 -0
  56. package/src/middlewares/validateTokens.ts +36 -0
  57. package/src/middlewares/validateUser.ts +75 -0
  58. package/src/middlewares/validateZod.ts +54 -0
  59. package/src/models/plugins/index.ts +7 -0
  60. package/src/models/plugins/paginate.plugin.ts +145 -0
  61. package/src/models/plugins/paginateNew.plugin.ts +206 -0
  62. package/src/models/plugins/simplePopulate.ts +12 -0
  63. package/src/models/plugins/toJSON.plugin.ts +51 -0
  64. package/src/organizations/organizations.controller.ts +101 -0
  65. package/src/organizations/organizations.model.ts +62 -0
  66. package/src/organizations/organizations.route.ts +119 -0
  67. package/src/organizations/organizations.schemas.ts +8 -0
  68. package/src/organizations/organizations.service.ts +85 -0
  69. package/src/organizations/organizations.validation.ts +76 -0
  70. package/src/pdf/pdf.controller.ts +18 -0
  71. package/src/pdf/pdf.route.ts +28 -0
  72. package/src/pdf/pdf.schemas.ts +7 -0
  73. package/src/pdf/pdf.service.ts +89 -0
  74. package/src/pdf/pdf.validation.ts +30 -0
  75. package/src/tokens/tokens.controller.ts +81 -0
  76. package/src/tokens/tokens.model.ts +24 -0
  77. package/src/tokens/tokens.route.ts +66 -0
  78. package/src/tokens/tokens.schemas.ts +15 -0
  79. package/src/tokens/tokens.service.ts +46 -0
  80. package/src/tokens/tokens.validation.ts +13 -0
  81. package/src/types/routeSpec.ts +1 -0
  82. package/src/users/users.controller.ts +234 -0
  83. package/src/users/users.model.ts +89 -0
  84. package/src/users/users.route.ts +171 -0
  85. package/src/users/users.schemas.ts +79 -0
  86. package/src/users/users.service.ts +393 -0
  87. package/src/users/users.validation.ts +166 -0
  88. package/src/utils/ApiError.ts +18 -0
  89. package/src/utils/buildRouterAndDocs.ts +85 -0
  90. package/src/utils/catchAsync.ts +9 -0
  91. package/src/utils/comparePapers.service.ts +48 -0
  92. package/src/utils/filterOptions.ts +37 -0
  93. package/src/utils/medicationName.ts +12 -0
  94. package/src/utils/pick.ts +16 -0
  95. package/src/utils/registerOpenApi.ts +32 -0
  96. package/src/utils/urlUtils.ts +14 -0
  97. package/src/utils/userName.ts +27 -0
  98. package/src/utils/zValidations.ts +89 -0
  99. package/src/validations/auth.validation.cjs +60 -0
  100. package/src/validations/custom.validation.ts +26 -0
  101. package/src/validations/index.cjs +2 -0
  102. package/tsconfig.json +22 -0
@@ -0,0 +1,609 @@
1
+ import AWS from 'aws-sdk';
2
+ import type { SES } from 'aws-sdk';
3
+ import config from '../../src/config/config';
4
+ import i18n from '../../src/i18n/i18n';
5
+
6
+ function urlStartsWithHttp(url: string): boolean {
7
+ return url.startsWith('http');
8
+ }
9
+
10
+ const button = ({ link, text, color = '#0076ff' }: { link: string; text: string; color?: string }): string => {
11
+ return `
12
+ <div><!--[if mso]>
13
+ <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="${link}" style="height:40px;v-text-anchor:middle;width:200px;" arcsize="15%" stroke="f" fillcolor="${color}">
14
+ <w:anchorlock/>
15
+ <center>
16
+ <![endif]-->
17
+ <a href="${link}"
18
+ style="background-color:${color};border-radius:4px;color:#ffffff;display:inline-block;font-family:sans-serif;font-size:15px;font-weight:bold;line-height:40px;text-align:center;text-decoration:none;width:200px;-webkit-text-size-adjust:none;">${text}</a>
19
+ <!--[if mso]>
20
+ </center>
21
+ </v:roundrect>
22
+ <![endif]--></div>
23
+ `;
24
+ };
25
+
26
+ const actionButton = ({ link, text, color = '#0076ff' }: { link: string; text: string; color?: string }): string => {
27
+ return `<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
28
+ <tr>
29
+ <td align="center">
30
+ <table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
31
+ <tr>
32
+ <td align="center">
33
+ <a href="${link}" class="f-fallback button" target="_blank">${text}</a>
34
+ </td>
35
+ </tr>
36
+ </table>
37
+ </td>
38
+ </tr>
39
+ </table>`;
40
+ };
41
+
42
+ interface SendEmailParams {
43
+ title?: string;
44
+ body?: string;
45
+ url?: string;
46
+ domain?: string;
47
+ image?: string;
48
+ email: string;
49
+ actionButtonText?: string;
50
+ lng?: string;
51
+ }
52
+
53
+ export const sendEmail = async ({
54
+ title = 'Kein Titel',
55
+ body = 'Kein Inhalt',
56
+ url = '',
57
+ domain = 'memo',
58
+ image,
59
+ email,
60
+ actionButtonText,
61
+ lng,
62
+ }: SendEmailParams): Promise<void> => {
63
+ const interactive = '#0076ff';
64
+ AWS.config.update({
65
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
66
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
67
+ region: 'eu-central-1',
68
+ });
69
+
70
+ const actionButtonTextWithLanguage = i18n.t(actionButtonText || 'Go to Application', { lng });
71
+
72
+ const ses: SES = new AWS.SES({ apiVersion: '2010-12-01' });
73
+
74
+ const toEmail = 'notifications@wirewire.de';
75
+ const base64ToName = Buffer.from(`Memo ${i18n.t('Notifications', { lng })}`).toString('base64');
76
+ const finalToName = `=?UTF-8?B?${base64ToName}?= <${toEmail}>`;
77
+
78
+ const params: SES.SendEmailRequest = {
79
+ Destination: {
80
+ ToAddresses: [email],
81
+ },
82
+ ConfigurationSetName: 'memo-transactional',
83
+ Message: {
84
+ Body: {
85
+ Html: {
86
+ Charset: 'UTF-8',
87
+ Data: `
88
+ <!DOCTYPE html>
89
+ <html>
90
+ <head>
91
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
92
+ <meta name="x-apple-disable-message-reformatting" />
93
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
94
+ <meta name="color-scheme" content="light dark" />
95
+ <meta name="supported-color-schemes" content="light dark" />
96
+ <title></title>
97
+ <style>
98
+ /* Base ------------------------------ */
99
+
100
+ @import url("https://fonts.googleapis.com/css?family=Open+Sans:400,700&display=swap");
101
+ body {
102
+ width: 100% !important;
103
+ height: 100%;
104
+ margin: 0;
105
+ -webkit-text-size-adjust: none;
106
+ }
107
+
108
+ a {
109
+ color: ${interactive};
110
+ }
111
+
112
+ a img {
113
+ border: none;
114
+ }
115
+
116
+ td {
117
+ word-break: break-word;
118
+ }
119
+
120
+ .preheader {
121
+ display: none !important;
122
+ visibility: hidden;
123
+ mso-hide: all;
124
+ font-size: 1px;
125
+ line-height: 1px;
126
+ max-height: 0;
127
+ max-width: 0;
128
+ opacity: 0;
129
+ overflow: hidden;
130
+ }
131
+ /* Type ------------------------------ */
132
+
133
+ body,
134
+ td,
135
+ th {
136
+ font-family: "Open Sans", Helvetica, Arial, sans-serif;
137
+ }
138
+
139
+ h1 {
140
+ margin-top: 0;
141
+ color: #333333;
142
+ font-size: 22px;
143
+ font-weight: bold;
144
+ }
145
+
146
+ h2 {
147
+ margin-top: 0;
148
+ color: #333333;
149
+ font-size: 16px;
150
+ font-weight: bold;
151
+ }
152
+
153
+ h3 {
154
+ margin-top: 0;
155
+ color: #333333;
156
+ font-size: 14px;
157
+ font-weight: bold;
158
+ }
159
+
160
+ td,
161
+ th {
162
+ font-size: 16px;
163
+ }
164
+
165
+ p,
166
+ ul,
167
+ ol,
168
+ blockquote {
169
+ margin: .4em 0 1.1875em;
170
+ font-size: 16px;
171
+ line-height: 1.625;
172
+ }
173
+
174
+ p.sub {
175
+ font-size: 13px;
176
+ }
177
+ /* Utilities ------------------------------ */
178
+
179
+ .align-right {
180
+ text-align: right;
181
+ }
182
+
183
+ .align-left {
184
+ text-align: left;
185
+ }
186
+
187
+ .align-center {
188
+ text-align: center;
189
+ }
190
+
191
+ .u-margin-bottom-none {
192
+ margin-bottom: 0;
193
+ }
194
+ /* Buttons ------------------------------ */
195
+
196
+ .button {
197
+ background-color: ${interactive};
198
+ border-top: 10px solid ${interactive};
199
+ border-right: 18px solid ${interactive};
200
+ border-bottom: 10px solid ${interactive};
201
+ border-left: 18px solid ${interactive};
202
+ display: inline-block;
203
+ color: #FFF !important;
204
+ text-decoration: none;
205
+ font-weight:bold;
206
+ border-radius: 4px;
207
+ /* box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16); */
208
+ -webkit-text-size-adjust: none;
209
+ box-sizing: border-box;
210
+ }
211
+
212
+ .button--green {
213
+ background-color: #22BC66;
214
+ border-top: 10px solid #22BC66;
215
+ border-right: 18px solid #22BC66;
216
+ border-bottom: 10px solid #22BC66;
217
+ border-left: 18px solid #22BC66;
218
+ }
219
+
220
+ .button--red {
221
+ background-color: #FF6136;
222
+ border-top: 10px solid #FF6136;
223
+ border-right: 18px solid #FF6136;
224
+ border-bottom: 10px solid #FF6136;
225
+ border-left: 18px solid #FF6136;
226
+ }
227
+
228
+ @media only screen and (max-width: 500px) {
229
+ .button {
230
+ width: 100% !important;
231
+ text-align: center !important;
232
+ }
233
+ }
234
+ /* Attribute list ------------------------------ */
235
+
236
+ .attributes {
237
+ margin: 0 0 21px;
238
+ }
239
+
240
+ .attributes_content {
241
+ background-color: #F4F4F7;
242
+ padding: 16px;
243
+ }
244
+
245
+ .attributes_item {
246
+ padding: 0;
247
+ }
248
+ /* Related Items ------------------------------ */
249
+
250
+ .related {
251
+ width: 100%;
252
+ margin: 0;
253
+ padding: 25px 0 0 0;
254
+ -premailer-width: 100%;
255
+ -premailer-cellpadding: 0;
256
+ -premailer-cellspacing: 0;
257
+ }
258
+
259
+ .related_item {
260
+ padding: 10px 0;
261
+ color: #CBCCCF;
262
+ font-size: 15px;
263
+ line-height: 18px;
264
+ }
265
+
266
+ .related_item-title {
267
+ display: block;
268
+ margin: .5em 0 0;
269
+ }
270
+
271
+ .related_item-thumb {
272
+ display: block;
273
+ padding-bottom: 10px;
274
+ }
275
+
276
+ .related_heading {
277
+ border-top: 1px solid #CBCCCF;
278
+ text-align: center;
279
+ padding: 25px 0 10px;
280
+ }
281
+ /* Discount Code ------------------------------ */
282
+
283
+ .discount {
284
+ width: 100%;
285
+ margin: 0;
286
+ padding: 24px;
287
+ -premailer-width: 100%;
288
+ -premailer-cellpadding: 0;
289
+ -premailer-cellspacing: 0;
290
+ background-color: #F4F4F7;
291
+ border: 2px dashed #CBCCCF;
292
+ }
293
+
294
+ .discount_heading {
295
+ text-align: center;
296
+ }
297
+
298
+ .discount_body {
299
+ text-align: center;
300
+ font-size: 15px;
301
+ }
302
+ /* Social Icons ------------------------------ */
303
+
304
+ .social {
305
+ width: auto;
306
+ }
307
+
308
+ .social td {
309
+ padding: 0;
310
+ width: auto;
311
+ }
312
+
313
+ .social_icon {
314
+ height: 20px;
315
+ margin: 0 8px 10px 8px;
316
+ padding: 0;
317
+ }
318
+ /* Data table ------------------------------ */
319
+
320
+ .purchase {
321
+ width: 100%;
322
+ margin: 0;
323
+ padding: 35px 0;
324
+ -premailer-width: 100%;
325
+ -premailer-cellpadding: 0;
326
+ -premailer-cellspacing: 0;
327
+ }
328
+
329
+ .purchase_content {
330
+ width: 100%;
331
+ margin: 0;
332
+ padding: 25px 0 0 0;
333
+ -premailer-width: 100%;
334
+ -premailer-cellpadding: 0;
335
+ -premailer-cellspacing: 0;
336
+ }
337
+
338
+ .purchase_item {
339
+ padding: 10px 0;
340
+ color: #51545E;
341
+ font-size: 15px;
342
+ line-height: 18px;
343
+ }
344
+
345
+ .purchase_heading {
346
+ padding-bottom: 8px;
347
+ border-bottom: 1px solid #EAEAEC;
348
+ }
349
+
350
+ .purchase_heading p {
351
+ margin: 0;
352
+ color: #85878E;
353
+ font-size: 12px;
354
+ }
355
+
356
+ .purchase_footer {
357
+ padding-top: 15px;
358
+ border-top: 1px solid #EAEAEC;
359
+ }
360
+
361
+ .purchase_total {
362
+ margin: 0;
363
+ text-align: right;
364
+ font-weight: bold;
365
+ color: #333333;
366
+ }
367
+
368
+ .purchase_total--label {
369
+ padding: 0 15px 0 0;
370
+ }
371
+
372
+ body {
373
+ background-color: #F2F4F6;
374
+ color: #51545E;
375
+ }
376
+
377
+ p {
378
+ color: #51545E;
379
+ }
380
+
381
+ .email-wrapper {
382
+ width: 100%;
383
+ margin: 0;
384
+ padding: 0;
385
+ -premailer-width: 100%;
386
+ -premailer-cellpadding: 0;
387
+ -premailer-cellspacing: 0;
388
+ background-color: #F2F4F6;
389
+ }
390
+
391
+ .email-image {
392
+ display: block;
393
+ width: 100px;
394
+ height: auto;
395
+ margin: auto;
396
+ margin-bottom: 20px;
397
+ }
398
+
399
+ .email-content {
400
+ width: 100%;
401
+ margin: 0;
402
+ padding: 0;
403
+ -premailer-width: 100%;
404
+ -premailer-cellpadding: 0;
405
+ -premailer-cellspacing: 0;
406
+ }
407
+ /* Masthead ----------------------- */
408
+
409
+ .email-masthead {
410
+ padding: 25px 0;
411
+ text-align: center;
412
+ }
413
+
414
+ .email-masthead_logo {
415
+ width: 94px;
416
+ }
417
+
418
+ .email-masthead_name {
419
+ font-size: 16px;
420
+ font-weight: normal;
421
+ color: #A8AAAF;
422
+ text-decoration: none;
423
+ text-shadow: 0 1px 0 white;
424
+ }
425
+ /* Body ------------------------------ */
426
+
427
+ .email-body {
428
+ width: 100%;
429
+ margin: 0;
430
+ padding: 0;
431
+ -premailer-width: 100%;
432
+ -premailer-cellpadding: 0;
433
+ -premailer-cellspacing: 0;
434
+ }
435
+
436
+ .email-body_inner {
437
+ width: 570px;
438
+ margin: 0 auto;
439
+ padding: 0;
440
+ -premailer-width: 570px;
441
+ -premailer-cellpadding: 0;
442
+ -premailer-cellspacing: 0;
443
+ background-color: #FFFFFF;
444
+ }
445
+
446
+ .email-footer {
447
+ width: 570px;
448
+ margin: 0 auto;
449
+ padding: 0;
450
+ -premailer-width: 570px;
451
+ -premailer-cellpadding: 0;
452
+ -premailer-cellspacing: 0;
453
+ text-align: center;
454
+ }
455
+
456
+ .email-footer p {
457
+ color: #A8AAAF;
458
+ }
459
+
460
+ .body-action {
461
+ width: 100%;
462
+ margin: 30px auto;
463
+ padding: 0;
464
+ -premailer-width: 100%;
465
+ -premailer-cellpadding: 0;
466
+ -premailer-cellspacing: 0;
467
+ text-align: center;
468
+ }
469
+
470
+ .callout {
471
+ color: #000;
472
+ background: #F2F4F6;
473
+ padding: 20px;
474
+ }
475
+
476
+ .body-sub {
477
+ margin-top: 25px;
478
+ padding-top: 25px;
479
+ border-top: 1px solid #EAEAEC;
480
+ }
481
+
482
+ .content-cell {
483
+ padding: 30px;
484
+ }
485
+ /*Media Queries ------------------------------ */
486
+
487
+ @media only screen and (max-width: 600px) {
488
+ .email-body_inner,
489
+ .email-footer {
490
+ width: 100% !important;
491
+ }
492
+ }
493
+
494
+ @media (prefers-color-scheme: dark) {
495
+ body,
496
+ .email-body,
497
+ .email-body_inner,
498
+ .email-content,
499
+ .email-wrapper,
500
+ .email-masthead,
501
+ .email-footer {
502
+ background-color: #333333 !important;
503
+ color: #FFF !important;
504
+ }
505
+ p,
506
+ ul,
507
+ ol,
508
+ blockquote,
509
+ h1,
510
+ h2,
511
+ h3,
512
+ span,
513
+ .purchase_item {
514
+ color: #FFF !important;
515
+ }
516
+ .attributes_content,
517
+ .discount {
518
+ background-color: #222 !important;
519
+ }
520
+ .email-masthead_name {
521
+ text-shadow: none !important;
522
+ }
523
+ }
524
+
525
+ :root {
526
+ color-scheme: light dark;
527
+ supported-color-schemes: light dark;
528
+ }
529
+ </style>
530
+ </head>
531
+ <body>
532
+ <span class="preheader">${body}</span>
533
+ <table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
534
+ <tr>
535
+ <td align="center">
536
+ <table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
537
+ <tr>
538
+ <td class="email-masthead">
539
+ <a href="https://${domain}.wirewire.de" class="f-fallback email-masthead_name">
540
+ ${domain === 'memo' ? 'ANABOX smart' : 'paperlesspaper'}
541
+ </a>
542
+ </td>
543
+ </tr>
544
+ <!-- Email Body -->
545
+ <tr>
546
+ <td class="email-body" width="570" cellpadding="0" cellspacing="0">
547
+ <table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
548
+ <!-- Body content -->
549
+ <tr>
550
+ <td class="content-cell align-center">
551
+ <div class="f-fallback">
552
+ ${image ? `<img class="email-image" src="${image}" alt="memo image" />` : ''}
553
+ <h1>${title}</h1>
554
+ <p>${body}</p>
555
+ <!-- Action -->
556
+ ${actionButton({
557
+ link: urlStartsWithHttp(url) ? url : `http://${domain}.wirewire.de${url}`,
558
+ text: actionButtonTextWithLanguage,
559
+ })}
560
+ </div>
561
+ </td>
562
+ </tr>
563
+ </table>
564
+ </td>
565
+ </tr>
566
+ <tr>
567
+ <td>
568
+ <table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
569
+ <tr>
570
+ <td class="content-cell" align="center">
571
+ <p class="f-fallback sub align-center">
572
+ ${domain === 'web' ? 'The Wire UG' : 'wirewire GmbH'}
573
+ <a href="http://${domain}.wirewire.de/account">Account</a>
574
+
575
+ ${config.env !== 'production' ? `<br/><br/>Environment: ${config.env}` : ''}
576
+ </p>
577
+ </td>
578
+ </tr>
579
+ </table>
580
+ </td>
581
+ </tr>
582
+ </table>
583
+ </td>
584
+ </tr>
585
+ </table>
586
+ </body>
587
+ </html>
588
+ `,
589
+ },
590
+ Text: {
591
+ Charset: 'UTF-8',
592
+ Data: `${title} ${body}`,
593
+ },
594
+ },
595
+ Subject: {
596
+ Charset: 'UTF-8',
597
+ Data: `${title} - Memo App`,
598
+ },
599
+ },
600
+ Source: finalToName,
601
+ };
602
+
603
+ try {
604
+ const data = await ses.sendEmail(params).promise();
605
+ console.log('Email submitted to SES', data);
606
+ } catch (error) {
607
+ console.error(error);
608
+ }
609
+ };