vintasend-medplum 0.4.19 → 0.5.0
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/medplum-attachment-manager.d.ts +8 -4
- package/dist/medplum-attachment-manager.d.ts.map +1 -1
- package/dist/medplum-attachment-manager.js +32 -18
- package/dist/medplum-backend.d.ts +15 -4
- package/dist/medplum-backend.d.ts.map +1 -1
- package/dist/medplum-backend.js +100 -83
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MedplumClient } from '@medplum/core';
|
|
2
2
|
import { BaseAttachmentManager } from 'vintasend/dist/services/attachment-manager/base-attachment-manager';
|
|
3
|
-
import type { AttachmentFileRecord, AttachmentFile, FileAttachment } from 'vintasend/dist/types/attachment';
|
|
3
|
+
import type { AttachmentFileRecord, AttachmentFile, FileAttachment, StorageIdentifiers } from 'vintasend/dist/types/attachment';
|
|
4
4
|
/**
|
|
5
5
|
* Medplum AttachmentManager implementation.
|
|
6
6
|
*
|
|
@@ -38,12 +38,16 @@ export declare class MedplumAttachmentManager extends BaseAttachmentManager {
|
|
|
38
38
|
*/
|
|
39
39
|
deleteFile(fileId: string): Promise<void>;
|
|
40
40
|
/**
|
|
41
|
-
*
|
|
41
|
+
* Delete a file from Medplum storage using storage identifiers.
|
|
42
|
+
*/
|
|
43
|
+
deleteFileByIdentifiers(storageIdentifiers: StorageIdentifiers): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Reconstruct an AttachmentFile from storage identifiers.
|
|
42
46
|
*
|
|
43
|
-
* @param
|
|
47
|
+
* @param storageIdentifiers - MedplumStorageIdentifiers containing 'medplumBinaryId' or 'url'
|
|
44
48
|
* @returns AttachmentFile instance for accessing the file
|
|
45
49
|
*/
|
|
46
|
-
reconstructAttachmentFile(
|
|
50
|
+
reconstructAttachmentFile(storageIdentifiers: StorageIdentifiers): AttachmentFile;
|
|
47
51
|
}
|
|
48
52
|
/**
|
|
49
53
|
* Medplum AttachmentFile implementation.
|
|
@@ -1 +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,
|
|
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,EACd,kBAAkB,EACnB,MAAM,iCAAiC,CAAC;AAGzC;;;;;;;;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;IAqEhC;;;;;OAKG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAkDnE;;;;;;OAMG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB/C;;OAEG;IACG,uBAAuB,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IASpF;;;;;OAKG;IACH,yBAAyB,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,cAAc;CAiBlF;AAED;;;;GAIG;AACH,qBAAa,qBAAsB,YAAW,cAAc;IAExD,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,QAAQ;gBADR,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,MAAM;IAG1B;;;;;;OAMG;IACG,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IA4C7B;;;;;OAKG;IACG,MAAM,IAAI,OAAO,CAAC,cAAc,CAAC;IAUvC;;;;;;;OAOG;IACG,GAAG,CAAC,SAAS,SAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAWtC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAG9B"}
|
|
@@ -64,17 +64,19 @@ export class MedplumAttachmentManager extends BaseAttachmentManager {
|
|
|
64
64
|
},
|
|
65
65
|
};
|
|
66
66
|
const createdMedia = await this.medplum.createResource(media);
|
|
67
|
+
const storageIdentifiers = {
|
|
68
|
+
id: createdMedia.id,
|
|
69
|
+
medplumBinaryId: createdBinary.id,
|
|
70
|
+
medplumMediaId: createdMedia.id,
|
|
71
|
+
url: binaryUrl,
|
|
72
|
+
};
|
|
67
73
|
return {
|
|
68
74
|
id: createdMedia.id,
|
|
69
75
|
filename,
|
|
70
76
|
contentType: finalContentType,
|
|
71
77
|
size: buffer.length,
|
|
72
78
|
checksum,
|
|
73
|
-
|
|
74
|
-
url: binaryUrl,
|
|
75
|
-
binaryId: createdBinary.id,
|
|
76
|
-
creation: createdMedia.content.creation,
|
|
77
|
-
},
|
|
79
|
+
storageIdentifiers,
|
|
78
80
|
createdAt: createdMedia.meta?.lastUpdated ? new Date(createdMedia.meta.lastUpdated) : new Date(),
|
|
79
81
|
updatedAt: createdMedia.meta?.lastUpdated ? new Date(createdMedia.meta.lastUpdated) : new Date(),
|
|
80
82
|
};
|
|
@@ -104,17 +106,19 @@ export class MedplumAttachmentManager extends BaseAttachmentManager {
|
|
|
104
106
|
// Extract checksum from identifier
|
|
105
107
|
const checksumIdentifier = media.identifier?.find((id) => id.system === 'http://vintasend.com/fhir/attachment-checksum');
|
|
106
108
|
const checksum = checksumIdentifier?.value || '';
|
|
109
|
+
const storageIdentifiers = {
|
|
110
|
+
id: media.id,
|
|
111
|
+
medplumBinaryId: binaryId || '',
|
|
112
|
+
medplumMediaId: media.id,
|
|
113
|
+
url: media.content.url || '',
|
|
114
|
+
};
|
|
107
115
|
return {
|
|
108
116
|
id: media.id,
|
|
109
117
|
filename: media.content.title || 'untitled',
|
|
110
118
|
contentType: media.content.contentType || 'application/octet-stream',
|
|
111
119
|
size: media.content.size || 0,
|
|
112
120
|
checksum,
|
|
113
|
-
|
|
114
|
-
url: media.content.url,
|
|
115
|
-
binaryId: binaryId,
|
|
116
|
-
creation: media.content.creation,
|
|
117
|
-
},
|
|
121
|
+
storageIdentifiers,
|
|
118
122
|
createdAt: media.meta?.lastUpdated ? new Date(media.meta.lastUpdated) : new Date(),
|
|
119
123
|
updatedAt: media.meta?.lastUpdated ? new Date(media.meta.lastUpdated) : new Date(),
|
|
120
124
|
};
|
|
@@ -153,22 +157,32 @@ export class MedplumAttachmentManager extends BaseAttachmentManager {
|
|
|
153
157
|
await this.medplum.deleteResource('Media', fileId);
|
|
154
158
|
}
|
|
155
159
|
/**
|
|
156
|
-
*
|
|
160
|
+
* Delete a file from Medplum storage using storage identifiers.
|
|
161
|
+
*/
|
|
162
|
+
async deleteFileByIdentifiers(storageIdentifiers) {
|
|
163
|
+
if (storageIdentifiers.id && typeof storageIdentifiers.id === 'string') {
|
|
164
|
+
await this.deleteFile(storageIdentifiers.id);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
throw new Error('Invalid storage identifiers: missing id');
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Reconstruct an AttachmentFile from storage identifiers.
|
|
157
171
|
*
|
|
158
|
-
* @param
|
|
172
|
+
* @param storageIdentifiers - MedplumStorageIdentifiers containing 'medplumBinaryId' or 'url'
|
|
159
173
|
* @returns AttachmentFile instance for accessing the file
|
|
160
174
|
*/
|
|
161
|
-
reconstructAttachmentFile(
|
|
162
|
-
let binaryId =
|
|
163
|
-
// If
|
|
164
|
-
if (!binaryId &&
|
|
165
|
-
const match =
|
|
175
|
+
reconstructAttachmentFile(storageIdentifiers) {
|
|
176
|
+
let binaryId = storageIdentifiers.medplumBinaryId;
|
|
177
|
+
// If medplumBinaryId not provided directly, try to extract from url
|
|
178
|
+
if (!binaryId && storageIdentifiers.url && typeof storageIdentifiers.url === 'string') {
|
|
179
|
+
const match = storageIdentifiers.url.match(/Binary\/([^/]+)/);
|
|
166
180
|
if (match) {
|
|
167
181
|
binaryId = match[1];
|
|
168
182
|
}
|
|
169
183
|
}
|
|
170
184
|
if (!binaryId) {
|
|
171
|
-
throw new Error('Storage
|
|
185
|
+
throw new Error('Storage identifiers must contain medplumBinaryId or a url with Binary reference');
|
|
172
186
|
}
|
|
173
187
|
return new MedplumAttachmentFile(this.medplum, binaryId);
|
|
174
188
|
}
|
|
@@ -55,9 +55,16 @@ export declare class MedplumNotificationBackend<Config extends BaseNotificationT
|
|
|
55
55
|
getAllOneOffNotifications(): Promise<DatabaseOneOffNotification<Config>[]>;
|
|
56
56
|
getOneOffNotifications(page: number, pageSize: number): Promise<DatabaseOneOffNotification<Config>[]>;
|
|
57
57
|
/**
|
|
58
|
-
*
|
|
58
|
+
* Store an attachment file record in the backend's database (Media resource).
|
|
59
|
+
* This is called after the AttachmentManager uploads a file and returns storageIdentifiers.
|
|
60
|
+
* The backend stores file metadata along with the storageIdentifiers for later retrieval.
|
|
59
61
|
*/
|
|
60
|
-
|
|
62
|
+
storeAttachmentFileRecord(record: AttachmentFileRecord): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Get an attachment file record from the backend's database.
|
|
65
|
+
* Reads the Media resource created by storeAttachmentFileRecord().
|
|
66
|
+
*/
|
|
67
|
+
getAttachmentFileRecord(fileId: string): Promise<AttachmentFileRecord | null>;
|
|
61
68
|
/**
|
|
62
69
|
* Helper: Convert FHIR Media resource to AttachmentFileRecord
|
|
63
70
|
* Extracts metadata directly from the Media resource using the same logic as MedplumAttachmentManager.getFile
|
|
@@ -68,10 +75,9 @@ export declare class MedplumNotificationBackend<Config extends BaseNotificationT
|
|
|
68
75
|
*/
|
|
69
76
|
private createFhirAttachment;
|
|
70
77
|
/**
|
|
71
|
-
* Helper: Create MedplumAttachmentFile from storage
|
|
78
|
+
* Helper: Create MedplumAttachmentFile from storage identifiers
|
|
72
79
|
*/
|
|
73
80
|
private createMedplumAttachmentFile;
|
|
74
|
-
getAttachmentFile(fileId: string): Promise<AttachmentFileRecord | null>;
|
|
75
81
|
findAttachmentFileByChecksum(checksum: string): Promise<AttachmentFileRecord | null>;
|
|
76
82
|
/**
|
|
77
83
|
* Find multiple attachment files by their checksums in a single query
|
|
@@ -80,9 +86,14 @@ export declare class MedplumNotificationBackend<Config extends BaseNotificationT
|
|
|
80
86
|
/**
|
|
81
87
|
* Helper: Process attachments and return payload items
|
|
82
88
|
* Optimizes by batching checksum lookups
|
|
89
|
+
* Works with any AttachmentManager implementation
|
|
83
90
|
*/
|
|
84
91
|
private processAttachments;
|
|
85
92
|
deleteAttachmentFile(fileId: string): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* Check if a file is referenced by any active notifications
|
|
95
|
+
*/
|
|
96
|
+
private isFileReferencedByNotifications;
|
|
86
97
|
getOrphanedAttachmentFiles(): Promise<AttachmentFileRecord[]>;
|
|
87
98
|
getAttachments(notificationId: Config['NotificationIdType']): Promise<StoredAttachment[]>;
|
|
88
99
|
deleteNotificationAttachment(notificationId: Config['NotificationIdType'], attachmentId: string): Promise<void>;
|
|
@@ -1 +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,UAAU,EAAE,MAAM,6CAA6C,CAAC;AAC9E,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;
|
|
1
|
+
{"version":3,"file":"medplum-backend.d.ts","sourceRoot":"","sources":["../src/medplum-backend.ts"],"names":[],"mappings":"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"}
|
package/dist/medplum-backend.js
CHANGED
|
@@ -444,47 +444,65 @@ export class MedplumNotificationBackend {
|
|
|
444
444
|
}
|
|
445
445
|
/* Attachment management methods */
|
|
446
446
|
/**
|
|
447
|
-
*
|
|
447
|
+
* Store an attachment file record in the backend's database (Media resource).
|
|
448
|
+
* This is called after the AttachmentManager uploads a file and returns storageIdentifiers.
|
|
449
|
+
* The backend stores file metadata along with the storageIdentifiers for later retrieval.
|
|
448
450
|
*/
|
|
449
|
-
async
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
451
|
+
async storeAttachmentFileRecord(record) {
|
|
452
|
+
const storageIds = record.storageIdentifiers;
|
|
453
|
+
// Create a Media resource in Medplum to store file metadata
|
|
454
|
+
// This is the backend's database record for the file
|
|
455
|
+
const media = {
|
|
456
|
+
resourceType: 'Media',
|
|
457
|
+
status: 'completed',
|
|
458
|
+
meta: {
|
|
459
|
+
tag: [{ code: 'vintasend-backend-attachment-metadata' }],
|
|
460
|
+
},
|
|
461
|
+
content: {
|
|
462
|
+
contentType: record.contentType,
|
|
463
|
+
url: storageIds.url,
|
|
464
|
+
size: record.size,
|
|
465
|
+
title: record.filename,
|
|
466
|
+
creation: record.createdAt.toISOString(),
|
|
467
|
+
},
|
|
468
|
+
identifier: [
|
|
469
|
+
{
|
|
470
|
+
system: 'http://vintasend.com/fhir/attachment-checksum',
|
|
471
|
+
value: record.checksum,
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
system: 'http://vintasend.com/fhir/binary-id',
|
|
475
|
+
value: storageIds.medplumBinaryId,
|
|
476
|
+
},
|
|
477
|
+
],
|
|
478
|
+
};
|
|
479
|
+
// Store storageIdentifiers as JSON string in an extension
|
|
480
|
+
// This keeps identifiers opaque - backend doesn't inspect specific fields
|
|
481
|
+
media.extension = media.extension || [];
|
|
482
|
+
media.extension.push({
|
|
483
|
+
url: 'http://vintasend.com/fhir/StructureDefinition/storage-identifiers',
|
|
484
|
+
valueString: JSON.stringify(storageIds),
|
|
485
|
+
});
|
|
486
|
+
await this.medplum.createResource(media);
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Get an attachment file record from the backend's database.
|
|
490
|
+
* Reads the Media resource created by storeAttachmentFileRecord().
|
|
491
|
+
*/
|
|
492
|
+
async getAttachmentFileRecord(fileId) {
|
|
493
|
+
try {
|
|
494
|
+
const media = await this.medplum.readResource('Media', fileId);
|
|
495
|
+
return this.mediaToAttachmentFileRecord(media);
|
|
474
496
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
for await (const chunk of file) {
|
|
478
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
479
|
-
}
|
|
497
|
+
catch {
|
|
498
|
+
return null;
|
|
480
499
|
}
|
|
481
|
-
return Buffer.concat(chunks);
|
|
482
500
|
}
|
|
483
501
|
/**
|
|
484
502
|
* Helper: Convert FHIR Media resource to AttachmentFileRecord
|
|
485
503
|
* Extracts metadata directly from the Media resource using the same logic as MedplumAttachmentManager.getFile
|
|
486
504
|
*/
|
|
487
|
-
|
|
505
|
+
mediaToAttachmentFileRecord(media) {
|
|
488
506
|
if (!media.id) {
|
|
489
507
|
throw new Error('Invalid Media resource: missing id');
|
|
490
508
|
}
|
|
@@ -504,17 +522,20 @@ export class MedplumNotificationBackend {
|
|
|
504
522
|
// Extract checksum from identifier
|
|
505
523
|
const checksumIdentifier = media.identifier?.find((id) => id.system === 'http://vintasend.com/fhir/attachment-checksum');
|
|
506
524
|
const checksum = checksumIdentifier?.value || '';
|
|
525
|
+
// Create proper MedplumStorageIdentifiers with correct field names
|
|
526
|
+
const storageIdentifiers = {
|
|
527
|
+
id: media.id,
|
|
528
|
+
medplumBinaryId: binaryId || '',
|
|
529
|
+
medplumMediaId: media.id,
|
|
530
|
+
url: media.content.url || '',
|
|
531
|
+
};
|
|
507
532
|
return {
|
|
508
533
|
id: media.id,
|
|
509
534
|
filename: media.content.title || 'untitled',
|
|
510
535
|
contentType: media.content.contentType || 'application/octet-stream',
|
|
511
536
|
size: media.content.size || 0,
|
|
512
537
|
checksum,
|
|
513
|
-
|
|
514
|
-
url: media.content.url,
|
|
515
|
-
binaryId: binaryId,
|
|
516
|
-
creation: media.content.creation,
|
|
517
|
-
},
|
|
538
|
+
storageIdentifiers,
|
|
518
539
|
createdAt: media.meta?.lastUpdated ? new Date(media.meta.lastUpdated) : new Date(),
|
|
519
540
|
updatedAt: media.meta?.lastUpdated ? new Date(media.meta.lastUpdated) : new Date(),
|
|
520
541
|
};
|
|
@@ -523,7 +544,7 @@ export class MedplumNotificationBackend {
|
|
|
523
544
|
* Helper: Create FHIR Attachment from file record
|
|
524
545
|
*/
|
|
525
546
|
async createFhirAttachment(fileRecord) {
|
|
526
|
-
const url = fileRecord.
|
|
547
|
+
const url = fileRecord.storageIdentifiers?.url;
|
|
527
548
|
return {
|
|
528
549
|
contentType: fileRecord.contentType,
|
|
529
550
|
url: url,
|
|
@@ -533,28 +554,19 @@ export class MedplumNotificationBackend {
|
|
|
533
554
|
};
|
|
534
555
|
}
|
|
535
556
|
/**
|
|
536
|
-
* Helper: Create MedplumAttachmentFile from storage
|
|
557
|
+
* Helper: Create MedplumAttachmentFile from storage identifiers
|
|
537
558
|
*/
|
|
538
559
|
createMedplumAttachmentFile(fileRecord) {
|
|
539
560
|
const manager = this.getAttachmentManager();
|
|
540
|
-
return manager.reconstructAttachmentFile(fileRecord.
|
|
541
|
-
}
|
|
542
|
-
async getAttachmentFile(fileId) {
|
|
543
|
-
try {
|
|
544
|
-
const media = await this.medplum.readResource('Media', fileId);
|
|
545
|
-
return this.mediaToAttachmentFileRecord(media);
|
|
546
|
-
}
|
|
547
|
-
catch {
|
|
548
|
-
return null;
|
|
549
|
-
}
|
|
561
|
+
return manager.reconstructAttachmentFile(fileRecord.storageIdentifiers);
|
|
550
562
|
}
|
|
551
563
|
async findAttachmentFileByChecksum(checksum) {
|
|
552
564
|
try {
|
|
553
|
-
const results = await this.medplum.searchResources('Media', `identifier=${checksum}&_tag=attachment-
|
|
565
|
+
const results = await this.medplum.searchResources('Media', `identifier=${checksum}&_tag=vintasend-backend-attachment-metadata`);
|
|
554
566
|
if (results.length === 0) {
|
|
555
567
|
return null;
|
|
556
568
|
}
|
|
557
|
-
const fileRecord =
|
|
569
|
+
const fileRecord = this.mediaToAttachmentFileRecord(results[0]);
|
|
558
570
|
return fileRecord;
|
|
559
571
|
}
|
|
560
572
|
catch {
|
|
@@ -571,13 +583,13 @@ export class MedplumNotificationBackend {
|
|
|
571
583
|
try {
|
|
572
584
|
// Build query with all checksums
|
|
573
585
|
const identifierQuery = checksums.join(',');
|
|
574
|
-
const results = await this.medplum.searchResources('Media', `identifier=${identifierQuery}&_tag=attachment-
|
|
586
|
+
const results = await this.medplum.searchResources('Media', `identifier=${identifierQuery}&_tag=vintasend-backend-attachment-metadata`);
|
|
575
587
|
// Map results by checksum
|
|
576
588
|
const filesByChecksum = new Map();
|
|
577
589
|
for (const media of results) {
|
|
578
|
-
const checksum = media.identifier?.
|
|
590
|
+
const checksum = media.identifier?.find((id) => id.system === 'http://vintasend.com/fhir/attachment-checksum')?.value;
|
|
579
591
|
if (checksum) {
|
|
580
|
-
const fileRecord =
|
|
592
|
+
const fileRecord = this.mediaToAttachmentFileRecord(media);
|
|
581
593
|
if (fileRecord) {
|
|
582
594
|
filesByChecksum.set(checksum, fileRecord);
|
|
583
595
|
}
|
|
@@ -592,6 +604,7 @@ export class MedplumNotificationBackend {
|
|
|
592
604
|
/**
|
|
593
605
|
* Helper: Process attachments and return payload items
|
|
594
606
|
* Optimizes by batching checksum lookups
|
|
607
|
+
* Works with any AttachmentManager implementation
|
|
595
608
|
*/
|
|
596
609
|
async processAttachments(attachments) {
|
|
597
610
|
const manager = this.getAttachmentManager();
|
|
@@ -621,11 +634,11 @@ export class MedplumNotificationBackend {
|
|
|
621
634
|
// Fetch all referenced files in parallel
|
|
622
635
|
const referencedFiles = await Promise.all(fileReferences.map(async ({ fileId }) => ({
|
|
623
636
|
fileId,
|
|
624
|
-
record: await this.
|
|
637
|
+
record: await this.getAttachmentFileRecord(fileId)
|
|
625
638
|
})));
|
|
626
|
-
// Calculate checksums for new uploads
|
|
639
|
+
// Calculate checksums for new uploads using manager's fileToBuffer
|
|
627
640
|
const uploadsWithChecksums = await Promise.all(newUploads.map(async (upload) => {
|
|
628
|
-
const buffer = await
|
|
641
|
+
const buffer = await manager.fileToBuffer(upload.file);
|
|
629
642
|
const checksum = await manager.calculateChecksum(buffer);
|
|
630
643
|
return { ...upload, buffer, checksum };
|
|
631
644
|
}));
|
|
@@ -655,8 +668,10 @@ export class MedplumNotificationBackend {
|
|
|
655
668
|
fileRecord = existingFile;
|
|
656
669
|
}
|
|
657
670
|
else {
|
|
658
|
-
// Upload new file
|
|
671
|
+
// Upload new file to the manager
|
|
659
672
|
fileRecord = await manager.uploadFile(upload.file, upload.filename, upload.contentType);
|
|
673
|
+
// Store the record in the backend's database
|
|
674
|
+
await this.storeAttachmentFileRecord(fileRecord);
|
|
660
675
|
}
|
|
661
676
|
fileRecords.push({
|
|
662
677
|
index: upload.index,
|
|
@@ -679,41 +694,40 @@ export class MedplumNotificationBackend {
|
|
|
679
694
|
return payload;
|
|
680
695
|
}
|
|
681
696
|
async deleteAttachmentFile(fileId) {
|
|
697
|
+
const manager = this.getAttachmentManager();
|
|
682
698
|
// Check if file exists first
|
|
683
|
-
const fileRecord = await this.
|
|
699
|
+
const fileRecord = await this.getAttachmentFileRecord(fileId);
|
|
684
700
|
if (!fileRecord) {
|
|
685
701
|
// File not found, return early
|
|
686
702
|
return;
|
|
687
703
|
}
|
|
688
704
|
// Check if file is still referenced by any notifications
|
|
689
|
-
|
|
690
|
-
_tag: 'notification',
|
|
691
|
-
});
|
|
692
|
-
const isReferenced = communications.some((comm) => comm.payload?.some((p) => p.contentAttachment?.url?.includes(fileId)));
|
|
693
|
-
if (isReferenced) {
|
|
705
|
+
if (await this.isFileReferencedByNotifications(fileId)) {
|
|
694
706
|
throw new Error('Cannot delete attachment file: still referenced by notifications');
|
|
695
707
|
}
|
|
696
|
-
// Delete from storage backend
|
|
697
|
-
|
|
698
|
-
|
|
708
|
+
// Delete from storage backend using storageIdentifiers
|
|
709
|
+
await manager.deleteFileByIdentifiers(fileRecord.storageIdentifiers);
|
|
710
|
+
// Delete backend's database record (Media resource)
|
|
711
|
+
await this.medplum.deleteResource('Media', fileId);
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Check if a file is referenced by any active notifications
|
|
715
|
+
*/
|
|
716
|
+
async isFileReferencedByNotifications(fileId) {
|
|
717
|
+
try {
|
|
718
|
+
const communications = await this.medplum.searchResources('Communication', {
|
|
719
|
+
_tag: 'notification',
|
|
720
|
+
});
|
|
721
|
+
return communications.some((comm) => comm.payload?.some((p) => p.contentAttachment?.url?.includes(fileId)));
|
|
699
722
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
const binaryId = fileRecord.storageMetadata.url.replace('Binary/', '');
|
|
703
|
-
try {
|
|
704
|
-
await this.medplum.deleteResource('Binary', binaryId);
|
|
705
|
-
}
|
|
706
|
-
catch {
|
|
707
|
-
// Binary may not exist or already deleted
|
|
708
|
-
}
|
|
723
|
+
catch {
|
|
724
|
+
return false;
|
|
709
725
|
}
|
|
710
|
-
// Delete Media resource
|
|
711
|
-
await this.medplum.deleteResource('Media', fileId);
|
|
712
726
|
}
|
|
713
727
|
async getOrphanedAttachmentFiles() {
|
|
714
|
-
// Get all Media resources tagged as
|
|
728
|
+
// Get all backend's file metadata records (Media resources tagged as backend metadata)
|
|
715
729
|
const allMedia = await this.medplum.searchResources('Media', {
|
|
716
|
-
_tag: 'attachment-
|
|
730
|
+
_tag: 'vintasend-backend-attachment-metadata',
|
|
717
731
|
});
|
|
718
732
|
// Get all notifications
|
|
719
733
|
const communications = await this.medplum.searchResources('Communication', {
|
|
@@ -734,7 +748,10 @@ export class MedplumNotificationBackend {
|
|
|
734
748
|
}
|
|
735
749
|
// Filter orphaned media
|
|
736
750
|
const orphaned = allMedia.filter((media) => media.id && !referencedIds.has(media.id));
|
|
737
|
-
const fileRecords =
|
|
751
|
+
const fileRecords = [];
|
|
752
|
+
for (const media of orphaned) {
|
|
753
|
+
fileRecords.push(this.mediaToAttachmentFileRecord(media));
|
|
754
|
+
}
|
|
738
755
|
return fileRecords.filter((record) => record !== null);
|
|
739
756
|
}
|
|
740
757
|
async getAttachments(notificationId) {
|
|
@@ -793,7 +810,7 @@ export class MedplumNotificationBackend {
|
|
|
793
810
|
createdAt: fileRecord.createdAt,
|
|
794
811
|
file: attachmentFile,
|
|
795
812
|
description: payloadData?.description,
|
|
796
|
-
storageMetadata: fileRecord.
|
|
813
|
+
storageMetadata: fileRecord.storageIdentifiers,
|
|
797
814
|
});
|
|
798
815
|
this.logger?.info(`[MedplumBackend.getAttachments] Added attachment ${media.id} to list`);
|
|
799
816
|
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { StorageIdentifiers } from 'vintasend/dist/types/attachment';
|
|
2
|
+
/**
|
|
3
|
+
* Medplum-specific storage identifiers.
|
|
4
|
+
* Contains references to FHIR Binary and Media resources.
|
|
5
|
+
*
|
|
6
|
+
* Used when MedplumAttachmentManager uploads files:
|
|
7
|
+
* - Creates a Binary resource to store the file data
|
|
8
|
+
* - Creates a Media resource to store metadata
|
|
9
|
+
* - Returns both IDs so backend can reconstruct file access later
|
|
10
|
+
*/
|
|
11
|
+
export interface MedplumStorageIdentifiers extends StorageIdentifiers {
|
|
12
|
+
id: string;
|
|
13
|
+
medplumBinaryId: string;
|
|
14
|
+
medplumMediaId: string;
|
|
15
|
+
url: string;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAE1E;;;;;;;;GAQG;AACH,MAAM,WAAW,yBAA0B,SAAQ,kBAAkB;IAEnE,EAAE,EAAE,MAAM,CAAC;IAGX,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IAGZ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vintasend-medplum",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@medplum/fhirtypes": "^5.0.10",
|
|
26
26
|
"pdfmake": "^0.2.23",
|
|
27
27
|
"pug": "^3.0.3",
|
|
28
|
-
"vintasend": "^0.
|
|
28
|
+
"vintasend": "^0.5.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@medplum/definitions": "^5.0.10",
|