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