@izara_project/izara-core-generate-service-code 1.0.82 → 1.0.84
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/package.json +4 -4
- package/src/compile/flowValidator.js +34 -0
- package/src/compile/loadSchemas.js +121 -7
- package/src/emit/attributeTree/emitAttributeTreeSchemas.js +388 -0
- package/src/emit/coreFunction/emitCoreFunctionArtifacts.js +29 -1
- package/src/emit/coreFunction/templates/InitialSetupConfig.ejs +16 -3
- package/src/emit/crud/crudActionPlan.js +43 -7
- package/src/emit/crud/emitCrudApplicationArtifacts.js +19 -4
- package/src/emit/crud/emitDynamicCrudFlowSchemas.js +27 -6
- package/src/emit/crud/generateCrudApplicationArtifacts.js +3 -2
- package/src/emit/crud/generateDynamicCrudFlowSchemas.js +3 -2
- package/src/emit/crud/loadLocalSchemaArrays.js +3 -3
- package/src/emit/crud/templates/CreateComplete_Main.ejs +1 -1
- package/src/emit/crud/templates/CreateEndpoint_Main.ejs +75 -21
- package/src/emit/crud/templates/CreateLogicalLib.ejs +73 -8
- package/src/emit/crud/templates/DeleteComplete_Main.ejs +1 -1
- package/src/emit/crud/templates/DeleteEndpoint_Main.ejs +39 -11
- package/src/emit/crud/templates/DeleteLogicalLib.ejs +6 -3
- package/src/emit/crud/templates/FlowEndpointRbacCallback_Main.ejs +2 -2
- package/src/emit/crud/templates/FlowEndpoint_HdrWbs.ejs +7 -0
- package/src/emit/crud/templates/FlowMain_Wbs.ejs +5 -2
- package/src/emit/crud/templates/GetEndpoint_Main.ejs +13 -0
- package/src/emit/crud/templates/UpdateComplete_Main.ejs +1 -1
- package/src/emit/crud/templates/UpdateEndpoint_Main.ejs +53 -4
- package/src/emit/crud/templates/UpdateLogicalLib.ejs +4 -3
- package/src/emit/custom/customFlows.js +49 -0
- package/src/emit/custom/emitCustomFlowApplicationArtifacts.js +168 -49
- package/src/emit/custom/generateCustomFlowApplicationArtifacts.js +1 -1
- package/src/emit/custom/templates/FlowEndpointRbacCallback_Main.ejs +194 -0
- package/src/emit/custom/templates/FlowEndpoint_Main.ejs +14 -22
- package/src/emit/custom/templates/FlowStep_Main.ejs +1 -1
- package/src/emit/function/templates/WbsFunction_Yaml.ejs +0 -2
- package/src/emit/rbac/emitDynamicRbacFlowSchemas.js +33 -8
- package/src/emit/rbac/emitRbacApplicationArtifacts.js +204 -7
- package/src/emit/rbac/templates/CancelUsage_Main.ejs +103 -0
- package/src/emit/rbac/templates/ConfirmUsage_Main.ejs +71 -0
- package/src/emit/rbac/templates/CreateTargetRole_Main.ejs +49 -6
- package/src/emit/rbac/templates/ListRbac_Main.ejs +9 -3
- package/src/emit/rbac/templates/RbacMutation_Main.ejs +1 -1
- package/src/emit/rbac/templates/ReservedDynamicUsage_Main.ejs +147 -0
- package/src/emit/rbac/templates/UserRbacFlow_Main.ejs +59 -23
- package/src/emit/rbac/templates/UserRoleMutation_Main.ejs +1 -1
- package/src/emit/relationship/emitDynamicRelationshipFlowSchemas.js +1 -1
- package/src/emit/relationship/templates/ChangeRelationship_Main.ejs +10 -3
- package/src/emit/resource/emitResourceArtifacts.js +350 -112
- package/src/emit/resource/templates/SystemDynamoDB_Yaml.ejs +53 -2
- package/src/emit/system/emitSystemWebSocketArtifacts.js +6 -4
- package/src/emit/system/templates/WebSocketConnect.ejs +71 -12
- package/src/emit/system/templates/WebSocketConnect_Yaml.ejs +7 -0
- package/src/emit/systemText/emitSystemTextSchemas.js +3 -2
- package/src/generate.js +41 -10
- package/test/attributeTreeSchemas.test.js +219 -0
- package/test/createTargetRoleHandler.test.js +59 -4
- package/test/crudApplicationArtifacts.test.js +23 -6
- package/test/customFlowApplicationArtifacts.test.js +5 -5
- package/test/dynamicCrudFlowSchemas.test.js +110 -13
- package/test/dynamicRbacFlowSchemas.test.js +15 -2
- package/test/publicApi.test.js +7 -7
- package/test/resourceArtifacts.test.js +70 -29
- package/test/systemTextSchemas.test.js +1 -1
|
@@ -144,12 +144,12 @@ Resources:
|
|
|
144
144
|
TableName: ${self:custom.iz_resourcePrefix}TargetRoles
|
|
145
145
|
BillingMode: PAY_PER_REQUEST
|
|
146
146
|
AttributeDefinitions:
|
|
147
|
-
- AttributeName:
|
|
147
|
+
- AttributeName: identifiersConcat
|
|
148
148
|
AttributeType: S
|
|
149
149
|
- AttributeName: roleIdKey
|
|
150
150
|
AttributeType: S
|
|
151
151
|
KeySchema:
|
|
152
|
-
- AttributeName:
|
|
152
|
+
- AttributeName: identifiersConcat
|
|
153
153
|
KeyType: HASH
|
|
154
154
|
- AttributeName: roleIdKey
|
|
155
155
|
KeyType: RANGE
|
|
@@ -220,6 +220,57 @@ Resources:
|
|
|
220
220
|
TimeToLiveSpecification:
|
|
221
221
|
AttributeName: ttlTimestamp
|
|
222
222
|
Enabled: true
|
|
223
|
+
|
|
224
|
+
RoleLimitTable:
|
|
225
|
+
Type: 'AWS::DynamoDB::Table'
|
|
226
|
+
Properties:
|
|
227
|
+
TableName: ${self:custom.iz_resourcePrefix}RoleLimit
|
|
228
|
+
BillingMode: PAY_PER_REQUEST
|
|
229
|
+
AttributeDefinitions:
|
|
230
|
+
- AttributeName: roleLimitId
|
|
231
|
+
AttributeType: S
|
|
232
|
+
- AttributeName: limitType
|
|
233
|
+
AttributeType: S
|
|
234
|
+
KeySchema:
|
|
235
|
+
- AttributeName: roleLimitId
|
|
236
|
+
KeyType: HASH
|
|
237
|
+
- AttributeName: limitType
|
|
238
|
+
KeyType: RANGE
|
|
239
|
+
|
|
240
|
+
RunningTotalUsageTable:
|
|
241
|
+
Type: 'AWS::DynamoDB::Table'
|
|
242
|
+
Properties:
|
|
243
|
+
TableName: ${self:custom.iz_resourcePrefix}RunningTotalUsage
|
|
244
|
+
BillingMode: PAY_PER_REQUEST
|
|
245
|
+
AttributeDefinitions:
|
|
246
|
+
- AttributeName: identifiersConcat
|
|
247
|
+
AttributeType: S
|
|
248
|
+
- AttributeName: limitTag
|
|
249
|
+
AttributeType: S
|
|
250
|
+
KeySchema:
|
|
251
|
+
- AttributeName: identifiersConcat
|
|
252
|
+
KeyType: HASH
|
|
253
|
+
- AttributeName: limitTag
|
|
254
|
+
KeyType: RANGE
|
|
255
|
+
|
|
256
|
+
RunningTotalReservedUsageTable:
|
|
257
|
+
Type: 'AWS::DynamoDB::Table'
|
|
258
|
+
Properties:
|
|
259
|
+
TableName: ${self:custom.iz_resourcePrefix}RunningTotalReservedUsage
|
|
260
|
+
BillingMode: PAY_PER_REQUEST
|
|
261
|
+
AttributeDefinitions:
|
|
262
|
+
- AttributeName: runningTotalReservedId
|
|
263
|
+
AttributeType: S
|
|
264
|
+
- AttributeName: usedTimestampId
|
|
265
|
+
AttributeType: S
|
|
266
|
+
KeySchema:
|
|
267
|
+
- AttributeName: runningTotalReservedId
|
|
268
|
+
KeyType: HASH
|
|
269
|
+
- AttributeName: usedTimestampId
|
|
270
|
+
KeyType: RANGE
|
|
271
|
+
TimeToLiveSpecification:
|
|
272
|
+
AttributeName: ttlTimestamp
|
|
273
|
+
Enabled: true
|
|
223
274
|
<% } %>
|
|
224
275
|
<% if (hasS3) { %>
|
|
225
276
|
ReservedDataMainTable:
|
|
@@ -14,9 +14,9 @@ async function _renderSystemTemplate(templateName, data = {}) {
|
|
|
14
14
|
|
|
15
15
|
export async function emitSystemWebSocketArtifacts(
|
|
16
16
|
flows,
|
|
17
|
-
{ includeRelationshipOwnTopic = false } = {}
|
|
17
|
+
{ includeRelationshipOwnTopic = false, objects = [], relationships = [] } = {}
|
|
18
18
|
) {
|
|
19
|
-
if (!hasSystemWebSocket(flows, { includeRelationshipOwnTopic })) {
|
|
19
|
+
if (!hasSystemWebSocket(flows, { includeRelationshipOwnTopic, objects, relationships })) {
|
|
20
20
|
return { artifacts: [], functions: [] };
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -115,8 +115,10 @@ export function systemWebSocketManagedPaths() {
|
|
|
115
115
|
|
|
116
116
|
export function hasSystemWebSocket(
|
|
117
117
|
flows,
|
|
118
|
-
{ includeRelationshipOwnTopic = false } = {}
|
|
118
|
+
{ includeRelationshipOwnTopic = false, objects = [], relationships = [] } = {}
|
|
119
119
|
) {
|
|
120
|
+
if (Array.isArray(objects) && objects.length > 0) return true;
|
|
121
|
+
if (Array.isArray(relationships) && relationships.length > 0) return true;
|
|
120
122
|
return (
|
|
121
123
|
Array.isArray(flows) &&
|
|
122
124
|
flows.some(
|
|
@@ -125,7 +127,7 @@ export function hasSystemWebSocket(
|
|
|
125
127
|
(flow.event.includes('s3') ||
|
|
126
128
|
(includeRelationshipOwnTopic &&
|
|
127
129
|
flow.event.includes('ownTopic') &&
|
|
128
|
-
flow.initiateBy === 'user'))
|
|
130
|
+
(flow.initiateBy === 'user' || flow.initiateBy === 'systemWithUser')))
|
|
129
131
|
)
|
|
130
132
|
);
|
|
131
133
|
}
|
|
@@ -1,45 +1,104 @@
|
|
|
1
1
|
import dynamodbSharedLib from '@izara_project/izara-core-library-dynamodb';
|
|
2
2
|
import {
|
|
3
3
|
middlewareHandler as middleware,
|
|
4
|
+
consts,
|
|
4
5
|
response
|
|
5
6
|
} from '@izara_project/izara-middleware';
|
|
6
7
|
|
|
7
8
|
export const main = middleware.wrap(async (event) => {
|
|
8
9
|
const { eventType, connectionId } = event.requestContext ?? {};
|
|
9
10
|
try {
|
|
10
|
-
event._izContext.logger.debug('Event:', event);
|
|
11
|
+
event._izContext.logger.debug('[Lib:WebSocketMain:webSocketConnect] Event:', event);
|
|
11
12
|
if (eventType === 'CONNECT') {
|
|
12
|
-
const
|
|
13
|
+
const fromCorrelationIds =
|
|
14
|
+
event._izContext?.correlationIds?.get(consts.SUBMITTED_BY_USER_ID);
|
|
15
|
+
const fromAuthorizerSubmittedBy =
|
|
16
|
+
event.requestContext?.authorizer?.submittedByUserId;
|
|
17
|
+
const fromAuthorizerPrincipal =
|
|
18
|
+
event.requestContext?.authorizer?.principalId;
|
|
19
|
+
const fromQueryString =
|
|
20
|
+
event.queryStringParameters?.submittedByUserId;
|
|
21
|
+
|
|
22
|
+
const submittedByUserId =
|
|
23
|
+
fromCorrelationIds ||
|
|
24
|
+
fromAuthorizerSubmittedBy ||
|
|
25
|
+
fromAuthorizerPrincipal ||
|
|
26
|
+
fromQueryString;
|
|
27
|
+
|
|
28
|
+
event._izContext.logger.debug(
|
|
29
|
+
'[Lib:WebSocketMain:webSocketConnect] submittedByUserId sources:',
|
|
30
|
+
{
|
|
31
|
+
connectionId,
|
|
32
|
+
fromCorrelationIds,
|
|
33
|
+
fromAuthorizerSubmittedBy,
|
|
34
|
+
fromAuthorizerPrincipal,
|
|
35
|
+
fromQueryString,
|
|
36
|
+
resolvedSubmittedByUserId: submittedByUserId,
|
|
37
|
+
authorizer: event.requestContext?.authorizer
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const tableName = await dynamodbSharedLib.tableName(
|
|
42
|
+
event._izContext,
|
|
43
|
+
'UserUploadRecords'
|
|
44
|
+
);
|
|
45
|
+
event._izContext.logger.debug(
|
|
46
|
+
'[Lib:WebSocketMain:webSocketConnect] Saving connection to table:',
|
|
47
|
+
{
|
|
48
|
+
tableName,
|
|
49
|
+
connectionId,
|
|
50
|
+
submittedByUserId
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
|
|
13
54
|
await dynamodbSharedLib.putItem(
|
|
14
55
|
event._izContext,
|
|
15
|
-
|
|
16
|
-
event._izContext,
|
|
17
|
-
'UserUploadRecords'
|
|
18
|
-
),
|
|
56
|
+
tableName,
|
|
19
57
|
{
|
|
20
58
|
connectionId,
|
|
21
|
-
userId,
|
|
22
59
|
submittedByUserId,
|
|
23
60
|
expirationTime: Math.floor(Date.now() / 1000) + 7200
|
|
24
61
|
}
|
|
25
62
|
);
|
|
63
|
+
event._izContext.logger.debug(
|
|
64
|
+
'[Lib:WebSocketMain:webSocketConnect] Successfully stored connection record'
|
|
65
|
+
);
|
|
26
66
|
//(<webSocketConnect>)
|
|
27
67
|
//(</webSocketConnect>)
|
|
28
68
|
} else if (eventType === 'DISCONNECT') {
|
|
69
|
+
const tableName = await dynamodbSharedLib.tableName(
|
|
70
|
+
event._izContext,
|
|
71
|
+
'UserUploadRecords'
|
|
72
|
+
);
|
|
73
|
+
event._izContext.logger.debug(
|
|
74
|
+
'[Lib:WebSocketMain:webSocketConnect] WebSocket DISCONNECT:',
|
|
75
|
+
{
|
|
76
|
+
tableName,
|
|
77
|
+
connectionId
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
|
|
29
81
|
await dynamodbSharedLib.deleteItem(
|
|
30
82
|
event._izContext,
|
|
31
|
-
|
|
32
|
-
event._izContext,
|
|
33
|
-
'UserUploadRecords'
|
|
34
|
-
),
|
|
83
|
+
tableName,
|
|
35
84
|
{ connectionId }
|
|
36
85
|
);
|
|
86
|
+
event._izContext.logger.debug(
|
|
87
|
+
'[Lib:WebSocketMain:webSocketConnect] Successfully deleted connection record'
|
|
88
|
+
);
|
|
37
89
|
//(<webSocketDisconnect>)
|
|
38
90
|
//(</webSocketDisconnect>)
|
|
39
91
|
}
|
|
92
|
+
|
|
93
|
+
event._izContext.logger.debug(
|
|
94
|
+
'[Lib:WebSocketMain:webSocketConnect] WebSocket event processed successfully'
|
|
95
|
+
);
|
|
40
96
|
return response.webSocketSuccess();
|
|
41
97
|
} catch (error) {
|
|
42
|
-
event._izContext.logger.error(
|
|
98
|
+
event._izContext.logger.error(
|
|
99
|
+
'[Lib:WebSocketMain:webSocketConnect] Error: ',
|
|
100
|
+
error
|
|
101
|
+
);
|
|
43
102
|
return response.failure(error);
|
|
44
103
|
}
|
|
45
104
|
});
|
|
@@ -5,6 +5,13 @@ WebSocketConnect:
|
|
|
5
5
|
events:
|
|
6
6
|
- websocket:
|
|
7
7
|
route: $connect
|
|
8
|
+
#<#WebSocketConnectAuthorizer#>
|
|
9
|
+
authorizer:
|
|
10
|
+
arn: arn:aws:lambda:${self:custom.iz_region}:${self:custom.iz_accountId}:function:UserAccount${self:custom.iz_stage}UserIdentityAuthorizer
|
|
11
|
+
identitySource:
|
|
12
|
+
- 'route.request.header.Authorization'
|
|
13
|
+
- 'route.request.querystring.access_token'
|
|
14
|
+
#<#/WebSocketConnectAuthorizer#>
|
|
8
15
|
- websocket:
|
|
9
16
|
route: $disconnect
|
|
10
17
|
- websocket:
|
|
@@ -177,8 +177,9 @@ export async function emitSystemTextSchemas({
|
|
|
177
177
|
// Dynamic CRUD flows
|
|
178
178
|
const hasCrudObjects = safeObjects.some(
|
|
179
179
|
(object) =>
|
|
180
|
-
object?.generatedBy === '
|
|
181
|
-
object?.generatedBy === '
|
|
180
|
+
object?.generatedBy === 'user' ||
|
|
181
|
+
object?.generatedBy === 'systemWithUser' ||
|
|
182
|
+
object?.generatedBy === 'systemNoUser'
|
|
182
183
|
);
|
|
183
184
|
if (hasCrudObjects) {
|
|
184
185
|
for (const action of CRUD_ACTIONS) {
|
package/src/generate.js
CHANGED
|
@@ -55,6 +55,12 @@ import {
|
|
|
55
55
|
emitSystemTextSchemas,
|
|
56
56
|
systemTextSchemaManagedPaths
|
|
57
57
|
} from './emit/systemText/emitSystemTextSchemas.js';
|
|
58
|
+
import {
|
|
59
|
+
emitAttributeTreeObjectSchemas,
|
|
60
|
+
emitAttributeTreeRelationshipSchemas,
|
|
61
|
+
attributeTreeObjectSchemaManagedPaths,
|
|
62
|
+
attributeTreeRelationshipSchemaManagedPaths
|
|
63
|
+
} from './emit/attributeTree/emitAttributeTreeSchemas.js';
|
|
58
64
|
|
|
59
65
|
export async function generate(rootPath, options = {}) {
|
|
60
66
|
const startedAt = Date.now();
|
|
@@ -62,11 +68,15 @@ export async function generate(rootPath, options = {}) {
|
|
|
62
68
|
logger?.info?.(`[INFO] [Generate] STATUS=STARTED | rootPath=${rootPath}`);
|
|
63
69
|
try {
|
|
64
70
|
const project = compileProject({ ...options, rootPath });
|
|
71
|
+
const schemaLoadOptions = {
|
|
72
|
+
logger,
|
|
73
|
+
skipValidation: options.skipValidation
|
|
74
|
+
};
|
|
65
75
|
const [rawObjects, relationships, flows, notifications] = await Promise.all([
|
|
66
|
-
loadLocalSchemaDirectory(project, 'ObjectSchemas'),
|
|
67
|
-
loadLocalSchemaDirectory(project, 'RelationshipSchemas'),
|
|
68
|
-
loadLocalSchemaDirectory(project, 'FlowSchemas'),
|
|
69
|
-
loadLocalSchemaDirectory(project, 'NotificationSchemas')
|
|
76
|
+
loadLocalSchemaDirectory(project, 'ObjectSchemas', schemaLoadOptions),
|
|
77
|
+
loadLocalSchemaDirectory(project, 'RelationshipSchemas', schemaLoadOptions),
|
|
78
|
+
loadLocalSchemaDirectory(project, 'FlowSchemas', schemaLoadOptions),
|
|
79
|
+
loadLocalSchemaDirectory(project, 'NotificationSchemas', schemaLoadOptions)
|
|
70
80
|
]);
|
|
71
81
|
const schemaBucketName =
|
|
72
82
|
options.schemaBucketName || process.env.iz_serviceSchemaBucketName;
|
|
@@ -92,7 +102,8 @@ export async function generate(rootPath, options = {}) {
|
|
|
92
102
|
flows,
|
|
93
103
|
notifications,
|
|
94
104
|
objectGraphServiceTags,
|
|
95
|
-
relationshipGraphServiceTags
|
|
105
|
+
relationshipGraphServiceTags,
|
|
106
|
+
logger
|
|
96
107
|
});
|
|
97
108
|
logger?.debug?.(
|
|
98
109
|
`[DEBUG] [Generate] STATUS=ARTIFACTS_EMITTED | ${groups.map((group) => `${group.scope}=${group.artifacts.length}`).join(' ')}`
|
|
@@ -133,7 +144,8 @@ async function _emitGroups({
|
|
|
133
144
|
flows,
|
|
134
145
|
notifications,
|
|
135
146
|
objectGraphServiceTags,
|
|
136
|
-
relationshipGraphServiceTags
|
|
147
|
+
relationshipGraphServiceTags,
|
|
148
|
+
logger
|
|
137
149
|
}) {
|
|
138
150
|
const [
|
|
139
151
|
crud,
|
|
@@ -148,7 +160,8 @@ async function _emitGroups({
|
|
|
148
160
|
emitCrudApplicationArtifacts({
|
|
149
161
|
objects,
|
|
150
162
|
flows,
|
|
151
|
-
serviceTag: project.serviceTag
|
|
163
|
+
serviceTag: project.serviceTag,
|
|
164
|
+
logger
|
|
152
165
|
}),
|
|
153
166
|
emitRelationshipApplicationArtifacts({
|
|
154
167
|
relationships,
|
|
@@ -157,7 +170,9 @@ async function _emitGroups({
|
|
|
157
170
|
}),
|
|
158
171
|
emitRbacApplicationArtifacts(objects),
|
|
159
172
|
emitSystemWebSocketArtifacts(flows, {
|
|
160
|
-
includeRelationshipOwnTopic: true
|
|
173
|
+
includeRelationshipOwnTopic: true,
|
|
174
|
+
objects,
|
|
175
|
+
relationships
|
|
161
176
|
}),
|
|
162
177
|
emitSystemFindDataArtifacts(objects),
|
|
163
178
|
emitResourceArtifacts({ objects, relationships, flows }),
|
|
@@ -211,7 +226,7 @@ async function _emitGroups({
|
|
|
211
226
|
{
|
|
212
227
|
scope: 'crud-flow-schemas',
|
|
213
228
|
managedPaths: dynamicCrudFlowSchemaPaths,
|
|
214
|
-
artifacts: emitDynamicCrudFlowSchemas({ objects, flows })
|
|
229
|
+
artifacts: emitDynamicCrudFlowSchemas({ objects, flows, logger })
|
|
215
230
|
},
|
|
216
231
|
{
|
|
217
232
|
scope: 'relationship-flow-schemas',
|
|
@@ -226,6 +241,22 @@ async function _emitGroups({
|
|
|
226
241
|
serviceTag: project.serviceTag
|
|
227
242
|
})
|
|
228
243
|
},
|
|
244
|
+
{
|
|
245
|
+
scope: 'attribute-tree-object-schemas',
|
|
246
|
+
managedPaths: attributeTreeObjectSchemaManagedPaths(objects),
|
|
247
|
+
artifacts: emitAttributeTreeObjectSchemas({
|
|
248
|
+
objects,
|
|
249
|
+
serviceTag: project.serviceTag
|
|
250
|
+
})
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
scope: 'attribute-tree-relationship-schemas',
|
|
254
|
+
managedPaths: attributeTreeRelationshipSchemaManagedPaths(objects),
|
|
255
|
+
artifacts: emitAttributeTreeRelationshipSchemas({
|
|
256
|
+
objects,
|
|
257
|
+
serviceTag: project.serviceTag
|
|
258
|
+
})
|
|
259
|
+
},
|
|
229
260
|
{
|
|
230
261
|
scope: 'crud-application',
|
|
231
262
|
managedPaths: crudApplicationManagedPaths(),
|
|
@@ -253,7 +284,7 @@ async function _emitGroups({
|
|
|
253
284
|
},
|
|
254
285
|
{
|
|
255
286
|
scope: 'custom-flow-application',
|
|
256
|
-
managedPaths: customFlowApplicationManagedPaths(flows),
|
|
287
|
+
managedPaths: customFlowApplicationManagedPaths(flows, { objects }),
|
|
257
288
|
artifacts: custom
|
|
258
289
|
},
|
|
259
290
|
{
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import test, { after } from 'node:test';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
emitAttributeTreeObjectSchemas,
|
|
9
|
+
emitAttributeTreeRelationshipSchemas,
|
|
10
|
+
attributeTreeObjectSchemaManagedPaths,
|
|
11
|
+
attributeTreeRelationshipSchemaManagedPaths
|
|
12
|
+
} from '../src/emit/attributeTree/emitAttributeTreeSchemas.js';
|
|
13
|
+
import { generate } from '../src/generate.js';
|
|
14
|
+
|
|
15
|
+
const projectRoots = new Set();
|
|
16
|
+
|
|
17
|
+
after(async () => {
|
|
18
|
+
await Promise.all(
|
|
19
|
+
[...projectRoots].map((rootPath) =>
|
|
20
|
+
rm(rootPath, { recursive: true, force: true })
|
|
21
|
+
)
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('emitAttributeTreeObjectSchemas returns 4 object schemas per attributeTree tag', () => {
|
|
26
|
+
const objects = [
|
|
27
|
+
{
|
|
28
|
+
objectType: 'sellOffer',
|
|
29
|
+
serviceTag: 'SellOfferManager',
|
|
30
|
+
storageResources: {
|
|
31
|
+
dynamoDB: {
|
|
32
|
+
storageType: 'dynamoDB',
|
|
33
|
+
tableName: 'SellOfferRecords'
|
|
34
|
+
},
|
|
35
|
+
myGraph: {
|
|
36
|
+
storageType: 'graph',
|
|
37
|
+
graphServerTag: 'GraphHandler'
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
addOnDataStructure: [
|
|
41
|
+
{
|
|
42
|
+
type: 'attributeTree',
|
|
43
|
+
attributeTreeTag: 'sellOfferTerm'
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const artifacts = emitAttributeTreeObjectSchemas({ objects });
|
|
50
|
+
assert.equal(artifacts.length, 4);
|
|
51
|
+
|
|
52
|
+
const paths = artifacts.map((a) => a.path);
|
|
53
|
+
assert.deepEqual(paths, [
|
|
54
|
+
'app/src/schemas/ObjectSchemas/generatedAttributeTree/sellOfferTermLink.js',
|
|
55
|
+
'app/src/schemas/ObjectSchemas/generatedAttributeTree/attributeSellOfferTermValue.js',
|
|
56
|
+
'app/src/schemas/ObjectSchemas/generatedAttributeTree/sellOfferTermLabel.js',
|
|
57
|
+
'app/src/schemas/ObjectSchemas/generatedAttributeTree/sellOfferTermValueStandard.js'
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
for (const artifact of artifacts) {
|
|
61
|
+
assert.doesNotMatch(artifact.content, /dynamoDB/);
|
|
62
|
+
assert.match(artifact.content, /GraphHandler/);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('emitAttributeTreeRelationshipSchemas returns 4 relationship schemas per attributeTree tag', () => {
|
|
67
|
+
const objects = [
|
|
68
|
+
{
|
|
69
|
+
objectType: 'sellOffer',
|
|
70
|
+
serviceTag: 'SellOfferManager',
|
|
71
|
+
storageResources: {
|
|
72
|
+
dynamoDB: {
|
|
73
|
+
storageType: 'dynamoDB',
|
|
74
|
+
tableName: 'SellOfferRecords'
|
|
75
|
+
},
|
|
76
|
+
myGraph: {
|
|
77
|
+
storageType: 'graph',
|
|
78
|
+
graphServerTag: 'GraphHandler'
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
addOnDataStructure: [
|
|
82
|
+
{
|
|
83
|
+
type: 'attributeTree',
|
|
84
|
+
attributeTreeTag: 'sellOfferTerm'
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
const artifacts = emitAttributeTreeRelationshipSchemas({
|
|
91
|
+
objects,
|
|
92
|
+
serviceTag: 'SellOfferManager'
|
|
93
|
+
});
|
|
94
|
+
assert.equal(artifacts.length, 4);
|
|
95
|
+
|
|
96
|
+
const paths = artifacts.map((a) => a.path);
|
|
97
|
+
assert.deepEqual(paths, [
|
|
98
|
+
'app/src/schemas/RelationshipSchemas/generatedAttributeTree/hasSellOfferTermLink.js',
|
|
99
|
+
'app/src/schemas/RelationshipSchemas/generatedAttributeTree/disabledSellOfferTermLink.js',
|
|
100
|
+
'app/src/schemas/RelationshipSchemas/generatedAttributeTree/isSellOfferTerm.js',
|
|
101
|
+
'app/src/schemas/RelationshipSchemas/generatedAttributeTree/isSellOfferTermLabel.js'
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
for (const artifact of artifacts) {
|
|
105
|
+
assert.doesNotMatch(artifact.content, /dynamoDB/);
|
|
106
|
+
assert.match(artifact.content, /GraphHandler/);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('managed paths return empty array when no attributeTree is present', () => {
|
|
111
|
+
const objects = [{ objectType: 'user', addOnDataStructure: [] }];
|
|
112
|
+
assert.deepEqual(attributeTreeObjectSchemaManagedPaths(objects), []);
|
|
113
|
+
assert.deepEqual(attributeTreeRelationshipSchemaManagedPaths(objects), []);
|
|
114
|
+
assert.deepEqual(emitAttributeTreeObjectSchemas({ objects }), []);
|
|
115
|
+
assert.deepEqual(emitAttributeTreeRelationshipSchemas({ objects }), []);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('generate commits and cleans up attributeTree dynamic schemas', async () => {
|
|
119
|
+
const rootPath = await createProjectWithAttributeTree({ hasAddOn: true });
|
|
120
|
+
const result = await generate(rootPath);
|
|
121
|
+
|
|
122
|
+
const expectedObjectPath = join(
|
|
123
|
+
rootPath,
|
|
124
|
+
'app/src/schemas/ObjectSchemas/generatedAttributeTree/sellOfferTermLink.js'
|
|
125
|
+
);
|
|
126
|
+
const expectedRelPath = join(
|
|
127
|
+
rootPath,
|
|
128
|
+
'app/src/schemas/RelationshipSchemas/generatedAttributeTree/hasSellOfferTermLink.js'
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const objectContent = await readFile(expectedObjectPath, 'utf8');
|
|
132
|
+
assert.match(objectContent, /sellOfferTermLink/);
|
|
133
|
+
assert.match(objectContent, /GraphHandler/);
|
|
134
|
+
assert.doesNotMatch(objectContent, /dynamoDB/);
|
|
135
|
+
|
|
136
|
+
const relContent = await readFile(expectedRelPath, 'utf8');
|
|
137
|
+
assert.match(relContent, /hasSellOfferTermLink/);
|
|
138
|
+
assert.match(relContent, /sellOfferTermLink/);
|
|
139
|
+
|
|
140
|
+
// Re-run without addOn to verify stale file cleanup
|
|
141
|
+
await createProjectWithAttributeTree({ rootPath, hasAddOn: false });
|
|
142
|
+
await generate(rootPath);
|
|
143
|
+
|
|
144
|
+
await assert.rejects(readFile(expectedObjectPath, 'utf8'), {
|
|
145
|
+
code: 'ENOENT'
|
|
146
|
+
});
|
|
147
|
+
await assert.rejects(readFile(expectedRelPath, 'utf8'), {
|
|
148
|
+
code: 'ENOENT'
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
async function createProjectWithAttributeTree({
|
|
153
|
+
rootPath: existingRoot,
|
|
154
|
+
hasAddOn = true
|
|
155
|
+
} = {}) {
|
|
156
|
+
const rootPath =
|
|
157
|
+
existingRoot || (await mkdtemp(join(tmpdir(), 'izara-attr-tree-test-')));
|
|
158
|
+
projectRoots.add(rootPath);
|
|
159
|
+
|
|
160
|
+
const schemaRoot = join(rootPath, 'app/src/schemas');
|
|
161
|
+
await mkdir(join(schemaRoot, 'ObjectSchemas'), { recursive: true });
|
|
162
|
+
await mkdir(join(schemaRoot, 'RelationshipSchemas'), { recursive: true });
|
|
163
|
+
await mkdir(join(schemaRoot, 'FlowSchemas'), { recursive: true });
|
|
164
|
+
await mkdir(join(rootPath, 'config'), { recursive: true });
|
|
165
|
+
|
|
166
|
+
await writeFile(
|
|
167
|
+
join(rootPath, 'config/serverless.config.yml'),
|
|
168
|
+
'service: SellOfferManager\ncustom:\n serviceTag: SellOfferManager\n'
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const addOnContent = hasAddOn
|
|
172
|
+
? `
|
|
173
|
+
addOnDataStructure: [
|
|
174
|
+
{
|
|
175
|
+
type: 'attributeTree',
|
|
176
|
+
attributeTreeTag: 'sellOfferTerm'
|
|
177
|
+
}
|
|
178
|
+
],`
|
|
179
|
+
: ' addOnDataStructure: [],';
|
|
180
|
+
|
|
181
|
+
await writeFile(
|
|
182
|
+
join(schemaRoot, 'ObjectSchemas/SellOffer.js'),
|
|
183
|
+
`export default [
|
|
184
|
+
{
|
|
185
|
+
objectType: 'sellOffer',
|
|
186
|
+
canDelete: false,
|
|
187
|
+
generatedBy: 'user',${addOnContent}
|
|
188
|
+
storageResources: {
|
|
189
|
+
dynamoDB: {
|
|
190
|
+
storageType: 'dynamoDB',
|
|
191
|
+
tableName: 'SellOfferRecords',
|
|
192
|
+
serviceTag: 'SellOfferManager'
|
|
193
|
+
},
|
|
194
|
+
myGraph: {
|
|
195
|
+
storageType: 'graph',
|
|
196
|
+
graphServerTag: 'GraphHandler'
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
fieldNames: {
|
|
200
|
+
sellOfferId: {
|
|
201
|
+
type: 'string',
|
|
202
|
+
randomOnCreate: true,
|
|
203
|
+
canUpdate: false,
|
|
204
|
+
storageResourceTags: ['dynamoDB', 'myGraph']
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
identifiers: [
|
|
208
|
+
{
|
|
209
|
+
type: 'partitionKey',
|
|
210
|
+
fieldName: 'sellOfferId'
|
|
211
|
+
}
|
|
212
|
+
]
|
|
213
|
+
}
|
|
214
|
+
];
|
|
215
|
+
`
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
return rootPath;
|
|
219
|
+
}
|
|
@@ -13,8 +13,22 @@ const OWN_TOPIC_FLOWS = [
|
|
|
13
13
|
'DeleteRolePermissions',
|
|
14
14
|
'DeleteTargetRole',
|
|
15
15
|
'CreateUserRole',
|
|
16
|
-
'DeleteUserFromRole'
|
|
16
|
+
'DeleteUserFromRole',
|
|
17
|
+
'CreateRoleLimit',
|
|
18
|
+
'DeleteRoleLimit',
|
|
19
|
+
'ReservedDynamicUsage',
|
|
20
|
+
'ConfirmUsage',
|
|
21
|
+
'CancelUsage'
|
|
17
22
|
];
|
|
23
|
+
|
|
24
|
+
const LIMIT_FLOWS = [
|
|
25
|
+
'CreateRoleLimit',
|
|
26
|
+
'DeleteRoleLimit',
|
|
27
|
+
'ReservedDynamicUsage',
|
|
28
|
+
'ConfirmUsage',
|
|
29
|
+
'CancelUsage'
|
|
30
|
+
];
|
|
31
|
+
|
|
18
32
|
after(() =>
|
|
19
33
|
Promise.all(
|
|
20
34
|
[...roots].map((root) => rm(root, { recursive: true, force: true }))
|
|
@@ -25,11 +39,15 @@ test('generates RBAC own-topic WebSocket entries alongside their SQS handlers',
|
|
|
25
39
|
const rootPath = await createProject(true);
|
|
26
40
|
const result = await generateCreateTargetRoleHandler(rootPath);
|
|
27
41
|
|
|
28
|
-
assert.equal(result.files.length,
|
|
42
|
+
assert.equal(result.files.length, 60);
|
|
29
43
|
assert.match(
|
|
30
44
|
await readFile(join(rootPath, result.files[0].path), 'utf8'),
|
|
31
45
|
/CreateTargetRole/
|
|
32
46
|
);
|
|
47
|
+
assert.match(
|
|
48
|
+
await readFile(join(rootPath, result.files[0].path), 'utf8'),
|
|
49
|
+
/roleName = 'owner'/
|
|
50
|
+
);
|
|
33
51
|
assert.match(
|
|
34
52
|
await readFile(join(rootPath, result.files[1].path), 'utf8'),
|
|
35
53
|
/CreateTargetRoleHdrSqs/
|
|
@@ -51,16 +69,53 @@ test('generates RBAC own-topic WebSocket entries alongside their SQS handlers',
|
|
|
51
69
|
),
|
|
52
70
|
/ListTargetRole/
|
|
53
71
|
);
|
|
54
|
-
|
|
72
|
+
assert.match(
|
|
73
|
+
await readFile(
|
|
74
|
+
join(
|
|
75
|
+
rootPath,
|
|
76
|
+
'app/src/generatedCode/FlowRbac/ValidatorRbac/source/ValidatorRbac_Main.js'
|
|
77
|
+
),
|
|
78
|
+
'utf8'
|
|
79
|
+
),
|
|
80
|
+
/Fast-path triggered: user self-action/
|
|
81
|
+
);
|
|
82
|
+
assert.match(
|
|
83
|
+
await readFile(
|
|
84
|
+
join(
|
|
85
|
+
rootPath,
|
|
86
|
+
'app/src/generatedCode/FlowLimit/ReservedDynamicUsage/source/ReservedDynamicUsage_Main.js'
|
|
87
|
+
),
|
|
88
|
+
'utf8'
|
|
89
|
+
),
|
|
90
|
+
/Math\.min\(maxIncrease, minDecrease\)/
|
|
91
|
+
);
|
|
92
|
+
assert.match(
|
|
93
|
+
await readFile(
|
|
94
|
+
join(
|
|
95
|
+
rootPath,
|
|
96
|
+
'app/src/generatedCode/FlowLimit/CreateRoleLimit/source/CreateRoleLimit_Main.js'
|
|
97
|
+
),
|
|
98
|
+
'utf8'
|
|
99
|
+
),
|
|
100
|
+
/actionType/
|
|
101
|
+
);
|
|
102
|
+
const rbacYaml = await readFile(
|
|
55
103
|
join(rootPath, 'app/sls_yaml/generatedCode/source/flow-rbac.yml'),
|
|
56
104
|
'utf8'
|
|
57
105
|
);
|
|
106
|
+
const limitYaml = await readFile(
|
|
107
|
+
join(rootPath, 'app/sls_yaml/generatedCode/source/flow-limits.yml'),
|
|
108
|
+
'utf8'
|
|
109
|
+
);
|
|
58
110
|
for (const flowTag of OWN_TOPIC_FLOWS) {
|
|
111
|
+
const isLimit = LIMIT_FLOWS.includes(flowTag);
|
|
112
|
+
const folder = isLimit ? 'FlowLimit' : 'FlowRbac';
|
|
113
|
+
const yaml = isLimit ? limitYaml : rbacYaml;
|
|
59
114
|
assert.match(
|
|
60
115
|
await readFile(
|
|
61
116
|
join(
|
|
62
117
|
rootPath,
|
|
63
|
-
`app/src/generatedCode
|
|
118
|
+
`app/src/generatedCode/${folder}/${flowTag}/source/${flowTag}_HdrWbs.js`
|
|
64
119
|
),
|
|
65
120
|
'utf8'
|
|
66
121
|
),
|