@openhi/constructs 0.0.110 → 0.0.112
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/chunk-23PUSHBV.mjs +24 -0
- package/lib/chunk-23PUSHBV.mjs.map +1 -0
- package/lib/chunk-2O3CXY2C.mjs +79 -0
- package/lib/chunk-2O3CXY2C.mjs.map +1 -0
- package/lib/{chunk-7FUAMZOF.mjs → chunk-53OHXLIL.mjs} +3 -3
- package/lib/chunk-6NBGYGFL.mjs +1803 -0
- package/lib/chunk-6NBGYGFL.mjs.map +1 -0
- package/lib/chunk-7RZHFI77.mjs +22 -0
- package/lib/chunk-7RZHFI77.mjs.map +1 -0
- package/lib/{chunk-7Q2IJ2J5.mjs → chunk-CUUKXDB2.mjs} +6 -6
- package/lib/chunk-FYHBHHWK.mjs +47 -0
- package/lib/chunk-FYHBHHWK.mjs.map +1 -0
- package/lib/{chunk-MULKGFIJ.mjs → chunk-GBDIGTNV.mjs} +165 -10
- package/lib/chunk-GBDIGTNV.mjs.map +1 -0
- package/lib/chunk-HQ67J7BP.mjs +199 -0
- package/lib/chunk-HQ67J7BP.mjs.map +1 -0
- package/lib/{chunk-AJ3G3THO.mjs → chunk-KO64HPWQ.mjs} +2 -2
- package/lib/{chunk-BB5MK4L3.mjs → chunk-KSFC72TT.mjs} +3 -3
- package/lib/{chunk-2TPJ6HOF.mjs → chunk-NZRW7ROK.mjs} +72 -54
- package/lib/chunk-NZRW7ROK.mjs.map +1 -0
- package/lib/chunk-QJDHVMKT.mjs +117 -0
- package/lib/chunk-QJDHVMKT.mjs.map +1 -0
- package/lib/{chunk-IS4VQRI4.mjs → chunk-QMBJ4VHC.mjs} +12 -47
- package/lib/chunk-QMBJ4VHC.mjs.map +1 -0
- package/lib/chunk-TRY7JGWO.mjs +16 -0
- package/lib/chunk-TRY7JGWO.mjs.map +1 -0
- package/lib/chunk-W4KR4CSL.mjs +236 -0
- package/lib/chunk-W4KR4CSL.mjs.map +1 -0
- package/lib/{chunk-AGF3RAAZ.mjs → chunk-WPCBVDFZ.mjs} +2 -2
- package/lib/chunk-WQWFVEVX.mjs +66 -0
- package/lib/chunk-WQWFVEVX.mjs.map +1 -0
- package/lib/{chunk-SYBADQXI.mjs → chunk-ZM4GDHHC.mjs} +77 -2
- package/lib/chunk-ZM4GDHHC.mjs.map +1 -0
- package/lib/data-store-postgres-replication.handler.js +26 -17
- package/lib/data-store-postgres-replication.handler.js.map +1 -1
- package/lib/data-store-postgres-replication.handler.mjs +5 -65
- package/lib/data-store-postgres-replication.handler.mjs.map +1 -1
- package/lib/delete-chunk.handler.d.mts +29 -0
- package/lib/delete-chunk.handler.d.ts +29 -0
- package/lib/delete-chunk.handler.js +2716 -0
- package/lib/delete-chunk.handler.js.map +1 -0
- package/lib/delete-chunk.handler.mjs +47 -0
- package/lib/delete-chunk.handler.mjs.map +1 -0
- package/lib/events-CjS-sm0W.d.mts +107 -0
- package/lib/events-CjS-sm0W.d.ts +107 -0
- package/lib/events-Da_cFgtc.d.mts +208 -0
- package/lib/events-Da_cFgtc.d.ts +208 -0
- package/lib/finalize.handler.d.mts +35 -0
- package/lib/finalize.handler.d.ts +35 -0
- package/lib/finalize.handler.js +875 -0
- package/lib/finalize.handler.js.map +1 -0
- package/lib/finalize.handler.mjs +166 -0
- package/lib/finalize.handler.mjs.map +1 -0
- package/lib/index.d.mts +189 -2
- package/lib/index.d.ts +500 -3
- package/lib/index.js +1753 -174
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +571 -17
- package/lib/index.mjs.map +1 -1
- package/lib/list-chunks.handler.d.mts +28 -0
- package/lib/list-chunks.handler.d.ts +28 -0
- package/lib/list-chunks.handler.js +2746 -0
- package/lib/list-chunks.handler.js.map +1 -0
- package/lib/list-chunks.handler.mjs +54 -0
- package/lib/list-chunks.handler.mjs.map +1 -0
- package/lib/platform-deploy-bridge.handler.js +76 -1
- package/lib/platform-deploy-bridge.handler.js.map +1 -1
- package/lib/platform-deploy-bridge.handler.mjs +1 -1
- package/lib/pre-token-generation.handler.js +1106 -155
- package/lib/pre-token-generation.handler.js.map +1 -1
- package/lib/pre-token-generation.handler.mjs +6 -4
- package/lib/pre-token-generation.handler.mjs.map +1 -1
- package/lib/provision-default-workspace.handler.js +1529 -142
- package/lib/provision-default-workspace.handler.js.map +1 -1
- package/lib/provision-default-workspace.handler.mjs +8 -4
- package/lib/provision-default-workspace.handler.mjs.map +1 -1
- package/lib/rename-finalize.handler.d.mts +30 -0
- package/lib/rename-finalize.handler.d.ts +30 -0
- package/lib/rename-finalize.handler.js +795 -0
- package/lib/rename-finalize.handler.js.map +1 -0
- package/lib/rename-finalize.handler.mjs +90 -0
- package/lib/rename-finalize.handler.mjs.map +1 -0
- package/lib/rename-list-targets.handler.d.mts +26 -0
- package/lib/rename-list-targets.handler.d.ts +26 -0
- package/lib/rename-list-targets.handler.js +2985 -0
- package/lib/rename-list-targets.handler.js.map +1 -0
- package/lib/rename-list-targets.handler.mjs +431 -0
- package/lib/rename-list-targets.handler.mjs.map +1 -0
- package/lib/rename-rewrite-chunk.handler.d.mts +35 -0
- package/lib/rename-rewrite-chunk.handler.d.ts +35 -0
- package/lib/rename-rewrite-chunk.handler.js +2021 -0
- package/lib/rename-rewrite-chunk.handler.js.map +1 -0
- package/lib/rename-rewrite-chunk.handler.mjs +27 -0
- package/lib/rename-rewrite-chunk.handler.mjs.map +1 -0
- package/lib/rest-api-lambda.handler.js +4087 -921
- package/lib/rest-api-lambda.handler.js.map +1 -1
- package/lib/rest-api-lambda.handler.mjs +1827 -81
- package/lib/rest-api-lambda.handler.mjs.map +1 -1
- package/lib/seed-demo-data.handler.js +1588 -124
- package/lib/seed-demo-data.handler.js.map +1 -1
- package/lib/seed-demo-data.handler.mjs +10 -6
- package/lib/seed-system-data.handler.js +1179 -155
- package/lib/seed-system-data.handler.js.map +1 -1
- package/lib/seed-system-data.handler.mjs +5 -4
- package/lib/seed-system-data.handler.mjs.map +1 -1
- package/package.json +1 -1
- package/lib/chunk-2TPJ6HOF.mjs.map +0 -1
- package/lib/chunk-IS4VQRI4.mjs.map +0 -1
- package/lib/chunk-MULKGFIJ.mjs.map +0 -1
- package/lib/chunk-QR5JVSCF.mjs +0 -862
- package/lib/chunk-QR5JVSCF.mjs.map +0 -1
- package/lib/chunk-SYBADQXI.mjs.map +0 -1
- /package/lib/{chunk-7FUAMZOF.mjs.map → chunk-53OHXLIL.mjs.map} +0 -0
- /package/lib/{chunk-7Q2IJ2J5.mjs.map → chunk-CUUKXDB2.mjs.map} +0 -0
- /package/lib/{chunk-AJ3G3THO.mjs.map → chunk-KO64HPWQ.mjs.map} +0 -0
- /package/lib/{chunk-BB5MK4L3.mjs.map → chunk-KSFC72TT.mjs.map} +0 -0
- /package/lib/{chunk-AGF3RAAZ.mjs.map → chunk-WPCBVDFZ.mjs.map} +0 -0
|
@@ -1,33 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildSchemaBootstrapStatements
|
|
3
|
+
} from "./chunk-2O3CXY2C.mjs";
|
|
1
4
|
import {
|
|
2
5
|
createRoleOperation
|
|
3
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-KO64HPWQ.mjs";
|
|
4
7
|
import {
|
|
5
8
|
getRoleByIdOperation
|
|
6
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-53OHXLIL.mjs";
|
|
7
10
|
import {
|
|
8
11
|
listMembershipsOperation,
|
|
9
12
|
listPractitionerRolesOperation,
|
|
10
13
|
listRoleAssignmentsOperation
|
|
11
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-KSFC72TT.mjs";
|
|
12
15
|
import {
|
|
13
16
|
createMembershipOperation,
|
|
14
17
|
createRoleAssignmentOperation,
|
|
15
18
|
createTenantOperation,
|
|
16
|
-
createWorkspaceOperation
|
|
17
|
-
|
|
19
|
+
createWorkspaceOperation,
|
|
20
|
+
extractDenormalizedReferenceDisplay
|
|
21
|
+
} from "./chunk-GBDIGTNV.mjs";
|
|
22
|
+
import {
|
|
23
|
+
buildMembershipUserProjectionItem,
|
|
24
|
+
buildMembershipWorkspaceProjectionItem,
|
|
25
|
+
buildRoleAssignmentUserProjectionItem,
|
|
26
|
+
buildRoleAssignmentWorkspaceProjectionItem,
|
|
27
|
+
extractReferenceSlug,
|
|
28
|
+
extractReferenceSlug2
|
|
29
|
+
} from "./chunk-HQ67J7BP.mjs";
|
|
30
|
+
import {
|
|
31
|
+
executeMultiWrite
|
|
32
|
+
} from "./chunk-QJDHVMKT.mjs";
|
|
18
33
|
import {
|
|
19
34
|
createUserOperation,
|
|
20
35
|
deleteUserOperation,
|
|
21
36
|
findUserBySubOperation,
|
|
22
37
|
getUserByIdOperation,
|
|
23
38
|
listUsersOperation,
|
|
39
|
+
membershipListByUserOperation,
|
|
24
40
|
switchUserTenantWorkspaceOperation,
|
|
25
41
|
updateUserOperation
|
|
26
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-NZRW7ROK.mjs";
|
|
27
43
|
import {
|
|
28
|
-
ForbiddenError,
|
|
29
|
-
NotFoundError,
|
|
30
|
-
ValidationError,
|
|
31
44
|
batchGetWithRetry,
|
|
32
45
|
buildUpdatedResourceWithAudit,
|
|
33
46
|
compressResource,
|
|
@@ -35,17 +48,23 @@ import {
|
|
|
35
48
|
decompressResource,
|
|
36
49
|
deleteDataEntityById,
|
|
37
50
|
dispatchListMode,
|
|
38
|
-
domainErrorToHttpStatus,
|
|
39
51
|
getDataEntityById,
|
|
40
52
|
getDynamoDataService,
|
|
41
53
|
listDataEntitiesByWorkspace,
|
|
42
54
|
mergeAuditIntoMeta,
|
|
43
55
|
updateDataEntityById
|
|
44
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-QMBJ4VHC.mjs";
|
|
57
|
+
import {
|
|
58
|
+
ForbiddenError,
|
|
59
|
+
NotFoundError,
|
|
60
|
+
ValidationError,
|
|
61
|
+
domainErrorToHttpStatus
|
|
62
|
+
} from "./chunk-FYHBHHWK.mjs";
|
|
45
63
|
import {
|
|
46
64
|
SHARD_COUNT,
|
|
47
65
|
getDynamoControlService
|
|
48
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-6NBGYGFL.mjs";
|
|
67
|
+
import "./chunk-TRY7JGWO.mjs";
|
|
49
68
|
import "./chunk-LZOMFHX3.mjs";
|
|
50
69
|
|
|
51
70
|
// src/data/lambda/rest-api-lambda.handler.ts
|
|
@@ -116,6 +135,81 @@ function openHiContextMiddleware(req, res, next) {
|
|
|
116
135
|
// src/data/rest-api/routes/control/configuration/configuration.ts
|
|
117
136
|
import express from "express";
|
|
118
137
|
|
|
138
|
+
// src/data/operations/control/configuration/configuration-user-projection.ts
|
|
139
|
+
import { normalizeLabel } from "@openhi/types";
|
|
140
|
+
var MISSING_NAME_SENTINEL = "-";
|
|
141
|
+
var ABSENT_USER_SENTINEL = "-";
|
|
142
|
+
function buildConfigurationUserProjectionSk(params) {
|
|
143
|
+
const normalizedConfigName = typeof params.key === "string" && params.key.length > 0 ? normalizeLabel(params.key) : MISSING_NAME_SENTINEL;
|
|
144
|
+
const safeNormalized = normalizedConfigName.length > 0 ? normalizedConfigName : MISSING_NAME_SENTINEL;
|
|
145
|
+
return `CONFIGURATION#${safeNormalized}#${params.configurationId}`;
|
|
146
|
+
}
|
|
147
|
+
function buildConfigurationUserProjectionItem(input) {
|
|
148
|
+
if (!input.userId || input.userId.length === 0 || input.userId === ABSENT_USER_SENTINEL) {
|
|
149
|
+
return void 0;
|
|
150
|
+
}
|
|
151
|
+
if (!input.configurationId || input.configurationId.length === 0) {
|
|
152
|
+
return void 0;
|
|
153
|
+
}
|
|
154
|
+
const sk = buildConfigurationUserProjectionSk({
|
|
155
|
+
key: input.key,
|
|
156
|
+
configurationId: input.configurationId
|
|
157
|
+
});
|
|
158
|
+
return {
|
|
159
|
+
userId: input.userId,
|
|
160
|
+
sk,
|
|
161
|
+
tenantId: input.tenantId,
|
|
162
|
+
configurationId: input.configurationId,
|
|
163
|
+
scope: "user",
|
|
164
|
+
displayName: input.key,
|
|
165
|
+
summary: input.summary,
|
|
166
|
+
vid: input.vid,
|
|
167
|
+
lastUpdated: input.lastUpdated
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function isUserScopedConfiguration(userId) {
|
|
171
|
+
return typeof userId === "string" && userId.length > 0 && userId !== ABSENT_USER_SENTINEL;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/data/operations/control/configuration/configuration-workspace-projection.ts
|
|
175
|
+
import { normalizeLabel as normalizeLabel2 } from "@openhi/types";
|
|
176
|
+
var MISSING_NAME_SENTINEL2 = "-";
|
|
177
|
+
var ABSENT_WORKSPACE_SENTINEL = "-";
|
|
178
|
+
var ABSENT_USER_SENTINEL2 = "-";
|
|
179
|
+
function buildConfigurationWorkspaceProjectionSk(params) {
|
|
180
|
+
const normalizedConfigName = typeof params.key === "string" && params.key.length > 0 ? normalizeLabel2(params.key) : MISSING_NAME_SENTINEL2;
|
|
181
|
+
const safeNormalized = normalizedConfigName.length > 0 ? normalizedConfigName : MISSING_NAME_SENTINEL2;
|
|
182
|
+
return `CONFIGURATION#${safeNormalized}#${params.configurationId}`;
|
|
183
|
+
}
|
|
184
|
+
function buildConfigurationWorkspaceProjectionItem(input) {
|
|
185
|
+
if (!input.workspaceId || input.workspaceId.length === 0 || input.workspaceId === ABSENT_WORKSPACE_SENTINEL) {
|
|
186
|
+
return void 0;
|
|
187
|
+
}
|
|
188
|
+
if (!input.configurationId || input.configurationId.length === 0) {
|
|
189
|
+
return void 0;
|
|
190
|
+
}
|
|
191
|
+
const sk = buildConfigurationWorkspaceProjectionSk({
|
|
192
|
+
key: input.key,
|
|
193
|
+
configurationId: input.configurationId
|
|
194
|
+
});
|
|
195
|
+
return {
|
|
196
|
+
tenantId: input.tenantId,
|
|
197
|
+
workspaceId: input.workspaceId,
|
|
198
|
+
sk,
|
|
199
|
+
configurationId: input.configurationId,
|
|
200
|
+
scope: "workspace",
|
|
201
|
+
displayName: input.key,
|
|
202
|
+
summary: input.summary,
|
|
203
|
+
vid: input.vid,
|
|
204
|
+
lastUpdated: input.lastUpdated
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function isWorkspaceScopedConfiguration(params) {
|
|
208
|
+
const workspaceIsReal = typeof params.workspaceId === "string" && params.workspaceId.length > 0 && params.workspaceId !== ABSENT_WORKSPACE_SENTINEL;
|
|
209
|
+
const userIsAbsent = typeof params.userId !== "string" || params.userId.length === 0 || params.userId === ABSENT_USER_SENTINEL2;
|
|
210
|
+
return workspaceIsReal && userIsAbsent;
|
|
211
|
+
}
|
|
212
|
+
|
|
119
213
|
// src/data/operations/control/configuration/configuration-create-operation.ts
|
|
120
214
|
var SK = "CURRENT";
|
|
121
215
|
async function createConfigurationOperation(params) {
|
|
@@ -144,7 +238,28 @@ async function createConfigurationOperation(params) {
|
|
|
144
238
|
roleId
|
|
145
239
|
});
|
|
146
240
|
const service = getDynamoControlService(tableName);
|
|
147
|
-
|
|
241
|
+
const userProjectionItem = isUserScopedConfiguration(userId) ? buildConfigurationUserProjectionItem({
|
|
242
|
+
tenantId,
|
|
243
|
+
userId,
|
|
244
|
+
configurationId: id,
|
|
245
|
+
key,
|
|
246
|
+
summary,
|
|
247
|
+
vid,
|
|
248
|
+
lastUpdated
|
|
249
|
+
}) : void 0;
|
|
250
|
+
const workspaceProjectionItem = isWorkspaceScopedConfiguration({
|
|
251
|
+
workspaceId,
|
|
252
|
+
userId
|
|
253
|
+
}) ? buildConfigurationWorkspaceProjectionItem({
|
|
254
|
+
tenantId,
|
|
255
|
+
workspaceId,
|
|
256
|
+
configurationId: id,
|
|
257
|
+
key,
|
|
258
|
+
summary,
|
|
259
|
+
vid,
|
|
260
|
+
lastUpdated
|
|
261
|
+
}) : void 0;
|
|
262
|
+
const canonicalItem = {
|
|
148
263
|
tenantId,
|
|
149
264
|
workspaceId,
|
|
150
265
|
userId,
|
|
@@ -156,7 +271,25 @@ async function createConfigurationOperation(params) {
|
|
|
156
271
|
vid,
|
|
157
272
|
lastUpdated,
|
|
158
273
|
sk: SK
|
|
159
|
-
}
|
|
274
|
+
};
|
|
275
|
+
const triples = [
|
|
276
|
+
{ entity: "configuration", action: "put", item: canonicalItem }
|
|
277
|
+
];
|
|
278
|
+
if (userProjectionItem) {
|
|
279
|
+
triples.push({
|
|
280
|
+
entity: "configurationUserProjection",
|
|
281
|
+
action: "put",
|
|
282
|
+
item: userProjectionItem
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
if (workspaceProjectionItem) {
|
|
286
|
+
triples.push({
|
|
287
|
+
entity: "configurationWorkspaceProjection",
|
|
288
|
+
action: "put",
|
|
289
|
+
item: workspaceProjectionItem
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
await executeMultiWrite({ service, triples });
|
|
160
293
|
const resource = typeof resourcePayload === "object" ? resourcePayload : JSON.parse(resourceStr);
|
|
161
294
|
return {
|
|
162
295
|
id,
|
|
@@ -461,7 +594,7 @@ async function deleteConfigurationOperation(params) {
|
|
|
461
594
|
const { tenantId, workspaceId, actorId, roleId: ctxRoleId } = context;
|
|
462
595
|
const roleId = ctxRoleId ?? "-";
|
|
463
596
|
const service = getDynamoControlService(tableName);
|
|
464
|
-
await service.entities.configuration.
|
|
597
|
+
const existing = await service.entities.configuration.get({
|
|
465
598
|
tenantId,
|
|
466
599
|
workspaceId,
|
|
467
600
|
userId: actorId,
|
|
@@ -469,6 +602,81 @@ async function deleteConfigurationOperation(params) {
|
|
|
469
602
|
key: id,
|
|
470
603
|
sk: SK2
|
|
471
604
|
}).go();
|
|
605
|
+
if (!existing.data) {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
const canonicalUserId = existing.data.userId;
|
|
609
|
+
const canonicalWorkspaceId = existing.data.workspaceId;
|
|
610
|
+
const canonicalKey = existing.data.key;
|
|
611
|
+
const canonicalId = existing.data.id;
|
|
612
|
+
const userProjectionItem = isUserScopedConfiguration(canonicalUserId) ? buildConfigurationUserProjectionItem({
|
|
613
|
+
tenantId: existing.data.tenantId,
|
|
614
|
+
userId: canonicalUserId,
|
|
615
|
+
configurationId: canonicalId,
|
|
616
|
+
key: canonicalKey,
|
|
617
|
+
// The placeholder summary / vid / lastUpdated values are
|
|
618
|
+
// unused by the delete path — ElectroDB only needs the
|
|
619
|
+
// composite key fields (`userId` + `sk`) to issue the
|
|
620
|
+
// DeleteItem. Supplying them keeps the entity-validation
|
|
621
|
+
// pass happy on a `put`-shaped item if the helper ever
|
|
622
|
+
// rejects a sparse delete payload.
|
|
623
|
+
summary: "",
|
|
624
|
+
vid: "",
|
|
625
|
+
lastUpdated: ""
|
|
626
|
+
}) : void 0;
|
|
627
|
+
const workspaceProjectionItem = isWorkspaceScopedConfiguration({
|
|
628
|
+
workspaceId: canonicalWorkspaceId,
|
|
629
|
+
userId: canonicalUserId
|
|
630
|
+
}) ? buildConfigurationWorkspaceProjectionItem({
|
|
631
|
+
tenantId: existing.data.tenantId,
|
|
632
|
+
workspaceId: canonicalWorkspaceId,
|
|
633
|
+
configurationId: canonicalId,
|
|
634
|
+
key: canonicalKey,
|
|
635
|
+
// The placeholder summary / vid / lastUpdated values are
|
|
636
|
+
// unused by the delete path — ElectroDB only needs the
|
|
637
|
+
// composite key fields (`tenantId` + `workspaceId` + `sk`) to
|
|
638
|
+
// issue the DeleteItem. Supplying them keeps entity-validation
|
|
639
|
+
// happy if the helper ever rejects a sparse delete payload.
|
|
640
|
+
summary: "",
|
|
641
|
+
vid: "",
|
|
642
|
+
lastUpdated: ""
|
|
643
|
+
}) : void 0;
|
|
644
|
+
const triples = [
|
|
645
|
+
{
|
|
646
|
+
entity: "configuration",
|
|
647
|
+
action: "delete",
|
|
648
|
+
item: {
|
|
649
|
+
tenantId,
|
|
650
|
+
workspaceId,
|
|
651
|
+
userId: actorId,
|
|
652
|
+
roleId,
|
|
653
|
+
key: id,
|
|
654
|
+
sk: SK2
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
];
|
|
658
|
+
if (userProjectionItem) {
|
|
659
|
+
triples.push({
|
|
660
|
+
entity: "configurationUserProjection",
|
|
661
|
+
action: "delete",
|
|
662
|
+
item: {
|
|
663
|
+
userId: userProjectionItem.userId,
|
|
664
|
+
sk: userProjectionItem.sk
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
if (workspaceProjectionItem) {
|
|
669
|
+
triples.push({
|
|
670
|
+
entity: "configurationWorkspaceProjection",
|
|
671
|
+
action: "delete",
|
|
672
|
+
item: {
|
|
673
|
+
tenantId: workspaceProjectionItem.tenantId,
|
|
674
|
+
workspaceId: workspaceProjectionItem.workspaceId,
|
|
675
|
+
sk: workspaceProjectionItem.sk
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
await executeMultiWrite({ service, triples });
|
|
472
680
|
}
|
|
473
681
|
|
|
474
682
|
// src/data/rest-api/routes/control/configuration/configuration-delete-route.ts
|
|
@@ -947,6 +1155,47 @@ async function updateConfigurationOperation(params) {
|
|
|
947
1155
|
lastUpdated,
|
|
948
1156
|
vid: nextVid
|
|
949
1157
|
}).go();
|
|
1158
|
+
const canonicalUserId = existing.data.userId;
|
|
1159
|
+
const canonicalWorkspaceId = existing.data.workspaceId;
|
|
1160
|
+
const userProjectionItem = isUserScopedConfiguration(canonicalUserId) ? buildConfigurationUserProjectionItem({
|
|
1161
|
+
tenantId,
|
|
1162
|
+
userId: canonicalUserId,
|
|
1163
|
+
configurationId: existing.data.id,
|
|
1164
|
+
key: existing.data.key,
|
|
1165
|
+
summary,
|
|
1166
|
+
vid: nextVid,
|
|
1167
|
+
lastUpdated
|
|
1168
|
+
}) : void 0;
|
|
1169
|
+
const workspaceProjectionItem = isWorkspaceScopedConfiguration({
|
|
1170
|
+
workspaceId: canonicalWorkspaceId,
|
|
1171
|
+
userId: canonicalUserId
|
|
1172
|
+
}) ? buildConfigurationWorkspaceProjectionItem({
|
|
1173
|
+
tenantId,
|
|
1174
|
+
workspaceId: canonicalWorkspaceId,
|
|
1175
|
+
configurationId: existing.data.id,
|
|
1176
|
+
key: existing.data.key,
|
|
1177
|
+
summary,
|
|
1178
|
+
vid: nextVid,
|
|
1179
|
+
lastUpdated
|
|
1180
|
+
}) : void 0;
|
|
1181
|
+
const refreshTriples = [];
|
|
1182
|
+
if (userProjectionItem) {
|
|
1183
|
+
refreshTriples.push({
|
|
1184
|
+
entity: "configurationUserProjection",
|
|
1185
|
+
action: "put",
|
|
1186
|
+
item: userProjectionItem
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
if (workspaceProjectionItem) {
|
|
1190
|
+
refreshTriples.push({
|
|
1191
|
+
entity: "configurationWorkspaceProjection",
|
|
1192
|
+
action: "put",
|
|
1193
|
+
item: workspaceProjectionItem
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
if (refreshTriples.length > 0) {
|
|
1197
|
+
await executeMultiWrite({ service, triples: refreshTriples });
|
|
1198
|
+
}
|
|
950
1199
|
const parsedResource = typeof resourcePayload === "object" ? resourcePayload : JSON.parse(resourceStr);
|
|
951
1200
|
return {
|
|
952
1201
|
id: existing.data.id,
|
|
@@ -1381,7 +1630,78 @@ async function createMembershipRoute(req, res) {
|
|
|
1381
1630
|
async function deleteMembershipOperation(params) {
|
|
1382
1631
|
const { context, id, tableName } = params;
|
|
1383
1632
|
const service = getDynamoControlService(tableName);
|
|
1384
|
-
await service.entities.membership.
|
|
1633
|
+
const existing = await service.entities.membership.get({ tenantId: context.tenantId, id, sk: "CURRENT" }).go();
|
|
1634
|
+
if (!existing.data) {
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
let parsed;
|
|
1638
|
+
try {
|
|
1639
|
+
parsed = typeof existing.data.resource === "string" ? JSON.parse(existing.data.resource) : void 0;
|
|
1640
|
+
} catch {
|
|
1641
|
+
parsed = void 0;
|
|
1642
|
+
}
|
|
1643
|
+
const userIdFromResource = parsed !== void 0 ? extractReferenceSlug(parsed, "user") : void 0;
|
|
1644
|
+
const workspaceIdFromResource = parsed !== void 0 ? extractReferenceSlug(parsed, "workspace") : void 0;
|
|
1645
|
+
const userProjectionItem = userIdFromResource !== void 0 ? buildMembershipUserProjectionItem({
|
|
1646
|
+
tenantId: context.tenantId,
|
|
1647
|
+
userId: userIdFromResource,
|
|
1648
|
+
workspaceId: workspaceIdFromResource,
|
|
1649
|
+
membershipId: id,
|
|
1650
|
+
// The placeholder summary / vid / lastUpdated values are
|
|
1651
|
+
// unused by the delete path — ElectroDB only needs the
|
|
1652
|
+
// composite key fields (`userId` + `sk`) to issue the
|
|
1653
|
+
// DeleteItem. Supplying them keeps the entity-validation
|
|
1654
|
+
// pass happy on a `put`-shaped item if the helper ever
|
|
1655
|
+
// rejects a sparse delete payload.
|
|
1656
|
+
summary: "",
|
|
1657
|
+
vid: "",
|
|
1658
|
+
lastUpdated: "",
|
|
1659
|
+
denormalizedTenantName: existing.data.denormalizedTenantName,
|
|
1660
|
+
denormalizedUserName: existing.data.denormalizedUserName,
|
|
1661
|
+
// The canonical row does not carry a workspace display name
|
|
1662
|
+
// (TR-024 § Open Item #4); the SK builder falls back to the
|
|
1663
|
+
// missing-name sentinel for the workspace-lane shape.
|
|
1664
|
+
denormalizedWorkspaceName: void 0
|
|
1665
|
+
}) : void 0;
|
|
1666
|
+
const workspaceProjectionItem = userIdFromResource !== void 0 && workspaceIdFromResource !== void 0 ? buildMembershipWorkspaceProjectionItem({
|
|
1667
|
+
tenantId: context.tenantId,
|
|
1668
|
+
workspaceId: workspaceIdFromResource,
|
|
1669
|
+
userId: userIdFromResource,
|
|
1670
|
+
membershipId: id,
|
|
1671
|
+
summary: "",
|
|
1672
|
+
vid: "",
|
|
1673
|
+
lastUpdated: "",
|
|
1674
|
+
denormalizedUserName: existing.data.denormalizedUserName
|
|
1675
|
+
}) : void 0;
|
|
1676
|
+
const triples = [
|
|
1677
|
+
{
|
|
1678
|
+
entity: "membership",
|
|
1679
|
+
action: "delete",
|
|
1680
|
+
item: { tenantId: context.tenantId, id, sk: "CURRENT" }
|
|
1681
|
+
}
|
|
1682
|
+
];
|
|
1683
|
+
if (userProjectionItem) {
|
|
1684
|
+
triples.push({
|
|
1685
|
+
entity: "membershipUserProjection",
|
|
1686
|
+
action: "delete",
|
|
1687
|
+
item: {
|
|
1688
|
+
userId: userProjectionItem.userId,
|
|
1689
|
+
sk: userProjectionItem.sk
|
|
1690
|
+
}
|
|
1691
|
+
});
|
|
1692
|
+
}
|
|
1693
|
+
if (workspaceProjectionItem) {
|
|
1694
|
+
triples.push({
|
|
1695
|
+
entity: "membershipWorkspaceProjection",
|
|
1696
|
+
action: "delete",
|
|
1697
|
+
item: {
|
|
1698
|
+
tenantId: workspaceProjectionItem.tenantId,
|
|
1699
|
+
workspaceId: workspaceProjectionItem.workspaceId,
|
|
1700
|
+
sk: workspaceProjectionItem.sk
|
|
1701
|
+
}
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
await executeMultiWrite({ service, triples });
|
|
1385
1705
|
}
|
|
1386
1706
|
|
|
1387
1707
|
// src/data/rest-api/routes/control/membership/membership-delete-route.ts
|
|
@@ -1473,16 +1793,76 @@ async function updateMembershipOperation(params) {
|
|
|
1473
1793
|
}
|
|
1474
1794
|
throw e;
|
|
1475
1795
|
}
|
|
1796
|
+
const resourceRecord = resource;
|
|
1797
|
+
const denormalizedTenantName = extractDenormalizedReferenceDisplay(
|
|
1798
|
+
resourceRecord,
|
|
1799
|
+
"tenant"
|
|
1800
|
+
);
|
|
1801
|
+
const denormalizedUserName = extractDenormalizedReferenceDisplay(
|
|
1802
|
+
resourceRecord,
|
|
1803
|
+
"user"
|
|
1804
|
+
);
|
|
1805
|
+
const denormalizedWorkspaceName = extractDenormalizedReferenceDisplay(
|
|
1806
|
+
resourceRecord,
|
|
1807
|
+
"workspace"
|
|
1808
|
+
);
|
|
1476
1809
|
const summary = JSON.stringify(extractSummary(resource));
|
|
1477
|
-
|
|
1810
|
+
const userIdFromResource = extractReferenceSlug(resourceRecord, "user");
|
|
1811
|
+
const workspaceIdFromResource = extractReferenceSlug(
|
|
1812
|
+
resourceRecord,
|
|
1813
|
+
"workspace"
|
|
1814
|
+
);
|
|
1815
|
+
const userProjectionItem = userIdFromResource !== void 0 ? buildMembershipUserProjectionItem({
|
|
1816
|
+
tenantId: context.tenantId,
|
|
1817
|
+
userId: userIdFromResource,
|
|
1818
|
+
workspaceId: workspaceIdFromResource,
|
|
1819
|
+
membershipId: id,
|
|
1820
|
+
summary,
|
|
1821
|
+
vid,
|
|
1822
|
+
lastUpdated,
|
|
1823
|
+
denormalizedTenantName,
|
|
1824
|
+
denormalizedUserName,
|
|
1825
|
+
denormalizedWorkspaceName
|
|
1826
|
+
}) : void 0;
|
|
1827
|
+
const workspaceProjectionItem = userIdFromResource !== void 0 && workspaceIdFromResource !== void 0 ? buildMembershipWorkspaceProjectionItem({
|
|
1828
|
+
tenantId: context.tenantId,
|
|
1829
|
+
workspaceId: workspaceIdFromResource,
|
|
1830
|
+
userId: userIdFromResource,
|
|
1831
|
+
membershipId: id,
|
|
1832
|
+
summary,
|
|
1833
|
+
vid,
|
|
1834
|
+
lastUpdated,
|
|
1835
|
+
denormalizedUserName
|
|
1836
|
+
}) : void 0;
|
|
1837
|
+
const canonicalItem = {
|
|
1478
1838
|
tenantId: context.tenantId,
|
|
1479
1839
|
id,
|
|
1480
1840
|
resource: JSON.stringify(resource),
|
|
1481
1841
|
summary,
|
|
1482
1842
|
vid,
|
|
1483
1843
|
lastUpdated,
|
|
1484
|
-
linkedDataIdentityRef
|
|
1485
|
-
|
|
1844
|
+
linkedDataIdentityRef,
|
|
1845
|
+
denormalizedTenantName,
|
|
1846
|
+
denormalizedUserName
|
|
1847
|
+
};
|
|
1848
|
+
const triples = [
|
|
1849
|
+
{ entity: "membership", action: "put", item: canonicalItem }
|
|
1850
|
+
];
|
|
1851
|
+
if (userProjectionItem) {
|
|
1852
|
+
triples.push({
|
|
1853
|
+
entity: "membershipUserProjection",
|
|
1854
|
+
action: "put",
|
|
1855
|
+
item: userProjectionItem
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
if (workspaceProjectionItem) {
|
|
1859
|
+
triples.push({
|
|
1860
|
+
entity: "membershipWorkspaceProjection",
|
|
1861
|
+
action: "put",
|
|
1862
|
+
item: workspaceProjectionItem
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
await executeMultiWrite({ service, triples });
|
|
1486
1866
|
return {
|
|
1487
1867
|
id,
|
|
1488
1868
|
resource,
|
|
@@ -1778,7 +2158,79 @@ async function createRoleAssignmentRoute(req, res) {
|
|
|
1778
2158
|
async function deleteRoleAssignmentOperation(params) {
|
|
1779
2159
|
const { context, id, tableName } = params;
|
|
1780
2160
|
const service = getDynamoControlService(tableName);
|
|
1781
|
-
await service.entities.roleAssignment.
|
|
2161
|
+
const existing = await service.entities.roleAssignment.get({ tenantId: context.tenantId, id, sk: "CURRENT" }).go();
|
|
2162
|
+
if (!existing.data) {
|
|
2163
|
+
return;
|
|
2164
|
+
}
|
|
2165
|
+
let parsed;
|
|
2166
|
+
try {
|
|
2167
|
+
parsed = typeof existing.data.resource === "string" ? JSON.parse(existing.data.resource) : void 0;
|
|
2168
|
+
} catch {
|
|
2169
|
+
parsed = void 0;
|
|
2170
|
+
}
|
|
2171
|
+
const userIdFromResource = parsed !== void 0 ? extractReferenceSlug2(parsed, "user") : void 0;
|
|
2172
|
+
const roleIdFromResource = parsed !== void 0 ? extractReferenceSlug2(parsed, "role") : void 0;
|
|
2173
|
+
const workspaceIdFromResource = parsed !== void 0 ? extractReferenceSlug2(parsed, "workspace") : void 0;
|
|
2174
|
+
const userProjectionItem = userIdFromResource !== void 0 && roleIdFromResource !== void 0 ? buildRoleAssignmentUserProjectionItem({
|
|
2175
|
+
tenantId: context.tenantId,
|
|
2176
|
+
userId: userIdFromResource,
|
|
2177
|
+
workspaceId: workspaceIdFromResource,
|
|
2178
|
+
roleId: roleIdFromResource,
|
|
2179
|
+
roleAssignmentId: id,
|
|
2180
|
+
// The placeholder summary / vid / lastUpdated values are
|
|
2181
|
+
// unused by the delete path — ElectroDB only needs the
|
|
2182
|
+
// composite key fields (`userId` + `sk`) to issue the
|
|
2183
|
+
// DeleteItem. Supplying them keeps the entity-validation
|
|
2184
|
+
// pass happy on a `put`-shaped item if the helper ever
|
|
2185
|
+
// rejects a sparse delete payload.
|
|
2186
|
+
summary: "",
|
|
2187
|
+
vid: "",
|
|
2188
|
+
lastUpdated: "",
|
|
2189
|
+
denormalizedTenantName: existing.data.denormalizedTenantName,
|
|
2190
|
+
denormalizedUserName: existing.data.denormalizedUserName,
|
|
2191
|
+
denormalizedRoleName: existing.data.denormalizedRoleName
|
|
2192
|
+
}) : void 0;
|
|
2193
|
+
const workspaceProjectionItem = userIdFromResource !== void 0 && roleIdFromResource !== void 0 && workspaceIdFromResource !== void 0 ? buildRoleAssignmentWorkspaceProjectionItem({
|
|
2194
|
+
tenantId: context.tenantId,
|
|
2195
|
+
workspaceId: workspaceIdFromResource,
|
|
2196
|
+
userId: userIdFromResource,
|
|
2197
|
+
roleId: roleIdFromResource,
|
|
2198
|
+
roleAssignmentId: id,
|
|
2199
|
+
summary: "",
|
|
2200
|
+
vid: "",
|
|
2201
|
+
lastUpdated: "",
|
|
2202
|
+
denormalizedUserName: existing.data.denormalizedUserName,
|
|
2203
|
+
denormalizedRoleName: existing.data.denormalizedRoleName
|
|
2204
|
+
}) : void 0;
|
|
2205
|
+
const triples = [
|
|
2206
|
+
{
|
|
2207
|
+
entity: "roleAssignment",
|
|
2208
|
+
action: "delete",
|
|
2209
|
+
item: { tenantId: context.tenantId, id, sk: "CURRENT" }
|
|
2210
|
+
}
|
|
2211
|
+
];
|
|
2212
|
+
if (userProjectionItem) {
|
|
2213
|
+
triples.push({
|
|
2214
|
+
entity: "roleAssignmentUserProjection",
|
|
2215
|
+
action: "delete",
|
|
2216
|
+
item: {
|
|
2217
|
+
userId: userProjectionItem.userId,
|
|
2218
|
+
sk: userProjectionItem.sk
|
|
2219
|
+
}
|
|
2220
|
+
});
|
|
2221
|
+
}
|
|
2222
|
+
if (workspaceProjectionItem) {
|
|
2223
|
+
triples.push({
|
|
2224
|
+
entity: "roleAssignmentWorkspaceProjection",
|
|
2225
|
+
action: "delete",
|
|
2226
|
+
item: {
|
|
2227
|
+
tenantId: workspaceProjectionItem.tenantId,
|
|
2228
|
+
workspaceId: workspaceProjectionItem.workspaceId,
|
|
2229
|
+
sk: workspaceProjectionItem.sk
|
|
2230
|
+
}
|
|
2231
|
+
});
|
|
2232
|
+
}
|
|
2233
|
+
await executeMultiWrite({ service, triples });
|
|
1782
2234
|
}
|
|
1783
2235
|
|
|
1784
2236
|
// src/data/rest-api/routes/control/roleassignment/roleassignment-delete-route.ts
|
|
@@ -1870,15 +2322,80 @@ async function updateRoleAssignmentOperation(params) {
|
|
|
1870
2322
|
const lastUpdated = context.date ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1871
2323
|
const vid = `${Date.now()}`;
|
|
1872
2324
|
const resource = { resourceType: "RoleAssignment", id, ...parsedResource };
|
|
2325
|
+
const resourceRecord = resource;
|
|
2326
|
+
const denormalizedTenantName = extractDenormalizedReferenceDisplay(
|
|
2327
|
+
resourceRecord,
|
|
2328
|
+
"tenant"
|
|
2329
|
+
);
|
|
2330
|
+
const denormalizedUserName = extractDenormalizedReferenceDisplay(
|
|
2331
|
+
resourceRecord,
|
|
2332
|
+
"user"
|
|
2333
|
+
);
|
|
2334
|
+
const denormalizedRoleName = extractDenormalizedReferenceDisplay(
|
|
2335
|
+
resourceRecord,
|
|
2336
|
+
"role"
|
|
2337
|
+
);
|
|
1873
2338
|
const summary = JSON.stringify(extractSummary3(resource));
|
|
1874
|
-
|
|
2339
|
+
const userIdFromResource = extractReferenceSlug2(resourceRecord, "user");
|
|
2340
|
+
const roleIdFromResource = extractReferenceSlug2(resourceRecord, "role");
|
|
2341
|
+
const workspaceIdFromResource = extractReferenceSlug2(
|
|
2342
|
+
resourceRecord,
|
|
2343
|
+
"workspace"
|
|
2344
|
+
);
|
|
2345
|
+
const userProjectionItem = userIdFromResource !== void 0 && roleIdFromResource !== void 0 ? buildRoleAssignmentUserProjectionItem({
|
|
2346
|
+
tenantId: context.tenantId,
|
|
2347
|
+
userId: userIdFromResource,
|
|
2348
|
+
workspaceId: workspaceIdFromResource,
|
|
2349
|
+
roleId: roleIdFromResource,
|
|
2350
|
+
roleAssignmentId: id,
|
|
2351
|
+
summary,
|
|
2352
|
+
vid,
|
|
2353
|
+
lastUpdated,
|
|
2354
|
+
denormalizedTenantName,
|
|
2355
|
+
denormalizedUserName,
|
|
2356
|
+
denormalizedRoleName
|
|
2357
|
+
}) : void 0;
|
|
2358
|
+
const workspaceProjectionItem = userIdFromResource !== void 0 && roleIdFromResource !== void 0 && workspaceIdFromResource !== void 0 ? buildRoleAssignmentWorkspaceProjectionItem({
|
|
2359
|
+
tenantId: context.tenantId,
|
|
2360
|
+
workspaceId: workspaceIdFromResource,
|
|
2361
|
+
userId: userIdFromResource,
|
|
2362
|
+
roleId: roleIdFromResource,
|
|
2363
|
+
roleAssignmentId: id,
|
|
2364
|
+
summary,
|
|
2365
|
+
vid,
|
|
2366
|
+
lastUpdated,
|
|
2367
|
+
denormalizedUserName,
|
|
2368
|
+
denormalizedRoleName
|
|
2369
|
+
}) : void 0;
|
|
2370
|
+
const canonicalItem = {
|
|
1875
2371
|
tenantId: context.tenantId,
|
|
1876
2372
|
id,
|
|
1877
2373
|
resource: JSON.stringify(resource),
|
|
1878
2374
|
summary,
|
|
1879
2375
|
vid,
|
|
1880
|
-
lastUpdated
|
|
1881
|
-
|
|
2376
|
+
lastUpdated,
|
|
2377
|
+
denormalizedTenantName,
|
|
2378
|
+
denormalizedUserName,
|
|
2379
|
+
denormalizedRoleName
|
|
2380
|
+
};
|
|
2381
|
+
const triples = [
|
|
2382
|
+
{ entity: "roleAssignment", action: "put", item: canonicalItem }
|
|
2383
|
+
];
|
|
2384
|
+
if (userProjectionItem) {
|
|
2385
|
+
triples.push({
|
|
2386
|
+
entity: "roleAssignmentUserProjection",
|
|
2387
|
+
action: "put",
|
|
2388
|
+
item: userProjectionItem
|
|
2389
|
+
});
|
|
2390
|
+
}
|
|
2391
|
+
if (workspaceProjectionItem) {
|
|
2392
|
+
triples.push({
|
|
2393
|
+
entity: "roleAssignmentWorkspaceProjection",
|
|
2394
|
+
action: "put",
|
|
2395
|
+
item: workspaceProjectionItem
|
|
2396
|
+
});
|
|
2397
|
+
}
|
|
2398
|
+
await executeMultiWrite({ service, triples });
|
|
1882
2399
|
return {
|
|
1883
2400
|
id,
|
|
1884
2401
|
resource,
|
|
@@ -2233,67 +2750,830 @@ async function getUserByIdRoute(req, res) {
|
|
|
2233
2750
|
}
|
|
2234
2751
|
}
|
|
2235
2752
|
|
|
2236
|
-
// src/data/
|
|
2237
|
-
async function
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2753
|
+
// src/data/operations/control/configuration/configuration-list-by-user-operation.ts
|
|
2754
|
+
async function configurationListByUserOperation(params) {
|
|
2755
|
+
const { userId, cursor = null, limit, order, tableName } = params;
|
|
2756
|
+
const service = getDynamoControlService(tableName);
|
|
2757
|
+
const goOptions = {
|
|
2758
|
+
cursor
|
|
2759
|
+
};
|
|
2760
|
+
if (limit !== void 0) {
|
|
2761
|
+
goOptions.limit = limit;
|
|
2762
|
+
}
|
|
2763
|
+
if (order !== void 0) {
|
|
2764
|
+
goOptions.order = order;
|
|
2765
|
+
}
|
|
2766
|
+
const result = await service.entities.configurationUserProjection.query.record({ userId }).begins({ sk: "CONFIGURATION#" }).go(goOptions);
|
|
2767
|
+
const items = (result.data ?? []).map((row) => ({
|
|
2768
|
+
userId: row.userId,
|
|
2769
|
+
sk: row.sk,
|
|
2770
|
+
tenantId: row.tenantId,
|
|
2771
|
+
configurationId: row.configurationId,
|
|
2772
|
+
scope: "user",
|
|
2773
|
+
displayName: row.displayName,
|
|
2774
|
+
summary: row.summary,
|
|
2775
|
+
vid: row.vid,
|
|
2776
|
+
lastUpdated: row.lastUpdated
|
|
2777
|
+
}));
|
|
2778
|
+
return { items, cursor: result.cursor ?? null };
|
|
2245
2779
|
}
|
|
2246
2780
|
|
|
2247
|
-
// src/data/
|
|
2248
|
-
async function
|
|
2249
|
-
const
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
}
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2781
|
+
// src/data/operations/control/configuration/configuration-list-by-workspace-operation.ts
|
|
2782
|
+
async function configurationListByWorkspaceOperation(params) {
|
|
2783
|
+
const {
|
|
2784
|
+
tenantId,
|
|
2785
|
+
workspaceId,
|
|
2786
|
+
cursor = null,
|
|
2787
|
+
limit,
|
|
2788
|
+
order,
|
|
2789
|
+
tableName
|
|
2790
|
+
} = params;
|
|
2791
|
+
const service = getDynamoControlService(tableName);
|
|
2792
|
+
const goOptions = {
|
|
2793
|
+
cursor
|
|
2794
|
+
};
|
|
2795
|
+
if (limit !== void 0) {
|
|
2796
|
+
goOptions.limit = limit;
|
|
2797
|
+
}
|
|
2798
|
+
if (order !== void 0) {
|
|
2799
|
+
goOptions.order = order;
|
|
2800
|
+
}
|
|
2801
|
+
const result = await service.entities.configurationWorkspaceProjection.query.record({ tenantId, workspaceId }).begins({ sk: "CONFIGURATION#" }).go(goOptions);
|
|
2802
|
+
const items = (result.data ?? []).map((row) => ({
|
|
2803
|
+
tenantId: row.tenantId,
|
|
2804
|
+
workspaceId: row.workspaceId,
|
|
2805
|
+
sk: row.sk,
|
|
2806
|
+
configurationId: row.configurationId,
|
|
2807
|
+
scope: "workspace",
|
|
2808
|
+
displayName: row.displayName,
|
|
2809
|
+
summary: row.summary,
|
|
2810
|
+
vid: row.vid,
|
|
2811
|
+
lastUpdated: row.lastUpdated
|
|
2812
|
+
}));
|
|
2813
|
+
return { items, cursor: result.cursor ?? null };
|
|
2814
|
+
}
|
|
2815
|
+
|
|
2816
|
+
// src/data/rest-api/routes/control/cross-cutting-route-helpers.ts
|
|
2817
|
+
function sendInvalidQuery400(res, diagnostics) {
|
|
2818
|
+
return res.status(400).json({
|
|
2819
|
+
resourceType: "OperationOutcome",
|
|
2820
|
+
issue: [{ severity: "error", code: "invalid", diagnostics }]
|
|
2821
|
+
});
|
|
2822
|
+
}
|
|
2823
|
+
function sendForbidden403(res, diagnostics) {
|
|
2824
|
+
return res.status(403).json({
|
|
2825
|
+
resourceType: "OperationOutcome",
|
|
2826
|
+
issue: [{ severity: "error", code: "forbidden", diagnostics }]
|
|
2827
|
+
});
|
|
2828
|
+
}
|
|
2829
|
+
var COMMON_KEYS = ["cursor", "limit", "order"];
|
|
2830
|
+
function parseCommonListQuery(req, res, options = {}) {
|
|
2831
|
+
const q = req.query ?? {};
|
|
2832
|
+
const allowedKeys = /* @__PURE__ */ new Set([
|
|
2833
|
+
...COMMON_KEYS,
|
|
2834
|
+
...options.extraKeys ?? []
|
|
2835
|
+
]);
|
|
2836
|
+
const extra = Object.keys(q).filter((k) => !allowedKeys.has(k));
|
|
2837
|
+
if (extra.length > 0) {
|
|
2838
|
+
return {
|
|
2839
|
+
ok: false,
|
|
2840
|
+
response: sendInvalidQuery400(
|
|
2841
|
+
res,
|
|
2842
|
+
`Unsupported query parameter${extra.length === 1 ? "" : "s"}: ${extra.join(", ")}.`
|
|
2843
|
+
)
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2846
|
+
const rawCursor = q.cursor;
|
|
2847
|
+
let cursor = null;
|
|
2848
|
+
if (rawCursor !== void 0) {
|
|
2849
|
+
if (typeof rawCursor !== "string") {
|
|
2850
|
+
return {
|
|
2851
|
+
ok: false,
|
|
2852
|
+
response: sendInvalidQuery400(
|
|
2853
|
+
res,
|
|
2854
|
+
"Query parameter `cursor` must be a string."
|
|
2855
|
+
)
|
|
2856
|
+
};
|
|
2276
2857
|
}
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2858
|
+
cursor = rawCursor;
|
|
2859
|
+
}
|
|
2860
|
+
const rawLimit = q.limit;
|
|
2861
|
+
let limit;
|
|
2862
|
+
if (rawLimit !== void 0) {
|
|
2863
|
+
if (typeof rawLimit !== "string" || rawLimit.length === 0) {
|
|
2864
|
+
return {
|
|
2865
|
+
ok: false,
|
|
2866
|
+
response: sendInvalidQuery400(
|
|
2867
|
+
res,
|
|
2868
|
+
"Query parameter `limit` must be a positive integer."
|
|
2869
|
+
)
|
|
2870
|
+
};
|
|
2871
|
+
}
|
|
2872
|
+
const parsed = Number(rawLimit);
|
|
2873
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
2874
|
+
return {
|
|
2875
|
+
ok: false,
|
|
2876
|
+
response: sendInvalidQuery400(
|
|
2877
|
+
res,
|
|
2878
|
+
"Query parameter `limit` must be a positive integer."
|
|
2879
|
+
)
|
|
2880
|
+
};
|
|
2881
|
+
}
|
|
2882
|
+
limit = parsed;
|
|
2883
|
+
}
|
|
2884
|
+
const rawOrder = q.order;
|
|
2885
|
+
let order;
|
|
2886
|
+
if (rawOrder !== void 0) {
|
|
2887
|
+
if (rawOrder !== "asc" && rawOrder !== "desc") {
|
|
2888
|
+
return {
|
|
2889
|
+
ok: false,
|
|
2890
|
+
response: sendInvalidQuery400(
|
|
2891
|
+
res,
|
|
2892
|
+
'Query parameter `order` must be one of "asc", "desc".'
|
|
2893
|
+
)
|
|
2894
|
+
};
|
|
2895
|
+
}
|
|
2896
|
+
order = rawOrder;
|
|
2897
|
+
}
|
|
2898
|
+
const value = order === void 0 ? limit === void 0 ? { cursor } : { cursor, limit } : limit === void 0 ? { cursor, order } : { cursor, limit, order };
|
|
2899
|
+
return { ok: true, value };
|
|
2900
|
+
}
|
|
2901
|
+
function buildPaginationLinks(opts) {
|
|
2902
|
+
const links = [
|
|
2903
|
+
{ relation: "self", url: composeUrl(opts.basePath, opts.query) }
|
|
2904
|
+
];
|
|
2905
|
+
if (opts.nextCursor !== null && opts.nextCursor.length > 0) {
|
|
2906
|
+
const nextQuery = { ...opts.query };
|
|
2907
|
+
nextQuery.cursor = opts.nextCursor;
|
|
2908
|
+
links.push({
|
|
2909
|
+
relation: "next",
|
|
2910
|
+
url: composeUrl(opts.basePath, nextQuery)
|
|
2283
2911
|
});
|
|
2284
2912
|
}
|
|
2913
|
+
return links;
|
|
2914
|
+
}
|
|
2915
|
+
function composeUrl(basePath, query) {
|
|
2916
|
+
const usp = new URLSearchParams();
|
|
2917
|
+
for (const [key, value] of Object.entries(query)) {
|
|
2918
|
+
if (value === void 0 || value === null) {
|
|
2919
|
+
continue;
|
|
2920
|
+
}
|
|
2921
|
+
if (Array.isArray(value)) {
|
|
2922
|
+
for (const v of value) {
|
|
2923
|
+
if (v !== void 0 && v !== null) {
|
|
2924
|
+
usp.append(key, String(v));
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
} else {
|
|
2928
|
+
usp.append(key, String(value));
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
const qs = usp.toString();
|
|
2932
|
+
return qs.length === 0 ? basePath : `${basePath}?${qs}`;
|
|
2285
2933
|
}
|
|
2286
2934
|
|
|
2287
|
-
// src/data/rest-api/routes/control/
|
|
2288
|
-
var
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2935
|
+
// src/data/rest-api/routes/control/projection-bundle-helpers.ts
|
|
2936
|
+
var EXT_BASE = "https://openhi.org/fhir/StructureDefinition";
|
|
2937
|
+
function parseProjectionSummary(summary) {
|
|
2938
|
+
if (typeof summary !== "string" || summary.length === 0) {
|
|
2939
|
+
return {};
|
|
2940
|
+
}
|
|
2941
|
+
try {
|
|
2942
|
+
const parsed = JSON.parse(summary);
|
|
2943
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
2944
|
+
return parsed;
|
|
2945
|
+
}
|
|
2946
|
+
} catch {
|
|
2947
|
+
}
|
|
2948
|
+
return {};
|
|
2949
|
+
}
|
|
2950
|
+
function maybeStringExt(url, value) {
|
|
2951
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
2952
|
+
return void 0;
|
|
2953
|
+
}
|
|
2954
|
+
return { url, valueString: value };
|
|
2955
|
+
}
|
|
2956
|
+
function buildMembershipUserProjectionEntryResource(row) {
|
|
2957
|
+
const extensions = [
|
|
2958
|
+
{ url: `${EXT_BASE}/projection-tenant-id`, valueString: row.tenantId }
|
|
2959
|
+
];
|
|
2960
|
+
const workspaceExt = maybeStringExt(
|
|
2961
|
+
`${EXT_BASE}/projection-workspace-id`,
|
|
2962
|
+
row.workspaceId
|
|
2963
|
+
);
|
|
2964
|
+
if (workspaceExt) {
|
|
2965
|
+
extensions.push(workspaceExt);
|
|
2966
|
+
}
|
|
2967
|
+
const tenantNameExt = maybeStringExt(
|
|
2968
|
+
`${EXT_BASE}/projection-denormalized-tenant-name`,
|
|
2969
|
+
row.denormalizedTenantName
|
|
2970
|
+
);
|
|
2971
|
+
if (tenantNameExt) {
|
|
2972
|
+
extensions.push(tenantNameExt);
|
|
2973
|
+
}
|
|
2974
|
+
const userNameExt = maybeStringExt(
|
|
2975
|
+
`${EXT_BASE}/projection-denormalized-user-name`,
|
|
2976
|
+
row.denormalizedUserName
|
|
2977
|
+
);
|
|
2978
|
+
if (userNameExt) {
|
|
2979
|
+
extensions.push(userNameExt);
|
|
2980
|
+
}
|
|
2981
|
+
const workspaceNameExt = maybeStringExt(
|
|
2982
|
+
`${EXT_BASE}/projection-denormalized-workspace-name`,
|
|
2983
|
+
row.denormalizedWorkspaceName
|
|
2984
|
+
);
|
|
2985
|
+
if (workspaceNameExt) {
|
|
2986
|
+
extensions.push(workspaceNameExt);
|
|
2987
|
+
}
|
|
2988
|
+
return {
|
|
2989
|
+
resourceType: "Membership",
|
|
2990
|
+
id: row.membershipId,
|
|
2991
|
+
...parseProjectionSummary(row.summary),
|
|
2992
|
+
meta: { versionId: row.vid, lastUpdated: row.lastUpdated },
|
|
2993
|
+
extension: extensions
|
|
2994
|
+
};
|
|
2995
|
+
}
|
|
2996
|
+
function buildMembershipWorkspaceProjectionEntryResource(row) {
|
|
2997
|
+
const extensions = [
|
|
2998
|
+
{ url: `${EXT_BASE}/projection-tenant-id`, valueString: row.tenantId },
|
|
2999
|
+
{
|
|
3000
|
+
url: `${EXT_BASE}/projection-workspace-id`,
|
|
3001
|
+
valueString: row.workspaceId
|
|
3002
|
+
}
|
|
3003
|
+
];
|
|
3004
|
+
const userNameExt = maybeStringExt(
|
|
3005
|
+
`${EXT_BASE}/projection-denormalized-user-name`,
|
|
3006
|
+
row.denormalizedUserName
|
|
3007
|
+
);
|
|
3008
|
+
if (userNameExt) {
|
|
3009
|
+
extensions.push(userNameExt);
|
|
3010
|
+
}
|
|
3011
|
+
return {
|
|
3012
|
+
resourceType: "Membership",
|
|
3013
|
+
id: row.membershipId,
|
|
3014
|
+
...parseProjectionSummary(row.summary),
|
|
3015
|
+
meta: { versionId: row.vid, lastUpdated: row.lastUpdated },
|
|
3016
|
+
extension: extensions
|
|
3017
|
+
};
|
|
3018
|
+
}
|
|
3019
|
+
function buildRoleAssignmentUserProjectionEntryResource(row) {
|
|
3020
|
+
const extensions = [
|
|
3021
|
+
{ url: `${EXT_BASE}/projection-tenant-id`, valueString: row.tenantId }
|
|
3022
|
+
];
|
|
3023
|
+
const workspaceExt = maybeStringExt(
|
|
3024
|
+
`${EXT_BASE}/projection-workspace-id`,
|
|
3025
|
+
row.workspaceId
|
|
3026
|
+
);
|
|
3027
|
+
if (workspaceExt) {
|
|
3028
|
+
extensions.push(workspaceExt);
|
|
3029
|
+
}
|
|
3030
|
+
extensions.push({
|
|
3031
|
+
url: `${EXT_BASE}/projection-role-id`,
|
|
3032
|
+
valueString: row.roleId
|
|
3033
|
+
});
|
|
3034
|
+
const tenantNameExt = maybeStringExt(
|
|
3035
|
+
`${EXT_BASE}/projection-denormalized-tenant-name`,
|
|
3036
|
+
row.denormalizedTenantName
|
|
3037
|
+
);
|
|
3038
|
+
if (tenantNameExt) {
|
|
3039
|
+
extensions.push(tenantNameExt);
|
|
3040
|
+
}
|
|
3041
|
+
const userNameExt = maybeStringExt(
|
|
3042
|
+
`${EXT_BASE}/projection-denormalized-user-name`,
|
|
3043
|
+
row.denormalizedUserName
|
|
3044
|
+
);
|
|
3045
|
+
if (userNameExt) {
|
|
3046
|
+
extensions.push(userNameExt);
|
|
3047
|
+
}
|
|
3048
|
+
const roleNameExt = maybeStringExt(
|
|
3049
|
+
`${EXT_BASE}/projection-denormalized-role-name`,
|
|
3050
|
+
row.denormalizedRoleName
|
|
3051
|
+
);
|
|
3052
|
+
if (roleNameExt) {
|
|
3053
|
+
extensions.push(roleNameExt);
|
|
3054
|
+
}
|
|
3055
|
+
return {
|
|
3056
|
+
resourceType: "RoleAssignment",
|
|
3057
|
+
id: row.roleAssignmentId,
|
|
3058
|
+
...parseProjectionSummary(row.summary),
|
|
3059
|
+
meta: { versionId: row.vid, lastUpdated: row.lastUpdated },
|
|
3060
|
+
extension: extensions
|
|
3061
|
+
};
|
|
3062
|
+
}
|
|
3063
|
+
function buildRoleAssignmentWorkspaceProjectionEntryResource(row) {
|
|
3064
|
+
const extensions = [
|
|
3065
|
+
{ url: `${EXT_BASE}/projection-tenant-id`, valueString: row.tenantId },
|
|
3066
|
+
{
|
|
3067
|
+
url: `${EXT_BASE}/projection-workspace-id`,
|
|
3068
|
+
valueString: row.workspaceId
|
|
3069
|
+
},
|
|
3070
|
+
{ url: `${EXT_BASE}/projection-role-id`, valueString: row.roleId }
|
|
3071
|
+
];
|
|
3072
|
+
const userNameExt = maybeStringExt(
|
|
3073
|
+
`${EXT_BASE}/projection-denormalized-user-name`,
|
|
3074
|
+
row.denormalizedUserName
|
|
3075
|
+
);
|
|
3076
|
+
if (userNameExt) {
|
|
3077
|
+
extensions.push(userNameExt);
|
|
3078
|
+
}
|
|
3079
|
+
const roleNameExt = maybeStringExt(
|
|
3080
|
+
`${EXT_BASE}/projection-denormalized-role-name`,
|
|
3081
|
+
row.denormalizedRoleName
|
|
3082
|
+
);
|
|
3083
|
+
if (roleNameExt) {
|
|
3084
|
+
extensions.push(roleNameExt);
|
|
3085
|
+
}
|
|
3086
|
+
return {
|
|
3087
|
+
resourceType: "RoleAssignment",
|
|
3088
|
+
id: row.roleAssignmentId,
|
|
3089
|
+
...parseProjectionSummary(row.summary),
|
|
3090
|
+
meta: { versionId: row.vid, lastUpdated: row.lastUpdated },
|
|
3091
|
+
extension: extensions
|
|
3092
|
+
};
|
|
3093
|
+
}
|
|
3094
|
+
function buildConfigurationUserProjectionEntryResource(row) {
|
|
3095
|
+
const extensions = [
|
|
3096
|
+
{ url: `${EXT_BASE}/projection-tenant-id`, valueString: row.tenantId },
|
|
3097
|
+
{ url: `${EXT_BASE}/projection-scope`, valueString: row.scope }
|
|
3098
|
+
];
|
|
3099
|
+
const displayNameExt = maybeStringExt(
|
|
3100
|
+
`${EXT_BASE}/projection-display-name`,
|
|
3101
|
+
row.displayName
|
|
3102
|
+
);
|
|
3103
|
+
if (displayNameExt) {
|
|
3104
|
+
extensions.push(displayNameExt);
|
|
3105
|
+
}
|
|
3106
|
+
return {
|
|
3107
|
+
resourceType: "Configuration",
|
|
3108
|
+
id: row.configurationId,
|
|
3109
|
+
...parseProjectionSummary(row.summary),
|
|
3110
|
+
meta: { versionId: row.vid, lastUpdated: row.lastUpdated },
|
|
3111
|
+
extension: extensions
|
|
3112
|
+
};
|
|
3113
|
+
}
|
|
3114
|
+
function buildConfigurationWorkspaceProjectionEntryResource(row) {
|
|
3115
|
+
const extensions = [
|
|
3116
|
+
{ url: `${EXT_BASE}/projection-tenant-id`, valueString: row.tenantId },
|
|
3117
|
+
{
|
|
3118
|
+
url: `${EXT_BASE}/projection-workspace-id`,
|
|
3119
|
+
valueString: row.workspaceId
|
|
3120
|
+
},
|
|
3121
|
+
{ url: `${EXT_BASE}/projection-scope`, valueString: row.scope }
|
|
3122
|
+
];
|
|
3123
|
+
const displayNameExt = maybeStringExt(
|
|
3124
|
+
`${EXT_BASE}/projection-display-name`,
|
|
3125
|
+
row.displayName
|
|
3126
|
+
);
|
|
3127
|
+
if (displayNameExt) {
|
|
3128
|
+
extensions.push(displayNameExt);
|
|
3129
|
+
}
|
|
3130
|
+
return {
|
|
3131
|
+
resourceType: "Configuration",
|
|
3132
|
+
id: row.configurationId,
|
|
3133
|
+
...parseProjectionSummary(row.summary),
|
|
3134
|
+
meta: { versionId: row.vid, lastUpdated: row.lastUpdated },
|
|
3135
|
+
extension: extensions
|
|
3136
|
+
};
|
|
3137
|
+
}
|
|
3138
|
+
|
|
3139
|
+
// src/data/rest-api/routes/control/user/user-list-configurations-route.ts
|
|
3140
|
+
async function listUserConfigurationsRoute(req, res) {
|
|
3141
|
+
const ctx = req.openhiContext;
|
|
3142
|
+
if (!ctx) {
|
|
3143
|
+
return sendForbidden403(
|
|
3144
|
+
res,
|
|
3145
|
+
"Missing or invalid OpenHI JWT claims (tenant, workspace, or audit context)."
|
|
3146
|
+
);
|
|
3147
|
+
}
|
|
3148
|
+
const userId = String(req.params.id);
|
|
3149
|
+
if (userId !== ctx.actorId) {
|
|
3150
|
+
return sendForbidden403(
|
|
3151
|
+
res,
|
|
3152
|
+
"Caller is not authorized to read Configurations for another User."
|
|
3153
|
+
);
|
|
3154
|
+
}
|
|
3155
|
+
const parsed = parseCommonListQuery(req, res);
|
|
3156
|
+
if (!parsed.ok) {
|
|
3157
|
+
return parsed.response;
|
|
3158
|
+
}
|
|
3159
|
+
try {
|
|
3160
|
+
const result = await configurationListByUserOperation({
|
|
3161
|
+
userId,
|
|
3162
|
+
cursor: parsed.value.cursor,
|
|
3163
|
+
...parsed.value.limit !== void 0 && { limit: parsed.value.limit },
|
|
3164
|
+
...parsed.value.order !== void 0 && { order: parsed.value.order }
|
|
3165
|
+
});
|
|
3166
|
+
const basePath = `${BASE_PATH.USER}/${userId}/Configuration`;
|
|
3167
|
+
const entries = result.items.map((row) => ({
|
|
3168
|
+
fullUrl: `${BASE_PATH.CONFIGURATION}/${row.configurationId}`,
|
|
3169
|
+
resource: buildConfigurationUserProjectionEntryResource(row)
|
|
3170
|
+
}));
|
|
3171
|
+
return res.json({
|
|
3172
|
+
resourceType: "Bundle",
|
|
3173
|
+
type: "searchset",
|
|
3174
|
+
total: entries.length,
|
|
3175
|
+
link: buildPaginationLinks({
|
|
3176
|
+
basePath,
|
|
3177
|
+
query: req.query,
|
|
3178
|
+
nextCursor: result.cursor
|
|
3179
|
+
}),
|
|
3180
|
+
entry: entries
|
|
3181
|
+
});
|
|
3182
|
+
} catch (err) {
|
|
3183
|
+
return sendOperationOutcome500(
|
|
3184
|
+
res,
|
|
3185
|
+
err,
|
|
3186
|
+
"GET /User/:id/Configuration error:"
|
|
3187
|
+
);
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
// src/data/operations/control/membership/membership-list-by-workspace-operation.ts
|
|
3192
|
+
async function membershipListByWorkspaceOperation(params) {
|
|
3193
|
+
const {
|
|
3194
|
+
tenantId,
|
|
3195
|
+
workspaceId,
|
|
3196
|
+
cursor = null,
|
|
3197
|
+
limit,
|
|
3198
|
+
order,
|
|
3199
|
+
tableName
|
|
3200
|
+
} = params;
|
|
3201
|
+
const service = getDynamoControlService(tableName);
|
|
3202
|
+
const goOptions = {
|
|
3203
|
+
cursor
|
|
3204
|
+
};
|
|
3205
|
+
if (limit !== void 0) {
|
|
3206
|
+
goOptions.limit = limit;
|
|
3207
|
+
}
|
|
3208
|
+
if (order !== void 0) {
|
|
3209
|
+
goOptions.order = order;
|
|
3210
|
+
}
|
|
3211
|
+
const result = await service.entities.membershipWorkspaceProjection.query.record({ tenantId, workspaceId }).begins({ sk: "MEMBERSHIP#" }).go(goOptions);
|
|
3212
|
+
const items = (result.data ?? []).map((row) => ({
|
|
3213
|
+
tenantId: row.tenantId,
|
|
3214
|
+
workspaceId: row.workspaceId,
|
|
3215
|
+
sk: row.sk,
|
|
3216
|
+
userId: row.userId,
|
|
3217
|
+
membershipId: row.membershipId,
|
|
3218
|
+
summary: row.summary,
|
|
3219
|
+
vid: row.vid,
|
|
3220
|
+
lastUpdated: row.lastUpdated,
|
|
3221
|
+
denormalizedUserName: row.denormalizedUserName
|
|
3222
|
+
}));
|
|
3223
|
+
return { items, cursor: result.cursor ?? null };
|
|
3224
|
+
}
|
|
3225
|
+
|
|
3226
|
+
// src/data/rest-api/routes/control/user/user-list-memberships-route.ts
|
|
3227
|
+
var ALLOWED_MODES = [
|
|
3228
|
+
"all",
|
|
3229
|
+
"tenant",
|
|
3230
|
+
"workspace",
|
|
3231
|
+
"workspaceInTenant"
|
|
3232
|
+
];
|
|
3233
|
+
async function listUserMembershipsRoute(req, res) {
|
|
3234
|
+
const ctx = req.openhiContext;
|
|
3235
|
+
if (!ctx) {
|
|
3236
|
+
return sendForbidden403(
|
|
3237
|
+
res,
|
|
3238
|
+
"Missing or invalid OpenHI JWT claims (tenant, workspace, or audit context)."
|
|
3239
|
+
);
|
|
3240
|
+
}
|
|
3241
|
+
const userId = String(req.params.id);
|
|
3242
|
+
if (userId !== ctx.actorId) {
|
|
3243
|
+
return sendForbidden403(
|
|
3244
|
+
res,
|
|
3245
|
+
"Caller is not authorized to read Memberships for another User."
|
|
3246
|
+
);
|
|
3247
|
+
}
|
|
3248
|
+
const parsed = parseCommonListQuery(req, res, {
|
|
3249
|
+
extraKeys: ["mode", "tenantId"]
|
|
3250
|
+
});
|
|
3251
|
+
if (!parsed.ok) {
|
|
3252
|
+
return parsed.response;
|
|
3253
|
+
}
|
|
3254
|
+
const rawMode = req.query.mode;
|
|
3255
|
+
let mode = "all";
|
|
3256
|
+
if (rawMode !== void 0) {
|
|
3257
|
+
if (typeof rawMode !== "string" || !ALLOWED_MODES.includes(rawMode)) {
|
|
3258
|
+
return sendInvalidQuery400(
|
|
3259
|
+
res,
|
|
3260
|
+
`Query parameter \`mode\` must be one of: ${ALLOWED_MODES.join(", ")}.`
|
|
3261
|
+
);
|
|
3262
|
+
}
|
|
3263
|
+
mode = rawMode;
|
|
3264
|
+
}
|
|
3265
|
+
const rawTenantId = req.query.tenantId;
|
|
3266
|
+
let tenantId;
|
|
3267
|
+
if (rawTenantId !== void 0) {
|
|
3268
|
+
if (typeof rawTenantId !== "string" || rawTenantId.length === 0) {
|
|
3269
|
+
return sendInvalidQuery400(
|
|
3270
|
+
res,
|
|
3271
|
+
"Query parameter `tenantId` must be a non-empty string."
|
|
3272
|
+
);
|
|
3273
|
+
}
|
|
3274
|
+
tenantId = rawTenantId;
|
|
3275
|
+
}
|
|
3276
|
+
if (mode === "workspaceInTenant" && !tenantId) {
|
|
3277
|
+
return sendInvalidQuery400(
|
|
3278
|
+
res,
|
|
3279
|
+
'Query parameter `tenantId` is required when `mode === "workspaceInTenant"`.'
|
|
3280
|
+
);
|
|
3281
|
+
}
|
|
3282
|
+
try {
|
|
3283
|
+
const result = await membershipListByUserOperation({
|
|
3284
|
+
userId,
|
|
3285
|
+
mode,
|
|
3286
|
+
tenantId,
|
|
3287
|
+
cursor: parsed.value.cursor,
|
|
3288
|
+
...parsed.value.limit !== void 0 && { limit: parsed.value.limit },
|
|
3289
|
+
...parsed.value.order !== void 0 && { order: parsed.value.order }
|
|
3290
|
+
});
|
|
3291
|
+
const basePath = `${BASE_PATH.USER}/${userId}/Membership`;
|
|
3292
|
+
const entries = result.items.map((row) => ({
|
|
3293
|
+
fullUrl: `${BASE_PATH.MEMBERSHIP}/${row.membershipId}`,
|
|
3294
|
+
resource: buildMembershipUserProjectionEntryResource(row)
|
|
3295
|
+
}));
|
|
3296
|
+
return res.json({
|
|
3297
|
+
resourceType: "Bundle",
|
|
3298
|
+
type: "searchset",
|
|
3299
|
+
total: entries.length,
|
|
3300
|
+
link: buildPaginationLinks({
|
|
3301
|
+
basePath,
|
|
3302
|
+
query: req.query,
|
|
3303
|
+
nextCursor: result.cursor
|
|
3304
|
+
}),
|
|
3305
|
+
entry: entries
|
|
3306
|
+
});
|
|
3307
|
+
} catch (err) {
|
|
3308
|
+
return sendOperationOutcome500(res, err, "GET /User/:id/Membership error:");
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
// src/data/operations/control/roleassignment/roleassignment-list-by-user-operation.ts
|
|
3313
|
+
function buildSkPrefix(mode) {
|
|
3314
|
+
switch (mode) {
|
|
3315
|
+
case "tenant":
|
|
3316
|
+
return "ROLEASSIGNMENT#TENANT#";
|
|
3317
|
+
case "workspace":
|
|
3318
|
+
case "workspaceInTenant":
|
|
3319
|
+
return "ROLEASSIGNMENT#WORKSPACE#";
|
|
3320
|
+
case "all":
|
|
3321
|
+
default:
|
|
3322
|
+
return "ROLEASSIGNMENT#";
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
async function roleAssignmentListByUserOperation(params) {
|
|
3326
|
+
const {
|
|
3327
|
+
userId,
|
|
3328
|
+
mode = "all",
|
|
3329
|
+
tenantId,
|
|
3330
|
+
workspaceId,
|
|
3331
|
+
cursor = null,
|
|
3332
|
+
limit,
|
|
3333
|
+
order,
|
|
3334
|
+
tableName
|
|
3335
|
+
} = params;
|
|
3336
|
+
if (mode === "workspaceInTenant" && !tenantId) {
|
|
3337
|
+
throw new Error(
|
|
3338
|
+
'roleAssignmentListByUserOperation: tenantId is required when mode === "workspaceInTenant"'
|
|
3339
|
+
);
|
|
3340
|
+
}
|
|
3341
|
+
const service = getDynamoControlService(tableName);
|
|
3342
|
+
const skPrefix = buildSkPrefix(mode);
|
|
3343
|
+
const goOptions = {
|
|
3344
|
+
cursor
|
|
3345
|
+
};
|
|
3346
|
+
if (limit !== void 0) {
|
|
3347
|
+
goOptions.limit = limit;
|
|
3348
|
+
}
|
|
3349
|
+
if (order !== void 0) {
|
|
3350
|
+
goOptions.order = order;
|
|
3351
|
+
}
|
|
3352
|
+
const baseQuery = service.entities.roleAssignmentUserProjection.query.record({ userId }).begins({ sk: skPrefix });
|
|
3353
|
+
const filteredQuery = mode === "workspaceInTenant" ? baseQuery.where((attr, op) => {
|
|
3354
|
+
const tenantClause = op.eq(attr.tenantId, tenantId);
|
|
3355
|
+
if (workspaceId === void 0 || workspaceId.length === 0) {
|
|
3356
|
+
return tenantClause;
|
|
3357
|
+
}
|
|
3358
|
+
return `${tenantClause} AND ${op.eq(attr.workspaceId, workspaceId)}`;
|
|
3359
|
+
}) : baseQuery;
|
|
3360
|
+
const result = await filteredQuery.go(goOptions);
|
|
3361
|
+
const items = (result.data ?? []).map((row) => ({
|
|
3362
|
+
userId: row.userId,
|
|
3363
|
+
sk: row.sk,
|
|
3364
|
+
tenantId: row.tenantId,
|
|
3365
|
+
workspaceId: row.workspaceId,
|
|
3366
|
+
roleId: row.roleId,
|
|
3367
|
+
roleAssignmentId: row.roleAssignmentId,
|
|
3368
|
+
summary: row.summary,
|
|
3369
|
+
vid: row.vid,
|
|
3370
|
+
lastUpdated: row.lastUpdated,
|
|
3371
|
+
denormalizedTenantName: row.denormalizedTenantName,
|
|
3372
|
+
denormalizedUserName: row.denormalizedUserName,
|
|
3373
|
+
denormalizedRoleName: row.denormalizedRoleName
|
|
3374
|
+
}));
|
|
3375
|
+
return { items, cursor: result.cursor ?? null };
|
|
3376
|
+
}
|
|
3377
|
+
|
|
3378
|
+
// src/data/operations/control/roleassignment/roleassignment-list-by-workspace-operation.ts
|
|
3379
|
+
function buildSkPrefix2(roleId) {
|
|
3380
|
+
if (roleId === void 0 || roleId.length === 0) {
|
|
3381
|
+
return "ROLEASSIGNMENT#";
|
|
3382
|
+
}
|
|
3383
|
+
return `ROLEASSIGNMENT#${roleId}#`;
|
|
3384
|
+
}
|
|
3385
|
+
async function roleAssignmentListByWorkspaceOperation(params) {
|
|
3386
|
+
const {
|
|
3387
|
+
tenantId,
|
|
3388
|
+
workspaceId,
|
|
3389
|
+
roleId,
|
|
3390
|
+
cursor = null,
|
|
3391
|
+
limit,
|
|
3392
|
+
order,
|
|
3393
|
+
tableName
|
|
3394
|
+
} = params;
|
|
3395
|
+
const service = getDynamoControlService(tableName);
|
|
3396
|
+
const skPrefix = buildSkPrefix2(roleId);
|
|
3397
|
+
const goOptions = {
|
|
3398
|
+
cursor
|
|
3399
|
+
};
|
|
3400
|
+
if (limit !== void 0) {
|
|
3401
|
+
goOptions.limit = limit;
|
|
3402
|
+
}
|
|
3403
|
+
if (order !== void 0) {
|
|
3404
|
+
goOptions.order = order;
|
|
3405
|
+
}
|
|
3406
|
+
const result = await service.entities.roleAssignmentWorkspaceProjection.query.record({ tenantId, workspaceId }).begins({ sk: skPrefix }).go(goOptions);
|
|
3407
|
+
const items = (result.data ?? []).map((row) => ({
|
|
3408
|
+
tenantId: row.tenantId,
|
|
3409
|
+
workspaceId: row.workspaceId,
|
|
3410
|
+
sk: row.sk,
|
|
3411
|
+
userId: row.userId,
|
|
3412
|
+
roleId: row.roleId,
|
|
3413
|
+
roleAssignmentId: row.roleAssignmentId,
|
|
3414
|
+
summary: row.summary,
|
|
3415
|
+
vid: row.vid,
|
|
3416
|
+
lastUpdated: row.lastUpdated,
|
|
3417
|
+
denormalizedUserName: row.denormalizedUserName,
|
|
3418
|
+
denormalizedRoleName: row.denormalizedRoleName
|
|
3419
|
+
}));
|
|
3420
|
+
return { items, cursor: result.cursor ?? null };
|
|
3421
|
+
}
|
|
3422
|
+
|
|
3423
|
+
// src/data/rest-api/routes/control/user/user-list-role-assignments-route.ts
|
|
3424
|
+
var ALLOWED_MODES2 = [
|
|
3425
|
+
"all",
|
|
3426
|
+
"tenant",
|
|
3427
|
+
"workspace",
|
|
3428
|
+
"workspaceInTenant"
|
|
3429
|
+
];
|
|
3430
|
+
async function listUserRoleAssignmentsRoute(req, res) {
|
|
3431
|
+
const ctx = req.openhiContext;
|
|
3432
|
+
if (!ctx) {
|
|
3433
|
+
return sendForbidden403(
|
|
3434
|
+
res,
|
|
3435
|
+
"Missing or invalid OpenHI JWT claims (tenant, workspace, or audit context)."
|
|
3436
|
+
);
|
|
3437
|
+
}
|
|
3438
|
+
const userId = String(req.params.id);
|
|
3439
|
+
if (userId !== ctx.actorId) {
|
|
3440
|
+
return sendForbidden403(
|
|
3441
|
+
res,
|
|
3442
|
+
"Caller is not authorized to read RoleAssignments for another User."
|
|
3443
|
+
);
|
|
3444
|
+
}
|
|
3445
|
+
const parsed = parseCommonListQuery(req, res, {
|
|
3446
|
+
extraKeys: ["mode", "tenantId"]
|
|
3447
|
+
});
|
|
3448
|
+
if (!parsed.ok) {
|
|
3449
|
+
return parsed.response;
|
|
3450
|
+
}
|
|
3451
|
+
const rawMode = req.query.mode;
|
|
3452
|
+
let mode = "all";
|
|
3453
|
+
if (rawMode !== void 0) {
|
|
3454
|
+
if (typeof rawMode !== "string" || !ALLOWED_MODES2.includes(rawMode)) {
|
|
3455
|
+
return sendInvalidQuery400(
|
|
3456
|
+
res,
|
|
3457
|
+
`Query parameter \`mode\` must be one of: ${ALLOWED_MODES2.join(", ")}.`
|
|
3458
|
+
);
|
|
3459
|
+
}
|
|
3460
|
+
mode = rawMode;
|
|
3461
|
+
}
|
|
3462
|
+
const rawTenantId = req.query.tenantId;
|
|
3463
|
+
let tenantId;
|
|
3464
|
+
if (rawTenantId !== void 0) {
|
|
3465
|
+
if (typeof rawTenantId !== "string" || rawTenantId.length === 0) {
|
|
3466
|
+
return sendInvalidQuery400(
|
|
3467
|
+
res,
|
|
3468
|
+
"Query parameter `tenantId` must be a non-empty string."
|
|
3469
|
+
);
|
|
3470
|
+
}
|
|
3471
|
+
tenantId = rawTenantId;
|
|
3472
|
+
}
|
|
3473
|
+
if (mode === "workspaceInTenant" && !tenantId) {
|
|
3474
|
+
return sendInvalidQuery400(
|
|
3475
|
+
res,
|
|
3476
|
+
'Query parameter `tenantId` is required when `mode === "workspaceInTenant"`.'
|
|
3477
|
+
);
|
|
3478
|
+
}
|
|
3479
|
+
try {
|
|
3480
|
+
const result = await roleAssignmentListByUserOperation({
|
|
3481
|
+
userId,
|
|
3482
|
+
mode,
|
|
3483
|
+
tenantId,
|
|
3484
|
+
cursor: parsed.value.cursor,
|
|
3485
|
+
...parsed.value.limit !== void 0 && { limit: parsed.value.limit },
|
|
3486
|
+
...parsed.value.order !== void 0 && { order: parsed.value.order }
|
|
3487
|
+
});
|
|
3488
|
+
const basePath = `${BASE_PATH.USER}/${userId}/RoleAssignment`;
|
|
3489
|
+
const entries = result.items.map((row) => ({
|
|
3490
|
+
fullUrl: `${BASE_PATH.ROLEASSIGNMENT}/${row.roleAssignmentId}`,
|
|
3491
|
+
resource: buildRoleAssignmentUserProjectionEntryResource(row)
|
|
3492
|
+
}));
|
|
3493
|
+
return res.json({
|
|
3494
|
+
resourceType: "Bundle",
|
|
3495
|
+
type: "searchset",
|
|
3496
|
+
total: entries.length,
|
|
3497
|
+
link: buildPaginationLinks({
|
|
3498
|
+
basePath,
|
|
3499
|
+
query: req.query,
|
|
3500
|
+
nextCursor: result.cursor
|
|
3501
|
+
}),
|
|
3502
|
+
entry: entries
|
|
3503
|
+
});
|
|
3504
|
+
} catch (err) {
|
|
3505
|
+
return sendOperationOutcome500(
|
|
3506
|
+
res,
|
|
3507
|
+
err,
|
|
3508
|
+
"GET /User/:id/RoleAssignment error:"
|
|
3509
|
+
);
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3513
|
+
// src/data/rest-api/routes/control/user/user-list-route.ts
|
|
3514
|
+
async function listUsersRoute(req, res) {
|
|
3515
|
+
return handleListRoute({
|
|
3516
|
+
req,
|
|
3517
|
+
res,
|
|
3518
|
+
basePath: BASE_PATH.USER,
|
|
3519
|
+
listOperation: listUsersOperation,
|
|
3520
|
+
errorLogContext: "GET /User list error:"
|
|
3521
|
+
});
|
|
3522
|
+
}
|
|
3523
|
+
|
|
3524
|
+
// src/data/rest-api/routes/control/user/user-update-route.ts
|
|
3525
|
+
async function updateUserRoute(req, res) {
|
|
3526
|
+
const bodyResult = requireJsonBody(req, res);
|
|
3527
|
+
if ("errorResponse" in bodyResult) return bodyResult.errorResponse;
|
|
3528
|
+
const id = String(req.params.id);
|
|
3529
|
+
const ctx = req.openhiContext;
|
|
3530
|
+
const body = bodyResult.body;
|
|
3531
|
+
try {
|
|
3532
|
+
const result = await updateUserOperation({
|
|
3533
|
+
context: ctx,
|
|
3534
|
+
id,
|
|
3535
|
+
body: {
|
|
3536
|
+
resource: body?.resource
|
|
3537
|
+
}
|
|
3538
|
+
});
|
|
3539
|
+
return res.json({ ...result.resource, meta: result.meta });
|
|
3540
|
+
} catch (err) {
|
|
3541
|
+
const status = domainErrorToHttpStatus(err);
|
|
3542
|
+
if (status === 404) {
|
|
3543
|
+
return res.status(404).json({
|
|
3544
|
+
resourceType: "OperationOutcome",
|
|
3545
|
+
issue: [
|
|
3546
|
+
{
|
|
3547
|
+
severity: "error",
|
|
3548
|
+
code: "not-found",
|
|
3549
|
+
diagnostics: err instanceof NotFoundError ? err.message : `User ${id} not found`
|
|
3550
|
+
}
|
|
3551
|
+
]
|
|
3552
|
+
});
|
|
3553
|
+
}
|
|
3554
|
+
console.error("PUT User error:", err);
|
|
3555
|
+
return res.status(500).json({
|
|
3556
|
+
resourceType: "OperationOutcome",
|
|
3557
|
+
issue: [
|
|
3558
|
+
{ severity: "error", code: "exception", diagnostics: String(err) }
|
|
3559
|
+
]
|
|
3560
|
+
});
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
|
|
3564
|
+
// src/data/rest-api/routes/control/user/user.ts
|
|
3565
|
+
var router6 = express6.Router();
|
|
3566
|
+
router6.get("/", listUsersRoute);
|
|
3567
|
+
router6.get("/:id", getUserByIdRoute);
|
|
3568
|
+
router6.post("/", createUserRoute);
|
|
3569
|
+
router6.put("/:id", updateUserRoute);
|
|
3570
|
+
router6.delete("/:id", deleteUserRoute);
|
|
3571
|
+
router6.get("/:id/Membership", listUserMembershipsRoute);
|
|
3572
|
+
router6.get("/:id/RoleAssignment", listUserRoleAssignmentsRoute);
|
|
3573
|
+
router6.get("/:id/Configuration", listUserConfigurationsRoute);
|
|
3574
|
+
|
|
3575
|
+
// src/data/rest-api/routes/control/user/user-operations.ts
|
|
3576
|
+
import express7 from "express";
|
|
2297
3577
|
|
|
2298
3578
|
// src/data/rest-api/routes/control/user/user-operation-helpers.ts
|
|
2299
3579
|
import { getCurrentInvoke as getCurrentInvoke2 } from "@codegenie/serverless-express";
|
|
@@ -2309,15 +3589,22 @@ function getCognitoSubFromRequest(req) {
|
|
|
2309
3589
|
}
|
|
2310
3590
|
|
|
2311
3591
|
// src/data/rest-api/routes/control/user/user-current-route.ts
|
|
3592
|
+
var INCLUDE_TOKENS = [
|
|
3593
|
+
"memberships",
|
|
3594
|
+
"roleassignments",
|
|
3595
|
+
"configurations"
|
|
3596
|
+
];
|
|
3597
|
+
var BOOTSTRAP_PROJECTION_PAGE_CAP = 100;
|
|
2312
3598
|
async function userCurrentRoute(req, res) {
|
|
2313
|
-
|
|
3599
|
+
const includeResult = parseIncludeParam(req.query);
|
|
3600
|
+
if (!includeResult.ok) {
|
|
2314
3601
|
return res.status(400).json({
|
|
2315
3602
|
resourceType: "OperationOutcome",
|
|
2316
3603
|
issue: [
|
|
2317
3604
|
{
|
|
2318
3605
|
severity: "error",
|
|
2319
3606
|
code: "invalid",
|
|
2320
|
-
diagnostics:
|
|
3607
|
+
diagnostics: includeResult.diagnostics
|
|
2321
3608
|
}
|
|
2322
3609
|
]
|
|
2323
3610
|
});
|
|
@@ -2373,16 +3660,237 @@ async function userCurrentRoute(req, res) {
|
|
|
2373
3660
|
});
|
|
2374
3661
|
}
|
|
2375
3662
|
const parsedResource = JSON.parse(found.resource);
|
|
2376
|
-
|
|
2377
|
-
return res.json({
|
|
3663
|
+
const userResource = {
|
|
2378
3664
|
resourceType: "User",
|
|
2379
3665
|
id: found.id,
|
|
2380
3666
|
...parsedResource
|
|
3667
|
+
};
|
|
3668
|
+
res.setHeader("Cache-Control", "no-store");
|
|
3669
|
+
const include = includeResult.include;
|
|
3670
|
+
if (!include) {
|
|
3671
|
+
return res.json(userResource);
|
|
3672
|
+
}
|
|
3673
|
+
const [memberships, roleAssignments, configurations] = await Promise.all([
|
|
3674
|
+
include.memberships ? membershipListByUserOperation({
|
|
3675
|
+
userId: found.id,
|
|
3676
|
+
limit: BOOTSTRAP_PROJECTION_PAGE_CAP
|
|
3677
|
+
}) : Promise.resolve({ items: [], cursor: null }),
|
|
3678
|
+
include.roleAssignments ? roleAssignmentListByUserOperation({
|
|
3679
|
+
userId: found.id,
|
|
3680
|
+
limit: BOOTSTRAP_PROJECTION_PAGE_CAP
|
|
3681
|
+
}) : Promise.resolve({ items: [], cursor: null }),
|
|
3682
|
+
include.configurations ? configurationListByUserOperation({
|
|
3683
|
+
userId: found.id,
|
|
3684
|
+
limit: BOOTSTRAP_PROJECTION_PAGE_CAP
|
|
3685
|
+
}) : Promise.resolve({ items: [], cursor: null })
|
|
3686
|
+
]);
|
|
3687
|
+
const basePath = `${BASE_PATH.USER}/$current`;
|
|
3688
|
+
const entries = [];
|
|
3689
|
+
entries.push({
|
|
3690
|
+
fullUrl: `${BASE_PATH.USER}/${found.id}`,
|
|
3691
|
+
resource: userResource
|
|
3692
|
+
});
|
|
3693
|
+
for (const row of memberships.items) {
|
|
3694
|
+
entries.push({
|
|
3695
|
+
fullUrl: `${BASE_PATH.MEMBERSHIP}/${row.membershipId}`,
|
|
3696
|
+
resource: buildMembershipEntryResource(row)
|
|
3697
|
+
});
|
|
3698
|
+
}
|
|
3699
|
+
for (const row of roleAssignments.items) {
|
|
3700
|
+
entries.push({
|
|
3701
|
+
fullUrl: `${BASE_PATH.ROLEASSIGNMENT}/${row.roleAssignmentId}`,
|
|
3702
|
+
resource: buildRoleAssignmentEntryResource(row)
|
|
3703
|
+
});
|
|
3704
|
+
}
|
|
3705
|
+
for (const row of configurations.items) {
|
|
3706
|
+
entries.push({
|
|
3707
|
+
fullUrl: `${BASE_PATH.CONFIGURATION}/${row.configurationId}`,
|
|
3708
|
+
resource: buildConfigurationEntryResource(row)
|
|
3709
|
+
});
|
|
3710
|
+
}
|
|
3711
|
+
return res.json({
|
|
3712
|
+
resourceType: "Bundle",
|
|
3713
|
+
type: "searchset",
|
|
3714
|
+
total: entries.length,
|
|
3715
|
+
link: [{ relation: "self", url: basePath }],
|
|
3716
|
+
entry: entries
|
|
2381
3717
|
});
|
|
2382
3718
|
} catch (err) {
|
|
2383
3719
|
return sendOperationOutcome500(res, err, "GET /User/$current error:");
|
|
2384
3720
|
}
|
|
2385
3721
|
}
|
|
3722
|
+
function parseIncludeParam(query) {
|
|
3723
|
+
const q = query ?? {};
|
|
3724
|
+
const keys = Object.keys(q);
|
|
3725
|
+
const extra = keys.filter((k) => k !== "include");
|
|
3726
|
+
if (extra.length > 0) {
|
|
3727
|
+
return {
|
|
3728
|
+
ok: false,
|
|
3729
|
+
diagnostics: "GET /User/$current only accepts the optional `?include=` query parameter."
|
|
3730
|
+
};
|
|
3731
|
+
}
|
|
3732
|
+
if (keys.length === 0) {
|
|
3733
|
+
return { ok: true, include: void 0 };
|
|
3734
|
+
}
|
|
3735
|
+
const raw = q.include;
|
|
3736
|
+
if (typeof raw !== "string") {
|
|
3737
|
+
return {
|
|
3738
|
+
ok: false,
|
|
3739
|
+
diagnostics: "GET /User/$current: `include` query parameter must be a comma-separated string."
|
|
3740
|
+
};
|
|
3741
|
+
}
|
|
3742
|
+
const trimmed = raw.trim();
|
|
3743
|
+
if (trimmed.length === 0) {
|
|
3744
|
+
return {
|
|
3745
|
+
ok: false,
|
|
3746
|
+
diagnostics: "GET /User/$current: `include` query parameter must not be empty."
|
|
3747
|
+
};
|
|
3748
|
+
}
|
|
3749
|
+
const tokens = trimmed.split(",").map((t) => t.trim().toLowerCase()).filter((t) => t.length > 0);
|
|
3750
|
+
const include = {
|
|
3751
|
+
memberships: false,
|
|
3752
|
+
roleAssignments: false,
|
|
3753
|
+
configurations: false
|
|
3754
|
+
};
|
|
3755
|
+
const allowed = INCLUDE_TOKENS;
|
|
3756
|
+
const mutable = {
|
|
3757
|
+
...include
|
|
3758
|
+
};
|
|
3759
|
+
for (const token of tokens) {
|
|
3760
|
+
if (!allowed.includes(token)) {
|
|
3761
|
+
return {
|
|
3762
|
+
ok: false,
|
|
3763
|
+
diagnostics: `GET /User/$current: unknown \`include\` token \`${token}\`. Allowed: ${INCLUDE_TOKENS.join(", ")}.`
|
|
3764
|
+
};
|
|
3765
|
+
}
|
|
3766
|
+
const t = token;
|
|
3767
|
+
if (t === "memberships") {
|
|
3768
|
+
mutable.memberships = true;
|
|
3769
|
+
} else if (t === "roleassignments") {
|
|
3770
|
+
mutable.roleAssignments = true;
|
|
3771
|
+
} else if (t === "configurations") {
|
|
3772
|
+
mutable.configurations = true;
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3775
|
+
return { ok: true, include: mutable };
|
|
3776
|
+
}
|
|
3777
|
+
function parseSummary(summary) {
|
|
3778
|
+
if (typeof summary !== "string" || summary.length === 0) {
|
|
3779
|
+
return {};
|
|
3780
|
+
}
|
|
3781
|
+
try {
|
|
3782
|
+
const parsed = JSON.parse(summary);
|
|
3783
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
3784
|
+
return parsed;
|
|
3785
|
+
}
|
|
3786
|
+
} catch {
|
|
3787
|
+
}
|
|
3788
|
+
return {};
|
|
3789
|
+
}
|
|
3790
|
+
function buildMembershipEntryResource(row) {
|
|
3791
|
+
return {
|
|
3792
|
+
resourceType: "Membership",
|
|
3793
|
+
id: row.membershipId,
|
|
3794
|
+
...parseSummary(row.summary),
|
|
3795
|
+
meta: { versionId: row.vid, lastUpdated: row.lastUpdated },
|
|
3796
|
+
extension: [
|
|
3797
|
+
{
|
|
3798
|
+
url: "https://openhi.org/fhir/StructureDefinition/projection-tenant-id",
|
|
3799
|
+
valueString: row.tenantId
|
|
3800
|
+
},
|
|
3801
|
+
...row.workspaceId ? [
|
|
3802
|
+
{
|
|
3803
|
+
url: "https://openhi.org/fhir/StructureDefinition/projection-workspace-id",
|
|
3804
|
+
valueString: row.workspaceId
|
|
3805
|
+
}
|
|
3806
|
+
] : [],
|
|
3807
|
+
...row.denormalizedTenantName ? [
|
|
3808
|
+
{
|
|
3809
|
+
url: "https://openhi.org/fhir/StructureDefinition/projection-denormalized-tenant-name",
|
|
3810
|
+
valueString: row.denormalizedTenantName
|
|
3811
|
+
}
|
|
3812
|
+
] : [],
|
|
3813
|
+
...row.denormalizedUserName ? [
|
|
3814
|
+
{
|
|
3815
|
+
url: "https://openhi.org/fhir/StructureDefinition/projection-denormalized-user-name",
|
|
3816
|
+
valueString: row.denormalizedUserName
|
|
3817
|
+
}
|
|
3818
|
+
] : [],
|
|
3819
|
+
...row.denormalizedWorkspaceName ? [
|
|
3820
|
+
{
|
|
3821
|
+
url: "https://openhi.org/fhir/StructureDefinition/projection-denormalized-workspace-name",
|
|
3822
|
+
valueString: row.denormalizedWorkspaceName
|
|
3823
|
+
}
|
|
3824
|
+
] : []
|
|
3825
|
+
]
|
|
3826
|
+
};
|
|
3827
|
+
}
|
|
3828
|
+
function buildRoleAssignmentEntryResource(row) {
|
|
3829
|
+
return {
|
|
3830
|
+
resourceType: "RoleAssignment",
|
|
3831
|
+
id: row.roleAssignmentId,
|
|
3832
|
+
...parseSummary(row.summary),
|
|
3833
|
+
meta: { versionId: row.vid, lastUpdated: row.lastUpdated },
|
|
3834
|
+
extension: [
|
|
3835
|
+
{
|
|
3836
|
+
url: "https://openhi.org/fhir/StructureDefinition/projection-tenant-id",
|
|
3837
|
+
valueString: row.tenantId
|
|
3838
|
+
},
|
|
3839
|
+
...row.workspaceId ? [
|
|
3840
|
+
{
|
|
3841
|
+
url: "https://openhi.org/fhir/StructureDefinition/projection-workspace-id",
|
|
3842
|
+
valueString: row.workspaceId
|
|
3843
|
+
}
|
|
3844
|
+
] : [],
|
|
3845
|
+
{
|
|
3846
|
+
url: "https://openhi.org/fhir/StructureDefinition/projection-role-id",
|
|
3847
|
+
valueString: row.roleId
|
|
3848
|
+
},
|
|
3849
|
+
...row.denormalizedTenantName ? [
|
|
3850
|
+
{
|
|
3851
|
+
url: "https://openhi.org/fhir/StructureDefinition/projection-denormalized-tenant-name",
|
|
3852
|
+
valueString: row.denormalizedTenantName
|
|
3853
|
+
}
|
|
3854
|
+
] : [],
|
|
3855
|
+
...row.denormalizedUserName ? [
|
|
3856
|
+
{
|
|
3857
|
+
url: "https://openhi.org/fhir/StructureDefinition/projection-denormalized-user-name",
|
|
3858
|
+
valueString: row.denormalizedUserName
|
|
3859
|
+
}
|
|
3860
|
+
] : [],
|
|
3861
|
+
...row.denormalizedRoleName ? [
|
|
3862
|
+
{
|
|
3863
|
+
url: "https://openhi.org/fhir/StructureDefinition/projection-denormalized-role-name",
|
|
3864
|
+
valueString: row.denormalizedRoleName
|
|
3865
|
+
}
|
|
3866
|
+
] : []
|
|
3867
|
+
]
|
|
3868
|
+
};
|
|
3869
|
+
}
|
|
3870
|
+
function buildConfigurationEntryResource(row) {
|
|
3871
|
+
return {
|
|
3872
|
+
resourceType: "Configuration",
|
|
3873
|
+
id: row.configurationId,
|
|
3874
|
+
...parseSummary(row.summary),
|
|
3875
|
+
meta: { versionId: row.vid, lastUpdated: row.lastUpdated },
|
|
3876
|
+
extension: [
|
|
3877
|
+
{
|
|
3878
|
+
url: "https://openhi.org/fhir/StructureDefinition/projection-tenant-id",
|
|
3879
|
+
valueString: row.tenantId
|
|
3880
|
+
},
|
|
3881
|
+
{
|
|
3882
|
+
url: "https://openhi.org/fhir/StructureDefinition/projection-scope",
|
|
3883
|
+
valueString: row.scope
|
|
3884
|
+
},
|
|
3885
|
+
...row.displayName ? [
|
|
3886
|
+
{
|
|
3887
|
+
url: "https://openhi.org/fhir/StructureDefinition/projection-display-name",
|
|
3888
|
+
valueString: row.displayName
|
|
3889
|
+
}
|
|
3890
|
+
] : []
|
|
3891
|
+
]
|
|
3892
|
+
};
|
|
3893
|
+
}
|
|
2386
3894
|
function hasNonEmptyBody(body) {
|
|
2387
3895
|
if (body == null) {
|
|
2388
3896
|
return false;
|
|
@@ -2597,6 +4105,204 @@ async function getWorkspaceByIdRoute(req, res) {
|
|
|
2597
4105
|
}
|
|
2598
4106
|
}
|
|
2599
4107
|
|
|
4108
|
+
// src/data/rest-api/routes/control/workspace/workspace-list-configurations-route.ts
|
|
4109
|
+
async function listWorkspaceConfigurationsRoute(req, res) {
|
|
4110
|
+
const ctx = req.openhiContext;
|
|
4111
|
+
if (!ctx) {
|
|
4112
|
+
return sendForbidden403(
|
|
4113
|
+
res,
|
|
4114
|
+
"Missing or invalid OpenHI JWT claims (tenant, workspace, or audit context)."
|
|
4115
|
+
);
|
|
4116
|
+
}
|
|
4117
|
+
const workspaceId = String(req.params.id);
|
|
4118
|
+
const tenantId = ctx.tenantId;
|
|
4119
|
+
const parsed = parseCommonListQuery(req, res);
|
|
4120
|
+
if (!parsed.ok) {
|
|
4121
|
+
return parsed.response;
|
|
4122
|
+
}
|
|
4123
|
+
try {
|
|
4124
|
+
const memberCheck = await membershipListByUserOperation({
|
|
4125
|
+
userId: ctx.actorId,
|
|
4126
|
+
mode: "workspaceInTenant",
|
|
4127
|
+
tenantId
|
|
4128
|
+
});
|
|
4129
|
+
const hasMembership = memberCheck.items.some(
|
|
4130
|
+
(row) => row.workspaceId === workspaceId
|
|
4131
|
+
);
|
|
4132
|
+
if (!hasMembership) {
|
|
4133
|
+
return sendForbidden403(
|
|
4134
|
+
res,
|
|
4135
|
+
`Caller is not a member of Workspace/${workspaceId} in Tenant/${tenantId}.`
|
|
4136
|
+
);
|
|
4137
|
+
}
|
|
4138
|
+
const result = await configurationListByWorkspaceOperation({
|
|
4139
|
+
tenantId,
|
|
4140
|
+
workspaceId,
|
|
4141
|
+
cursor: parsed.value.cursor,
|
|
4142
|
+
...parsed.value.limit !== void 0 && { limit: parsed.value.limit },
|
|
4143
|
+
...parsed.value.order !== void 0 && { order: parsed.value.order }
|
|
4144
|
+
});
|
|
4145
|
+
const basePath = `${BASE_PATH.WORKSPACE}/${workspaceId}/Configuration`;
|
|
4146
|
+
const entries = result.items.map((row) => ({
|
|
4147
|
+
fullUrl: `${BASE_PATH.CONFIGURATION}/${row.configurationId}`,
|
|
4148
|
+
resource: buildConfigurationWorkspaceProjectionEntryResource(row)
|
|
4149
|
+
}));
|
|
4150
|
+
return res.json({
|
|
4151
|
+
resourceType: "Bundle",
|
|
4152
|
+
type: "searchset",
|
|
4153
|
+
total: entries.length,
|
|
4154
|
+
link: buildPaginationLinks({
|
|
4155
|
+
basePath,
|
|
4156
|
+
query: req.query,
|
|
4157
|
+
nextCursor: result.cursor
|
|
4158
|
+
}),
|
|
4159
|
+
entry: entries
|
|
4160
|
+
});
|
|
4161
|
+
} catch (err) {
|
|
4162
|
+
return sendOperationOutcome500(
|
|
4163
|
+
res,
|
|
4164
|
+
err,
|
|
4165
|
+
"GET /Workspace/:id/Configuration error:"
|
|
4166
|
+
);
|
|
4167
|
+
}
|
|
4168
|
+
}
|
|
4169
|
+
|
|
4170
|
+
// src/data/rest-api/routes/control/workspace/workspace-list-memberships-route.ts
|
|
4171
|
+
async function listWorkspaceMembershipsRoute(req, res) {
|
|
4172
|
+
const ctx = req.openhiContext;
|
|
4173
|
+
if (!ctx) {
|
|
4174
|
+
return sendForbidden403(
|
|
4175
|
+
res,
|
|
4176
|
+
"Missing or invalid OpenHI JWT claims (tenant, workspace, or audit context)."
|
|
4177
|
+
);
|
|
4178
|
+
}
|
|
4179
|
+
const workspaceId = String(req.params.id);
|
|
4180
|
+
const tenantId = ctx.tenantId;
|
|
4181
|
+
const parsed = parseCommonListQuery(req, res);
|
|
4182
|
+
if (!parsed.ok) {
|
|
4183
|
+
return parsed.response;
|
|
4184
|
+
}
|
|
4185
|
+
try {
|
|
4186
|
+
const memberCheck = await membershipListByUserOperation({
|
|
4187
|
+
userId: ctx.actorId,
|
|
4188
|
+
mode: "workspaceInTenant",
|
|
4189
|
+
tenantId
|
|
4190
|
+
});
|
|
4191
|
+
const hasMembership = memberCheck.items.some(
|
|
4192
|
+
(row) => row.workspaceId === workspaceId
|
|
4193
|
+
);
|
|
4194
|
+
if (!hasMembership) {
|
|
4195
|
+
return sendForbidden403(
|
|
4196
|
+
res,
|
|
4197
|
+
`Caller is not a member of Workspace/${workspaceId} in Tenant/${tenantId}.`
|
|
4198
|
+
);
|
|
4199
|
+
}
|
|
4200
|
+
const result = await membershipListByWorkspaceOperation({
|
|
4201
|
+
tenantId,
|
|
4202
|
+
workspaceId,
|
|
4203
|
+
cursor: parsed.value.cursor,
|
|
4204
|
+
...parsed.value.limit !== void 0 && { limit: parsed.value.limit },
|
|
4205
|
+
...parsed.value.order !== void 0 && { order: parsed.value.order }
|
|
4206
|
+
});
|
|
4207
|
+
const basePath = `${BASE_PATH.WORKSPACE}/${workspaceId}/Membership`;
|
|
4208
|
+
const entries = result.items.map((row) => ({
|
|
4209
|
+
fullUrl: `${BASE_PATH.MEMBERSHIP}/${row.membershipId}`,
|
|
4210
|
+
resource: buildMembershipWorkspaceProjectionEntryResource(row)
|
|
4211
|
+
}));
|
|
4212
|
+
return res.json({
|
|
4213
|
+
resourceType: "Bundle",
|
|
4214
|
+
type: "searchset",
|
|
4215
|
+
total: entries.length,
|
|
4216
|
+
link: buildPaginationLinks({
|
|
4217
|
+
basePath,
|
|
4218
|
+
query: req.query,
|
|
4219
|
+
nextCursor: result.cursor
|
|
4220
|
+
}),
|
|
4221
|
+
entry: entries
|
|
4222
|
+
});
|
|
4223
|
+
} catch (err) {
|
|
4224
|
+
return sendOperationOutcome500(
|
|
4225
|
+
res,
|
|
4226
|
+
err,
|
|
4227
|
+
"GET /Workspace/:id/Membership error:"
|
|
4228
|
+
);
|
|
4229
|
+
}
|
|
4230
|
+
}
|
|
4231
|
+
|
|
4232
|
+
// src/data/rest-api/routes/control/workspace/workspace-list-role-assignments-route.ts
|
|
4233
|
+
async function listWorkspaceRoleAssignmentsRoute(req, res) {
|
|
4234
|
+
const ctx = req.openhiContext;
|
|
4235
|
+
if (!ctx) {
|
|
4236
|
+
return sendForbidden403(
|
|
4237
|
+
res,
|
|
4238
|
+
"Missing or invalid OpenHI JWT claims (tenant, workspace, or audit context)."
|
|
4239
|
+
);
|
|
4240
|
+
}
|
|
4241
|
+
const workspaceId = String(req.params.id);
|
|
4242
|
+
const tenantId = ctx.tenantId;
|
|
4243
|
+
const parsed = parseCommonListQuery(req, res, { extraKeys: ["roleId"] });
|
|
4244
|
+
if (!parsed.ok) {
|
|
4245
|
+
return parsed.response;
|
|
4246
|
+
}
|
|
4247
|
+
const rawRoleId = req.query.roleId;
|
|
4248
|
+
let roleId;
|
|
4249
|
+
if (rawRoleId !== void 0) {
|
|
4250
|
+
if (typeof rawRoleId !== "string" || rawRoleId.length === 0) {
|
|
4251
|
+
return sendInvalidQuery400(
|
|
4252
|
+
res,
|
|
4253
|
+
"Query parameter `roleId` must be a non-empty string."
|
|
4254
|
+
);
|
|
4255
|
+
}
|
|
4256
|
+
roleId = rawRoleId;
|
|
4257
|
+
}
|
|
4258
|
+
try {
|
|
4259
|
+
const memberCheck = await membershipListByUserOperation({
|
|
4260
|
+
userId: ctx.actorId,
|
|
4261
|
+
mode: "workspaceInTenant",
|
|
4262
|
+
tenantId
|
|
4263
|
+
});
|
|
4264
|
+
const hasMembership = memberCheck.items.some(
|
|
4265
|
+
(row) => row.workspaceId === workspaceId
|
|
4266
|
+
);
|
|
4267
|
+
if (!hasMembership) {
|
|
4268
|
+
return sendForbidden403(
|
|
4269
|
+
res,
|
|
4270
|
+
`Caller is not a member of Workspace/${workspaceId} in Tenant/${tenantId}.`
|
|
4271
|
+
);
|
|
4272
|
+
}
|
|
4273
|
+
const result = await roleAssignmentListByWorkspaceOperation({
|
|
4274
|
+
tenantId,
|
|
4275
|
+
workspaceId,
|
|
4276
|
+
roleId,
|
|
4277
|
+
cursor: parsed.value.cursor,
|
|
4278
|
+
...parsed.value.limit !== void 0 && { limit: parsed.value.limit },
|
|
4279
|
+
...parsed.value.order !== void 0 && { order: parsed.value.order }
|
|
4280
|
+
});
|
|
4281
|
+
const basePath = `${BASE_PATH.WORKSPACE}/${workspaceId}/RoleAssignment`;
|
|
4282
|
+
const entries = result.items.map((row) => ({
|
|
4283
|
+
fullUrl: `${BASE_PATH.ROLEASSIGNMENT}/${row.roleAssignmentId}`,
|
|
4284
|
+
resource: buildRoleAssignmentWorkspaceProjectionEntryResource(row)
|
|
4285
|
+
}));
|
|
4286
|
+
return res.json({
|
|
4287
|
+
resourceType: "Bundle",
|
|
4288
|
+
type: "searchset",
|
|
4289
|
+
total: entries.length,
|
|
4290
|
+
link: buildPaginationLinks({
|
|
4291
|
+
basePath,
|
|
4292
|
+
query: req.query,
|
|
4293
|
+
nextCursor: result.cursor
|
|
4294
|
+
}),
|
|
4295
|
+
entry: entries
|
|
4296
|
+
});
|
|
4297
|
+
} catch (err) {
|
|
4298
|
+
return sendOperationOutcome500(
|
|
4299
|
+
res,
|
|
4300
|
+
err,
|
|
4301
|
+
"GET /Workspace/:id/RoleAssignment error:"
|
|
4302
|
+
);
|
|
4303
|
+
}
|
|
4304
|
+
}
|
|
4305
|
+
|
|
2600
4306
|
// src/data/operations/control/workspace/workspace-list-operation.ts
|
|
2601
4307
|
var SK8 = "CURRENT";
|
|
2602
4308
|
async function listWorkspacesOperation(params) {
|
|
@@ -2717,6 +4423,9 @@ router8.get("/:id", getWorkspaceByIdRoute);
|
|
|
2717
4423
|
router8.post("/", createWorkspaceRoute);
|
|
2718
4424
|
router8.put("/:id", updateWorkspaceRoute);
|
|
2719
4425
|
router8.delete("/:id", deleteWorkspaceRoute);
|
|
4426
|
+
router8.get("/:id/Membership", listWorkspaceMembershipsRoute);
|
|
4427
|
+
router8.get("/:id/RoleAssignment", listWorkspaceRoleAssignmentsRoute);
|
|
4428
|
+
router8.get("/:id/Configuration", listWorkspaceConfigurationsRoute);
|
|
2720
4429
|
|
|
2721
4430
|
// src/data/rest-api/routes/data/account/account.ts
|
|
2722
4431
|
import express9 from "express";
|
|
@@ -3727,6 +5436,7 @@ var DataApiPostgresQueryRunner = class {
|
|
|
3727
5436
|
this.schema = options.schema;
|
|
3728
5437
|
}
|
|
3729
5438
|
async query(sql, params) {
|
|
5439
|
+
await this.ensureSchemaBootstrapped();
|
|
3730
5440
|
const out = await this.client.send(
|
|
3731
5441
|
new ExecuteStatementCommand({
|
|
3732
5442
|
resourceArn: this.clusterArn,
|
|
@@ -3752,6 +5462,42 @@ var DataApiPostgresQueryRunner = class {
|
|
|
3752
5462
|
}
|
|
3753
5463
|
return JSON.parse(out.formattedRecords);
|
|
3754
5464
|
}
|
|
5465
|
+
/**
|
|
5466
|
+
* Run `CREATE SCHEMA / CREATE TABLE / CREATE INDEX` idempotent DDL once per
|
|
5467
|
+
* runner instance. The replication Lambda's cold start does the same thing
|
|
5468
|
+
* (data-store-postgres-replication.handler) but in a fresh environment with
|
|
5469
|
+
* no DynamoDB writes yet the read path can be the first thing that touches
|
|
5470
|
+
* the cluster — without this, every search returns
|
|
5471
|
+
* `relation "<schema>.resources" does not exist` until something has been
|
|
5472
|
+
* written. Memoized so subsequent queries on a warm container don't pay the
|
|
5473
|
+
* DDL cost; resets the promise on failure so the next call retries.
|
|
5474
|
+
*
|
|
5475
|
+
* Data API's `ExecuteStatement` accepts only a single SQL statement per
|
|
5476
|
+
* call, so each DDL statement from `buildSchemaBootstrapStatements` is
|
|
5477
|
+
* issued individually. `IF NOT EXISTS` on every statement makes this safe
|
|
5478
|
+
* to race across concurrent containers.
|
|
5479
|
+
*/
|
|
5480
|
+
async ensureSchemaBootstrapped() {
|
|
5481
|
+
if (!this.bootstrapPromise) {
|
|
5482
|
+
this.bootstrapPromise = this.runBootstrap().catch((err) => {
|
|
5483
|
+
this.bootstrapPromise = void 0;
|
|
5484
|
+
throw err;
|
|
5485
|
+
});
|
|
5486
|
+
}
|
|
5487
|
+
return this.bootstrapPromise;
|
|
5488
|
+
}
|
|
5489
|
+
async runBootstrap() {
|
|
5490
|
+
for (const statement of buildSchemaBootstrapStatements(this.schema)) {
|
|
5491
|
+
await this.client.send(
|
|
5492
|
+
new ExecuteStatementCommand({
|
|
5493
|
+
resourceArn: this.clusterArn,
|
|
5494
|
+
secretArn: this.secretArn,
|
|
5495
|
+
database: this.database,
|
|
5496
|
+
sql: statement
|
|
5497
|
+
})
|
|
5498
|
+
);
|
|
5499
|
+
}
|
|
5500
|
+
}
|
|
3755
5501
|
};
|
|
3756
5502
|
function qualifyResourcesTable(sql, schema) {
|
|
3757
5503
|
return sql.replace(
|