@izara_project/izara-core-generate-service-code 1.0.66 → 1.0.68
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/.prettierrc +11 -0
- package/docs/TriggeredCache Flow.drawio +193 -0
- package/docs/TriggeredCacheFlow.mmd +47 -0
- package/package.json +16 -12
- package/scripts/format-ejs.js +105 -0
- package/src/compile/compileProject.js +16 -1
- package/src/compile/loadSchemas.js +108 -1
- package/src/emit/crud/emitCrudApplicationArtifacts.js +9 -5
- package/src/emit/crud/emitDynamicCrudFlowSchemas.js +1 -2
- package/src/emit/crud/templates/CreateEndpoint_Main.ejs +300 -109
- package/src/emit/crud/templates/DeleteEndpoint_Main.ejs +221 -143
- package/src/emit/crud/templates/FlowEndpointBeforeLogical_Main.ejs +22 -6
- package/src/emit/crud/templates/FlowEndpointComplete_Main.ejs +150 -38
- package/src/emit/crud/templates/FlowEndpoint_HdrWbs.ejs +51 -31
- package/src/emit/crud/templates/FlowHandler_HdrApi.ejs +57 -31
- package/src/emit/crud/templates/FlowHandler_HdrInv.ejs +28 -26
- package/src/emit/crud/templates/FlowHandler_HdrSqs.ejs +15 -11
- package/src/emit/crud/templates/FlowMain_Wbs.ejs +53 -38
- package/src/emit/crud/templates/GetEndpoint_Main.ejs +6 -3
- package/src/emit/crud/templates/GetRangeEndpoint_Main.ejs +2 -2
- package/src/emit/crud/templates/UpdateEndpoint_Main.ejs +407 -252
- package/src/emit/custom/customFlows.js +112 -31
- package/src/emit/custom/emitCustomFlowApplicationArtifacts.js +89 -77
- package/src/emit/custom/templates/FlowEndpoint_Main.ejs +49 -37
- package/src/emit/custom/templates/FlowStep_Main.ejs +7 -9
- package/src/emit/custom/templates/s3/ConfirmReserved_HdrSqs.ejs +59 -54
- package/src/emit/custom/templates/s3/ConfirmReserved_Main.ejs +33 -23
- package/src/emit/custom/templates/s3/CreateObject_HdrWbs.ejs +30 -23
- package/src/emit/custom/templates/s3/CreateObject_Main.ejs +56 -39
- package/src/emit/custom/templates/s3/CreatePreSignUrl_HdrSqs.ejs +56 -51
- package/src/emit/custom/templates/s3/CreatePreSignUrl_Main.ejs +112 -75
- package/src/emit/custom/templates/s3/FlowHandler_HdrS3.ejs +12 -7
- package/src/emit/custom/templates/s3/ProcessS3File_Main.ejs +11 -8
- package/src/emit/custom/templates/s3/ReservedLimit_HdrSqs.ejs +64 -55
- package/src/emit/custom/templates/s3/ReservedLimit_Main.ejs +74 -52
- package/src/emit/custom/templates/triggerCache/CheckTriggerCache_HdrSqs.ejs +58 -14
- package/src/emit/custom/templates/triggerCache/CheckTriggerCache_Main.ejs +12 -17
- package/src/emit/custom/templates/triggerCache/TriggerCacheComplete_Main.ejs +20 -13
- package/src/emit/custom/templates/triggerCache/TriggerCache_Main.ejs +4 -1
- package/src/emit/function/functionYamlArtifact.js +46 -12
- package/src/emit/rbac/emitDynamicRbacFlowSchemas.js +28 -15
- package/src/emit/rbac/emitRbacApplicationArtifacts.js +179 -66
- package/src/emit/rbac/templates/CreateTargetRole_HdrSqs.ejs +41 -18
- package/src/emit/rbac/templates/CreateTargetRole_Main.ejs +23 -3
- package/src/emit/rbac/templates/ListRbac_HdrApi.ejs +27 -5
- package/src/emit/rbac/templates/ListRbac_HdrSqs.ejs +41 -8
- package/src/emit/rbac/templates/ListRbac_Main.ejs +26 -6
- package/src/emit/rbac/templates/RbacMutation_Main.ejs +23 -3
- package/src/emit/rbac/templates/UserRbacFlow_HdrSqs.ejs +46 -0
- package/src/emit/rbac/templates/UserRbacFlow_Main.ejs +152 -33
- package/src/emit/rbac/templates/UserRoleMutation_Main.ejs +24 -4
- package/src/emit/relationship/emitDynamicRelationshipFlowSchemas.js +1 -2
- package/src/emit/relationship/emitRelationshipApplicationArtifacts.js +6 -5
- package/src/emit/relationship/templates/ChangeRelationshipComplete_Main.ejs +64 -37
- package/src/emit/relationship/templates/ChangeRelationship_Main.ejs +260 -144
- package/src/emit/relationship/templates/CreateRelationshipComplete_Main.ejs +68 -43
- package/src/emit/relationship/templates/CreateRelationship_Main.ejs +315 -171
- package/src/emit/relationship/templates/DeleteRelationshipComplete_Main.ejs +67 -41
- package/src/emit/relationship/templates/DeleteRelationship_Main.ejs +226 -115
- package/src/emit/relationship/templates/FlowHandler_HdrApi.ejs +41 -33
- package/src/emit/relationship/templates/FlowHandler_HdrDsq.ejs +15 -11
- package/src/emit/relationship/templates/FlowHandler_HdrInv.ejs +28 -26
- package/src/emit/relationship/templates/FlowHandler_HdrSqs.ejs +15 -11
- package/src/emit/relationship/templates/GetRelationshipComplete_Main.ejs +46 -26
- package/src/emit/relationship/templates/GetRelationship_Main.ejs +233 -126
- package/src/emit/relationship/templates/MoveRelationshipComplete_Main.ejs +72 -44
- package/src/emit/relationship/templates/MoveRelationship_Main.ejs +266 -150
- package/src/emit/relationship/templates/UpdateRelationshipComplete_Main.ejs +74 -45
- package/src/emit/relationship/templates/UpdateRelationship_Main.ejs +285 -152
- package/src/emit/resource/emitResourceArtifacts.js +499 -108
- package/src/emit/resource/templates/DynamoDBTable_Yaml.ejs +18 -18
- package/src/emit/resource/templates/FlowOut_Yaml.ejs +5 -5
- package/src/emit/resource/templates/GeneratedLambdaRoleServiceConfig.ejs +138 -0
- package/src/emit/resource/templates/ResourceBeforeLogicalYaml.ejs +44 -44
- package/src/emit/resource/templates/ResourceSqsYaml.ejs +36 -36
- package/src/emit/resource/templates/ResourceYaml.ejs +48 -48
- package/src/emit/resource/templates/S3Bucket_Yaml.ejs +19 -0
- package/src/emit/resource/templates/SharedResource_Yaml.ejs +11 -11
- package/src/emit/resource/templates/SystemDynamoDB_Yaml.ejs +105 -105
- package/src/emit/system/emitSystemWebSocketArtifacts.js +2 -1
- package/src/generate.js +8 -4
- package/test/createTargetRoleHandler.test.js +63 -14
- package/test/crudApplicationArtifacts.test.js +1 -1
- package/test/customFlowApplicationArtifacts.test.js +71 -43
- package/test/dynamicCrudFlowSchemas.test.js +6 -6
- package/test/dynamicRbacFlowSchemas.test.js +8 -12
- package/test/dynamicRelationshipFlowSchemas.test.js +3 -3
- package/test/publicApi.test.js +11 -13
- package/test/resourceArtifacts.test.js +13 -3
- package/src/emit/rbac/templates/UserRbacFlow_HdrInv.ejs +0 -10
|
@@ -1,37 +1,122 @@
|
|
|
1
1
|
import ejs from 'ejs';
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
customFlowRoleName,
|
|
6
|
+
flowDsqSteps,
|
|
7
|
+
flowExtTopicSteps,
|
|
8
|
+
generatedOwnTopicFlows,
|
|
9
|
+
supportedCustomFlows
|
|
10
|
+
} from '../custom/customFlows.js';
|
|
5
11
|
import { hasSystemWebSocket } from '../system/emitSystemWebSocketArtifacts.js';
|
|
6
12
|
|
|
7
13
|
const TEMPLATE_URL = new URL('./templates/', import.meta.url);
|
|
8
14
|
const RESOURCE_ROOT = 'resource/sls_yaml/generatedCode/source';
|
|
9
15
|
const APP_ROOT = 'app/sls_yaml/generatedCode/source';
|
|
16
|
+
const INITIAL_SETUP_ROOT = 'app/initial_setup/generatedCode/source';
|
|
10
17
|
const CRUD_ACTIONS = ['Create', 'Update', 'Delete', 'Get'];
|
|
11
|
-
const RELATIONSHIP_ACTIONS = [
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
const RELATIONSHIP_ACTIONS = [
|
|
19
|
+
'CreateRelationship',
|
|
20
|
+
'UpdateRelationship',
|
|
21
|
+
'DeleteRelationship',
|
|
22
|
+
'GetRelationship',
|
|
23
|
+
'ChangeRelationship',
|
|
24
|
+
'MoveRelationship'
|
|
25
|
+
];
|
|
26
|
+
const RBAC_ACTIONS = [
|
|
27
|
+
'CreateTargetRole',
|
|
28
|
+
'ListTargetRole',
|
|
29
|
+
'DeleteTargetRole',
|
|
30
|
+
'CreateRolePermissions',
|
|
31
|
+
'ListRolePermissions',
|
|
32
|
+
'DeleteRolePermissions',
|
|
33
|
+
'CreateUserRole',
|
|
34
|
+
'ListUserInRoles',
|
|
35
|
+
'DeleteUserFromRole'
|
|
36
|
+
];
|
|
37
|
+
const SYSTEM_TABLES = [
|
|
38
|
+
'WebSocketTask',
|
|
39
|
+
'UserUploadRecords',
|
|
40
|
+
'RegisterRecords',
|
|
41
|
+
'AwaitingMultipleSteps',
|
|
42
|
+
'AwaitingMultipleStepByPending',
|
|
43
|
+
'FindDataMain',
|
|
44
|
+
'LogicalResultsMain',
|
|
45
|
+
'LogicalResultsData',
|
|
46
|
+
'AwaitingStep',
|
|
47
|
+
'TargetRoles',
|
|
48
|
+
'RolePermissions',
|
|
49
|
+
'RoleUsers',
|
|
50
|
+
'UserRoles'
|
|
51
|
+
];
|
|
52
|
+
const RBAC_TABLES = [
|
|
53
|
+
'TargetRoles',
|
|
54
|
+
'RolePermissions',
|
|
55
|
+
'RoleUsers',
|
|
56
|
+
'UserRoles',
|
|
57
|
+
'WebSocketTask'
|
|
58
|
+
];
|
|
59
|
+
const LEGACY_PROCESS_QUEUES = [
|
|
60
|
+
'FindDataHdrSqs',
|
|
61
|
+
'ProcessLogicalHdrSqs',
|
|
62
|
+
'PaginateProcessLogicalHdrDsq',
|
|
63
|
+
'PaginateProcessLogicalHdrSqs'
|
|
64
|
+
];
|
|
65
|
+
const DYNAMO_ACTIONS = [
|
|
66
|
+
'dynamodb:DeleteItem',
|
|
67
|
+
'dynamodb:GetItem',
|
|
68
|
+
'dynamodb:PutItem',
|
|
69
|
+
'dynamodb:Query',
|
|
70
|
+
'dynamodb:UpdateItem'
|
|
71
|
+
];
|
|
72
|
+
const SQS_CONSUMER_ACTIONS = [
|
|
73
|
+
'sqs:DeleteMessage',
|
|
74
|
+
'sqs:GetQueueAttributes',
|
|
75
|
+
'sqs:ReceiveMessage'
|
|
76
|
+
];
|
|
18
77
|
|
|
19
|
-
export async function emitResourceArtifacts({
|
|
78
|
+
export async function emitResourceArtifacts({
|
|
79
|
+
objects,
|
|
80
|
+
relationships,
|
|
81
|
+
flows = []
|
|
82
|
+
}) {
|
|
20
83
|
if (![objects, relationships, flows].every(Array.isArray)) {
|
|
21
84
|
throw new TypeError('objects, relationships, and flows must be arrays');
|
|
22
85
|
}
|
|
23
86
|
const state = createResourceState({ objects, relationships, flows });
|
|
24
|
-
const [inSqs, out, dynamo, iam] = await Promise.all([
|
|
87
|
+
const [inSqs, out, dynamo, iam, lambdaRoleConfig, s3] = await Promise.all([
|
|
25
88
|
renderInSqs(state),
|
|
26
89
|
renderOut(state),
|
|
27
90
|
renderDynamo(state),
|
|
28
|
-
renderIam(state)
|
|
91
|
+
renderIam(state),
|
|
92
|
+
renderLambdaRoleConfig(state),
|
|
93
|
+
renderS3(state)
|
|
29
94
|
]);
|
|
30
95
|
return [
|
|
31
|
-
{
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
96
|
+
{
|
|
97
|
+
path: `${RESOURCE_ROOT}/generated-sns-in-sqs.yml`,
|
|
98
|
+
content: normalizeYaml(inSqs)
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
path: `${RESOURCE_ROOT}/generated-sns-out.yml`,
|
|
102
|
+
content: normalizeYaml(out)
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
path: `${RESOURCE_ROOT}/generated-dynamoDB-table.yml`,
|
|
106
|
+
content: normalizeYaml(dynamo)
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
path: `${RESOURCE_ROOT}/generated-s3.yml`,
|
|
110
|
+
content: normalizeYaml(s3)
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
path: `${APP_ROOT}/generate-shared-resource.yml`,
|
|
114
|
+
content: normalizeYaml(iam)
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
path: `${INITIAL_SETUP_ROOT}/generatedLambdaRoleServiceConfig.js`,
|
|
118
|
+
content: lambdaRoleConfig
|
|
119
|
+
}
|
|
35
120
|
];
|
|
36
121
|
}
|
|
37
122
|
|
|
@@ -39,33 +124,49 @@ export const resourceArtifactPaths = [
|
|
|
39
124
|
`${RESOURCE_ROOT}/generated-sns-in-sqs.yml`,
|
|
40
125
|
`${RESOURCE_ROOT}/generated-sns-out.yml`,
|
|
41
126
|
`${RESOURCE_ROOT}/generated-dynamoDB-table.yml`,
|
|
42
|
-
`${
|
|
127
|
+
`${RESOURCE_ROOT}/generated-s3.yml`,
|
|
128
|
+
`${APP_ROOT}/generate-shared-resource.yml`,
|
|
129
|
+
`${INITIAL_SETUP_ROOT}/generatedLambdaRoleServiceConfig.js`
|
|
43
130
|
];
|
|
44
131
|
|
|
45
132
|
function createResourceState({ objects, relationships, flows }) {
|
|
46
133
|
const crud = objects.some(isCrudObject);
|
|
47
134
|
const rbac = objects.some(hasRbacAddOn);
|
|
48
|
-
const ownTopics = new Set(
|
|
135
|
+
const ownTopics = new Set(
|
|
136
|
+
flows
|
|
137
|
+
.filter(flow => includesEvent(flow, 'ownTopic'))
|
|
138
|
+
.map(flow => upperFirst(flow.flowTag))
|
|
139
|
+
);
|
|
49
140
|
const sqsQueues = new Set([
|
|
50
141
|
...LEGACY_PROCESS_QUEUES,
|
|
51
|
-
...flows
|
|
142
|
+
...flows
|
|
143
|
+
.filter(flow => includesEvent(flow, 'extTopic'))
|
|
144
|
+
.map(flow => `Process${upperFirst(flow.flowTag)}HdrSqs`)
|
|
52
145
|
]);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
146
|
+
flowExtTopicSteps(flows).forEach(({ flowTag, stepName }) =>
|
|
147
|
+
sqsQueues.add(`${flowTag}${stepName}HdrSqs`)
|
|
148
|
+
);
|
|
149
|
+
flows
|
|
150
|
+
.filter(flow => includesEvent(flow, 's3'))
|
|
151
|
+
.forEach(flow => {
|
|
152
|
+
const tag = upperFirst(flow.flowTag);
|
|
153
|
+
sqsQueues.add(`${tag}CreatePreSignUrlHdrSqs`);
|
|
154
|
+
sqsQueues.add(`${tag}ReservedLimitHdrSqs`);
|
|
155
|
+
sqsQueues.add(`${tag}ConfirmAfterUploadHdrSqs`);
|
|
156
|
+
});
|
|
157
|
+
const dsqQueues = new Set(
|
|
158
|
+
flows
|
|
159
|
+
.filter(flow => includesEvent(flow, 'dsq'))
|
|
160
|
+
.map(flow => `${upperFirst(flow.flowTag)}HdrDsq`)
|
|
161
|
+
);
|
|
162
|
+
flowDsqSteps(flows).forEach(({ flowTag, stepName }) =>
|
|
163
|
+
dsqQueues.add(`${flowTag}${stepName}HdrDsq`)
|
|
164
|
+
);
|
|
66
165
|
if (crud) {
|
|
67
166
|
CRUD_ACTIONS.forEach(action => ownTopics.add(action));
|
|
68
|
-
['Create', 'Update', 'Delete'].forEach(action =>
|
|
167
|
+
['Create', 'Update', 'Delete'].forEach(action =>
|
|
168
|
+
sqsQueues.add(`${action}CompleteHdrSqs`)
|
|
169
|
+
);
|
|
69
170
|
}
|
|
70
171
|
if (relationships.length > 0) {
|
|
71
172
|
RELATIONSHIP_ACTIONS.forEach(action => {
|
|
@@ -79,75 +180,157 @@ function createResourceState({ objects, relationships, flows }) {
|
|
|
79
180
|
ownTopics.add(action);
|
|
80
181
|
sqsQueues.add(`Process${action}HdrSqs`);
|
|
81
182
|
});
|
|
82
|
-
objects
|
|
183
|
+
objects
|
|
184
|
+
.filter(hasRbacAddOn)
|
|
185
|
+
.forEach(object => {
|
|
186
|
+
const flowTag = upperFirst(object.objectType) + 'RbacFlow';
|
|
187
|
+
ownTopics.add(flowTag);
|
|
188
|
+
});
|
|
83
189
|
}
|
|
84
190
|
const beforeLogicalByAction = new Map();
|
|
85
|
-
for (const flow of flows.filter(
|
|
191
|
+
for (const flow of flows.filter(
|
|
192
|
+
flow => flow?.generatedCodeConfig?.codeHookTag === 'beforeLogical'
|
|
193
|
+
)) {
|
|
86
194
|
const action = upperFirst(flow.generatedCodeConfig.generatedCodeTag);
|
|
87
195
|
if (!action || !flow.flowTag) continue;
|
|
88
196
|
const flowTags = beforeLogicalByAction.get(action) ?? new Set();
|
|
89
197
|
flowTags.add(flow.flowTag);
|
|
90
198
|
beforeLogicalByAction.set(action, flowTags);
|
|
91
199
|
}
|
|
92
|
-
const beforeLogical = [...beforeLogicalByAction].map(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
...
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
200
|
+
const beforeLogical = [...beforeLogicalByAction].map(
|
|
201
|
+
([action, flowTags]) => ({ action, flowTags: [...flowTags].sort() })
|
|
202
|
+
);
|
|
203
|
+
const customRoles = [
|
|
204
|
+
...new Set([
|
|
205
|
+
...supportedCustomFlows(flows).map(flow =>
|
|
206
|
+
customFlowRoleName(flow.flowTag)
|
|
207
|
+
),
|
|
208
|
+
...generatedOwnTopicFlows(flows).map(flow =>
|
|
209
|
+
customFlowRoleName(flow.flowTag)
|
|
210
|
+
),
|
|
211
|
+
...flowExtTopicSteps(flows).map(({ flow }) =>
|
|
212
|
+
customFlowRoleName(flow.flowTag)
|
|
213
|
+
),
|
|
214
|
+
...flowDsqSteps(flows).map(({ flow }) => customFlowRoleName(flow.flowTag))
|
|
215
|
+
])
|
|
216
|
+
];
|
|
217
|
+
const websocket = hasSystemWebSocket(flows, {
|
|
218
|
+
includeRelationshipOwnTopic: true
|
|
219
|
+
});
|
|
100
220
|
const webSocketRoles = new Set(websocket ? ['WebSocketMainRole'] : []);
|
|
101
221
|
if (crud) webSocketRoles.add('FlowObjectsRole');
|
|
102
222
|
if (relationships.length > 0) webSocketRoles.add('FlowRelationshipRole');
|
|
103
223
|
if (rbac) webSocketRoles.add('FlowRbacRole');
|
|
104
224
|
[...supportedCustomFlows(flows), ...generatedOwnTopicFlows(flows)]
|
|
105
|
-
.filter(
|
|
225
|
+
.filter(
|
|
226
|
+
flow => includesEvent(flow, 'ownTopic') || includesEvent(flow, 's3')
|
|
227
|
+
)
|
|
106
228
|
.forEach(flow => webSocketRoles.add(customFlowRoleName(flow.flowTag)));
|
|
107
229
|
const outTopics = new Set([
|
|
108
230
|
...ownTopics,
|
|
109
231
|
...beforeLogical.map(({ action }) => action),
|
|
110
|
-
...flows
|
|
232
|
+
...flows
|
|
233
|
+
.filter(flow => flow?.outputTopic && upperFirst(flow.flowTag))
|
|
234
|
+
.map(flow => upperFirst(flow.flowTag))
|
|
111
235
|
]);
|
|
112
236
|
const objectTableNames = [...collectTables(objects).keys()];
|
|
113
237
|
const relationshipTableNames = [...collectTables(relationships).keys()];
|
|
114
|
-
const tableMap = new Map([
|
|
238
|
+
const tableMap = new Map([
|
|
239
|
+
...collectTables(objects),
|
|
240
|
+
...collectTables(relationships)
|
|
241
|
+
]);
|
|
242
|
+
const externalDynamoTables = objects.flatMap(
|
|
243
|
+
object => object.externalDynamoResources ?? []
|
|
244
|
+
);
|
|
245
|
+
const s3Buckets = flows
|
|
246
|
+
.filter(flow => includesEvent(flow, 's3') && flow.createBucket === true)
|
|
247
|
+
.map(flow => flow.bucketName)
|
|
248
|
+
.filter(Boolean);
|
|
115
249
|
return {
|
|
116
250
|
crud,
|
|
117
251
|
rbac,
|
|
118
252
|
relationships: relationships.length > 0,
|
|
119
253
|
ownTopics: [...ownTopics].filter(Boolean).sort(),
|
|
120
254
|
sqsQueues: [...sqsQueues].filter(Boolean).sort(),
|
|
121
|
-
triggerCacheQueues,
|
|
122
255
|
dsqQueues: [...dsqQueues].filter(Boolean).sort(),
|
|
123
256
|
beforeLogical,
|
|
124
257
|
customRoles,
|
|
125
258
|
websocket,
|
|
126
259
|
webSocketRoles,
|
|
127
260
|
webSocketSubscriptions: websocket
|
|
128
|
-
? [...outTopics]
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
261
|
+
? [...outTopics]
|
|
262
|
+
.filter(
|
|
263
|
+
topic =>
|
|
264
|
+
flows.some(
|
|
265
|
+
flow =>
|
|
266
|
+
(flow?.outputTopic ||
|
|
267
|
+
(includesEvent(flow, 'ownTopic') &&
|
|
268
|
+
flow.initiateBy === 'user')) &&
|
|
269
|
+
upperFirst(flow.flowTag) === topic
|
|
270
|
+
) ||
|
|
271
|
+
(relationships.length > 0 &&
|
|
272
|
+
topic !== 'GetRelationship' &&
|
|
273
|
+
RELATIONSHIP_ACTIONS.includes(topic))
|
|
274
|
+
)
|
|
275
|
+
.sort()
|
|
132
276
|
: [],
|
|
133
277
|
outTopics: [...outTopics].filter(Boolean).sort(),
|
|
134
|
-
tables: [...tableMap.entries()].map(([tableName, keys]) => ({
|
|
278
|
+
tables: [...tableMap.entries()].map(([tableName, keys]) => ({
|
|
279
|
+
tableName,
|
|
280
|
+
...keys
|
|
281
|
+
})),
|
|
135
282
|
allTableNames: [...new Set([...SYSTEM_TABLES, ...tableMap.keys()])].sort(),
|
|
136
283
|
objectTableNames,
|
|
137
284
|
relationshipTableNames,
|
|
138
|
-
|
|
285
|
+
externalDynamoTables,
|
|
286
|
+
objectTablesByType: new Map(
|
|
287
|
+
objects
|
|
288
|
+
.map(object => [
|
|
289
|
+
object.objectType,
|
|
290
|
+
Object.values(object.storageResources ?? {}).find(
|
|
291
|
+
resource => resource?.storageType === 'dynamoDB'
|
|
292
|
+
)?.tableName
|
|
293
|
+
])
|
|
294
|
+
.filter(([, tableName]) => tableName)
|
|
295
|
+
),
|
|
296
|
+
s3Buckets,
|
|
139
297
|
flows
|
|
140
298
|
};
|
|
141
299
|
}
|
|
142
300
|
|
|
301
|
+
async function renderS3(state) {
|
|
302
|
+
if (state.s3Buckets.length === 0) return 'Resources:';
|
|
303
|
+
const template = await readFile(new URL('S3Bucket_Yaml.ejs', TEMPLATE_URL), 'utf8');
|
|
304
|
+
const chunks = await Promise.all(
|
|
305
|
+
state.s3Buckets.map(bucketName => ejs.render(template, { bucketName }))
|
|
306
|
+
);
|
|
307
|
+
return `Resources:\n${chunks.join('\n')}`;
|
|
308
|
+
}
|
|
309
|
+
|
|
143
310
|
async function renderInSqs(state) {
|
|
144
311
|
const chunks = await Promise.all([
|
|
145
|
-
...state.ownTopics.map(queueName =>
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
...state.
|
|
149
|
-
|
|
150
|
-
|
|
312
|
+
...state.ownTopics.map(queueName =>
|
|
313
|
+
render('ResourceYaml.ejs', { queueName })
|
|
314
|
+
),
|
|
315
|
+
...state.sqsQueues.map(queueName =>
|
|
316
|
+
render('ResourceSqsYaml.ejs', { queueName })
|
|
317
|
+
),
|
|
318
|
+
...state.dsqQueues.map(queueName =>
|
|
319
|
+
render('ResourceSqsYaml.ejs', { queueName })
|
|
320
|
+
),
|
|
321
|
+
...state.beforeLogical.map(({ action, flowTags }) =>
|
|
322
|
+
render('ResourceBeforeLogicalYaml.ejs', {
|
|
323
|
+
queueName: `${action}BeforeLogical`,
|
|
324
|
+
flowTags
|
|
325
|
+
})
|
|
326
|
+
),
|
|
327
|
+
...(state.websocket
|
|
328
|
+
? [
|
|
329
|
+
render('ResourceSqsYaml.ejs', {
|
|
330
|
+
queueName: 'WebSocketCompleteHdrSqs'
|
|
331
|
+
})
|
|
332
|
+
]
|
|
333
|
+
: [])
|
|
151
334
|
]);
|
|
152
335
|
return `Resources:\n${chunks.join('\n')}\n${state.webSocketSubscriptions.map(renderWebSocketSubscription).join('\n')}`;
|
|
153
336
|
}
|
|
@@ -171,19 +354,62 @@ async function renderIam(state) {
|
|
|
171
354
|
if (state.rbac) roles.push('FlowRbacRole');
|
|
172
355
|
if (state.websocket) roles.push('WebSocketMainRole');
|
|
173
356
|
roles.push(...state.customRoles);
|
|
174
|
-
return render('SharedResource_Yaml.ejs', {
|
|
357
|
+
return render('SharedResource_Yaml.ejs', {
|
|
358
|
+
roles: roles.sort().map(roleName => ({
|
|
359
|
+
roleName,
|
|
360
|
+
statements: roleStatements(state, roleName)
|
|
361
|
+
}))
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async function renderLambdaRoleConfig(state) {
|
|
366
|
+
const s3Flows = state.flows
|
|
367
|
+
.filter(flow => includesEvent(flow, 's3'))
|
|
368
|
+
.map(flow => ({
|
|
369
|
+
roleName: upperFirst(flow.flowTag)
|
|
370
|
+
}));
|
|
371
|
+
return render('GeneratedLambdaRoleServiceConfig.ejs', {
|
|
372
|
+
crud: state.crud,
|
|
373
|
+
relationships: state.relationships,
|
|
374
|
+
s3Flows
|
|
375
|
+
});
|
|
175
376
|
}
|
|
176
377
|
|
|
177
378
|
function roleStatements(state, roleName) {
|
|
178
379
|
const statements = [];
|
|
179
380
|
if (roleNeedsSchemaRead(state, roleName)) {
|
|
180
|
-
statements.push({
|
|
381
|
+
statements.push({
|
|
382
|
+
action: ['s3:GetObject'],
|
|
383
|
+
resource: [
|
|
384
|
+
'arn:aws:s3:::${self:custom.iz_serviceSchemaBucketName}/perServiceSchemas/*'
|
|
385
|
+
]
|
|
386
|
+
});
|
|
181
387
|
}
|
|
182
|
-
addStatement(
|
|
183
|
-
|
|
184
|
-
|
|
388
|
+
addStatement(
|
|
389
|
+
statements,
|
|
390
|
+
dynamoActionsForRole(roleName),
|
|
391
|
+
[
|
|
392
|
+
...dynamoArns(roleTableNames(state, roleName)),
|
|
393
|
+
...externalDynamoArns(roleExternalDynamoTables(state, roleName))
|
|
394
|
+
]
|
|
395
|
+
);
|
|
396
|
+
addStatement(
|
|
397
|
+
statements,
|
|
398
|
+
SQS_CONSUMER_ACTIONS,
|
|
399
|
+
queueArns(roleQueueNames(state, roleName))
|
|
400
|
+
);
|
|
401
|
+
addStatement(
|
|
402
|
+
statements,
|
|
403
|
+
['sns:Publish'],
|
|
404
|
+
topicArns(roleTopicNames(state, roleName))
|
|
405
|
+
);
|
|
185
406
|
if (state.webSocketRoles.has(roleName)) {
|
|
186
|
-
statements.push({
|
|
407
|
+
statements.push({
|
|
408
|
+
action: ['execute-api:ManageConnections'],
|
|
409
|
+
resource: [
|
|
410
|
+
'arn:aws:execute-api:${self:custom.iz_region}:${self:custom.iz_accountId}:*/*'
|
|
411
|
+
]
|
|
412
|
+
});
|
|
187
413
|
}
|
|
188
414
|
return statements;
|
|
189
415
|
}
|
|
@@ -199,15 +425,39 @@ function roleNeedsSchemaRead(state, roleName) {
|
|
|
199
425
|
|
|
200
426
|
function roleTableNames(state, roleName) {
|
|
201
427
|
if (roleName === 'RegisterRole') return [];
|
|
202
|
-
if (roleName === 'WebSocketMainRole')
|
|
203
|
-
|
|
204
|
-
if (roleName === '
|
|
428
|
+
if (roleName === 'WebSocketMainRole')
|
|
429
|
+
return ['UserUploadRecords', 'WebSocketTask'];
|
|
430
|
+
if (roleName === 'FlowObjectsRole')
|
|
431
|
+
return [
|
|
432
|
+
...state.objectTableNames,
|
|
433
|
+
'AwaitingMultipleSteps',
|
|
434
|
+
'AwaitingMultipleStepByPending',
|
|
435
|
+
'WebSocketTask'
|
|
436
|
+
];
|
|
437
|
+
if (roleName === 'FlowRelationshipRole')
|
|
438
|
+
return [
|
|
439
|
+
...state.objectTableNames,
|
|
440
|
+
...state.relationshipTableNames,
|
|
441
|
+
'AwaitingMultipleSteps',
|
|
442
|
+
'AwaitingMultipleStepByPending',
|
|
443
|
+
'WebSocketTask'
|
|
444
|
+
];
|
|
205
445
|
if (roleName === 'FlowRbacRole') return RBAC_TABLES;
|
|
206
|
-
if (roleName === 'ProcFindDataRole')
|
|
207
|
-
|
|
446
|
+
if (roleName === 'ProcFindDataRole')
|
|
447
|
+
return [
|
|
448
|
+
...state.objectTableNames,
|
|
449
|
+
...state.relationshipTableNames,
|
|
450
|
+
'FindDataMain',
|
|
451
|
+
'LogicalResultsMain',
|
|
452
|
+
'LogicalResultsData'
|
|
453
|
+
];
|
|
454
|
+
const flow = state.flows.find(
|
|
455
|
+
candidate => customFlowRoleName(candidate?.flowTag ?? '') === roleName
|
|
456
|
+
);
|
|
208
457
|
if (!flow) return [];
|
|
209
458
|
const tables = [];
|
|
210
|
-
if (includesEvent(flow, 'ownTopic') || includesEvent(flow, 's3'))
|
|
459
|
+
if (includesEvent(flow, 'ownTopic') || includesEvent(flow, 's3'))
|
|
460
|
+
tables.push('WebSocketTask');
|
|
211
461
|
if (includesEvent(flow, 's3')) tables.push('UserUploadRecords');
|
|
212
462
|
if (flow.statusType === 'storedCache') {
|
|
213
463
|
const object = state.objectTablesByType.get(flow.objType?.objectType);
|
|
@@ -217,56 +467,152 @@ function roleTableNames(state, roleName) {
|
|
|
217
467
|
}
|
|
218
468
|
|
|
219
469
|
function dynamoActionsForRole(roleName) {
|
|
220
|
-
if (roleName === 'ProcFindDataRole')
|
|
221
|
-
|
|
222
|
-
if (roleName === '
|
|
470
|
+
if (roleName === 'ProcFindDataRole')
|
|
471
|
+
return ['dynamodb:GetItem', 'dynamodb:Query'];
|
|
472
|
+
if (roleName === 'WebSocketMainRole')
|
|
473
|
+
return ['dynamodb:DeleteItem', 'dynamodb:PutItem', 'dynamodb:Query'];
|
|
474
|
+
if (roleName === 'FlowRbacRole')
|
|
475
|
+
return [...DYNAMO_ACTIONS, 'dynamodb:TransactWriteItems'];
|
|
223
476
|
return DYNAMO_ACTIONS;
|
|
224
477
|
}
|
|
225
478
|
|
|
226
479
|
function roleQueueNames(state, roleName) {
|
|
227
480
|
if (roleName === 'ProcFindDataRole') return LEGACY_PROCESS_QUEUES;
|
|
228
|
-
if (roleName === 'WebSocketMainRole')
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
481
|
+
if (roleName === 'WebSocketMainRole')
|
|
482
|
+
return state.websocket ? ['WebSocketCompleteHdrSqs'] : [];
|
|
483
|
+
if (roleName === 'FlowObjectsRole')
|
|
484
|
+
return [
|
|
485
|
+
...CRUD_ACTIONS.map(action => `${action}HdrSqs`),
|
|
486
|
+
...CRUD_ACTIONS.filter(action => action !== 'Get').map(
|
|
487
|
+
action => `${action}CompleteHdrSqs`
|
|
488
|
+
),
|
|
489
|
+
...state.beforeLogical
|
|
490
|
+
.filter(({ action }) => CRUD_ACTIONS.includes(action))
|
|
491
|
+
.map(({ action }) => `${action}BeforeLogicalHdrSqs`)
|
|
492
|
+
];
|
|
493
|
+
if (roleName === 'FlowRelationshipRole')
|
|
494
|
+
return [
|
|
495
|
+
...RELATIONSHIP_ACTIONS.flatMap(action => [
|
|
496
|
+
`${action}HdrSqs`,
|
|
497
|
+
`${action}CompleteHdrSqs`
|
|
498
|
+
]),
|
|
499
|
+
'GetRelationshipHdrDsq'
|
|
500
|
+
];
|
|
501
|
+
if (roleName === 'FlowRbacRole')
|
|
502
|
+
return RBAC_ACTIONS.flatMap(action => [
|
|
503
|
+
`${action}HdrSqs`,
|
|
504
|
+
`Process${action}HdrSqs`
|
|
505
|
+
]);
|
|
506
|
+
const flow = state.flows.find(
|
|
507
|
+
candidate => customFlowRoleName(candidate?.flowTag ?? '') === roleName
|
|
508
|
+
);
|
|
240
509
|
if (!flow) return [];
|
|
241
510
|
const tag = upperFirst(flow.flowTag);
|
|
242
511
|
return [
|
|
243
|
-
...(includesEvent(flow, 'extTopic') ? [
|
|
512
|
+
...(includesEvent(flow, 'extTopic') ? [`${tag}HdrSqs`] : []),
|
|
244
513
|
...(includesEvent(flow, 'ownTopic') ? [`${tag}HdrSqs`] : []),
|
|
245
|
-
...
|
|
246
|
-
...
|
|
247
|
-
|
|
514
|
+
...(includesEvent(flow, 'dsq') ? [`${tag}HdrDsq`] : []),
|
|
515
|
+
...flowExtTopicSteps([flow]).map(
|
|
516
|
+
({ flowTag, stepName }) => `${flowTag}${stepName}HdrSqs`
|
|
517
|
+
),
|
|
518
|
+
...flowDsqSteps([flow]).map(
|
|
519
|
+
({ flowTag, stepName }) => `${flowTag}${stepName}HdrDsq`
|
|
520
|
+
),
|
|
521
|
+
...state.sqsQueues.filter(
|
|
522
|
+
queueName => includesEvent(flow, 's3') && queueName.startsWith(tag)
|
|
523
|
+
)
|
|
248
524
|
];
|
|
249
525
|
}
|
|
250
526
|
|
|
251
527
|
function roleTopicNames(state, roleName) {
|
|
252
528
|
if (roleName === 'ProcFindDataRole') return [];
|
|
253
|
-
if (roleName === 'FlowObjectsRole')
|
|
254
|
-
|
|
255
|
-
if (roleName === '
|
|
256
|
-
|
|
257
|
-
|
|
529
|
+
if (roleName === 'FlowObjectsRole')
|
|
530
|
+
return state.outTopics.filter(topic => CRUD_ACTIONS.includes(topic));
|
|
531
|
+
if (roleName === 'FlowRelationshipRole')
|
|
532
|
+
return state.outTopics.filter(topic =>
|
|
533
|
+
RELATIONSHIP_ACTIONS.includes(topic)
|
|
534
|
+
);
|
|
535
|
+
if (roleName === 'FlowRbacRole')
|
|
536
|
+
return state.outTopics.filter(topic => RBAC_ACTIONS.includes(topic));
|
|
537
|
+
const flow = state.flows.find(
|
|
538
|
+
candidate => customFlowRoleName(candidate?.flowTag ?? '') === roleName
|
|
539
|
+
);
|
|
540
|
+
return flow
|
|
541
|
+
? state.outTopics.filter(topic => topic === upperFirst(flow.flowTag))
|
|
542
|
+
: [];
|
|
258
543
|
}
|
|
259
544
|
|
|
260
545
|
function dynamoArns(tableNames) {
|
|
261
|
-
return [...new Set(tableNames)]
|
|
546
|
+
return [...new Set(tableNames)]
|
|
547
|
+
.sort()
|
|
548
|
+
.map(
|
|
549
|
+
tableName =>
|
|
550
|
+
`arn:aws:dynamodb:${'${self:custom.iz_region}'}:${'${self:custom.iz_accountId}'}:table/${'${self:custom.iz_resourcePrefix}'}${tableName}`
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Generates DynamoDB ARNs for tables that belong to a parent service
|
|
556
|
+
* (via extendObjType). Uses the parent serviceTag instead of the
|
|
557
|
+
* current service's resourcePrefix, because those tables are owned
|
|
558
|
+
* by a different CloudFormation stack.
|
|
559
|
+
*
|
|
560
|
+
* ARN format:
|
|
561
|
+
* ...table/{parentServiceTag}_{iz_stage}_{tableName}
|
|
562
|
+
*/
|
|
563
|
+
function externalDynamoArns(externalTables) {
|
|
564
|
+
const seen = new Set();
|
|
565
|
+
const unique = [];
|
|
566
|
+
for (const table of externalTables) {
|
|
567
|
+
const dedupeKey = `${table.serviceTag}::${table.tableName}`;
|
|
568
|
+
if (!seen.has(dedupeKey)) {
|
|
569
|
+
seen.add(dedupeKey);
|
|
570
|
+
unique.push(table);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return unique
|
|
574
|
+
.sort((a, b) =>
|
|
575
|
+
`${a.serviceTag}_${a.tableName}`.localeCompare(
|
|
576
|
+
`${b.serviceTag}_${b.tableName}`
|
|
577
|
+
)
|
|
578
|
+
)
|
|
579
|
+
.map(
|
|
580
|
+
({ serviceTag, tableName }) =>
|
|
581
|
+
`arn:aws:dynamodb:${'${self:custom.iz_region}'}:${'${self:custom.iz_accountId}'}:table/${serviceTag}${'${self:custom.iz_stage}'}${tableName}`
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Returns the external DynamoDB table references (cross-service)
|
|
587
|
+
* that should be accessible from a given IAM role.
|
|
588
|
+
*/
|
|
589
|
+
function roleExternalDynamoTables(state, roleName) {
|
|
590
|
+
if (
|
|
591
|
+
roleName === 'FlowObjectsRole' ||
|
|
592
|
+
roleName === 'FlowRelationshipRole' ||
|
|
593
|
+
roleName === 'ProcFindDataRole'
|
|
594
|
+
) {
|
|
595
|
+
return state.externalDynamoTables;
|
|
596
|
+
}
|
|
597
|
+
return [];
|
|
262
598
|
}
|
|
263
599
|
|
|
264
600
|
function queueArns(queueNames) {
|
|
265
|
-
return [...new Set(queueNames)]
|
|
601
|
+
return [...new Set(queueNames)]
|
|
602
|
+
.sort()
|
|
603
|
+
.map(
|
|
604
|
+
queueName =>
|
|
605
|
+
`arn:aws:sqs:${'${self:custom.iz_region}'}:${'${self:custom.iz_accountId}'}:${'${self:custom.iz_resourcePrefix}'}${queueName}`
|
|
606
|
+
);
|
|
266
607
|
}
|
|
267
608
|
|
|
268
609
|
function topicArns(topicNames) {
|
|
269
|
-
return [...new Set(topicNames)]
|
|
610
|
+
return [...new Set(topicNames)]
|
|
611
|
+
.sort()
|
|
612
|
+
.map(
|
|
613
|
+
topic =>
|
|
614
|
+
`arn:aws:sns:${'${self:custom.iz_region}'}:${'${self:custom.iz_accountId}'}:${'${self:custom.iz_serviceTag}'}_${'${self:custom.iz_stage}'}_${topic}_Out`
|
|
615
|
+
);
|
|
270
616
|
}
|
|
271
617
|
|
|
272
618
|
function renderWebSocketSubscription(topicName) {
|
|
@@ -277,11 +623,27 @@ function collectTables(schemas) {
|
|
|
277
623
|
const tables = new Map();
|
|
278
624
|
for (const schema of schemas) {
|
|
279
625
|
if (!schema?.storageResources) continue;
|
|
280
|
-
const identifiers = Array.isArray(schema.identifiers)
|
|
281
|
-
|
|
282
|
-
|
|
626
|
+
const identifiers = Array.isArray(schema.identifiers)
|
|
627
|
+
? schema.identifiers
|
|
628
|
+
: [];
|
|
629
|
+
const pk =
|
|
630
|
+
keyName(
|
|
631
|
+
identifiers.find(
|
|
632
|
+
identifier =>
|
|
633
|
+
identifier.type === 'partitionKey' ||
|
|
634
|
+
identifier.type === 'identifier'
|
|
635
|
+
)
|
|
636
|
+
) ?? 'PK';
|
|
637
|
+
const sk = keyName(
|
|
638
|
+
identifiers.find(identifier => identifier.type === 'sortKey')
|
|
639
|
+
);
|
|
283
640
|
for (const resource of Object.values(schema.storageResources)) {
|
|
284
|
-
if (
|
|
641
|
+
if (
|
|
642
|
+
resource?.storageType === 'dynamoDB' &&
|
|
643
|
+
resource.tableName &&
|
|
644
|
+
!tables.has(resource.tableName)
|
|
645
|
+
)
|
|
646
|
+
tables.set(resource.tableName, { pk, sk });
|
|
285
647
|
}
|
|
286
648
|
}
|
|
287
649
|
return tables;
|
|
@@ -289,12 +651,41 @@ function collectTables(schemas) {
|
|
|
289
651
|
|
|
290
652
|
function keyName(identifier) {
|
|
291
653
|
if (!identifier) return undefined;
|
|
292
|
-
return
|
|
654
|
+
return (
|
|
655
|
+
identifier.name ??
|
|
656
|
+
identifier.fieldName ??
|
|
657
|
+
(Array.isArray(identifier.fieldNames)
|
|
658
|
+
? identifier.fieldNames.join(identifier.deliminator ?? '_')
|
|
659
|
+
: undefined)
|
|
660
|
+
);
|
|
293
661
|
}
|
|
294
662
|
|
|
295
|
-
function isCrudObject(object) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
663
|
+
function isCrudObject(object) {
|
|
664
|
+
return (
|
|
665
|
+
object?.generatedBy === 'userGenerated' ||
|
|
666
|
+
object?.generatedBy === 'systemGenerated'
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
function hasRbacAddOn(object) {
|
|
670
|
+
return (
|
|
671
|
+
Array.isArray(object?.addOnDataStructure) &&
|
|
672
|
+
object.addOnDataStructure.some(addOn => addOn?.type === 'rbac')
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
function includesEvent(flow, event) {
|
|
676
|
+
return Array.isArray(flow?.event) && flow.event.includes(event);
|
|
677
|
+
}
|
|
678
|
+
function upperFirst(value) {
|
|
679
|
+
return typeof value === 'string' && value
|
|
680
|
+
? value[0].toUpperCase() + value.slice(1)
|
|
681
|
+
: '';
|
|
682
|
+
}
|
|
683
|
+
function normalizeYaml(content) {
|
|
684
|
+
return `${content.trimEnd()}\n`;
|
|
685
|
+
}
|
|
686
|
+
async function render(templateName, data) {
|
|
687
|
+
return ejs.render(
|
|
688
|
+
await readFile(new URL(templateName, TEMPLATE_URL), 'utf8'),
|
|
689
|
+
{ resourceQueueName: data.queueName, ...data }
|
|
690
|
+
);
|
|
691
|
+
}
|