vintasend-medplum 0.4.1 → 0.4.3
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 +269 -5
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/medplum-adapter.d.ts +1 -0
- package/dist/medplum-adapter.d.ts.map +1 -0
- package/dist/medplum-attachment-manager.d.ts +1 -0
- package/dist/medplum-attachment-manager.d.ts.map +1 -0
- package/dist/medplum-attachment-manager.js +7 -10
- package/dist/medplum-backend.d.ts +1 -0
- package/dist/medplum-backend.d.ts.map +1 -0
- package/dist/medplum-backend.js +41 -53
- package/dist/medplum-logger.d.ts +1 -0
- package/dist/medplum-logger.d.ts.map +1 -0
- package/dist/pug-inline-email-template-renderer.d.ts +29 -0
- package/dist/pug-inline-email-template-renderer.d.ts.map +1 -0
- package/dist/pug-inline-email-template-renderer.js +68 -0
- package/dist/scripts/compile-pug-templates.d.ts +3 -0
- package/dist/scripts/compile-pug-templates.d.ts.map +1 -0
- package/dist/scripts/compile-pug-templates.js +71 -0
- package/package.json +13 -4
- package/src/scripts/compile-pug-templates.ts +90 -0
package/README.md
CHANGED
|
@@ -11,8 +11,61 @@ This implementation uses FHIR (Fast Healthcare Interoperability Resources) stand
|
|
|
11
11
|
- **MedplumNotificationBackend**: Stores notifications as FHIR `Communication` resources
|
|
12
12
|
- **MedplumNotificationAdapter**: Sends email notifications via Medplum's email API
|
|
13
13
|
- **MedplumAttachmentManager**: Manages file attachments using FHIR `Binary` and `Media` resources
|
|
14
|
+
- **PugInlineEmailTemplateRenderer**: Renders Pug email templates from pre-compiled JSON (ideal for production)
|
|
14
15
|
- **MedplumLogger**: Simple console-based logger
|
|
15
16
|
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Install the package
|
|
21
|
+
npm install vintasend-medplum @medplum/core
|
|
22
|
+
|
|
23
|
+
# Compile your Pug templates
|
|
24
|
+
npx compile-pug-templates ./templates ./src/compiled-templates.json
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { MedplumClient } from '@medplum/core';
|
|
29
|
+
import {
|
|
30
|
+
MedplumNotificationBackend,
|
|
31
|
+
MedplumNotificationAdapter,
|
|
32
|
+
PugInlineEmailTemplateRenderer,
|
|
33
|
+
MedplumLogger
|
|
34
|
+
} from 'vintasend-medplum';
|
|
35
|
+
import { NotificationService } from 'vintasend';
|
|
36
|
+
import compiledTemplates from './compiled-templates.json';
|
|
37
|
+
|
|
38
|
+
// Initialize Medplum client
|
|
39
|
+
const medplum = new MedplumClient({
|
|
40
|
+
baseUrl: 'https://api.medplum.com',
|
|
41
|
+
clientId: 'your-client-id',
|
|
42
|
+
clientSecret: 'your-client-secret',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Create services
|
|
46
|
+
const renderer = new PugInlineEmailTemplateRenderer(compiledTemplates);
|
|
47
|
+
const adapter = new MedplumNotificationAdapter(medplum, renderer);
|
|
48
|
+
const backend = new MedplumNotificationBackend(medplum);
|
|
49
|
+
const logger = new MedplumLogger();
|
|
50
|
+
|
|
51
|
+
// Initialize notification service
|
|
52
|
+
const notificationService = new NotificationService(backend, [adapter], logger);
|
|
53
|
+
|
|
54
|
+
// Send a notification
|
|
55
|
+
await notificationService.createNotification({
|
|
56
|
+
userId: 'Patient/123',
|
|
57
|
+
notificationType: 'EMAIL',
|
|
58
|
+
contextName: 'welcome',
|
|
59
|
+
contextParameters: { firstName: 'John' },
|
|
60
|
+
title: 'Welcome!',
|
|
61
|
+
bodyTemplate: 'welcome.pug',
|
|
62
|
+
subjectTemplate: 'subjects/welcome.pug',
|
|
63
|
+
sendAfter: new Date(),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
await notificationService.processPendingNotifications();
|
|
67
|
+
```
|
|
68
|
+
|
|
16
69
|
## How It Works
|
|
17
70
|
|
|
18
71
|
### FHIR Resource Mapping
|
|
@@ -94,6 +147,76 @@ npm install vintasend-medplum @medplum/core
|
|
|
94
147
|
|
|
95
148
|
## Setup
|
|
96
149
|
|
|
150
|
+
### Template Compilation
|
|
151
|
+
|
|
152
|
+
VintaSend Medplum uses pre-compiled Pug email templates that are embedded in your application as JSON. This approach ensures templates are bundled with your code and don't require file system access at runtime.
|
|
153
|
+
|
|
154
|
+
**Step 1: Organize Your Templates**
|
|
155
|
+
|
|
156
|
+
Create a directory structure for your templates:
|
|
157
|
+
```
|
|
158
|
+
templates/
|
|
159
|
+
welcome.pug
|
|
160
|
+
password-reset.pug
|
|
161
|
+
notifications/
|
|
162
|
+
order-confirmation.pug
|
|
163
|
+
shipment-update.pug
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Step 2: Compile Templates**
|
|
167
|
+
|
|
168
|
+
Run the compilation script using npx:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
npx compile-pug-templates [input-directory] [output-file]
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Both arguments are optional:
|
|
175
|
+
- `input-directory`: Directory containing .pug templates (default: `./templates`)
|
|
176
|
+
- `output-file`: Output JSON file path (default: `compiled-templates.json`)
|
|
177
|
+
|
|
178
|
+
Examples:
|
|
179
|
+
```bash
|
|
180
|
+
# Use default values (./templates → compiled-templates.json)
|
|
181
|
+
npx compile-pug-templates
|
|
182
|
+
|
|
183
|
+
# Specify only input directory (output to compiled-templates.json)
|
|
184
|
+
npx compile-pug-templates ./email-templates
|
|
185
|
+
|
|
186
|
+
# Specify both arguments
|
|
187
|
+
npx compile-pug-templates ./templates ./src/compiled-templates.json
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Or add it to your package.json scripts:
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"scripts": {
|
|
194
|
+
"compile-templates": "compile-pug-templates ./templates ./src/compiled-templates.json"
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
This generates a JSON file where keys are relative paths and values are template contents:
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"welcome.pug": "doctype html\nhtml\n body\n h1 Welcome {{firstName}}!",
|
|
203
|
+
"notifications/order-confirmation.pug": "doctype html\n..."
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Step 3: Import and Use Compiled Templates**
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
import { PugInlineEmailTemplateRenderer } from 'vintasend-medplum';
|
|
211
|
+
import compiledTemplates from './compiled-templates.json';
|
|
212
|
+
|
|
213
|
+
// Create the template renderer with compiled templates
|
|
214
|
+
const templateRenderer = new PugInlineEmailTemplateRenderer(compiledTemplates);
|
|
215
|
+
|
|
216
|
+
// Use with notification adapter
|
|
217
|
+
const adapter = new MedplumNotificationAdapter(medplum, templateRenderer);
|
|
218
|
+
```
|
|
219
|
+
|
|
97
220
|
### Basic Configuration
|
|
98
221
|
|
|
99
222
|
```typescript
|
|
@@ -151,6 +274,56 @@ const backend = new MedplumNotificationBackend(medplum, {
|
|
|
151
274
|
|
|
152
275
|
## Usage Examples
|
|
153
276
|
|
|
277
|
+
### Configuring Templates
|
|
278
|
+
|
|
279
|
+
When creating notifications, reference your templates using the same paths used during compilation:
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
// If you compiled templates/welcome.pug
|
|
283
|
+
await notificationService.createNotification({
|
|
284
|
+
userId: 'Patient/123',
|
|
285
|
+
notificationType: 'EMAIL',
|
|
286
|
+
contextName: 'user-welcome',
|
|
287
|
+
contextParameters: {
|
|
288
|
+
firstName: 'John',
|
|
289
|
+
lastName: 'Doe',
|
|
290
|
+
},
|
|
291
|
+
title: 'Welcome to our platform!',
|
|
292
|
+
bodyTemplate: 'welcome.pug', // Path from compiled templates
|
|
293
|
+
subjectTemplate: 'subjects/welcome.pug', // Can be in subdirectories
|
|
294
|
+
sendAfter: new Date(),
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// If you compiled templates/notifications/order-confirmation.pug
|
|
298
|
+
await notificationService.createNotification({
|
|
299
|
+
userId: 'Patient/456',
|
|
300
|
+
notificationType: 'EMAIL',
|
|
301
|
+
contextName: 'order-confirmation',
|
|
302
|
+
contextParameters: {
|
|
303
|
+
orderNumber: '12345',
|
|
304
|
+
totalAmount: '$99.99',
|
|
305
|
+
},
|
|
306
|
+
title: 'Order Confirmation',
|
|
307
|
+
bodyTemplate: 'notifications/order-confirmation.pug',
|
|
308
|
+
subjectTemplate: 'notifications/subjects/order-confirmation.pug',
|
|
309
|
+
sendAfter: new Date(),
|
|
310
|
+
});
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Template Example (welcome.pug):**
|
|
314
|
+
```pug
|
|
315
|
+
doctype html
|
|
316
|
+
html
|
|
317
|
+
head
|
|
318
|
+
title Welcome
|
|
319
|
+
body
|
|
320
|
+
h1 Welcome #{firstName} #{lastName}!
|
|
321
|
+
p Thank you for joining our platform.
|
|
322
|
+
p
|
|
323
|
+
| If you have any questions, feel free to
|
|
324
|
+
a(href="mailto:support@example.com") contact us
|
|
325
|
+
```
|
|
326
|
+
|
|
154
327
|
### Sending a Simple Notification
|
|
155
328
|
|
|
156
329
|
```typescript
|
|
@@ -395,6 +568,34 @@ constructor(
|
|
|
395
568
|
**Key Methods:**
|
|
396
569
|
- `send(notification, context)` - Send an email notification with attachments
|
|
397
570
|
|
|
571
|
+
### PugInlineEmailTemplateRenderer
|
|
572
|
+
|
|
573
|
+
```typescript
|
|
574
|
+
class PugInlineEmailTemplateRenderer<Config extends BaseNotificationTypeConfig>
|
|
575
|
+
implements BaseEmailTemplateRenderer<Config>
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
Template renderer that compiles Pug templates from pre-compiled JSON strings instead of reading from file paths. This is ideal for production deployments where templates are embedded in the application.
|
|
579
|
+
|
|
580
|
+
**Constructor:**
|
|
581
|
+
```typescript
|
|
582
|
+
constructor(generatedTemplates: Record<string, string>)
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
**Parameters:**
|
|
586
|
+
- `generatedTemplates` - Object mapping template paths to template content strings (generated by `compile-pug-templates` script)
|
|
587
|
+
|
|
588
|
+
**Key Methods:**
|
|
589
|
+
- `render(notification, context)` - Compile and render both subject and body templates using the notification's template paths
|
|
590
|
+
|
|
591
|
+
**Example:**
|
|
592
|
+
```typescript
|
|
593
|
+
import compiledTemplates from './compiled-templates.json';
|
|
594
|
+
|
|
595
|
+
const renderer = new PugInlineEmailTemplateRenderer(compiledTemplates);
|
|
596
|
+
const adapter = new MedplumNotificationAdapter(medplum, renderer);
|
|
597
|
+
```
|
|
598
|
+
|
|
398
599
|
### MedplumAttachmentManager
|
|
399
600
|
|
|
400
601
|
```typescript
|
|
@@ -430,7 +631,70 @@ Provides access to files stored in FHIR Binary resources.
|
|
|
430
631
|
|
|
431
632
|
## Best Practices
|
|
432
633
|
|
|
433
|
-
### 1.
|
|
634
|
+
### 1. Template Organization
|
|
635
|
+
|
|
636
|
+
Organize your templates in a clear directory structure:
|
|
637
|
+
|
|
638
|
+
```
|
|
639
|
+
templates/
|
|
640
|
+
subjects/ # Email subject templates
|
|
641
|
+
welcome.pug
|
|
642
|
+
order-confirmation.pug
|
|
643
|
+
bodies/ # Or organize by feature
|
|
644
|
+
welcome.pug
|
|
645
|
+
order-confirmation.pug
|
|
646
|
+
notifications/ # Group related templates
|
|
647
|
+
orders/
|
|
648
|
+
confirmation.pug
|
|
649
|
+
shipped.pug
|
|
650
|
+
users/
|
|
651
|
+
welcome.pug
|
|
652
|
+
password-reset.pug
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
### 2. Template Naming Conventions
|
|
656
|
+
|
|
657
|
+
Use consistent, descriptive names:
|
|
658
|
+
```typescript
|
|
659
|
+
// ✅ Good - clear and descriptive
|
|
660
|
+
bodyTemplate: 'notifications/orders/confirmation.pug'
|
|
661
|
+
subjectTemplate: 'subjects/order-confirmation.pug'
|
|
662
|
+
|
|
663
|
+
// ❌ Bad - unclear purpose
|
|
664
|
+
bodyTemplate: 'template1.pug'
|
|
665
|
+
subjectTemplate: 'subj.pug'
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
### 3. Template Variables
|
|
669
|
+
|
|
670
|
+
Document the expected context variables in each template:
|
|
671
|
+
|
|
672
|
+
```pug
|
|
673
|
+
//- templates/welcome.pug
|
|
674
|
+
//- Expected variables: firstName, lastName, loginUrl
|
|
675
|
+
doctype html
|
|
676
|
+
html
|
|
677
|
+
body
|
|
678
|
+
h1 Welcome #{firstName} #{lastName}!
|
|
679
|
+
a(href=loginUrl) Login to your account
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
### 4. Compilation in Build Process
|
|
683
|
+
|
|
684
|
+
Add template compilation to your build pipeline:
|
|
685
|
+
|
|
686
|
+
```json
|
|
687
|
+
{
|
|
688
|
+
"scripts": {
|
|
689
|
+
"compile-templates": "compile-pug-templates ./templates ./src/compiled-templates.json",
|
|
690
|
+
"build": "npm run compile-templates && tsc"
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
This ensures templates are always compiled before building your application.
|
|
696
|
+
|
|
697
|
+
### 5. Use FHIR References Consistently
|
|
434
698
|
|
|
435
699
|
Always use proper FHIR reference format for user IDs:
|
|
436
700
|
|
|
@@ -444,7 +708,7 @@ userId: '123'
|
|
|
444
708
|
userId: 'user-456'
|
|
445
709
|
```
|
|
446
710
|
|
|
447
|
-
###
|
|
711
|
+
### 6. Configure Custom Extension URLs
|
|
448
712
|
|
|
449
713
|
Use your own domain for extension URLs in production:
|
|
450
714
|
|
|
@@ -454,7 +718,7 @@ const backend = new MedplumNotificationBackend(medplum, {
|
|
|
454
718
|
});
|
|
455
719
|
```
|
|
456
720
|
|
|
457
|
-
###
|
|
721
|
+
### 7. Handle Large Attachments Carefully
|
|
458
722
|
|
|
459
723
|
For large files, consider:
|
|
460
724
|
- Using file size limits
|
|
@@ -469,7 +733,7 @@ if (buffer.length > maxSize) {
|
|
|
469
733
|
}
|
|
470
734
|
```
|
|
471
735
|
|
|
472
|
-
###
|
|
736
|
+
### 8. Clean Up Orphaned Files
|
|
473
737
|
|
|
474
738
|
Regularly run cleanup to remove orphaned attachment files:
|
|
475
739
|
|
|
@@ -483,7 +747,7 @@ for (const file of orphaned) {
|
|
|
483
747
|
}
|
|
484
748
|
```
|
|
485
749
|
|
|
486
|
-
###
|
|
750
|
+
### 9. Use Pagination
|
|
487
751
|
|
|
488
752
|
Always paginate when fetching large result sets:
|
|
489
753
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { MedplumAttachmentManager, MedplumAttachmentFile } from './medplum-attachment-manager';
|
|
2
2
|
export { MedplumNotificationBackend, MedplumNotificationBackendFactory } from './medplum-backend';
|
|
3
3
|
export { MedplumNotificationAdapter } from './medplum-adapter';
|
|
4
|
+
export { PugInlineEmailTemplateRenderer, PugInlineEmailTemplateRendererFactory } from './pug-inline-email-template-renderer';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC/F,OAAO,EAAE,0BAA0B,EAAE,iCAAiC,EAAE,MAAM,mBAAmB,CAAC;AAClG,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,8BAA8B,EAAE,qCAAqC,EAAE,MAAM,sCAAsC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { MedplumAttachmentManager, MedplumAttachmentFile } from './medplum-attachment-manager';
|
|
2
2
|
export { MedplumNotificationBackend, MedplumNotificationBackendFactory } from './medplum-backend';
|
|
3
3
|
export { MedplumNotificationAdapter } from './medplum-adapter';
|
|
4
|
+
export { PugInlineEmailTemplateRenderer, PugInlineEmailTemplateRendererFactory } from './pug-inline-email-template-renderer';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"medplum-adapter.d.ts","sourceRoot":"","sources":["../src/medplum-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,sFAAsF,CAAC;AACtI,OAAO,EAAE,uBAAuB,EAAE,MAAM,yEAAyE,CAAC;AAClH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AACjF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+CAA+C,CAAC;AAChG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAExE,qBAAa,0BAA0B,CACrC,gBAAgB,SAAS,yBAAyB,CAAC,MAAM,CAAC,EAC1D,MAAM,SAAS,0BAA0B,CACzC,SAAQ,uBAAuB,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAIvD,OAAO,CAAC,OAAO;IAHV,GAAG,EAAE,MAAM,GAAG,IAAI,CAAmB;gBAGlC,OAAO,EAAE,aAAa,EAC9B,gBAAgB,EAAE,gBAAgB;IAMpC,IAAI,mBAAmB,IAAI,OAAO,CAEjC;IAEK,IAAI,CAAC,YAAY,EAAE,uBAAuB,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;cA0C7E,kBAAkB,CAChC,WAAW,EAAE,gBAAgB,EAAE,GAC9B,OAAO,CAAC,KAAK,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC,CAAC;CAYJ"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"medplum-attachment-manager.d.ts","sourceRoot":"","sources":["../src/medplum-attachment-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,oEAAoE,CAAC;AAC3G,OAAO,KAAK,EACV,oBAAoB,EACpB,cAAc,EACd,cAAc,EACf,MAAM,iCAAiC,CAAC;AAEzC;;;;;;;;GAQG;AACH,qBAAa,wBAAyB,SAAQ,qBAAqB;IACrD,OAAO,CAAC,OAAO;gBAAP,OAAO,EAAE,aAAa;IAI1C;;;;;;;OAOG;IACG,UAAU,CACd,IAAI,EAAE,cAAc,EACpB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,oBAAoB,CAAC;IA6DhC;;;;;OAKG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IA0BnE;;;;;;OAMG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB/C;;;;;OAKG;IACH,yBAAyB,CAAC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,cAAc;CAgBpF;AAED;;;;GAIG;AACH,qBAAa,qBAAsB,YAAW,cAAc;IAIxD,OAAO,CAAC,OAAO;IAHjB,OAAO,CAAC,QAAQ,CAAS;gBAGf,OAAO,EAAE,aAAa,EAC9B,SAAS,EAAE,MAAM;IAUnB;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IAQ7B;;;;;OAKG;IACG,MAAM,IAAI,OAAO,CAAC,cAAc,CAAC;IAUvC;;;;;;;;OAQG;IACG,GAAG,CAAC,SAAS,SAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAO5C;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAG9B"}
|
|
@@ -22,7 +22,6 @@ export class MedplumAttachmentManager extends BaseAttachmentManager {
|
|
|
22
22
|
* @returns AttachmentFileRecord with metadata about the uploaded file
|
|
23
23
|
*/
|
|
24
24
|
async uploadFile(file, filename, contentType) {
|
|
25
|
-
var _a, _b;
|
|
26
25
|
// Convert file to Buffer
|
|
27
26
|
const buffer = await this.fileToBuffer(file);
|
|
28
27
|
// Calculate checksum for deduplication
|
|
@@ -71,8 +70,8 @@ export class MedplumAttachmentManager extends BaseAttachmentManager {
|
|
|
71
70
|
url: binaryUrl,
|
|
72
71
|
creation: createdMedia.content.creation,
|
|
73
72
|
},
|
|
74
|
-
createdAt:
|
|
75
|
-
updatedAt:
|
|
73
|
+
createdAt: createdMedia.meta?.lastUpdated ? new Date(createdMedia.meta.lastUpdated) : new Date(),
|
|
74
|
+
updatedAt: createdMedia.meta?.lastUpdated ? new Date(createdMedia.meta.lastUpdated) : new Date(),
|
|
76
75
|
};
|
|
77
76
|
}
|
|
78
77
|
/**
|
|
@@ -82,7 +81,6 @@ export class MedplumAttachmentManager extends BaseAttachmentManager {
|
|
|
82
81
|
* @returns The file record or null if not found
|
|
83
82
|
*/
|
|
84
83
|
async getFile(fileId) {
|
|
85
|
-
var _a, _b, _c, _d;
|
|
86
84
|
try {
|
|
87
85
|
const media = await this.medplum.readResource('Media', fileId);
|
|
88
86
|
if (!media.content || !media.id) {
|
|
@@ -93,16 +91,16 @@ export class MedplumAttachmentManager extends BaseAttachmentManager {
|
|
|
93
91
|
filename: media.content.title || 'untitled',
|
|
94
92
|
contentType: media.content.contentType || 'application/octet-stream',
|
|
95
93
|
size: media.content.size || 0,
|
|
96
|
-
checksum:
|
|
94
|
+
checksum: media.identifier?.[0]?.value || '',
|
|
97
95
|
storageMetadata: {
|
|
98
96
|
url: media.content.url,
|
|
99
97
|
creation: media.content.creation,
|
|
100
98
|
},
|
|
101
|
-
createdAt:
|
|
102
|
-
updatedAt:
|
|
99
|
+
createdAt: media.meta?.lastUpdated ? new Date(media.meta.lastUpdated) : new Date(),
|
|
100
|
+
updatedAt: media.meta?.lastUpdated ? new Date(media.meta.lastUpdated) : new Date(),
|
|
103
101
|
};
|
|
104
102
|
}
|
|
105
|
-
catch
|
|
103
|
+
catch {
|
|
106
104
|
return null;
|
|
107
105
|
}
|
|
108
106
|
}
|
|
@@ -114,11 +112,10 @@ export class MedplumAttachmentManager extends BaseAttachmentManager {
|
|
|
114
112
|
* @param fileId - The Media resource ID
|
|
115
113
|
*/
|
|
116
114
|
async deleteFile(fileId) {
|
|
117
|
-
var _a;
|
|
118
115
|
// Get Media resource to find the Binary URL
|
|
119
116
|
const media = await this.medplum.readResource('Media', fileId);
|
|
120
117
|
// Extract Binary ID from URL
|
|
121
|
-
const binaryUrl =
|
|
118
|
+
const binaryUrl = media.content?.url;
|
|
122
119
|
if (binaryUrl) {
|
|
123
120
|
const match = binaryUrl.match(/Binary\/([^/]+)/);
|
|
124
121
|
if (match) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"medplum-backend.d.ts","sourceRoot":"","sources":["../src/medplum-backend.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oEAAoE,CAAC;AAChH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yEAAyE,CAAC;AACvH,OAAO,KAAK,EAEV,oBAAoB,EAGpB,gBAAgB,EACjB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,KAAK,EACV,uBAAuB,EACvB,oBAAoB,EACpB,iBAAiB,EAClB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+CAA+C,CAAC;AAChG,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACxB,MAAM,2CAA2C,CAAC;AAEnD,KAAK,iCAAiC,GAAG;IACvC,oCAAoC,CAAC,EAAE,MAAM,CAAC;CAC/C,CAAC;AAGF,qBAAa,0BAA0B,CAAC,MAAM,SAAS,0BAA0B,CAAE,YAAW,uBAAuB,CAAC,MAAM,CAAC;IAG/G,OAAO,CAAC,OAAO;IAAiB,OAAO,CAAC,OAAO;IAF3D,OAAO,CAAC,iBAAiB,CAAC,CAAwB;gBAE9B,OAAO,EAAE,aAAa,EAAU,OAAO,GAAE,iCAE5D;IAED;;OAEG;IACH,uBAAuB,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI;IAI7D;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,yBAAyB;IA8D3B,0BAA0B,IAAI,OAAO,CAAC,uBAAuB,CAAC,MAAM,CAAC,EAAE,CAAC;IAQxE,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC,MAAM,CAAC,EAAE,CAAC;IAYnG,yBAAyB,IAAI,OAAO,CAAC,uBAAuB,CAAC,MAAM,CAAC,EAAE,CAAC;IAUvE,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC,MAAM,CAAC,EAAE,CAAC;IAYlG,iCAAiC,CACrC,eAAe,EAAE,MAAM,CAAC,YAAY,CAAC,GACpC,OAAO,CAAC,oBAAoB,CAAC,MAAM,CAAC,EAAE,CAAC;IAapC,8BAA8B,CAClC,eAAe,EAAE,MAAM,CAAC,YAAY,CAAC,EACrC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,oBAAoB,CAAC,MAAM,CAAC,EAAE,CAAC;IAepC,mBAAmB,CAAC,YAAY,EAAE,iBAAiB,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;IA0CnG,yBAAyB,CAC7B,cAAc,EAAE,MAAM,CAAC,oBAAoB,CAAC,EAC5C,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,GAC9D,OAAO,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAiBlC,mBAAmB,IAAI,OAAO,CAAC,uBAAuB,CAAC,MAAM,CAAC,EAAE,CAAC;IAOjE,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC,MAAM,CAAC,EAAE,CAAC;IAS5F,wBAAwB,CAC5B,aAAa,EAAE,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,EAAE,GACrD,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,EAAE,CAAC;IAqCpC,UAAU,CACd,cAAc,EAAE,MAAM,CAAC,oBAAoB,CAAC,EAC5C,cAAc,UAAO,GACpB,OAAO,CAAC,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAmBrC,YAAY,CAChB,cAAc,EAAE,MAAM,CAAC,oBAAoB,CAAC,EAC5C,cAAc,UAAO,GACpB,OAAO,CAAC,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAmBrC,UAAU,CACd,cAAc,EAAE,MAAM,CAAC,oBAAoB,CAAC,EAC5C,WAAW,UAAO,GACjB,OAAO,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAYlC,kBAAkB,CAAC,cAAc,EAAE,MAAM,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/E,eAAe,CACnB,cAAc,EAAE,MAAM,CAAC,oBAAoB,CAAC,EAE5C,UAAU,EAAE,OAAO,GAClB,OAAO,CAAC,uBAAuB,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IAS5C,iCAAiC,CACrC,gBAAgB,EAAE,MAAM,CAAC,YAAY,CAAC,GACrC,OAAO,CAAC,oBAAoB,CAAC,MAAM,CAAC,EAAE,CAAC;IAWpC,8BAA8B,CAClC,gBAAgB,EAAE,MAAM,CAAC,YAAY,CAAC,EACtC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,oBAAoB,CAAC,MAAM,CAAC,EAAE,CAAC;IAapC,4BAA4B,CAAC,cAAc,EAAE,MAAM,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IA0BvG,gBAAgB,CAAC,cAAc,EAAE,MAAM,CAAC,oBAAoB,CAAC,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAWtG,yBAAyB,CAC7B,YAAY,EAAE,IAAI,CAAC,uBAAuB,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,GACxD,OAAO,CAAC,0BAA0B,CAAC,MAAM,CAAC,CAAC;IA+CxC,+BAA+B,CACnC,cAAc,EAAE,MAAM,CAAC,oBAAoB,CAAC,EAC5C,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,0BAA0B,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,GACpE,OAAO,CAAC,0BAA0B,CAAC,MAAM,CAAC,CAAC;IAiBxC,qBAAqB,CACzB,cAAc,EAAE,MAAM,CAAC,oBAAoB,CAAC,EAC5C,SAAS,EAAE,OAAO,GACjB,OAAO,CAAC,0BAA0B,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IAU/C,yBAAyB,IAAI,OAAO,CAAC,0BAA0B,CAAC,MAAM,CAAC,EAAE,CAAC;IAO1E,sBAAsB,CAC1B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,0BAA0B,CAAC,MAAM,CAAC,EAAE,CAAC;IAWhD;;OAEG;YACW,YAAY;IAoC1B;;OAEG;IACH,OAAO,CAAC,2BAA2B;IAqBnC;;OAEG;YACW,oBAAoB;IAWlC;;OAEG;IACH,OAAO,CAAC,2BAA2B;IAK7B,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IASvE,4BAA4B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAiB1F;;OAEG;YACW,8BAA8B;IA4B5C;;;OAGG;YACW,kBAAkB;IAiH1B,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwCnD,0BAA0B,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC;IA6B7D,cAAc,CAAC,cAAc,EAAE,MAAM,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IA4DzF,4BAA4B,CAChC,cAAc,EAAE,MAAM,CAAC,oBAAoB,CAAC,EAC5C,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC;CA2BjB;AAED,qBAAa,iCAAiC,CAAC,MAAM,SAAS,0BAA0B;IACtF,MAAM,CAAC,OAAO,EAAE,aAAa;CAG9B"}
|
package/dist/medplum-backend.js
CHANGED
|
@@ -21,27 +21,26 @@ export class MedplumNotificationBackend {
|
|
|
21
21
|
return this.attachmentManager;
|
|
22
22
|
}
|
|
23
23
|
mapToDatabaseNotification(communication) {
|
|
24
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
|
|
25
24
|
const notificationId = communication.id;
|
|
26
|
-
const referenceString =
|
|
25
|
+
const referenceString = communication.recipient?.[0]?.reference;
|
|
27
26
|
// Extract subjectTemplate from payload extension
|
|
28
|
-
const subjectTemplateExtension =
|
|
29
|
-
const subjectTemplate =
|
|
27
|
+
const subjectTemplateExtension = communication.payload?.[0]?.extension?.find((ext) => ext.url === this.options.emailNotificationSubjectExtensionUrl);
|
|
28
|
+
const subjectTemplate = subjectTemplateExtension?.valueString || null;
|
|
30
29
|
// Extract one-off notification fields from extensions
|
|
31
|
-
const emailOrPhoneExtension =
|
|
32
|
-
const firstNameExtension =
|
|
33
|
-
const lastNameExtension =
|
|
34
|
-
const emailOrPhone = emailOrPhoneExtension
|
|
35
|
-
const firstName = firstNameExtension
|
|
36
|
-
const lastName = lastNameExtension
|
|
30
|
+
const emailOrPhoneExtension = communication.extension?.find((ext) => ext.url === 'http://vintasend.com/fhir/StructureDefinition/emailOrPhone');
|
|
31
|
+
const firstNameExtension = communication.extension?.find((ext) => ext.url === 'http://vintasend.com/fhir/StructureDefinition/firstName');
|
|
32
|
+
const lastNameExtension = communication.extension?.find((ext) => ext.url === 'http://vintasend.com/fhir/StructureDefinition/lastName');
|
|
33
|
+
const emailOrPhone = emailOrPhoneExtension?.valueString;
|
|
34
|
+
const firstName = firstNameExtension?.valueString;
|
|
35
|
+
const lastName = lastNameExtension?.valueString;
|
|
37
36
|
const baseNotification = {
|
|
38
37
|
id: notificationId,
|
|
39
|
-
notificationType:
|
|
40
|
-
title:
|
|
41
|
-
contextName:
|
|
42
|
-
contextParameters: JSON.parse(
|
|
38
|
+
notificationType: communication.meta?.tag?.[2]?.code,
|
|
39
|
+
title: communication.topic?.text || null,
|
|
40
|
+
contextName: communication.meta?.tag?.[1]?.code,
|
|
41
|
+
contextParameters: JSON.parse(communication.note?.[0]?.text || '{}'),
|
|
43
42
|
sendAfter: communication.sent ? new Date(communication.sent) : null,
|
|
44
|
-
bodyTemplate:
|
|
43
|
+
bodyTemplate: communication.payload?.[0]?.contentString || '',
|
|
45
44
|
subjectTemplate: subjectTemplate,
|
|
46
45
|
extraParams: {},
|
|
47
46
|
status: communication.status === 'completed' ? 'SENT' : communication.status === 'stopped' ? 'FAILED' : 'PENDING_SEND',
|
|
@@ -49,8 +48,8 @@ export class MedplumNotificationBackend {
|
|
|
49
48
|
adapterUsed: null,
|
|
50
49
|
sentAt: communication.status === 'completed' ? new Date(communication.sent || new Date()) : null,
|
|
51
50
|
readAt: null,
|
|
52
|
-
createdAt: new Date(
|
|
53
|
-
updatedAt: new Date(
|
|
51
|
+
createdAt: new Date(communication.meta?.lastUpdated || new Date()),
|
|
52
|
+
updatedAt: new Date(communication.meta?.lastUpdated || new Date()),
|
|
54
53
|
};
|
|
55
54
|
// Check if this is a one-off notification
|
|
56
55
|
if (emailOrPhone && firstName && lastName) {
|
|
@@ -132,7 +131,6 @@ export class MedplumNotificationBackend {
|
|
|
132
131
|
.filter((n) => 'userId' in n);
|
|
133
132
|
}
|
|
134
133
|
async persistNotification(notification) {
|
|
135
|
-
var _a;
|
|
136
134
|
// Build base payload with body template
|
|
137
135
|
const payload = notification.bodyTemplate
|
|
138
136
|
? [
|
|
@@ -154,7 +152,7 @@ export class MedplumNotificationBackend {
|
|
|
154
152
|
const communication = {
|
|
155
153
|
resourceType: 'Communication',
|
|
156
154
|
status: 'in-progress',
|
|
157
|
-
sent:
|
|
155
|
+
sent: notification.sendAfter?.toISOString(),
|
|
158
156
|
topic: { text: notification.title || undefined },
|
|
159
157
|
payload,
|
|
160
158
|
recipient: [{ reference: notification.userId }],
|
|
@@ -199,13 +197,12 @@ export class MedplumNotificationBackend {
|
|
|
199
197
|
return communications.map((comm) => this.mapToDatabaseNotification(comm));
|
|
200
198
|
}
|
|
201
199
|
async bulkPersistNotifications(notifications) {
|
|
202
|
-
var _a;
|
|
203
200
|
const ids = [];
|
|
204
201
|
for (const notification of notifications) {
|
|
205
202
|
const communication = {
|
|
206
203
|
resourceType: 'Communication',
|
|
207
204
|
status: 'in-progress',
|
|
208
|
-
sent:
|
|
205
|
+
sent: notification.sendAfter?.toISOString(),
|
|
209
206
|
topic: { text: notification.title || undefined },
|
|
210
207
|
payload: notification.bodyTemplate
|
|
211
208
|
? [
|
|
@@ -234,7 +231,7 @@ export class MedplumNotificationBackend {
|
|
|
234
231
|
}
|
|
235
232
|
async markAsSent(notificationId, checkIsPending = true) {
|
|
236
233
|
const notification = await this.getNotification(notificationId, false);
|
|
237
|
-
if (checkIsPending &&
|
|
234
|
+
if (checkIsPending && notification?.status !== 'PENDING_SEND') {
|
|
238
235
|
throw new Error('Notification is not pending');
|
|
239
236
|
}
|
|
240
237
|
// Use internal update that returns AnyDatabaseNotification
|
|
@@ -252,7 +249,7 @@ export class MedplumNotificationBackend {
|
|
|
252
249
|
}
|
|
253
250
|
async markAsFailed(notificationId, checkIsPending = true) {
|
|
254
251
|
const notification = await this.getNotification(notificationId, false);
|
|
255
|
-
if (checkIsPending &&
|
|
252
|
+
if (checkIsPending && notification?.status !== 'PENDING_SEND') {
|
|
256
253
|
throw new Error('Notification is not pending');
|
|
257
254
|
}
|
|
258
255
|
// Use internal update that returns AnyDatabaseNotification
|
|
@@ -270,7 +267,7 @@ export class MedplumNotificationBackend {
|
|
|
270
267
|
}
|
|
271
268
|
async markAsRead(notificationId, checkIsSent = true) {
|
|
272
269
|
const notification = await this.getNotification(notificationId, false);
|
|
273
|
-
if (checkIsSent &&
|
|
270
|
+
if (checkIsSent && notification?.status !== 'SENT') {
|
|
274
271
|
throw new Error('Notification is not sent');
|
|
275
272
|
}
|
|
276
273
|
// Ensure it's a regular notification (not one-off)
|
|
@@ -289,7 +286,7 @@ export class MedplumNotificationBackend {
|
|
|
289
286
|
const communication = await this.medplum.readResource('Communication', notificationId);
|
|
290
287
|
return this.mapToDatabaseNotification(communication);
|
|
291
288
|
}
|
|
292
|
-
catch
|
|
289
|
+
catch {
|
|
293
290
|
return null;
|
|
294
291
|
}
|
|
295
292
|
}
|
|
@@ -316,10 +313,9 @@ export class MedplumNotificationBackend {
|
|
|
316
313
|
.filter((notif) => 'userId' in notif && !notif.readAt);
|
|
317
314
|
}
|
|
318
315
|
async getUserEmailFromNotification(notificationId) {
|
|
319
|
-
var _a, _b, _c, _d;
|
|
320
316
|
try {
|
|
321
317
|
const communication = await this.medplum.readResource('Communication', notificationId);
|
|
322
|
-
const practitionerRef =
|
|
318
|
+
const practitionerRef = communication.recipient?.[0]?.reference;
|
|
323
319
|
if (!practitionerRef) {
|
|
324
320
|
return undefined;
|
|
325
321
|
}
|
|
@@ -330,7 +326,7 @@ export class MedplumNotificationBackend {
|
|
|
330
326
|
return undefined;
|
|
331
327
|
}
|
|
332
328
|
const resource = await this.medplum.readResource(resourceType, id);
|
|
333
|
-
return
|
|
329
|
+
return resource.telecom?.find((t) => t.system === 'email')?.value;
|
|
334
330
|
}
|
|
335
331
|
catch (error) {
|
|
336
332
|
// eslint-disable-next-line no-console
|
|
@@ -348,7 +344,6 @@ export class MedplumNotificationBackend {
|
|
|
348
344
|
}
|
|
349
345
|
/* One-off notification methods */
|
|
350
346
|
async persistOneOffNotification(notification) {
|
|
351
|
-
var _a;
|
|
352
347
|
// Build base payload with body template
|
|
353
348
|
const payload = notification.bodyTemplate
|
|
354
349
|
? [
|
|
@@ -370,7 +365,7 @@ export class MedplumNotificationBackend {
|
|
|
370
365
|
const communication = {
|
|
371
366
|
resourceType: 'Communication',
|
|
372
367
|
status: 'in-progress',
|
|
373
|
-
sent:
|
|
368
|
+
sent: notification.sendAfter?.toISOString(),
|
|
374
369
|
topic: { text: notification.title || undefined },
|
|
375
370
|
payload,
|
|
376
371
|
note: [{ text: JSON.stringify(notification.contextParameters) }],
|
|
@@ -471,7 +466,6 @@ export class MedplumNotificationBackend {
|
|
|
471
466
|
* Helper: Convert FHIR Media resource to AttachmentFileRecord
|
|
472
467
|
*/
|
|
473
468
|
mediaToAttachmentFileRecord(media) {
|
|
474
|
-
var _a, _b, _c, _d;
|
|
475
469
|
const content = media.content;
|
|
476
470
|
if (!content || !media.id) {
|
|
477
471
|
throw new Error('Invalid Media resource: missing content or id');
|
|
@@ -481,21 +475,20 @@ export class MedplumNotificationBackend {
|
|
|
481
475
|
filename: content.title || 'untitled',
|
|
482
476
|
contentType: content.contentType || 'application/octet-stream',
|
|
483
477
|
size: content.size || 0,
|
|
484
|
-
checksum:
|
|
478
|
+
checksum: media.identifier?.[0]?.value || '',
|
|
485
479
|
storageMetadata: {
|
|
486
480
|
url: content.url,
|
|
487
481
|
creation: content.creation,
|
|
488
482
|
},
|
|
489
|
-
createdAt:
|
|
490
|
-
updatedAt:
|
|
483
|
+
createdAt: media.meta?.lastUpdated ? new Date(media.meta.lastUpdated) : new Date(),
|
|
484
|
+
updatedAt: media.meta?.lastUpdated ? new Date(media.meta.lastUpdated) : new Date(),
|
|
491
485
|
};
|
|
492
486
|
}
|
|
493
487
|
/**
|
|
494
488
|
* Helper: Create FHIR Attachment from file record
|
|
495
489
|
*/
|
|
496
490
|
async createFhirAttachment(fileRecord) {
|
|
497
|
-
|
|
498
|
-
const url = (_a = fileRecord.storageMetadata) === null || _a === void 0 ? void 0 : _a.url;
|
|
491
|
+
const url = fileRecord.storageMetadata?.url;
|
|
499
492
|
return {
|
|
500
493
|
contentType: fileRecord.contentType,
|
|
501
494
|
url: url,
|
|
@@ -516,7 +509,7 @@ export class MedplumNotificationBackend {
|
|
|
516
509
|
const media = await this.medplum.readResource('Media', fileId);
|
|
517
510
|
return this.mediaToAttachmentFileRecord(media);
|
|
518
511
|
}
|
|
519
|
-
catch
|
|
512
|
+
catch {
|
|
520
513
|
return null;
|
|
521
514
|
}
|
|
522
515
|
}
|
|
@@ -528,7 +521,7 @@ export class MedplumNotificationBackend {
|
|
|
528
521
|
}
|
|
529
522
|
return this.mediaToAttachmentFileRecord(results[0]);
|
|
530
523
|
}
|
|
531
|
-
catch
|
|
524
|
+
catch {
|
|
532
525
|
return null;
|
|
533
526
|
}
|
|
534
527
|
}
|
|
@@ -536,7 +529,6 @@ export class MedplumNotificationBackend {
|
|
|
536
529
|
* Find multiple attachment files by their checksums in a single query
|
|
537
530
|
*/
|
|
538
531
|
async findAttachmentFilesByChecksums(checksums) {
|
|
539
|
-
var _a, _b;
|
|
540
532
|
if (checksums.length === 0) {
|
|
541
533
|
return new Map();
|
|
542
534
|
}
|
|
@@ -547,14 +539,14 @@ export class MedplumNotificationBackend {
|
|
|
547
539
|
// Map results by checksum
|
|
548
540
|
const filesByChecksum = new Map();
|
|
549
541
|
for (const media of results) {
|
|
550
|
-
const checksum =
|
|
542
|
+
const checksum = media.identifier?.[0]?.value;
|
|
551
543
|
if (checksum) {
|
|
552
544
|
filesByChecksum.set(checksum, this.mediaToAttachmentFileRecord(media));
|
|
553
545
|
}
|
|
554
546
|
}
|
|
555
547
|
return filesByChecksum;
|
|
556
548
|
}
|
|
557
|
-
catch
|
|
549
|
+
catch {
|
|
558
550
|
return new Map();
|
|
559
551
|
}
|
|
560
552
|
}
|
|
@@ -648,7 +640,6 @@ export class MedplumNotificationBackend {
|
|
|
648
640
|
return payload;
|
|
649
641
|
}
|
|
650
642
|
async deleteAttachmentFile(fileId) {
|
|
651
|
-
var _a;
|
|
652
643
|
// Check if file exists first
|
|
653
644
|
const fileRecord = await this.getAttachmentFile(fileId);
|
|
654
645
|
if (!fileRecord) {
|
|
@@ -659,7 +650,7 @@ export class MedplumNotificationBackend {
|
|
|
659
650
|
const communications = await this.medplum.searchResources('Communication', {
|
|
660
651
|
_tag: 'notification',
|
|
661
652
|
});
|
|
662
|
-
const isReferenced = communications.some((comm) =>
|
|
653
|
+
const isReferenced = communications.some((comm) => comm.payload?.some((p) => p.contentAttachment?.url?.includes(fileId)));
|
|
663
654
|
if (isReferenced) {
|
|
664
655
|
throw new Error('Cannot delete attachment file: still referenced by notifications');
|
|
665
656
|
}
|
|
@@ -668,12 +659,12 @@ export class MedplumNotificationBackend {
|
|
|
668
659
|
await this.attachmentManager.deleteFile(fileId);
|
|
669
660
|
}
|
|
670
661
|
// Delete Binary resource if it exists
|
|
671
|
-
if (
|
|
662
|
+
if (fileRecord.storageMetadata?.url) {
|
|
672
663
|
const binaryId = fileRecord.storageMetadata.url.replace('Binary/', '');
|
|
673
664
|
try {
|
|
674
665
|
await this.medplum.deleteResource('Binary', binaryId);
|
|
675
666
|
}
|
|
676
|
-
catch
|
|
667
|
+
catch {
|
|
677
668
|
// Binary may not exist or already deleted
|
|
678
669
|
}
|
|
679
670
|
}
|
|
@@ -681,7 +672,6 @@ export class MedplumNotificationBackend {
|
|
|
681
672
|
await this.medplum.deleteResource('Media', fileId);
|
|
682
673
|
}
|
|
683
674
|
async getOrphanedAttachmentFiles() {
|
|
684
|
-
var _a;
|
|
685
675
|
// Get all Media resources tagged as attachment-files
|
|
686
676
|
const allMedia = await this.medplum.searchResources('Media', {
|
|
687
677
|
_tag: 'attachment-file',
|
|
@@ -694,7 +684,7 @@ export class MedplumNotificationBackend {
|
|
|
694
684
|
const referencedIds = new Set();
|
|
695
685
|
for (const comm of communications) {
|
|
696
686
|
for (const payload of comm.payload || []) {
|
|
697
|
-
const url =
|
|
687
|
+
const url = payload.contentAttachment?.url;
|
|
698
688
|
if (url) {
|
|
699
689
|
// Extract Media ID from URL (assuming format like Media/{id})
|
|
700
690
|
const match = url.match(/Media\/([^/]+)/);
|
|
@@ -750,13 +740,13 @@ export class MedplumNotificationBackend {
|
|
|
750
740
|
checksum: fileRecord.checksum,
|
|
751
741
|
createdAt: fileRecord.createdAt,
|
|
752
742
|
file: attachmentFile,
|
|
753
|
-
description: payloadData
|
|
743
|
+
description: payloadData?.description,
|
|
754
744
|
storageMetadata: fileRecord.storageMetadata,
|
|
755
745
|
});
|
|
756
746
|
}
|
|
757
747
|
return attachments;
|
|
758
748
|
}
|
|
759
|
-
catch
|
|
749
|
+
catch {
|
|
760
750
|
return [];
|
|
761
751
|
}
|
|
762
752
|
}
|
|
@@ -764,8 +754,7 @@ export class MedplumNotificationBackend {
|
|
|
764
754
|
const communication = await this.medplum.readResource('Communication', notificationId);
|
|
765
755
|
// Check if attachment exists
|
|
766
756
|
const hasAttachment = (communication.payload || []).some((p) => {
|
|
767
|
-
|
|
768
|
-
const url = (_a = p.contentAttachment) === null || _a === void 0 ? void 0 : _a.url;
|
|
757
|
+
const url = p.contentAttachment?.url;
|
|
769
758
|
return url && url.includes(attachmentId);
|
|
770
759
|
});
|
|
771
760
|
if (!hasAttachment) {
|
|
@@ -773,8 +762,7 @@ export class MedplumNotificationBackend {
|
|
|
773
762
|
}
|
|
774
763
|
// Filter out the attachment from payload
|
|
775
764
|
const updatedPayload = (communication.payload || []).filter((p) => {
|
|
776
|
-
|
|
777
|
-
const url = (_a = p.contentAttachment) === null || _a === void 0 ? void 0 : _a.url;
|
|
765
|
+
const url = p.contentAttachment?.url;
|
|
778
766
|
if (!url)
|
|
779
767
|
return true;
|
|
780
768
|
return !url.includes(attachmentId);
|
package/dist/medplum-logger.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"medplum-logger.d.ts","sourceRoot":"","sources":["../src/medplum-logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6CAA6C,CAAC;AAE9E,qBAAa,aAAc,YAAW,UAAU;IAC9C,OAAO,CAAC,MAAM,CAAiB;;IAM/B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI5B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAG5B"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { BaseEmailTemplateRenderer } from 'vintasend';
|
|
2
|
+
import type { JsonObject } from 'vintasend/dist/types/json-values';
|
|
3
|
+
import type { DatabaseNotification } from 'vintasend/dist/types/notification';
|
|
4
|
+
import type { BaseNotificationTypeConfig } from 'vintasend/dist/types/notification-type-config';
|
|
5
|
+
import type { BaseLogger } from 'vintasend/dist/services/loggers/base-logger';
|
|
6
|
+
/**
|
|
7
|
+
* Custom email template renderer that compiles Pug templates from strings
|
|
8
|
+
* instead of reading from file paths.
|
|
9
|
+
*
|
|
10
|
+
* This is necessary for bot deployments where templates are embedded as constants
|
|
11
|
+
* rather than separate files.
|
|
12
|
+
*/
|
|
13
|
+
export declare class PugInlineEmailTemplateRenderer<Config extends BaseNotificationTypeConfig> implements BaseEmailTemplateRenderer<Config> {
|
|
14
|
+
private templates;
|
|
15
|
+
private logger;
|
|
16
|
+
constructor(generatedTemplates: Record<string, string>);
|
|
17
|
+
/**
|
|
18
|
+
* Inject logger (called by VintaSend when logger exists)
|
|
19
|
+
*/
|
|
20
|
+
injectLogger(logger: BaseLogger): void;
|
|
21
|
+
render(notification: DatabaseNotification<Config>, context: JsonObject): Promise<{
|
|
22
|
+
subject: string;
|
|
23
|
+
body: string;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
export declare class PugInlineEmailTemplateRendererFactory<Config extends BaseNotificationTypeConfig> {
|
|
27
|
+
create(generatedTemplates: Record<string, string>): PugInlineEmailTemplateRenderer<Config>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=pug-inline-email-template-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pug-inline-email-template-renderer.d.ts","sourceRoot":"","sources":["../src/pug-inline-email-template-renderer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+CAA+C,CAAC;AAChG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6CAA6C,CAAC;AAG9E;;;;;;GAMG;AACH,qBAAa,8BAA8B,CAAC,MAAM,SAAS,0BAA0B,CACnF,YAAW,yBAAyB,CAAC,MAAM,CAAC;IAE5C,OAAO,CAAC,SAAS,CAAyB;IAC1C,OAAO,CAAC,MAAM,CAA2B;gBAE7B,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAItD;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAIhC,MAAM,CACV,YAAY,EAAE,oBAAoB,CAAC,MAAM,CAAC,EAC1C,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CA6C9C;AAED,qBAAa,qCAAqC,CAAC,MAAM,SAAS,0BAA0B;IAC1F,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,8BAA8B,CAAC,MAAM,CAAC;CAG3F"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as pug from 'pug';
|
|
2
|
+
/**
|
|
3
|
+
* Custom email template renderer that compiles Pug templates from strings
|
|
4
|
+
* instead of reading from file paths.
|
|
5
|
+
*
|
|
6
|
+
* This is necessary for bot deployments where templates are embedded as constants
|
|
7
|
+
* rather than separate files.
|
|
8
|
+
*/
|
|
9
|
+
export class PugInlineEmailTemplateRenderer {
|
|
10
|
+
constructor(generatedTemplates) {
|
|
11
|
+
this.logger = null;
|
|
12
|
+
this.templates = generatedTemplates;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Inject logger (called by VintaSend when logger exists)
|
|
16
|
+
*/
|
|
17
|
+
injectLogger(logger) {
|
|
18
|
+
this.logger = logger;
|
|
19
|
+
}
|
|
20
|
+
async render(notification, context) {
|
|
21
|
+
// Check if body template is provided
|
|
22
|
+
const bodyTemplateKey = notification.bodyTemplate;
|
|
23
|
+
if (!bodyTemplateKey) {
|
|
24
|
+
throw new Error('Body template is required');
|
|
25
|
+
}
|
|
26
|
+
if (!(bodyTemplateKey in this.templates)) {
|
|
27
|
+
throw new Error(`Body template "${bodyTemplateKey}" not found in templates`);
|
|
28
|
+
}
|
|
29
|
+
// Check if subject template is provided
|
|
30
|
+
const subjectTemplateKey = notification.subjectTemplate;
|
|
31
|
+
if (!subjectTemplateKey) {
|
|
32
|
+
throw new Error('Subject template is required');
|
|
33
|
+
}
|
|
34
|
+
if (!(subjectTemplateKey in this.templates)) {
|
|
35
|
+
throw new Error(`Subject template "${subjectTemplateKey}" not found in templates`);
|
|
36
|
+
}
|
|
37
|
+
let body;
|
|
38
|
+
try {
|
|
39
|
+
// Compile and render the body template from string
|
|
40
|
+
const bodyTemplate = pug.compile(this.templates[bodyTemplateKey]);
|
|
41
|
+
body = bodyTemplate(context);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (this.logger) {
|
|
45
|
+
this.logger.error('[PugInlineEmailTemplateRenderer] Error rendering body template');
|
|
46
|
+
}
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
let subject;
|
|
50
|
+
try {
|
|
51
|
+
// Compile and render the subject template from string
|
|
52
|
+
const subjectTemplate = pug.compile(this.templates[subjectTemplateKey]);
|
|
53
|
+
subject = subjectTemplate(context);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
if (this.logger) {
|
|
57
|
+
this.logger.error('[PugInlineEmailTemplateRenderer] Error rendering subject template');
|
|
58
|
+
}
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
return { subject, body };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export class PugInlineEmailTemplateRendererFactory {
|
|
65
|
+
create(generatedTemplates) {
|
|
66
|
+
return new PugInlineEmailTemplateRenderer(generatedTemplates);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile-pug-templates.d.ts","sourceRoot":"","sources":["../../src/scripts/compile-pug-templates.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
/**
|
|
5
|
+
* Recursively finds all .pug files in a directory
|
|
6
|
+
*/
|
|
7
|
+
function findPugFiles(dir, baseDir, files = new Map()) {
|
|
8
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
9
|
+
for (const entry of entries) {
|
|
10
|
+
const fullPath = path.join(dir, entry.name);
|
|
11
|
+
if (entry.isDirectory()) {
|
|
12
|
+
// Recursively search subdirectories
|
|
13
|
+
findPugFiles(fullPath, baseDir, files);
|
|
14
|
+
}
|
|
15
|
+
else if (entry.isFile() && entry.name.endsWith('.pug')) {
|
|
16
|
+
// Read the pug file content
|
|
17
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
18
|
+
// Use relative path from base directory as the key
|
|
19
|
+
// Normalize to POSIX-style path (forward slashes) for cross-platform consistency
|
|
20
|
+
const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, '/');
|
|
21
|
+
files.set(relativePath, content);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return files;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Main function to compile pug templates to JSON
|
|
28
|
+
*/
|
|
29
|
+
function compilePugTemplates(inputDir = './templates', outputFile = 'compiled-templates.json') {
|
|
30
|
+
// Validate input directory exists
|
|
31
|
+
if (!fs.existsSync(inputDir)) {
|
|
32
|
+
console.error(`Error: Directory "${inputDir}" does not exist.`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
if (!fs.statSync(inputDir).isDirectory()) {
|
|
36
|
+
console.error(`Error: "${inputDir}" is not a directory.`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
console.log(`Searching for .pug files in: ${inputDir}`);
|
|
40
|
+
// Find all pug files
|
|
41
|
+
const pugFiles = findPugFiles(inputDir, inputDir);
|
|
42
|
+
console.log(`Found ${pugFiles.size} .pug file(s)`);
|
|
43
|
+
// Convert Map to plain object
|
|
44
|
+
const result = {};
|
|
45
|
+
for (const [filePath, content] of pugFiles.entries()) {
|
|
46
|
+
result[filePath] = content;
|
|
47
|
+
console.log(` - ${filePath}`);
|
|
48
|
+
}
|
|
49
|
+
// Write to output file
|
|
50
|
+
const outputContent = JSON.stringify(result, null, 2);
|
|
51
|
+
fs.writeFileSync(outputFile, outputContent, 'utf-8');
|
|
52
|
+
console.log(`\nSuccessfully compiled templates to: ${outputFile}`);
|
|
53
|
+
}
|
|
54
|
+
// Parse command line arguments
|
|
55
|
+
const args = process.argv.slice(2);
|
|
56
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
57
|
+
console.log('Usage: compile-pug-templates [input-directory] [output-file]');
|
|
58
|
+
console.log('');
|
|
59
|
+
console.log('Arguments:');
|
|
60
|
+
console.log(' input-directory Directory containing .pug templates (default: ./templates)');
|
|
61
|
+
console.log(' output-file Output JSON file path (default: compiled-templates.json)');
|
|
62
|
+
console.log('');
|
|
63
|
+
console.log('Examples:');
|
|
64
|
+
console.log(' compile-pug-templates');
|
|
65
|
+
console.log(' compile-pug-templates ./templates');
|
|
66
|
+
console.log(' compile-pug-templates ./templates ./compiled-templates.json');
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
const [inputDir, outputFile] = args;
|
|
70
|
+
// Run the compilation
|
|
71
|
+
compilePugTemplates(inputDir, outputFile);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vintasend-medplum",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -8,26 +8,35 @@
|
|
|
8
8
|
"prepublishOnly": "npm run build",
|
|
9
9
|
"test": "jest",
|
|
10
10
|
"test:watch": "jest --watch",
|
|
11
|
-
"test:coverage": "jest --coverage"
|
|
11
|
+
"test:coverage": "jest --coverage",
|
|
12
|
+
"compile-templates": "ts-node src/scripts/compile-pug-templates.ts"
|
|
12
13
|
},
|
|
13
14
|
"files": [
|
|
14
|
-
"dist"
|
|
15
|
+
"dist",
|
|
16
|
+
"src/scripts/compile-pug-templates.ts"
|
|
15
17
|
],
|
|
18
|
+
"bin": {
|
|
19
|
+
"compile-pug-templates": "dist/scripts/compile-pug-templates.js"
|
|
20
|
+
},
|
|
16
21
|
"author": "Hugo Bessa",
|
|
17
22
|
"license": "MIT",
|
|
18
23
|
"peerDependencies": {
|
|
19
24
|
"@medplum/core": "^5.0.10",
|
|
20
25
|
"@medplum/fhirtypes": "^5.0.10",
|
|
21
26
|
"pdfmake": "^0.2.23",
|
|
22
|
-
"
|
|
27
|
+
"pug": "^3.0.3",
|
|
28
|
+
"vintasend": "^0.4.3"
|
|
23
29
|
},
|
|
24
30
|
"devDependencies": {
|
|
25
31
|
"@medplum/definitions": "^5.0.10",
|
|
26
32
|
"@medplum/mock": "^5.0.10",
|
|
27
33
|
"@types/jest": "^30.0.0",
|
|
34
|
+
"@types/node": "^22.0.0",
|
|
28
35
|
"@types/pdfmake": "^0.2.12",
|
|
36
|
+
"@types/pug": "^2.0.10",
|
|
29
37
|
"jest": "^30.2.0",
|
|
30
38
|
"ts-jest": "^29.4.6",
|
|
39
|
+
"ts-node": "^10.9.2",
|
|
31
40
|
"typescript": "^5.9.3"
|
|
32
41
|
}
|
|
33
42
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
interface PugTemplatesMap {
|
|
6
|
+
[key: string]: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Recursively finds all .pug files in a directory
|
|
11
|
+
*/
|
|
12
|
+
function findPugFiles(dir: string, baseDir: string, files: Map<string, string> = new Map()): Map<string, string> {
|
|
13
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
14
|
+
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
const fullPath = path.join(dir, entry.name);
|
|
17
|
+
|
|
18
|
+
if (entry.isDirectory()) {
|
|
19
|
+
// Recursively search subdirectories
|
|
20
|
+
findPugFiles(fullPath, baseDir, files);
|
|
21
|
+
} else if (entry.isFile() && entry.name.endsWith('.pug')) {
|
|
22
|
+
// Read the pug file content
|
|
23
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
24
|
+
// Use relative path from base directory as the key
|
|
25
|
+
// Normalize to POSIX-style path (forward slashes) for cross-platform consistency
|
|
26
|
+
const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, '/');
|
|
27
|
+
files.set(relativePath, content);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return files;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Main function to compile pug templates to JSON
|
|
36
|
+
*/
|
|
37
|
+
function compilePugTemplates(inputDir: string = './templates', outputFile: string = 'compiled-templates.json'): void {
|
|
38
|
+
// Validate input directory exists
|
|
39
|
+
if (!fs.existsSync(inputDir)) {
|
|
40
|
+
console.error(`Error: Directory "${inputDir}" does not exist.`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!fs.statSync(inputDir).isDirectory()) {
|
|
45
|
+
console.error(`Error: "${inputDir}" is not a directory.`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log(`Searching for .pug files in: ${inputDir}`);
|
|
50
|
+
|
|
51
|
+
// Find all pug files
|
|
52
|
+
const pugFiles = findPugFiles(inputDir, inputDir);
|
|
53
|
+
|
|
54
|
+
console.log(`Found ${pugFiles.size} .pug file(s)`);
|
|
55
|
+
|
|
56
|
+
// Convert Map to plain object
|
|
57
|
+
const result: PugTemplatesMap = {};
|
|
58
|
+
for (const [filePath, content] of pugFiles.entries()) {
|
|
59
|
+
result[filePath] = content;
|
|
60
|
+
console.log(` - ${filePath}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Write to output file
|
|
64
|
+
const outputContent = JSON.stringify(result, null, 2);
|
|
65
|
+
fs.writeFileSync(outputFile, outputContent, 'utf-8');
|
|
66
|
+
|
|
67
|
+
console.log(`\nSuccessfully compiled templates to: ${outputFile}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Parse command line arguments
|
|
71
|
+
const args = process.argv.slice(2);
|
|
72
|
+
|
|
73
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
74
|
+
console.log('Usage: compile-pug-templates [input-directory] [output-file]');
|
|
75
|
+
console.log('');
|
|
76
|
+
console.log('Arguments:');
|
|
77
|
+
console.log(' input-directory Directory containing .pug templates (default: ./templates)');
|
|
78
|
+
console.log(' output-file Output JSON file path (default: compiled-templates.json)');
|
|
79
|
+
console.log('');
|
|
80
|
+
console.log('Examples:');
|
|
81
|
+
console.log(' compile-pug-templates');
|
|
82
|
+
console.log(' compile-pug-templates ./templates');
|
|
83
|
+
console.log(' compile-pug-templates ./templates ./compiled-templates.json');
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const [inputDir, outputFile] = args;
|
|
88
|
+
|
|
89
|
+
// Run the compilation
|
|
90
|
+
compilePugTemplates(inputDir, outputFile);
|