@openhi/constructs 0.0.26 → 0.0.27
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.
|
@@ -55,19 +55,31 @@ function openHiContextMiddleware(req, res, next) {
|
|
|
55
55
|
});
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
|
+
const invoke = getCurrentInvoke();
|
|
59
|
+
const event = invoke?.event ?? req.apiGateway?.event;
|
|
60
|
+
const requestId = typeof event?.requestContext?.requestId === "string" ? event.requestContext.requestId : void 0;
|
|
58
61
|
req.openhiContext = {
|
|
59
62
|
tenantId: claims.openhi_tenant_id,
|
|
60
63
|
workspaceId: claims.openhi_workspace_id,
|
|
61
64
|
date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
actorId: claims.openhi_user_id,
|
|
66
|
+
actorName: claims.openhi_user_name,
|
|
67
|
+
actorType: "human",
|
|
68
|
+
roleId: typeof claims.openhi_role_id === "string" && claims.openhi_role_id !== "" ? claims.openhi_role_id : void 0,
|
|
69
|
+
requestId,
|
|
70
|
+
source: "rest",
|
|
71
|
+
clientId: typeof claims.openhi_client_id === "string" && claims.openhi_client_id !== "" ? claims.openhi_client_id : void 0
|
|
64
72
|
};
|
|
65
73
|
next();
|
|
66
74
|
}
|
|
67
75
|
|
|
68
|
-
// src/data/rest-api/
|
|
76
|
+
// src/data/rest-api/routes/configuration/configuration.ts
|
|
69
77
|
import express from "express";
|
|
70
78
|
|
|
79
|
+
// src/data/rest-api/routes/configuration/configuration-common.ts
|
|
80
|
+
var BASE_PATH = "/Configuration";
|
|
81
|
+
var SK = "CURRENT";
|
|
82
|
+
|
|
71
83
|
// src/lib/compression.ts
|
|
72
84
|
import { gzipSync, gunzipSync } from "zlib";
|
|
73
85
|
var ENVELOPE_VERSION = 1;
|
|
@@ -124,16 +136,124 @@ function decompressResource(compressedOrRaw) {
|
|
|
124
136
|
return compressedOrRaw;
|
|
125
137
|
}
|
|
126
138
|
|
|
127
|
-
// src/data/dynamo/
|
|
139
|
+
// src/data/dynamo/dynamo-service.ts
|
|
128
140
|
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
129
141
|
import { Service } from "electrodb";
|
|
130
142
|
|
|
131
|
-
// src/data/dynamo/
|
|
143
|
+
// src/data/dynamo/entities/configuration.ts
|
|
132
144
|
import { Entity } from "electrodb";
|
|
133
|
-
var
|
|
145
|
+
var Configuration = new Entity({
|
|
146
|
+
model: {
|
|
147
|
+
entity: "configuration",
|
|
148
|
+
service: "openhi",
|
|
149
|
+
version: "01"
|
|
150
|
+
},
|
|
151
|
+
attributes: {
|
|
152
|
+
/** Sort key. "CURRENT" for current version; version history in S3. */
|
|
153
|
+
sk: {
|
|
154
|
+
type: "string",
|
|
155
|
+
required: true,
|
|
156
|
+
default: "CURRENT"
|
|
157
|
+
},
|
|
158
|
+
/** Tenant scope. Use "BASELINE" when the config is baseline default (no tenant). */
|
|
159
|
+
tenantId: {
|
|
160
|
+
type: "string",
|
|
161
|
+
required: true,
|
|
162
|
+
default: "BASELINE"
|
|
163
|
+
},
|
|
164
|
+
/** Workspace scope. Use "-" when absent. */
|
|
165
|
+
workspaceId: {
|
|
166
|
+
type: "string",
|
|
167
|
+
required: true,
|
|
168
|
+
default: "-"
|
|
169
|
+
},
|
|
170
|
+
/** User scope. Use "-" when absent. */
|
|
171
|
+
userId: {
|
|
172
|
+
type: "string",
|
|
173
|
+
required: true,
|
|
174
|
+
default: "-"
|
|
175
|
+
},
|
|
176
|
+
/** Role scope. Use "-" when absent. */
|
|
177
|
+
roleId: {
|
|
178
|
+
type: "string",
|
|
179
|
+
required: true,
|
|
180
|
+
default: "-"
|
|
181
|
+
},
|
|
182
|
+
/** Config type (category), e.g. endpoints, branding, display. */
|
|
183
|
+
key: {
|
|
184
|
+
type: "string",
|
|
185
|
+
required: true
|
|
186
|
+
},
|
|
187
|
+
/** FHIR Resource.id; logical id in URL and for the Configuration resource. */
|
|
188
|
+
id: {
|
|
189
|
+
type: "string",
|
|
190
|
+
required: true
|
|
191
|
+
},
|
|
192
|
+
/** Payload as JSON string. JSON.stringify(resource) on write; JSON.parse(item.resource) on read. */
|
|
193
|
+
resource: {
|
|
194
|
+
type: "string",
|
|
195
|
+
required: true
|
|
196
|
+
},
|
|
197
|
+
/** Version id (e.g. ULID). Tracks current version; S3 history key. */
|
|
198
|
+
vid: {
|
|
199
|
+
type: "string",
|
|
200
|
+
required: true
|
|
201
|
+
},
|
|
202
|
+
lastUpdated: {
|
|
203
|
+
type: "string",
|
|
204
|
+
required: true
|
|
205
|
+
},
|
|
206
|
+
deleted: {
|
|
207
|
+
type: "boolean",
|
|
208
|
+
required: false
|
|
209
|
+
},
|
|
210
|
+
bundleId: {
|
|
211
|
+
type: "string",
|
|
212
|
+
required: false
|
|
213
|
+
},
|
|
214
|
+
msgId: {
|
|
215
|
+
type: "string",
|
|
216
|
+
required: false
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
indexes: {
|
|
220
|
+
/** Base table: PK, SK (data store key names). PK is built from tenantId, workspaceId, userId, roleId; SK is built from key and sk. Do not supply PK or SK from outside. */
|
|
221
|
+
record: {
|
|
222
|
+
pk: {
|
|
223
|
+
field: "PK",
|
|
224
|
+
composite: ["tenantId", "workspaceId", "userId", "roleId"],
|
|
225
|
+
template: "OHI#CONFIG#TID#${tenantId}#WID#${workspaceId}#UID#${userId}#RID#${roleId}"
|
|
226
|
+
},
|
|
227
|
+
sk: {
|
|
228
|
+
field: "SK",
|
|
229
|
+
composite: ["key", "sk"],
|
|
230
|
+
template: "KEY#${key}#SK#${sk}"
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
/** GSI4 — Resource Type Index: list all Configuration in a tenant or workspace (no scan). Use for "list configs scoped to this tenant" (workspaceId = "-") or "list configs scoped to this workspace". Does not support hierarchical resolution in one query; use base table GetItem in fallback order (user → workspace → tenant → baseline) for that. */
|
|
234
|
+
gsi4: {
|
|
235
|
+
index: "GSI4",
|
|
236
|
+
condition: () => true,
|
|
237
|
+
pk: {
|
|
238
|
+
field: "GSI4PK",
|
|
239
|
+
composite: ["tenantId", "workspaceId"],
|
|
240
|
+
template: "TID#${tenantId}#WID#${workspaceId}#RT#Configuration"
|
|
241
|
+
},
|
|
242
|
+
sk: {
|
|
243
|
+
field: "GSI4SK",
|
|
244
|
+
composite: ["key", "sk"],
|
|
245
|
+
template: "KEY#${key}#SK#${sk}"
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// src/data/dynamo/entities/patient.ts
|
|
252
|
+
import { Entity as Entity2 } from "electrodb";
|
|
253
|
+
var Patient = new Entity2({
|
|
134
254
|
model: {
|
|
135
255
|
entity: "patient",
|
|
136
|
-
service: "
|
|
256
|
+
service: "openhi",
|
|
137
257
|
version: "01"
|
|
138
258
|
},
|
|
139
259
|
attributes: {
|
|
@@ -309,7 +429,7 @@ var Patient = new Entity({
|
|
|
309
429
|
}
|
|
310
430
|
});
|
|
311
431
|
|
|
312
|
-
// src/data/dynamo/
|
|
432
|
+
// src/data/dynamo/dynamo-service.ts
|
|
313
433
|
var table = process.env.DYNAMO_TABLE_NAME ?? "jesttesttable";
|
|
314
434
|
var client = new DynamoDBClient({
|
|
315
435
|
...process.env.MOCK_DYNAMODB_ENDPOINT && {
|
|
@@ -318,171 +438,275 @@ var client = new DynamoDBClient({
|
|
|
318
438
|
region: "local"
|
|
319
439
|
}
|
|
320
440
|
});
|
|
321
|
-
var entities = { patient: Patient };
|
|
322
|
-
var
|
|
323
|
-
function
|
|
324
|
-
|
|
441
|
+
var entities = { patient: Patient, configuration: Configuration };
|
|
442
|
+
var DynamoDataService = new Service(entities, { table, client });
|
|
443
|
+
function getDynamoDataService(tableName) {
|
|
444
|
+
const resolved = tableName ?? table;
|
|
445
|
+
return new Service(entities, { table: resolved, client });
|
|
325
446
|
}
|
|
326
447
|
|
|
327
|
-
// src/data/
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
448
|
+
// src/data/rest-api/routes/configuration/configuration-create.ts
|
|
449
|
+
async function createConfiguration(req, res) {
|
|
450
|
+
const ctx = req.openhiContext;
|
|
451
|
+
const {
|
|
452
|
+
tenantId: ctxTenantId,
|
|
453
|
+
workspaceId: ctxWorkspaceId,
|
|
454
|
+
actorId: ctxActorId,
|
|
455
|
+
date
|
|
456
|
+
} = ctx;
|
|
457
|
+
const body = req.body;
|
|
458
|
+
const key = body?.key;
|
|
459
|
+
if (!key || typeof key !== "string") {
|
|
460
|
+
return res.status(400).json({
|
|
461
|
+
resourceType: "OperationOutcome",
|
|
462
|
+
issue: [
|
|
463
|
+
{
|
|
464
|
+
severity: "error",
|
|
465
|
+
code: "required",
|
|
466
|
+
diagnostics: "Configuration key is required"
|
|
467
|
+
}
|
|
468
|
+
]
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
const id = body?.id ?? `config-${key}-${Date.now()}`;
|
|
472
|
+
const resourcePayload = body?.resource;
|
|
473
|
+
const resourceStr = typeof resourcePayload === "string" ? resourcePayload : JSON.stringify(resourcePayload ?? {});
|
|
474
|
+
const tenantId = body?.tenantId ?? ctxTenantId;
|
|
475
|
+
const workspaceId = body?.workspaceId ?? ctxWorkspaceId;
|
|
476
|
+
const userId = body?.userId ?? ctxActorId ?? "-";
|
|
477
|
+
const roleId = body?.roleId ?? "-";
|
|
478
|
+
const vid = body?.vid ?? (date.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36));
|
|
479
|
+
const lastUpdated = body?.lastUpdated ?? date;
|
|
480
|
+
const service = getDynamoDataService();
|
|
481
|
+
try {
|
|
482
|
+
await service.entities.configuration.put({
|
|
483
|
+
tenantId,
|
|
484
|
+
workspaceId,
|
|
485
|
+
userId,
|
|
486
|
+
roleId,
|
|
487
|
+
key,
|
|
488
|
+
id,
|
|
489
|
+
resource: compressResource(resourceStr),
|
|
490
|
+
vid,
|
|
491
|
+
lastUpdated,
|
|
492
|
+
sk: SK
|
|
493
|
+
}).go();
|
|
494
|
+
const config = {
|
|
495
|
+
resourceType: "Configuration",
|
|
496
|
+
id,
|
|
497
|
+
key,
|
|
498
|
+
resource: typeof resourcePayload === "object" ? resourcePayload : JSON.parse(resourceStr),
|
|
499
|
+
meta: { lastUpdated, versionId: vid }
|
|
500
|
+
};
|
|
501
|
+
return res.status(201).location(`${BASE_PATH}/${key}`).json(config);
|
|
502
|
+
} catch (err) {
|
|
503
|
+
console.error("POST Configuration error:", err);
|
|
504
|
+
return res.status(500).json({
|
|
505
|
+
resourceType: "OperationOutcome",
|
|
506
|
+
issue: [
|
|
507
|
+
{ severity: "error", code: "exception", diagnostics: String(err) }
|
|
508
|
+
]
|
|
509
|
+
});
|
|
340
510
|
}
|
|
341
|
-
set(`${OPENHI_EXT}/created-date`, audit.createdDate, "valueDateTime");
|
|
342
|
-
set(`${OPENHI_EXT}/created-by-id`, audit.createdById, "valueString");
|
|
343
|
-
set(`${OPENHI_EXT}/created-by-name`, audit.createdByName, "valueString");
|
|
344
|
-
set(`${OPENHI_EXT}/modified-date`, audit.modifiedDate, "valueDateTime");
|
|
345
|
-
set(`${OPENHI_EXT}/modified-by-id`, audit.modifiedById, "valueString");
|
|
346
|
-
set(`${OPENHI_EXT}/modified-by-name`, audit.modifiedByName, "valueString");
|
|
347
|
-
set(`${OPENHI_EXT}/deleted-date`, audit.deletedDate, "valueDateTime");
|
|
348
|
-
set(`${OPENHI_EXT}/deleted-by-id`, audit.deletedById, "valueString");
|
|
349
|
-
set(`${OPENHI_EXT}/deleted-by-name`, audit.deletedByName, "valueString");
|
|
350
|
-
return { ...existing, extension: Array.from(byUrl.values()) };
|
|
351
511
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
512
|
+
|
|
513
|
+
// src/data/rest-api/routes/configuration/configuration-delete.ts
|
|
514
|
+
async function deleteConfiguration(req, res) {
|
|
515
|
+
const key = String(req.params.key);
|
|
516
|
+
const ctx = req.openhiContext;
|
|
517
|
+
const { tenantId, workspaceId, actorId, roleId: ctxRoleId } = ctx;
|
|
518
|
+
const roleId = ctxRoleId ?? "-";
|
|
519
|
+
const service = getDynamoDataService();
|
|
520
|
+
try {
|
|
521
|
+
await service.entities.configuration.delete({ tenantId, workspaceId, userId: actorId, roleId, key, sk: SK }).go();
|
|
522
|
+
return res.status(204).send();
|
|
523
|
+
} catch (err) {
|
|
524
|
+
console.error("DELETE Configuration error:", err);
|
|
525
|
+
return res.status(500).json({
|
|
526
|
+
resourceType: "OperationOutcome",
|
|
527
|
+
issue: [
|
|
528
|
+
{ severity: "error", code: "exception", diagnostics: String(err) }
|
|
529
|
+
]
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// src/data/rest-api/routes/configuration/configuration-get-by-key.ts
|
|
535
|
+
async function getConfigurationByKey(req, res) {
|
|
536
|
+
const key = String(req.params.key);
|
|
537
|
+
const ctx = req.openhiContext;
|
|
538
|
+
const { tenantId, workspaceId, actorId, roleId: ctxRoleId } = ctx;
|
|
539
|
+
const roleId = ctxRoleId ?? "-";
|
|
540
|
+
const service = getDynamoDataService();
|
|
541
|
+
try {
|
|
542
|
+
const result = await service.entities.configuration.get({ tenantId, workspaceId, userId: actorId, roleId, key, sk: SK }).go();
|
|
543
|
+
if (!result.data) {
|
|
544
|
+
return res.status(404).json({
|
|
545
|
+
resourceType: "OperationOutcome",
|
|
546
|
+
issue: [
|
|
547
|
+
{
|
|
548
|
+
severity: "error",
|
|
549
|
+
code: "not-found",
|
|
550
|
+
diagnostics: `Configuration ${key} not found`
|
|
551
|
+
}
|
|
552
|
+
]
|
|
553
|
+
});
|
|
357
554
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
555
|
+
const resource = JSON.parse(
|
|
556
|
+
decompressResource(result.data.resource)
|
|
557
|
+
);
|
|
558
|
+
return res.json({
|
|
559
|
+
...resource,
|
|
560
|
+
resourceType: "Configuration",
|
|
561
|
+
id: result.data.id,
|
|
562
|
+
key: result.data.key
|
|
563
|
+
});
|
|
564
|
+
} catch (err) {
|
|
565
|
+
console.error("GET Configuration error:", err);
|
|
566
|
+
return res.status(500).json({
|
|
567
|
+
resourceType: "OperationOutcome",
|
|
568
|
+
issue: [
|
|
569
|
+
{
|
|
570
|
+
severity: "error",
|
|
571
|
+
code: "exception",
|
|
572
|
+
diagnostics: String(err)
|
|
366
573
|
}
|
|
367
|
-
|
|
368
|
-
}
|
|
574
|
+
]
|
|
575
|
+
});
|
|
369
576
|
}
|
|
370
|
-
throw new Error(
|
|
371
|
-
"File must be a FHIR Patient resource or a Bundle containing at least one Patient entry"
|
|
372
|
-
);
|
|
373
|
-
}
|
|
374
|
-
var SK = "CURRENT";
|
|
375
|
-
var defaultAudit = {
|
|
376
|
-
createdDate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
377
|
-
createdById: "import",
|
|
378
|
-
createdByName: "Bulk import",
|
|
379
|
-
modifiedDate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
380
|
-
modifiedById: "import",
|
|
381
|
-
modifiedByName: "Bulk import"
|
|
382
|
-
};
|
|
383
|
-
function patientToPutAttrs(patient, options) {
|
|
384
|
-
const {
|
|
385
|
-
tenantId,
|
|
386
|
-
workspaceId,
|
|
387
|
-
createdDate,
|
|
388
|
-
createdById,
|
|
389
|
-
createdByName,
|
|
390
|
-
modifiedDate,
|
|
391
|
-
modifiedById,
|
|
392
|
-
modifiedByName
|
|
393
|
-
} = options;
|
|
394
|
-
const lastUpdated = patient.meta?.lastUpdated ?? modifiedDate ?? defaultAudit.modifiedDate ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
395
|
-
const auditMerged = {
|
|
396
|
-
...defaultAudit,
|
|
397
|
-
...createdDate != null && { createdDate },
|
|
398
|
-
...createdById != null && { createdById },
|
|
399
|
-
...createdByName != null && { createdByName },
|
|
400
|
-
...modifiedDate != null && { modifiedDate },
|
|
401
|
-
...modifiedById != null && { modifiedById },
|
|
402
|
-
...modifiedByName != null && { modifiedByName }
|
|
403
|
-
};
|
|
404
|
-
const patientWithMeta = {
|
|
405
|
-
...patient,
|
|
406
|
-
meta: mergeAuditIntoMeta(patient.meta, auditMerged)
|
|
407
|
-
};
|
|
408
|
-
if (lastUpdated && !patientWithMeta.meta.lastUpdated) {
|
|
409
|
-
patientWithMeta.meta.lastUpdated = lastUpdated;
|
|
410
|
-
}
|
|
411
|
-
return {
|
|
412
|
-
sk: SK,
|
|
413
|
-
tenantId,
|
|
414
|
-
workspaceId,
|
|
415
|
-
id: patient.id,
|
|
416
|
-
resource: compressResource(JSON.stringify(patientWithMeta)),
|
|
417
|
-
vid: lastUpdated.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36),
|
|
418
|
-
lastUpdated,
|
|
419
|
-
identifierSystem: "",
|
|
420
|
-
identifierValue: "",
|
|
421
|
-
facilityId: "",
|
|
422
|
-
normalizedName: ""
|
|
423
|
-
};
|
|
424
577
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
578
|
+
|
|
579
|
+
// src/data/rest-api/dynamic-configuration.ts
|
|
580
|
+
import {
|
|
581
|
+
DescribeParametersCommand,
|
|
582
|
+
GetParametersCommand,
|
|
583
|
+
SSMClient
|
|
584
|
+
} from "@aws-sdk/client-ssm";
|
|
585
|
+
var BASE_PATH2 = "/Configuration";
|
|
586
|
+
var TAG_KEY_BRANCH = "openhi:branch-name";
|
|
587
|
+
var TAG_KEY_HTTP_API_PARAM = "openhi:param-name";
|
|
588
|
+
function getSsmDynamicConfigEnvFilter() {
|
|
589
|
+
const branchTagValue = process.env.BRANCH_TAG_VALUE;
|
|
590
|
+
const httpApiTagValue = process.env.HTTP_API_TAG_VALUE;
|
|
591
|
+
if (branchTagValue == null || branchTagValue === "" || httpApiTagValue == null || httpApiTagValue === "") {
|
|
592
|
+
return null;
|
|
437
593
|
}
|
|
438
|
-
return {
|
|
439
|
-
id: data.id,
|
|
440
|
-
tenantId: data.tenantId,
|
|
441
|
-
workspaceId: data.workspaceId
|
|
442
|
-
};
|
|
594
|
+
return { branchTagValue, httpApiTagValue };
|
|
443
595
|
}
|
|
444
|
-
async function
|
|
445
|
-
const
|
|
446
|
-
if (
|
|
447
|
-
|
|
448
|
-
"Usage: import-patient.ts <path-to-patient.json> [tenantId] [workspaceId]"
|
|
449
|
-
);
|
|
450
|
-
process.exit(1);
|
|
596
|
+
async function getDynamicConfigurationEntries(context) {
|
|
597
|
+
const envFilter = getSsmDynamicConfigEnvFilter();
|
|
598
|
+
if (envFilter == null) {
|
|
599
|
+
return getStaticDummyEntry(context);
|
|
451
600
|
}
|
|
601
|
+
const region = process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION;
|
|
602
|
+
const client2 = new SSMClient({ region });
|
|
452
603
|
try {
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
604
|
+
const describeResult = await client2.send(
|
|
605
|
+
new DescribeParametersCommand({
|
|
606
|
+
ParameterFilters: [
|
|
607
|
+
{
|
|
608
|
+
Key: `tag:${TAG_KEY_BRANCH}`,
|
|
609
|
+
Option: "Equals",
|
|
610
|
+
Values: [envFilter.branchTagValue]
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
Key: `tag:${TAG_KEY_HTTP_API_PARAM}`,
|
|
614
|
+
Option: "Equals",
|
|
615
|
+
Values: [envFilter.httpApiTagValue]
|
|
616
|
+
}
|
|
617
|
+
],
|
|
618
|
+
MaxResults: 50
|
|
619
|
+
})
|
|
459
620
|
);
|
|
621
|
+
const names = (describeResult.Parameters ?? []).map((p) => p.Name).filter((n) => n != null);
|
|
622
|
+
if (names.length === 0) {
|
|
623
|
+
return getStaticDummyEntry(context);
|
|
624
|
+
}
|
|
625
|
+
const parameters = [];
|
|
626
|
+
for (let i = 0; i < names.length; i += 10) {
|
|
627
|
+
const batch = names.slice(i, i + 10);
|
|
628
|
+
const getResult = await client2.send(
|
|
629
|
+
new GetParametersCommand({
|
|
630
|
+
Names: batch,
|
|
631
|
+
WithDecryption: true
|
|
632
|
+
})
|
|
633
|
+
);
|
|
634
|
+
for (const p of getResult.Parameters ?? []) {
|
|
635
|
+
const name = p.Name;
|
|
636
|
+
const value = p.Value;
|
|
637
|
+
if (name != null && value != null) {
|
|
638
|
+
parameters.push({ name, value });
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
const parameterList = parameters.map((p) => {
|
|
643
|
+
const shortName = p.name.includes("/") ? p.name.split("/").slice(-1)[0] : p.name;
|
|
644
|
+
return { name: shortName, valueString: p.value };
|
|
645
|
+
});
|
|
646
|
+
const entry = {
|
|
647
|
+
fullUrl: `${BASE_PATH2}/ssm-dynamic`,
|
|
648
|
+
resource: {
|
|
649
|
+
resourceType: "Configuration",
|
|
650
|
+
id: "ssm-dynamic",
|
|
651
|
+
key: "ssm-dynamic",
|
|
652
|
+
resource: {
|
|
653
|
+
parameter: parameterList
|
|
654
|
+
},
|
|
655
|
+
meta: {
|
|
656
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
657
|
+
versionId: "1"
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
return [entry];
|
|
460
662
|
} catch (err) {
|
|
461
|
-
console.error(err);
|
|
462
|
-
|
|
663
|
+
console.error("getDynamicConfigurationEntries SSM error:", err);
|
|
664
|
+
return getStaticDummyEntry(context);
|
|
463
665
|
}
|
|
464
666
|
}
|
|
465
|
-
|
|
466
|
-
|
|
667
|
+
function getStaticDummyEntry(_context) {
|
|
668
|
+
const dummy = {
|
|
669
|
+
fullUrl: `${BASE_PATH2}/dynamic-dummy`,
|
|
670
|
+
resource: {
|
|
671
|
+
resourceType: "Configuration",
|
|
672
|
+
id: "dynamic-dummy",
|
|
673
|
+
key: "dynamic-dummy",
|
|
674
|
+
resource: {
|
|
675
|
+
parameter: [
|
|
676
|
+
{
|
|
677
|
+
name: "description",
|
|
678
|
+
valueString: "Statically generated dummy configuration (not from DynamoDB)."
|
|
679
|
+
},
|
|
680
|
+
{ name: "source", valueString: "dynamic-configuration" }
|
|
681
|
+
]
|
|
682
|
+
},
|
|
683
|
+
meta: {
|
|
684
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
685
|
+
versionId: "1"
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
return [dummy];
|
|
467
690
|
}
|
|
468
691
|
|
|
469
|
-
// src/data/rest-api/
|
|
470
|
-
|
|
471
|
-
var router = express.Router();
|
|
472
|
-
var SK2 = "CURRENT";
|
|
473
|
-
var TABLE_NAME = process.env.DYNAMO_TABLE_NAME ?? "jesttesttable";
|
|
474
|
-
async function listPatients(req, res) {
|
|
692
|
+
// src/data/rest-api/routes/configuration/configuration-list.ts
|
|
693
|
+
async function listConfigurations(req, res) {
|
|
475
694
|
const { tenantId, workspaceId } = req.openhiContext;
|
|
476
|
-
const service =
|
|
695
|
+
const service = getDynamoDataService();
|
|
477
696
|
try {
|
|
478
|
-
const result = await service.entities.
|
|
479
|
-
const
|
|
697
|
+
const result = await service.entities.configuration.query.gsi4({ tenantId, workspaceId }).go();
|
|
698
|
+
const dynamoEntries = (result.data ?? []).map((item) => {
|
|
480
699
|
const resource = JSON.parse(decompressResource(item.resource));
|
|
481
700
|
return {
|
|
482
|
-
fullUrl: `${BASE_PATH}/${item.
|
|
483
|
-
resource: { ...resource, id: item.id }
|
|
701
|
+
fullUrl: `${BASE_PATH}/${item.key}`,
|
|
702
|
+
resource: { ...resource, id: item.id, key: item.key }
|
|
484
703
|
};
|
|
485
704
|
});
|
|
705
|
+
const dynamicEntries = await getDynamicConfigurationEntries({
|
|
706
|
+
tenantId,
|
|
707
|
+
workspaceId
|
|
708
|
+
});
|
|
709
|
+
const entries = [...dynamoEntries, ...dynamicEntries];
|
|
486
710
|
const bundle = {
|
|
487
711
|
resourceType: "Bundle",
|
|
488
712
|
type: "searchset",
|
|
@@ -492,7 +716,7 @@ async function listPatients(req, res) {
|
|
|
492
716
|
};
|
|
493
717
|
return res.json(bundle);
|
|
494
718
|
} catch (err) {
|
|
495
|
-
console.error("GET /
|
|
719
|
+
console.error("GET /Configuration list error:", err);
|
|
496
720
|
return res.status(500).json({
|
|
497
721
|
resourceType: "OperationOutcome",
|
|
498
722
|
issue: [
|
|
@@ -505,44 +729,71 @@ async function listPatients(req, res) {
|
|
|
505
729
|
});
|
|
506
730
|
}
|
|
507
731
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const
|
|
512
|
-
const
|
|
732
|
+
|
|
733
|
+
// src/data/rest-api/routes/configuration/configuration-update.ts
|
|
734
|
+
async function updateConfiguration(req, res) {
|
|
735
|
+
const key = String(req.params.key);
|
|
736
|
+
const ctx = req.openhiContext;
|
|
737
|
+
const { tenantId, workspaceId, actorId, date, roleId: ctxRoleId } = ctx;
|
|
738
|
+
const roleId = ctxRoleId ?? "-";
|
|
739
|
+
const body = req.body;
|
|
740
|
+
const resourcePayload = body?.resource;
|
|
741
|
+
const resourceStr = typeof resourcePayload === "string" ? resourcePayload : JSON.stringify(resourcePayload ?? {});
|
|
742
|
+
const lastUpdated = body?.lastUpdated ?? date;
|
|
743
|
+
const service = getDynamoDataService();
|
|
513
744
|
try {
|
|
514
|
-
const
|
|
515
|
-
if (!
|
|
745
|
+
const existing = await service.entities.configuration.get({ tenantId, workspaceId, userId: actorId, roleId, key, sk: SK }).go();
|
|
746
|
+
if (!existing.data) {
|
|
516
747
|
return res.status(404).json({
|
|
517
748
|
resourceType: "OperationOutcome",
|
|
518
749
|
issue: [
|
|
519
750
|
{
|
|
520
751
|
severity: "error",
|
|
521
752
|
code: "not-found",
|
|
522
|
-
diagnostics: `
|
|
753
|
+
diagnostics: `Configuration ${key} not found`
|
|
523
754
|
}
|
|
524
755
|
]
|
|
525
756
|
});
|
|
526
757
|
}
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
758
|
+
const nextVid = existing.data.vid != null ? String(Number(existing.data.vid) + 1) : date.replace(/[-:T.Z]/g, "").slice(0, 12) || "2";
|
|
759
|
+
await service.entities.configuration.patch({ tenantId, workspaceId, userId: actorId, roleId, key, sk: SK }).set({
|
|
760
|
+
resource: compressResource(resourceStr),
|
|
761
|
+
lastUpdated,
|
|
762
|
+
vid: nextVid
|
|
763
|
+
}).go();
|
|
764
|
+
const config = {
|
|
765
|
+
resourceType: "Configuration",
|
|
766
|
+
id: existing.data.id,
|
|
767
|
+
key: existing.data.key,
|
|
768
|
+
resource: typeof resourcePayload === "object" ? resourcePayload : JSON.parse(resourceStr),
|
|
769
|
+
meta: { lastUpdated, versionId: nextVid }
|
|
770
|
+
};
|
|
771
|
+
return res.json(config);
|
|
531
772
|
} catch (err) {
|
|
532
|
-
console.error("
|
|
773
|
+
console.error("PUT Configuration error:", err);
|
|
533
774
|
return res.status(500).json({
|
|
534
775
|
resourceType: "OperationOutcome",
|
|
535
776
|
issue: [
|
|
536
|
-
{
|
|
537
|
-
severity: "error",
|
|
538
|
-
code: "exception",
|
|
539
|
-
diagnostics: String(err)
|
|
540
|
-
}
|
|
777
|
+
{ severity: "error", code: "exception", diagnostics: String(err) }
|
|
541
778
|
]
|
|
542
779
|
});
|
|
543
780
|
}
|
|
544
781
|
}
|
|
545
|
-
|
|
782
|
+
|
|
783
|
+
// src/data/rest-api/routes/configuration/configuration.ts
|
|
784
|
+
var router = express.Router();
|
|
785
|
+
router.get("/", listConfigurations);
|
|
786
|
+
router.get("/:key", getConfigurationByKey);
|
|
787
|
+
router.post("/", createConfiguration);
|
|
788
|
+
router.put("/:key", updateConfiguration);
|
|
789
|
+
router.delete("/:key", deleteConfiguration);
|
|
790
|
+
|
|
791
|
+
// src/data/rest-api/routes/patient/patient.ts
|
|
792
|
+
import express2 from "express";
|
|
793
|
+
|
|
794
|
+
// src/data/rest-api/routes/patient/patient-common.ts
|
|
795
|
+
var BASE_PATH3 = "/Patient";
|
|
796
|
+
var SK2 = "CURRENT";
|
|
546
797
|
function requireJsonBody(req, res) {
|
|
547
798
|
const raw = req.body;
|
|
548
799
|
if (raw == null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
@@ -561,426 +812,220 @@ function requireJsonBody(req, res) {
|
|
|
561
812
|
}
|
|
562
813
|
return { body: raw };
|
|
563
814
|
}
|
|
564
|
-
async function createPatient(req, res) {
|
|
565
|
-
const bodyResult = requireJsonBody(req, res);
|
|
566
|
-
if ("errorResponse" in bodyResult) return bodyResult.errorResponse;
|
|
567
|
-
const ctx = req.openhiContext;
|
|
568
|
-
const { tenantId, workspaceId, date, userId, username } = ctx;
|
|
569
|
-
const body = bodyResult.body;
|
|
570
|
-
const id = body?.id ?? `patient-${Date.now()}`;
|
|
571
|
-
const patient = {
|
|
572
|
-
...body,
|
|
573
|
-
resourceType: "Patient",
|
|
574
|
-
id,
|
|
575
|
-
meta: {
|
|
576
|
-
...body?.meta ?? {},
|
|
577
|
-
lastUpdated: date,
|
|
578
|
-
versionId: "1"
|
|
579
|
-
}
|
|
580
|
-
};
|
|
581
|
-
const options = {
|
|
582
|
-
tenantId,
|
|
583
|
-
workspaceId,
|
|
584
|
-
createdDate: date,
|
|
585
|
-
createdById: userId,
|
|
586
|
-
createdByName: username,
|
|
587
|
-
modifiedDate: date,
|
|
588
|
-
modifiedById: userId,
|
|
589
|
-
modifiedByName: username
|
|
590
|
-
};
|
|
591
|
-
const service = getEhrR4DataService(TABLE_NAME);
|
|
592
|
-
try {
|
|
593
|
-
const attrs = patientToPutAttrs(patient, options);
|
|
594
|
-
await service.entities.patient.put(
|
|
595
|
-
attrs
|
|
596
|
-
).go();
|
|
597
|
-
return res.status(201).location(`${BASE_PATH}/${id}`).json(patient);
|
|
598
|
-
} catch (err) {
|
|
599
|
-
console.error("POST Patient error:", err);
|
|
600
|
-
return res.status(500).json({
|
|
601
|
-
resourceType: "OperationOutcome",
|
|
602
|
-
issue: [
|
|
603
|
-
{ severity: "error", code: "exception", diagnostics: String(err) }
|
|
604
|
-
]
|
|
605
|
-
});
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
router.post("/", createPatient);
|
|
609
|
-
async function updatePatient(req, res) {
|
|
610
|
-
const bodyResult = requireJsonBody(req, res);
|
|
611
|
-
if ("errorResponse" in bodyResult) return bodyResult.errorResponse;
|
|
612
|
-
const id = String(req.params.id);
|
|
613
|
-
const ctx = req.openhiContext;
|
|
614
|
-
const { tenantId, workspaceId, date, userId, username } = ctx;
|
|
615
|
-
const body = bodyResult.body;
|
|
616
|
-
const patient = {
|
|
617
|
-
...body,
|
|
618
|
-
resourceType: "Patient",
|
|
619
|
-
id,
|
|
620
|
-
meta: {
|
|
621
|
-
...body?.meta ?? {},
|
|
622
|
-
lastUpdated: date,
|
|
623
|
-
versionId: "2"
|
|
624
|
-
}
|
|
625
|
-
};
|
|
626
|
-
const service = getEhrR4DataService(TABLE_NAME);
|
|
627
|
-
try {
|
|
628
|
-
const existing = await service.entities.patient.get({ tenantId, workspaceId, id, sk: SK2 }).go();
|
|
629
|
-
if (!existing.data) {
|
|
630
|
-
return res.status(404).json({
|
|
631
|
-
resourceType: "OperationOutcome",
|
|
632
|
-
issue: [
|
|
633
|
-
{
|
|
634
|
-
severity: "error",
|
|
635
|
-
code: "not-found",
|
|
636
|
-
diagnostics: `Patient ${id} not found`
|
|
637
|
-
}
|
|
638
|
-
]
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
const existingMeta = existing.data.resource != null ? JSON.parse(decompressResource(existing.data.resource)).meta : void 0;
|
|
642
|
-
const patientWithMeta = {
|
|
643
|
-
...patient,
|
|
644
|
-
meta: mergeAuditIntoMeta(
|
|
645
|
-
patient.meta ?? existingMeta,
|
|
646
|
-
{
|
|
647
|
-
modifiedDate: date,
|
|
648
|
-
modifiedById: userId,
|
|
649
|
-
modifiedByName: username
|
|
650
|
-
}
|
|
651
|
-
)
|
|
652
|
-
};
|
|
653
|
-
await service.entities.patient.patch({ tenantId, workspaceId, id, sk: SK2 }).set({
|
|
654
|
-
resource: compressResource(JSON.stringify(patientWithMeta)),
|
|
655
|
-
lastUpdated: date
|
|
656
|
-
}).go();
|
|
657
|
-
return res.json(patientWithMeta);
|
|
658
|
-
} catch (err) {
|
|
659
|
-
console.error("PUT Patient error:", err);
|
|
660
|
-
return res.status(500).json({
|
|
661
|
-
resourceType: "OperationOutcome",
|
|
662
|
-
issue: [
|
|
663
|
-
{ severity: "error", code: "exception", diagnostics: String(err) }
|
|
664
|
-
]
|
|
665
|
-
});
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
router.put("/:id", updatePatient);
|
|
669
|
-
async function deletePatient(req, res) {
|
|
670
|
-
const id = String(req.params.id);
|
|
671
|
-
const { tenantId, workspaceId } = req.openhiContext;
|
|
672
|
-
const service = getEhrR4DataService(TABLE_NAME);
|
|
673
|
-
try {
|
|
674
|
-
await service.entities.patient.delete({ tenantId, workspaceId, id, sk: SK2 }).go();
|
|
675
|
-
return res.status(204).send();
|
|
676
|
-
} catch (err) {
|
|
677
|
-
console.error("DELETE Patient error:", err);
|
|
678
|
-
return res.status(500).json({
|
|
679
|
-
resourceType: "OperationOutcome",
|
|
680
|
-
issue: [
|
|
681
|
-
{ severity: "error", code: "exception", diagnostics: String(err) }
|
|
682
|
-
]
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
router.delete("/:id", deletePatient);
|
|
687
|
-
|
|
688
|
-
// src/data/rest-api/ohi/Configuration.ts
|
|
689
|
-
import express2 from "express";
|
|
690
815
|
|
|
691
|
-
// src/data/
|
|
692
|
-
import {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
return null;
|
|
816
|
+
// src/data/import-patient.ts
|
|
817
|
+
import { readFileSync } from "fs";
|
|
818
|
+
import { resolve } from "path";
|
|
819
|
+
var OPENHI_EXT = "http://openhi.org/fhir/StructureDefinition";
|
|
820
|
+
function mergeAuditIntoMeta(meta, audit) {
|
|
821
|
+
const existing = meta ?? {};
|
|
822
|
+
const ext = [
|
|
823
|
+
...Array.isArray(existing.extension) ? existing.extension : []
|
|
824
|
+
];
|
|
825
|
+
const byUrl = new Map(ext.map((e) => [e.url, e]));
|
|
826
|
+
function set(url, value, type) {
|
|
827
|
+
if (value == null) return;
|
|
828
|
+
byUrl.set(url, { url, [type]: value });
|
|
705
829
|
}
|
|
706
|
-
|
|
830
|
+
set(`${OPENHI_EXT}/created-date`, audit.createdDate, "valueDateTime");
|
|
831
|
+
set(`${OPENHI_EXT}/created-by-id`, audit.createdById, "valueString");
|
|
832
|
+
set(`${OPENHI_EXT}/created-by-name`, audit.createdByName, "valueString");
|
|
833
|
+
set(`${OPENHI_EXT}/modified-date`, audit.modifiedDate, "valueDateTime");
|
|
834
|
+
set(`${OPENHI_EXT}/modified-by-id`, audit.modifiedById, "valueString");
|
|
835
|
+
set(`${OPENHI_EXT}/modified-by-name`, audit.modifiedByName, "valueString");
|
|
836
|
+
set(`${OPENHI_EXT}/deleted-date`, audit.deletedDate, "valueDateTime");
|
|
837
|
+
set(`${OPENHI_EXT}/deleted-by-id`, audit.deletedById, "valueString");
|
|
838
|
+
set(`${OPENHI_EXT}/deleted-by-name`, audit.deletedByName, "valueString");
|
|
839
|
+
return { ...existing, extension: Array.from(byUrl.values()) };
|
|
707
840
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
const region = process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION;
|
|
714
|
-
const client3 = new SSMClient({ region });
|
|
715
|
-
try {
|
|
716
|
-
const describeResult = await client3.send(
|
|
717
|
-
new DescribeParametersCommand({
|
|
718
|
-
ParameterFilters: [
|
|
719
|
-
{
|
|
720
|
-
Key: `tag:${TAG_KEY_BRANCH}`,
|
|
721
|
-
Option: "Equals",
|
|
722
|
-
Values: [envFilter.branchTagValue]
|
|
723
|
-
},
|
|
724
|
-
{
|
|
725
|
-
Key: `tag:${TAG_KEY_HTTP_API_PARAM}`,
|
|
726
|
-
Option: "Equals",
|
|
727
|
-
Values: [envFilter.httpApiTagValue]
|
|
728
|
-
}
|
|
729
|
-
],
|
|
730
|
-
MaxResults: 50
|
|
731
|
-
})
|
|
732
|
-
);
|
|
733
|
-
const names = (describeResult.Parameters ?? []).map((p) => p.Name).filter((n) => n != null);
|
|
734
|
-
if (names.length === 0) {
|
|
735
|
-
return getStaticDummyEntry(context);
|
|
841
|
+
function extractPatient(parsed) {
|
|
842
|
+
if (parsed && typeof parsed === "object" && "resourceType" in parsed) {
|
|
843
|
+
const root = parsed;
|
|
844
|
+
if (root.resourceType === "Patient" && root.id) {
|
|
845
|
+
return root;
|
|
736
846
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
);
|
|
746
|
-
for (const p of getResult.Parameters ?? []) {
|
|
747
|
-
const name = p.Name;
|
|
748
|
-
const value = p.Value;
|
|
749
|
-
if (name != null && value != null) {
|
|
750
|
-
parameters.push({ name, value });
|
|
847
|
+
if (root.resourceType === "Bundle" && "entry" in parsed) {
|
|
848
|
+
const entries = parsed.entry;
|
|
849
|
+
if (Array.isArray(entries)) {
|
|
850
|
+
const patientEntry = entries.find(
|
|
851
|
+
(e) => e?.resource?.resourceType === "Patient" && e.resource.id
|
|
852
|
+
);
|
|
853
|
+
if (patientEntry?.resource) {
|
|
854
|
+
return patientEntry.resource;
|
|
751
855
|
}
|
|
752
856
|
}
|
|
753
857
|
}
|
|
754
|
-
const parameterList = parameters.map((p) => {
|
|
755
|
-
const shortName = p.name.includes("/") ? p.name.split("/").slice(-1)[0] : p.name;
|
|
756
|
-
return { name: shortName, valueString: p.value };
|
|
757
|
-
});
|
|
758
|
-
const entry = {
|
|
759
|
-
fullUrl: `${BASE_PATH2}/ssm-dynamic`,
|
|
760
|
-
resource: {
|
|
761
|
-
resourceType: "Configuration",
|
|
762
|
-
id: "ssm-dynamic",
|
|
763
|
-
key: "ssm-dynamic",
|
|
764
|
-
resource: {
|
|
765
|
-
parameter: parameterList
|
|
766
|
-
},
|
|
767
|
-
meta: {
|
|
768
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
769
|
-
versionId: "1"
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
};
|
|
773
|
-
return [entry];
|
|
774
|
-
} catch (err) {
|
|
775
|
-
console.error("getDynamicConfigurationEntries SSM error:", err);
|
|
776
|
-
return getStaticDummyEntry(context);
|
|
777
858
|
}
|
|
859
|
+
throw new Error(
|
|
860
|
+
"File must be a FHIR Patient resource or a Bundle containing at least one Patient entry"
|
|
861
|
+
);
|
|
778
862
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
863
|
+
var SK3 = "CURRENT";
|
|
864
|
+
var defaultAudit = {
|
|
865
|
+
createdDate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
866
|
+
createdById: "import",
|
|
867
|
+
createdByName: "Bulk import",
|
|
868
|
+
modifiedDate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
869
|
+
modifiedById: "import",
|
|
870
|
+
modifiedByName: "Bulk import"
|
|
871
|
+
};
|
|
872
|
+
function patientToPutAttrs(patient, options) {
|
|
873
|
+
const {
|
|
874
|
+
tenantId,
|
|
875
|
+
workspaceId,
|
|
876
|
+
createdDate,
|
|
877
|
+
createdById,
|
|
878
|
+
createdByName,
|
|
879
|
+
modifiedDate,
|
|
880
|
+
modifiedById,
|
|
881
|
+
modifiedByName
|
|
882
|
+
} = options;
|
|
883
|
+
const lastUpdated = patient.meta?.lastUpdated ?? modifiedDate ?? defaultAudit.modifiedDate ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
884
|
+
const auditMerged = {
|
|
885
|
+
...defaultAudit,
|
|
886
|
+
...createdDate != null && { createdDate },
|
|
887
|
+
...createdById != null && { createdById },
|
|
888
|
+
...createdByName != null && { createdByName },
|
|
889
|
+
...modifiedDate != null && { modifiedDate },
|
|
890
|
+
...modifiedById != null && { modifiedById },
|
|
891
|
+
...modifiedByName != null && { modifiedByName }
|
|
892
|
+
};
|
|
893
|
+
const patientWithMeta = {
|
|
894
|
+
...patient,
|
|
895
|
+
meta: mergeAuditIntoMeta(patient.meta, auditMerged)
|
|
896
|
+
};
|
|
897
|
+
if (lastUpdated && !patientWithMeta.meta.lastUpdated) {
|
|
898
|
+
patientWithMeta.meta.lastUpdated = lastUpdated;
|
|
899
|
+
}
|
|
900
|
+
return {
|
|
901
|
+
sk: SK3,
|
|
902
|
+
tenantId,
|
|
903
|
+
workspaceId,
|
|
904
|
+
id: patient.id,
|
|
905
|
+
resource: compressResource(JSON.stringify(patientWithMeta)),
|
|
906
|
+
vid: lastUpdated.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36),
|
|
907
|
+
lastUpdated,
|
|
908
|
+
identifierSystem: "",
|
|
909
|
+
identifierValue: "",
|
|
910
|
+
facilityId: "",
|
|
911
|
+
normalizedName: ""
|
|
800
912
|
};
|
|
801
|
-
return [dummy];
|
|
802
913
|
}
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
},
|
|
829
|
-
/** Workspace scope. Use "-" when absent. */
|
|
830
|
-
workspaceId: {
|
|
831
|
-
type: "string",
|
|
832
|
-
required: true,
|
|
833
|
-
default: "-"
|
|
834
|
-
},
|
|
835
|
-
/** User scope. Use "-" when absent. */
|
|
836
|
-
userId: {
|
|
837
|
-
type: "string",
|
|
838
|
-
required: true,
|
|
839
|
-
default: "-"
|
|
840
|
-
},
|
|
841
|
-
/** Role scope. Use "-" when absent. */
|
|
842
|
-
roleId: {
|
|
843
|
-
type: "string",
|
|
844
|
-
required: true,
|
|
845
|
-
default: "-"
|
|
846
|
-
},
|
|
847
|
-
/** Config type (category), e.g. endpoints, branding, display. */
|
|
848
|
-
key: {
|
|
849
|
-
type: "string",
|
|
850
|
-
required: true
|
|
851
|
-
},
|
|
852
|
-
/** FHIR Resource.id; logical id in URL and for the Configuration resource. */
|
|
853
|
-
id: {
|
|
854
|
-
type: "string",
|
|
855
|
-
required: true
|
|
856
|
-
},
|
|
857
|
-
/** Payload as JSON string. JSON.stringify(resource) on write; JSON.parse(item.resource) on read. */
|
|
858
|
-
resource: {
|
|
859
|
-
type: "string",
|
|
860
|
-
required: true
|
|
861
|
-
},
|
|
862
|
-
/** Version id (e.g. ULID). Tracks current version; S3 history key. */
|
|
863
|
-
vid: {
|
|
864
|
-
type: "string",
|
|
865
|
-
required: true
|
|
866
|
-
},
|
|
867
|
-
lastUpdated: {
|
|
868
|
-
type: "string",
|
|
869
|
-
required: true
|
|
870
|
-
},
|
|
871
|
-
deleted: {
|
|
872
|
-
type: "boolean",
|
|
873
|
-
required: false
|
|
874
|
-
},
|
|
875
|
-
bundleId: {
|
|
876
|
-
type: "string",
|
|
877
|
-
required: false
|
|
878
|
-
},
|
|
879
|
-
msgId: {
|
|
880
|
-
type: "string",
|
|
881
|
-
required: false
|
|
882
|
-
}
|
|
883
|
-
},
|
|
884
|
-
indexes: {
|
|
885
|
-
/** Base table: PK, SK (data store key names). PK is built from tenantId, workspaceId, userId, roleId; SK is built from key and sk. Do not supply PK or SK from outside. */
|
|
886
|
-
record: {
|
|
887
|
-
pk: {
|
|
888
|
-
field: "PK",
|
|
889
|
-
composite: ["tenantId", "workspaceId", "userId", "roleId"],
|
|
890
|
-
template: "OHI#CONFIG#TID#${tenantId}#WID#${workspaceId}#UID#${userId}#RID#${roleId}"
|
|
891
|
-
},
|
|
892
|
-
sk: {
|
|
893
|
-
field: "SK",
|
|
894
|
-
composite: ["key", "sk"],
|
|
895
|
-
template: "KEY#${key}#SK#${sk}"
|
|
896
|
-
}
|
|
897
|
-
},
|
|
898
|
-
/** GSI4 — Resource Type Index: list all Configuration in a tenant or workspace (no scan). Use for "list configs scoped to this tenant" (workspaceId = "-") or "list configs scoped to this workspace". Does not support hierarchical resolution in one query; use base table GetItem in fallback order (user → workspace → tenant → baseline) for that. */
|
|
899
|
-
gsi4: {
|
|
900
|
-
index: "GSI4",
|
|
901
|
-
condition: () => true,
|
|
902
|
-
pk: {
|
|
903
|
-
field: "GSI4PK",
|
|
904
|
-
composite: ["tenantId", "workspaceId"],
|
|
905
|
-
template: "TID#${tenantId}#WID#${workspaceId}#RT#Configuration"
|
|
906
|
-
},
|
|
907
|
-
sk: {
|
|
908
|
-
field: "GSI4SK",
|
|
909
|
-
composite: ["key", "sk"],
|
|
910
|
-
template: "KEY#${key}#SK#${sk}"
|
|
911
|
-
}
|
|
912
|
-
}
|
|
914
|
+
async function importPatientFromFile(filePath, options) {
|
|
915
|
+
const resolved = resolve(filePath);
|
|
916
|
+
const raw = readFileSync(resolved, "utf-8");
|
|
917
|
+
const parsed = JSON.parse(raw);
|
|
918
|
+
const patient = extractPatient(parsed);
|
|
919
|
+
const service = getDynamoDataService(options.tableName);
|
|
920
|
+
const attrs = patientToPutAttrs(patient, options);
|
|
921
|
+
const result = await service.entities.patient.put(attrs).go();
|
|
922
|
+
const data = result.data;
|
|
923
|
+
if (!data) {
|
|
924
|
+
throw new Error(`Put failed for Patient ${patient.id}`);
|
|
925
|
+
}
|
|
926
|
+
return {
|
|
927
|
+
id: data.id,
|
|
928
|
+
tenantId: data.tenantId,
|
|
929
|
+
workspaceId: data.workspaceId
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
async function main() {
|
|
933
|
+
const [, , fileArg, tenantId = "tenant-1", workspaceId = "ws-1"] = process.argv;
|
|
934
|
+
if (!fileArg) {
|
|
935
|
+
console.error(
|
|
936
|
+
"Usage: import-patient.ts <path-to-patient.json> [tenantId] [workspaceId]"
|
|
937
|
+
);
|
|
938
|
+
process.exit(1);
|
|
913
939
|
}
|
|
914
|
-
|
|
940
|
+
try {
|
|
941
|
+
const result = await importPatientFromFile(fileArg, {
|
|
942
|
+
tenantId,
|
|
943
|
+
workspaceId
|
|
944
|
+
});
|
|
945
|
+
console.log(
|
|
946
|
+
`Imported Patient ${result.id} (tenant=${result.tenantId}, workspace=${result.workspaceId})`
|
|
947
|
+
);
|
|
948
|
+
} catch (err) {
|
|
949
|
+
console.error(err);
|
|
950
|
+
process.exit(1);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
if (__require.main === module) {
|
|
954
|
+
void main();
|
|
955
|
+
}
|
|
915
956
|
|
|
916
|
-
// src/data/
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
957
|
+
// src/data/rest-api/routes/patient/patient-create.ts
|
|
958
|
+
async function createPatient(req, res) {
|
|
959
|
+
const bodyResult = requireJsonBody(req, res);
|
|
960
|
+
if ("errorResponse" in bodyResult) return bodyResult.errorResponse;
|
|
961
|
+
const ctx = req.openhiContext;
|
|
962
|
+
const { tenantId, workspaceId, date, actorId, actorName } = ctx;
|
|
963
|
+
const body = bodyResult.body;
|
|
964
|
+
const id = body?.id ?? `patient-${Date.now()}`;
|
|
965
|
+
const patient = {
|
|
966
|
+
...body,
|
|
967
|
+
resourceType: "Patient",
|
|
968
|
+
id,
|
|
969
|
+
meta: {
|
|
970
|
+
...body?.meta ?? {},
|
|
971
|
+
lastUpdated: date,
|
|
972
|
+
versionId: "1"
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
const options = {
|
|
976
|
+
tenantId,
|
|
977
|
+
workspaceId,
|
|
978
|
+
createdDate: date,
|
|
979
|
+
createdById: actorId,
|
|
980
|
+
createdByName: actorName,
|
|
981
|
+
modifiedDate: date,
|
|
982
|
+
modifiedById: actorId,
|
|
983
|
+
modifiedByName: actorName
|
|
984
|
+
};
|
|
985
|
+
const service = getDynamoDataService();
|
|
986
|
+
try {
|
|
987
|
+
const attrs = patientToPutAttrs(patient, options);
|
|
988
|
+
await service.entities.patient.put(
|
|
989
|
+
attrs
|
|
990
|
+
).go();
|
|
991
|
+
return res.status(201).location(`${BASE_PATH3}/${id}`).json(patient);
|
|
992
|
+
} catch (err) {
|
|
993
|
+
console.error("POST Patient error:", err);
|
|
994
|
+
return res.status(500).json({
|
|
995
|
+
resourceType: "OperationOutcome",
|
|
996
|
+
issue: [
|
|
997
|
+
{ severity: "error", code: "exception", diagnostics: String(err) }
|
|
998
|
+
]
|
|
999
|
+
});
|
|
923
1000
|
}
|
|
924
|
-
});
|
|
925
|
-
var entities2 = { configuration: Configuration };
|
|
926
|
-
var OhiDataService = new Service2(entities2, { table: table2, client: client2 });
|
|
927
|
-
function getOhiDataService(tableName) {
|
|
928
|
-
return new Service2(entities2, { table: tableName, client: client2 });
|
|
929
1001
|
}
|
|
930
1002
|
|
|
931
|
-
// src/data/rest-api/
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
var SK3 = "CURRENT";
|
|
935
|
-
var TABLE_NAME2 = process.env.DYNAMO_TABLE_NAME ?? "jesttesttable";
|
|
936
|
-
async function listConfigurations(req, res) {
|
|
1003
|
+
// src/data/rest-api/routes/patient/patient-delete.ts
|
|
1004
|
+
async function deletePatient(req, res) {
|
|
1005
|
+
const id = String(req.params.id);
|
|
937
1006
|
const { tenantId, workspaceId } = req.openhiContext;
|
|
938
|
-
const service =
|
|
1007
|
+
const service = getDynamoDataService();
|
|
939
1008
|
try {
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
const resource = JSON.parse(decompressResource(item.resource));
|
|
943
|
-
return {
|
|
944
|
-
fullUrl: `${BASE_PATH3}/${item.key}`,
|
|
945
|
-
resource: { ...resource, id: item.id, key: item.key }
|
|
946
|
-
};
|
|
947
|
-
});
|
|
948
|
-
const dynamicEntries = await getDynamicConfigurationEntries({
|
|
949
|
-
tenantId,
|
|
950
|
-
workspaceId
|
|
951
|
-
});
|
|
952
|
-
const entries = [...dynamoEntries, ...dynamicEntries];
|
|
953
|
-
const bundle = {
|
|
954
|
-
resourceType: "Bundle",
|
|
955
|
-
type: "searchset",
|
|
956
|
-
total: entries.length,
|
|
957
|
-
link: [{ relation: "self", url: BASE_PATH3 }],
|
|
958
|
-
entry: entries
|
|
959
|
-
};
|
|
960
|
-
return res.json(bundle);
|
|
1009
|
+
await service.entities.patient.delete({ tenantId, workspaceId, id, sk: SK2 }).go();
|
|
1010
|
+
return res.status(204).send();
|
|
961
1011
|
} catch (err) {
|
|
962
|
-
console.error("
|
|
1012
|
+
console.error("DELETE Patient error:", err);
|
|
963
1013
|
return res.status(500).json({
|
|
964
1014
|
resourceType: "OperationOutcome",
|
|
965
1015
|
issue: [
|
|
966
|
-
{
|
|
967
|
-
severity: "error",
|
|
968
|
-
code: "exception",
|
|
969
|
-
diagnostics: String(err)
|
|
970
|
-
}
|
|
1016
|
+
{ severity: "error", code: "exception", diagnostics: String(err) }
|
|
971
1017
|
]
|
|
972
1018
|
});
|
|
973
1019
|
}
|
|
974
1020
|
}
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
const
|
|
979
|
-
const { tenantId, workspaceId
|
|
980
|
-
const
|
|
981
|
-
const service = getOhiDataService(TABLE_NAME2);
|
|
1021
|
+
|
|
1022
|
+
// src/data/rest-api/routes/patient/patient-get-by-id.ts
|
|
1023
|
+
async function getPatientById(req, res) {
|
|
1024
|
+
const id = String(req.params.id);
|
|
1025
|
+
const { tenantId, workspaceId } = req.openhiContext;
|
|
1026
|
+
const service = getDynamoDataService();
|
|
982
1027
|
try {
|
|
983
|
-
const result = await service.entities.
|
|
1028
|
+
const result = await service.entities.patient.get({ tenantId, workspaceId, id, sk: SK2 }).go();
|
|
984
1029
|
if (!result.data) {
|
|
985
1030
|
return res.status(404).json({
|
|
986
1031
|
resourceType: "OperationOutcome",
|
|
@@ -988,7 +1033,7 @@ async function getConfigurationByKey(req, res) {
|
|
|
988
1033
|
{
|
|
989
1034
|
severity: "error",
|
|
990
1035
|
code: "not-found",
|
|
991
|
-
diagnostics: `
|
|
1036
|
+
diagnostics: `Patient ${id} not found`
|
|
992
1037
|
}
|
|
993
1038
|
]
|
|
994
1039
|
});
|
|
@@ -996,14 +1041,9 @@ async function getConfigurationByKey(req, res) {
|
|
|
996
1041
|
const resource = JSON.parse(
|
|
997
1042
|
decompressResource(result.data.resource)
|
|
998
1043
|
);
|
|
999
|
-
return res.json({
|
|
1000
|
-
...resource,
|
|
1001
|
-
resourceType: "Configuration",
|
|
1002
|
-
id: result.data.id,
|
|
1003
|
-
key: result.data.key
|
|
1004
|
-
});
|
|
1044
|
+
return res.json({ ...resource, id: result.data.id });
|
|
1005
1045
|
} catch (err) {
|
|
1006
|
-
console.error("GET
|
|
1046
|
+
console.error("GET Patient error:", err);
|
|
1007
1047
|
return res.status(500).json({
|
|
1008
1048
|
resourceType: "OperationOutcome",
|
|
1009
1049
|
issue: [
|
|
@@ -1016,83 +1056,64 @@ async function getConfigurationByKey(req, res) {
|
|
|
1016
1056
|
});
|
|
1017
1057
|
}
|
|
1018
1058
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
const {
|
|
1023
|
-
|
|
1024
|
-
workspaceId: ctxWorkspaceId,
|
|
1025
|
-
userId: ctxUserId,
|
|
1026
|
-
date
|
|
1027
|
-
} = ctx;
|
|
1028
|
-
const body = req.body;
|
|
1029
|
-
const key = body?.key;
|
|
1030
|
-
if (!key || typeof key !== "string") {
|
|
1031
|
-
return res.status(400).json({
|
|
1032
|
-
resourceType: "OperationOutcome",
|
|
1033
|
-
issue: [
|
|
1034
|
-
{
|
|
1035
|
-
severity: "error",
|
|
1036
|
-
code: "required",
|
|
1037
|
-
diagnostics: "Configuration key is required"
|
|
1038
|
-
}
|
|
1039
|
-
]
|
|
1040
|
-
});
|
|
1041
|
-
}
|
|
1042
|
-
const id = body?.id ?? `config-${key}-${Date.now()}`;
|
|
1043
|
-
const resourcePayload = body?.resource;
|
|
1044
|
-
const resourceStr = typeof resourcePayload === "string" ? resourcePayload : JSON.stringify(resourcePayload ?? {});
|
|
1045
|
-
const tenantId = body?.tenantId ?? ctxTenantId;
|
|
1046
|
-
const workspaceId = body?.workspaceId ?? ctxWorkspaceId;
|
|
1047
|
-
const userId = body?.userId ?? ctxUserId ?? "-";
|
|
1048
|
-
const roleId = body?.roleId ?? "-";
|
|
1049
|
-
const vid = body?.vid ?? (date.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36));
|
|
1050
|
-
const lastUpdated = body?.lastUpdated ?? date;
|
|
1051
|
-
const service = getOhiDataService(TABLE_NAME2);
|
|
1059
|
+
|
|
1060
|
+
// src/data/rest-api/routes/patient/patient-list.ts
|
|
1061
|
+
async function listPatients(req, res) {
|
|
1062
|
+
const { tenantId, workspaceId } = req.openhiContext;
|
|
1063
|
+
const service = getDynamoDataService();
|
|
1052
1064
|
try {
|
|
1053
|
-
await service.entities.
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
id,
|
|
1068
|
-
key,
|
|
1069
|
-
resource: typeof resourcePayload === "object" ? resourcePayload : JSON.parse(resourceStr),
|
|
1070
|
-
meta: { lastUpdated, versionId: vid }
|
|
1065
|
+
const result = await service.entities.patient.query.gsi4({ tenantId, workspaceId }).go();
|
|
1066
|
+
const entries = (result.data ?? []).map((item) => {
|
|
1067
|
+
const resource = JSON.parse(decompressResource(item.resource));
|
|
1068
|
+
return {
|
|
1069
|
+
fullUrl: `${BASE_PATH3}/${item.id}`,
|
|
1070
|
+
resource: { ...resource, id: item.id }
|
|
1071
|
+
};
|
|
1072
|
+
});
|
|
1073
|
+
const bundle = {
|
|
1074
|
+
resourceType: "Bundle",
|
|
1075
|
+
type: "searchset",
|
|
1076
|
+
total: entries.length,
|
|
1077
|
+
link: [{ relation: "self", url: BASE_PATH3 }],
|
|
1078
|
+
entry: entries
|
|
1071
1079
|
};
|
|
1072
|
-
return res.
|
|
1080
|
+
return res.json(bundle);
|
|
1073
1081
|
} catch (err) {
|
|
1074
|
-
console.error("
|
|
1082
|
+
console.error("GET /Patient list error:", err);
|
|
1075
1083
|
return res.status(500).json({
|
|
1076
1084
|
resourceType: "OperationOutcome",
|
|
1077
1085
|
issue: [
|
|
1078
|
-
{
|
|
1086
|
+
{
|
|
1087
|
+
severity: "error",
|
|
1088
|
+
code: "exception",
|
|
1089
|
+
diagnostics: String(err)
|
|
1090
|
+
}
|
|
1079
1091
|
]
|
|
1080
1092
|
});
|
|
1081
1093
|
}
|
|
1082
1094
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1095
|
+
|
|
1096
|
+
// src/data/rest-api/routes/patient/patient-update.ts
|
|
1097
|
+
async function updatePatient(req, res) {
|
|
1098
|
+
const bodyResult = requireJsonBody(req, res);
|
|
1099
|
+
if ("errorResponse" in bodyResult) return bodyResult.errorResponse;
|
|
1100
|
+
const id = String(req.params.id);
|
|
1086
1101
|
const ctx = req.openhiContext;
|
|
1087
|
-
const { tenantId, workspaceId,
|
|
1088
|
-
const
|
|
1089
|
-
const
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1102
|
+
const { tenantId, workspaceId, date, actorId, actorName } = ctx;
|
|
1103
|
+
const body = bodyResult.body;
|
|
1104
|
+
const patient = {
|
|
1105
|
+
...body,
|
|
1106
|
+
resourceType: "Patient",
|
|
1107
|
+
id,
|
|
1108
|
+
meta: {
|
|
1109
|
+
...body?.meta ?? {},
|
|
1110
|
+
lastUpdated: date,
|
|
1111
|
+
versionId: "2"
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
1114
|
+
const service = getDynamoDataService();
|
|
1094
1115
|
try {
|
|
1095
|
-
const existing = await service.entities.
|
|
1116
|
+
const existing = await service.entities.patient.get({ tenantId, workspaceId, id, sk: SK2 }).go();
|
|
1096
1117
|
if (!existing.data) {
|
|
1097
1118
|
return res.status(404).json({
|
|
1098
1119
|
resourceType: "OperationOutcome",
|
|
@@ -1100,47 +1121,30 @@ async function updateConfiguration(req, res) {
|
|
|
1100
1121
|
{
|
|
1101
1122
|
severity: "error",
|
|
1102
1123
|
code: "not-found",
|
|
1103
|
-
diagnostics: `
|
|
1124
|
+
diagnostics: `Patient ${id} not found`
|
|
1104
1125
|
}
|
|
1105
1126
|
]
|
|
1106
1127
|
});
|
|
1107
1128
|
}
|
|
1108
|
-
const
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
meta: { lastUpdated, versionId: nextVid }
|
|
1129
|
+
const existingMeta = existing.data.resource != null ? JSON.parse(decompressResource(existing.data.resource)).meta : void 0;
|
|
1130
|
+
const patientWithMeta = {
|
|
1131
|
+
...patient,
|
|
1132
|
+
meta: mergeAuditIntoMeta(
|
|
1133
|
+
patient.meta ?? existingMeta,
|
|
1134
|
+
{
|
|
1135
|
+
modifiedDate: date,
|
|
1136
|
+
modifiedById: actorId,
|
|
1137
|
+
modifiedByName: actorName
|
|
1138
|
+
}
|
|
1139
|
+
)
|
|
1120
1140
|
};
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
issue: [
|
|
1127
|
-
{ severity: "error", code: "exception", diagnostics: String(err) }
|
|
1128
|
-
]
|
|
1129
|
-
});
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
router2.put("/:key", updateConfiguration);
|
|
1133
|
-
async function deleteConfiguration(req, res) {
|
|
1134
|
-
const key = String(req.params.key);
|
|
1135
|
-
const ctx = req.openhiContext;
|
|
1136
|
-
const { tenantId, workspaceId, userId } = ctx;
|
|
1137
|
-
const roleId = "roleId" in ctx && typeof ctx.roleId === "string" ? ctx.roleId : "-";
|
|
1138
|
-
const service = getOhiDataService(TABLE_NAME2);
|
|
1139
|
-
try {
|
|
1140
|
-
await service.entities.configuration.delete({ tenantId, workspaceId, userId, roleId, key, sk: SK3 }).go();
|
|
1141
|
-
return res.status(204).send();
|
|
1141
|
+
await service.entities.patient.patch({ tenantId, workspaceId, id, sk: SK2 }).set({
|
|
1142
|
+
resource: compressResource(JSON.stringify(patientWithMeta)),
|
|
1143
|
+
lastUpdated: date
|
|
1144
|
+
}).go();
|
|
1145
|
+
return res.json(patientWithMeta);
|
|
1142
1146
|
} catch (err) {
|
|
1143
|
-
console.error("
|
|
1147
|
+
console.error("PUT Patient error:", err);
|
|
1144
1148
|
return res.status(500).json({
|
|
1145
1149
|
resourceType: "OperationOutcome",
|
|
1146
1150
|
issue: [
|
|
@@ -1149,7 +1153,14 @@ async function deleteConfiguration(req, res) {
|
|
|
1149
1153
|
});
|
|
1150
1154
|
}
|
|
1151
1155
|
}
|
|
1152
|
-
|
|
1156
|
+
|
|
1157
|
+
// src/data/rest-api/routes/patient/patient.ts
|
|
1158
|
+
var router2 = express2.Router();
|
|
1159
|
+
router2.get("/", listPatients);
|
|
1160
|
+
router2.get("/:id", getPatientById);
|
|
1161
|
+
router2.post("/", createPatient);
|
|
1162
|
+
router2.put("/:id", updatePatient);
|
|
1163
|
+
router2.delete("/:id", deletePatient);
|
|
1153
1164
|
|
|
1154
1165
|
// src/data/rest-api/rest-api.ts
|
|
1155
1166
|
var app = express3();
|
|
@@ -1162,10 +1173,9 @@ app.use(normalizeJsonBodyMiddleware);
|
|
|
1162
1173
|
app.get("/", (_req, res) => {
|
|
1163
1174
|
return res.status(200).json({ message: "POC App is running" });
|
|
1164
1175
|
});
|
|
1165
|
-
app.use("/
|
|
1166
|
-
app.use("/
|
|
1167
|
-
app.use("/
|
|
1168
|
-
app.use("/ohi/Configuration", router2);
|
|
1176
|
+
app.use(["/Patient", "/Configuration"], openHiContextMiddleware);
|
|
1177
|
+
app.use("/Patient", router2);
|
|
1178
|
+
app.use("/Configuration", router);
|
|
1169
1179
|
|
|
1170
1180
|
// src/data/lambda/rest-api-lambda.handler.ts
|
|
1171
1181
|
var handler = serverlessExpress({ app });
|