@izara_project/izara-core-generate-service-code 1.0.99 → 1.0.100
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 +1 -1
- package/src/compile/compileProject.js +16 -5
- package/src/compile/flowValidator.js +3 -1
- package/src/compile/loadSchemas.js +18 -4
- package/src/compile/schemaValidatorSetup.js +16 -0
- package/src/emit/application/coreFunction/emitCoreFunctionArtifacts.js +108 -108
- package/src/emit/application/coreFunction/templates/InitialSetupRunner.ejs +2 -2
- package/src/emit/application/crud/templates/CreateComplete_Main.ejs +2 -0
- package/src/emit/application/crud/templates/CreateLogicalLib.ejs +14 -7
- package/src/emit/application/crud/templates/DeleteComplete_Main.ejs +2 -0
- package/src/emit/application/crud/templates/FlowEndpoint_HdrWbs.ejs +7 -5
- package/src/emit/application/crud/templates/UpdateComplete_Main.ejs +2 -0
- package/src/emit/application/custom/customFlowPaths.js +13 -16
- package/src/emit/application/custom/customFlowS3.js +274 -63
- package/src/emit/application/custom/customFlows.js +131 -0
- package/src/emit/application/custom/emitCustomFlowApplicationArtifacts.js +3 -1
- package/src/emit/application/custom/templates/s3/ConfirmAfterUpload_HdrSqs.ejs +110 -0
- package/src/emit/application/custom/templates/s3/ConfirmAfterUpload_Main.ejs +89 -0
- package/src/emit/application/custom/templates/s3/ConfirmComplete_HdrSqs.ejs +110 -0
- package/src/emit/application/custom/templates/s3/ConfirmComplete_Main.ejs +89 -0
- package/src/emit/application/custom/templates/s3/CreateObject_HdrWbs.ejs +2 -1
- package/src/emit/application/custom/templates/s3/CreateObject_Main.ejs +4 -0
- package/src/emit/application/custom/templates/s3/CreatePreSignUrl_HdrSqs.ejs +42 -0
- package/src/emit/application/custom/templates/s3/CreatePreSignUrl_Main.ejs +23 -0
- package/src/emit/application/custom/templates/s3/ProcessS3File_Main.ejs +135 -27
- package/src/emit/application/custom/templates/s3/Start_Main.ejs +197 -0
- package/src/emit/application/rbac/emitDynamicRbacFlowSchemas.js +5 -1
- package/src/emit/application/rbac/emitRbacApplicationArtifacts.js +67 -60
- package/src/emit/application/rbac/templates/ReservedDynamicUsage_Main.ejs +27 -9
- package/src/emit/application/rbac/templates/UserRbacFlow_Main.ejs +4 -3
- package/src/emit/application/system/templates/Register_HdrWbs.ejs +2 -1
- package/src/emit/infrastructure/functions/functionYamlArtifact.js +3 -8
- package/src/emit/infrastructure/functions/templates/S3Function_Yaml.ejs +2 -3
- package/src/emit/infrastructure/resources/iamResourceArtifacts.js +22 -0
- package/src/emit/infrastructure/resources/resourceState.js +15 -5
- package/src/emit/infrastructure/resources/s3ResourceArtifacts.js +19 -3
- package/src/emit/infrastructure/resources/templates/S3Bucket_Yaml.ejs +1 -1
- package/src/emit/infrastructure/resources/templates/SystemDynamoDB_Yaml.ejs +2 -2
- package/src/emit/schemas/error/emitErrorSchemas.js +120 -10
- package/src/emit/schemas/error/templates/errorConsts.ejs +12 -0
- package/src/generate.js +32 -30
- package/test/integration/publicApi.test.js +33 -24
- package/test/unit/application/createTargetRoleHandler.test.js +33 -10
- package/test/unit/application/customFlowApplicationArtifacts.test.js +6 -6
- package/test/unit/application/customFlowS3.test.js +730 -0
- package/test/unit/infrastructure/functionYamlArtifact.test.js +6 -4
- package/test/unit/infrastructure/resourceArtifacts.test.js +51 -0
- package/test/unit/schemas/dynamicRbacFlowSchemas.test.js +8 -6
- package/test/unit/schemas/dynamicRelationshipFlowSchemas.test.js +51 -0
- package/test/unit/schemas/errorSchemas.test.js +84 -5
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (C) 2026 Sven Mason <http://izara.io>
|
|
3
|
+
|
|
4
|
+
This program is free software: you can redistribute it and/or modify
|
|
5
|
+
it under the terms of the GNU Affero General Public License as
|
|
6
|
+
published by the Free Software Foundation, either version 3 of the
|
|
7
|
+
License, or (at your option) any later version.
|
|
8
|
+
|
|
9
|
+
This program is distributed in the hope that it will be useful,
|
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
GNU Affero General Public License for more details.
|
|
13
|
+
|
|
14
|
+
You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { v7 as uuidv7 } from 'uuid';
|
|
19
|
+
import { createPresignedPost } from '@aws-sdk/s3-presigned-post';
|
|
20
|
+
import { S3Client } from '@aws-sdk/client-s3';
|
|
21
|
+
import { consts } from '@izara_project/izara-middleware';
|
|
22
|
+
import dynamodbSharedLib from '@izara_project/izara-core-library-dynamodb';
|
|
23
|
+
import { webSocket } from '@izara_project/izara-core-library-external-request';
|
|
24
|
+
const { postToConnection } = webSocket;
|
|
25
|
+
import { utils } from '@izara_project/izara-core-library-service-schemas';
|
|
26
|
+
const { createFlowTypeConcat } = utils;
|
|
27
|
+
|
|
28
|
+
const client = new S3Client({ region: process.env.AWS_REGION });
|
|
29
|
+
|
|
30
|
+
//(<optionalRequired>)
|
|
31
|
+
//(</optionalRequired>)
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Step 1: Start (Deferred-Node S3 Pipeline - ADR-016)
|
|
35
|
+
* Generates UUIDv7, computes S3 Presigned POST in-memory, writes ephemeral WebSocketTask,
|
|
36
|
+
* and returns upload parameters to client without writing entity records to the database.
|
|
37
|
+
*
|
|
38
|
+
* @param {Object} _izContext
|
|
39
|
+
* @param {Object} requestParams
|
|
40
|
+
* @param {Object} callingFlowConfig
|
|
41
|
+
* @returns {Promise<Object>}
|
|
42
|
+
*/
|
|
43
|
+
export default async function <%- functionName %>(
|
|
44
|
+
_izContext,
|
|
45
|
+
requestParams,
|
|
46
|
+
callingFlowConfig = {}
|
|
47
|
+
//(<additionalParams>)
|
|
48
|
+
//(</additionalParams>)
|
|
49
|
+
) {
|
|
50
|
+
try {
|
|
51
|
+
_izContext.logger.debug('<%- functionName %> started', {
|
|
52
|
+
requestParams,
|
|
53
|
+
callingFlowConfig
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const connectionId = _izContext.correlationIds.get(consts.CONNECTION_ID);
|
|
57
|
+
const submittedByUserId = _izContext.correlationIds?.get(
|
|
58
|
+
consts.SUBMITTED_BY_USER_ID
|
|
59
|
+
);
|
|
60
|
+
if (!submittedByUserId) {
|
|
61
|
+
throw new Error('Submitted by user id is required');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//(<beforeCreateObjectHook>)
|
|
65
|
+
//(</beforeCreateObjectHook>)
|
|
66
|
+
|
|
67
|
+
<% if (isRbac) { -%>
|
|
68
|
+
const { checkRbacPermission } =
|
|
69
|
+
await import('@izara_project/izara-authorization');
|
|
70
|
+
|
|
71
|
+
//(<rbacParameterGroups_<%- flowTag %>>)
|
|
72
|
+
const parameterGroups = {};
|
|
73
|
+
//(</rbacParameterGroups_<%- flowTag %>>)
|
|
74
|
+
|
|
75
|
+
const resultPermissionCheck = await checkRbacPermission(
|
|
76
|
+
_izContext,
|
|
77
|
+
{
|
|
78
|
+
flowTag: requestParams.flowTag || '<%- flowTag %>',
|
|
79
|
+
serviceTag: process.env.iz_serviceTag
|
|
80
|
+
},
|
|
81
|
+
parameterGroups,
|
|
82
|
+
{
|
|
83
|
+
targetIdentifiers: requestParams.targetIdentifiers,
|
|
84
|
+
rbacObjType: {
|
|
85
|
+
serviceTag: '<%- parentServiceTag || rbacServiceTag %>',
|
|
86
|
+
objectType: '<%- parentObjectType || objectType %>'
|
|
87
|
+
},
|
|
88
|
+
sync: true
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
_izContext.logger.debug(
|
|
93
|
+
`[${process.env.iz_serviceTag}:<%- flowTag %>] checkRbacPermission result:`,
|
|
94
|
+
resultPermissionCheck
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
if (resultPermissionCheck && resultPermissionCheck.permission !== true) {
|
|
98
|
+
throw new Error('Permission denied');
|
|
99
|
+
}
|
|
100
|
+
<% } -%>
|
|
101
|
+
|
|
102
|
+
const <%- objectType %>Id = uuidv7();
|
|
103
|
+
|
|
104
|
+
<% if (typeof parentIdKeys !== 'undefined' && parentIdKeys && parentIdKeys.length > 0) { -%>
|
|
105
|
+
const targetParentId = requestParams.targetIdentifiers?.<%- parentIdKeys[0] %>;
|
|
106
|
+
<% for (const key of parentIdKeys) { -%>
|
|
107
|
+
const target<%- key.charAt(0).toUpperCase() + key.slice(1) %> = requestParams.targetIdentifiers?.<%- key %>;
|
|
108
|
+
if (!target<%- key.charAt(0).toUpperCase() + key.slice(1) %>) {
|
|
109
|
+
throw new Error('targetIdentifiers.<%- key %> is required');
|
|
110
|
+
}
|
|
111
|
+
<% } -%>
|
|
112
|
+
<% } -%>
|
|
113
|
+
|
|
114
|
+
let maxFileSizeMb = <%- maxFileSizeMb %>;
|
|
115
|
+
if (requestParams.file?.size) {
|
|
116
|
+
maxFileSizeMb = Math.ceil(requestParams.file.size / 1048576);
|
|
117
|
+
} else if (requestParams.file?.fileSize) {
|
|
118
|
+
maxFileSizeMb = requestParams.file.fileSize;
|
|
119
|
+
} else if (requestParams.fieldNames?.fileSize) {
|
|
120
|
+
maxFileSizeMb = requestParams.fieldNames.fileSize;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
//(<createKeyForUploadFile>)
|
|
124
|
+
let importType = '<%- defaultImportType %>';
|
|
125
|
+
let uploadPath = '';
|
|
126
|
+
//(</createKeyForUploadFile>)
|
|
127
|
+
const Key = `<%- s3KeyPrefix %>${uploadPath}`;
|
|
128
|
+
const maximumSize = maxFileSizeMb * 1048576;
|
|
129
|
+
|
|
130
|
+
let createPreSignUrlParam = {
|
|
131
|
+
Bucket: '<%- bucketName %>',
|
|
132
|
+
Key: Key,
|
|
133
|
+
Conditions: [
|
|
134
|
+
['content-length-range', 0, maximumSize],
|
|
135
|
+
['eq', '$x-amz-meta-<%- objectType %>Id', <%- objectType %>Id],
|
|
136
|
+
<% if (typeof parentIdKeys !== 'undefined' && parentIdKeys && parentIdKeys.length > 0) { -%>
|
|
137
|
+
<% for (const key of parentIdKeys) { -%>
|
|
138
|
+
['eq', '$x-amz-meta-<%- key %>', target<%- key.charAt(0).toUpperCase() + key.slice(1) %>],
|
|
139
|
+
<% } -%>
|
|
140
|
+
<% } -%>
|
|
141
|
+
['starts-with', '$x-amz-meta-submittedByUserId', ''],
|
|
142
|
+
['starts-with', '$Content-Type', '<%- contentTypePrefix %>']
|
|
143
|
+
//(<conditionCreateUrlLink>)
|
|
144
|
+
//(</conditionCreateUrlLink>)
|
|
145
|
+
],
|
|
146
|
+
Fields: {
|
|
147
|
+
'x-amz-meta-<%- objectType %>Id': <%- objectType %>Id,
|
|
148
|
+
<% if (typeof parentIdKeys !== 'undefined' && parentIdKeys && parentIdKeys.length > 0) { -%>
|
|
149
|
+
<% for (const key of parentIdKeys) { -%>
|
|
150
|
+
'x-amz-meta-<%- key %>': target<%- key.charAt(0).toUpperCase() + key.slice(1) %>,
|
|
151
|
+
<% } -%>
|
|
152
|
+
<% } -%>
|
|
153
|
+
'x-amz-meta-submittedByUserId': submittedByUserId,
|
|
154
|
+
//(<fieldCreateUrlLink>)
|
|
155
|
+
//(</fieldCreateUrlLink>)
|
|
156
|
+
},
|
|
157
|
+
Expires: <%- expires %>
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
//(<modifyPreSignUrlParam>)
|
|
161
|
+
//(</modifyPreSignUrlParam>)
|
|
162
|
+
|
|
163
|
+
const uploadUrl = await createPresignedPost(client, createPreSignUrlParam);
|
|
164
|
+
|
|
165
|
+
if (connectionId) {
|
|
166
|
+
await dynamodbSharedLib.putItem(
|
|
167
|
+
_izContext,
|
|
168
|
+
await dynamodbSharedLib.tableName(_izContext, 'WebSocketTask'),
|
|
169
|
+
{
|
|
170
|
+
taskKey: `${createFlowTypeConcat(_izContext, { flowTag: '<%- flowTag %>', serviceTag: process.env.iz_serviceTag })}${<%- objectType %>Id}`,
|
|
171
|
+
connectionId: connectionId,
|
|
172
|
+
expirationTime: Math.floor(Date.now() / 1000) + 7200
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
await postToConnection(
|
|
177
|
+
_izContext,
|
|
178
|
+
{
|
|
179
|
+
status: 'success',
|
|
180
|
+
data: {
|
|
181
|
+
uploadUrl: uploadUrl,
|
|
182
|
+
<%- objectType %>Id: <%- objectType %>Id
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
connectionId
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
uploadUrl: uploadUrl,
|
|
191
|
+
<%- objectType %>Id: <%- objectType %>Id
|
|
192
|
+
};
|
|
193
|
+
} catch (err) {
|
|
194
|
+
_izContext.logger.error('Error in <%- functionName %>:', err);
|
|
195
|
+
throw err;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -20,7 +20,7 @@ const RBAC_CORE_FLOW_DEFINITIONS = [
|
|
|
20
20
|
const LIMIT_FLOW_DEFINITIONS = [
|
|
21
21
|
{
|
|
22
22
|
flowTag: 'ReservedDynamicUsage',
|
|
23
|
-
event: ['
|
|
23
|
+
event: ['lambdaSyncInv', 'lambdaSyncApi']
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
flowTag: 'ConfirmUsage',
|
|
@@ -29,6 +29,10 @@ const LIMIT_FLOW_DEFINITIONS = [
|
|
|
29
29
|
{
|
|
30
30
|
flowTag: 'CancelUsage',
|
|
31
31
|
event: ['ownTopic', 'lambdaSyncInv', 'lambdaSyncApi']
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
flowTag: 'ReservedUsageTtlExpired',
|
|
35
|
+
event: ['dss']
|
|
32
36
|
}
|
|
33
37
|
];
|
|
34
38
|
|
|
@@ -174,14 +174,21 @@ const STATIC_RBAC_APPLICATION_PATHS = [
|
|
|
174
174
|
];
|
|
175
175
|
|
|
176
176
|
const STATIC_LIMIT_APPLICATION_PATHS = [
|
|
177
|
-
...ACCOUNT_LIMIT_HANDLERS.flatMap((flowTag) =>
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
177
|
+
...ACCOUNT_LIMIT_HANDLERS.flatMap((flowTag) => {
|
|
178
|
+
const paths = [
|
|
179
|
+
`app/src/generatedCode/FlowLimit/${flowTag}/source/${flowTag}_Main.js`,
|
|
180
|
+
`app/src/generatedCode/FlowLimit/${flowTag}/source/${flowTag}_HdrInv.js`,
|
|
181
|
+
`app/src/generatedCode/FlowLimit/${flowTag}/source/${flowTag}_HdrApi.js`
|
|
182
|
+
];
|
|
183
|
+
if (flowTag !== 'ReservedDynamicUsage') {
|
|
184
|
+
paths.splice(
|
|
185
|
+
1,
|
|
186
|
+
0,
|
|
187
|
+
`app/src/generatedCode/FlowLimit/${flowTag}/source/${flowTag}_HdrSqs.js`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
return paths;
|
|
191
|
+
}),
|
|
185
192
|
'app/src/generatedCode/FlowLimit/ReservedUsageTtlExpired/source/ReservedUsageTtlExpired_HdrDss.js',
|
|
186
193
|
FLOW_LIMITS_YAML_PATH
|
|
187
194
|
];
|
|
@@ -212,65 +219,65 @@ export function rbacApplicationManagedPaths(objects = []) {
|
|
|
212
219
|
async function createAccountLimitHandler(flowTag) {
|
|
213
220
|
const sourcePath = `app/src/generatedCode/FlowLimit/${flowTag}/source`;
|
|
214
221
|
const handlerPath = `src/generatedCode/FlowLimit/${flowTag}/source`;
|
|
215
|
-
const webSocket = await createWebSocketEntry(
|
|
216
|
-
flowTag,
|
|
217
|
-
sourcePath,
|
|
218
|
-
handlerPath
|
|
219
|
-
);
|
|
220
222
|
const shared = {
|
|
221
223
|
flowTag,
|
|
222
224
|
flowStepName: '',
|
|
223
225
|
mainFileName: `${flowTag}_Main`,
|
|
224
226
|
functionName: flowTag
|
|
225
227
|
};
|
|
228
|
+
const artifacts = [
|
|
229
|
+
{
|
|
230
|
+
path: `${sourcePath}/${flowTag}_Main.js`,
|
|
231
|
+
content: await renderRbacTemplate(`${flowTag}_Main.ejs`, { flowTag })
|
|
232
|
+
}
|
|
233
|
+
];
|
|
234
|
+
const functions = [];
|
|
235
|
+
if (flowTag !== 'ReservedDynamicUsage') {
|
|
236
|
+
artifacts.push({
|
|
237
|
+
path: `${sourcePath}/${flowTag}_HdrSqs.js`,
|
|
238
|
+
content: await renderRbacTemplate('ListRbac_HdrSqs.ejs', { flowTag })
|
|
239
|
+
});
|
|
240
|
+
functions.push({
|
|
241
|
+
type: 'sqs',
|
|
242
|
+
functionName: `${flowTag}HdrSqs`,
|
|
243
|
+
handlerPath: `${handlerPath}/${flowTag}_HdrSqs.main`,
|
|
244
|
+
roleName: 'FlowRbacRole',
|
|
245
|
+
queueName: `${flowTag}HdrSqs`,
|
|
246
|
+
flowTag: flowTag,
|
|
247
|
+
flowStepName: ''
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
artifacts.push(
|
|
251
|
+
{
|
|
252
|
+
path: `${sourcePath}/${flowTag}_HdrInv.js`,
|
|
253
|
+
content: await renderSharedTemplate('FlowHandler_HdrInv.ejs', shared)
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
path: `${sourcePath}/${flowTag}_HdrApi.js`,
|
|
257
|
+
content: await renderSharedTemplate('FlowHandler_HdrApi.ejs', shared)
|
|
258
|
+
}
|
|
259
|
+
);
|
|
260
|
+
functions.push(
|
|
261
|
+
{
|
|
262
|
+
type: 'inv',
|
|
263
|
+
functionName: `${flowTag}HdrInv`,
|
|
264
|
+
handlerPath: `${handlerPath}/${flowTag}_HdrInv.main`,
|
|
265
|
+
roleName: 'FlowRbacRole',
|
|
266
|
+
flowTag: flowTag,
|
|
267
|
+
flowStepName: ''
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
type: 'api',
|
|
271
|
+
functionName: `${flowTag}HdrApi`,
|
|
272
|
+
handlerPath: `${handlerPath}/${flowTag}_HdrApi.main`,
|
|
273
|
+
roleName: 'FlowRbacRole',
|
|
274
|
+
flowTag: flowTag,
|
|
275
|
+
flowStepName: ''
|
|
276
|
+
}
|
|
277
|
+
);
|
|
226
278
|
return {
|
|
227
|
-
artifacts
|
|
228
|
-
|
|
229
|
-
path: `${sourcePath}/${flowTag}_Main.js`,
|
|
230
|
-
content: await renderRbacTemplate(`${flowTag}_Main.ejs`, { flowTag })
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
path: `${sourcePath}/${flowTag}_HdrSqs.js`,
|
|
234
|
-
content: await renderRbacTemplate('ListRbac_HdrSqs.ejs', { flowTag })
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
path: `${sourcePath}/${flowTag}_HdrInv.js`,
|
|
238
|
-
content: await renderSharedTemplate('FlowHandler_HdrInv.ejs', shared)
|
|
239
|
-
},
|
|
240
|
-
{
|
|
241
|
-
path: `${sourcePath}/${flowTag}_HdrApi.js`,
|
|
242
|
-
content: await renderSharedTemplate('FlowHandler_HdrApi.ejs', shared)
|
|
243
|
-
},
|
|
244
|
-
...webSocket.artifacts
|
|
245
|
-
],
|
|
246
|
-
functions: [
|
|
247
|
-
{
|
|
248
|
-
type: 'sqs',
|
|
249
|
-
functionName: `${flowTag}HdrSqs`,
|
|
250
|
-
handlerPath: `${handlerPath}/${flowTag}_HdrSqs.main`,
|
|
251
|
-
roleName: 'FlowRbacRole',
|
|
252
|
-
queueName: `${flowTag}HdrSqs`,
|
|
253
|
-
flowTag: flowTag,
|
|
254
|
-
flowStepName: ''
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
type: 'inv',
|
|
258
|
-
functionName: `${flowTag}HdrInv`,
|
|
259
|
-
handlerPath: `${handlerPath}/${flowTag}_HdrInv.main`,
|
|
260
|
-
roleName: 'FlowRbacRole',
|
|
261
|
-
flowTag: flowTag,
|
|
262
|
-
flowStepName: ''
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
type: 'api',
|
|
266
|
-
functionName: `${flowTag}HdrApi`,
|
|
267
|
-
handlerPath: `${handlerPath}/${flowTag}_HdrApi.main`,
|
|
268
|
-
roleName: 'FlowRbacRole',
|
|
269
|
-
flowTag: flowTag,
|
|
270
|
-
flowStepName: ''
|
|
271
|
-
},
|
|
272
|
-
...webSocket.functions
|
|
273
|
-
]
|
|
279
|
+
artifacts,
|
|
280
|
+
functions
|
|
274
281
|
};
|
|
275
282
|
}
|
|
276
283
|
|
|
@@ -161,7 +161,7 @@ export default async function ReservedDynamicUsage(_izContext, requestParams) {
|
|
|
161
161
|
let passStatus = false;
|
|
162
162
|
if (currentTotal + value <= maxLimit) {
|
|
163
163
|
passStatus = true;
|
|
164
|
-
const
|
|
164
|
+
const expirationTime = Math.floor(Date.now() / 1000) + 3600;
|
|
165
165
|
|
|
166
166
|
await dynamodbSharedLib.putItem(
|
|
167
167
|
_izContext,
|
|
@@ -173,7 +173,7 @@ export default async function ReservedDynamicUsage(_izContext, requestParams) {
|
|
|
173
173
|
flowTypeConcat,
|
|
174
174
|
usageCount: value,
|
|
175
175
|
runningTotalUsageStatus: 'reserved',
|
|
176
|
-
|
|
176
|
+
expirationTime
|
|
177
177
|
}
|
|
178
178
|
);
|
|
179
179
|
|
|
@@ -196,13 +196,31 @@ export default async function ReservedDynamicUsage(_izContext, requestParams) {
|
|
|
196
196
|
identifiersConcat
|
|
197
197
|
};
|
|
198
198
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
199
|
+
const eventSourceTag =
|
|
200
|
+
_izContext.izEventSource?.eventSourceTag ||
|
|
201
|
+
requestParams?.izEventSource?.eventSourceTag;
|
|
202
|
+
|
|
203
|
+
let isSync = false;
|
|
204
|
+
if (
|
|
205
|
+
eventSourceTag === 'DirectInvoke' ||
|
|
206
|
+
eventSourceTag === 'ApiGateway' ||
|
|
207
|
+
eventSourceTag === 'ApiGatewayV2' ||
|
|
208
|
+
requestParams?.hasOwnProperty('__context__')
|
|
209
|
+
) {
|
|
210
|
+
isSync = true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (!isSync) {
|
|
214
|
+
await sns.publishMessage(_izContext, {
|
|
215
|
+
message: { returnResult },
|
|
216
|
+
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
217
|
+
_izContext,
|
|
218
|
+
'ReservedDynamicUsage_Out'
|
|
219
|
+
)
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return returnResult;
|
|
206
224
|
} catch (error) {
|
|
207
225
|
_izContext.logger.error(`[${process.env.iz_serviceTag}:ReservedDynamicUsage] error:`, error);
|
|
208
226
|
throw error;
|
|
@@ -55,7 +55,8 @@ async function _getL2Cache(_izContext, submittedByUserId, cacheSubKey) {
|
|
|
55
55
|
userId: submittedByUserId,
|
|
56
56
|
cacheKey: cacheSubKey
|
|
57
57
|
});
|
|
58
|
-
|
|
58
|
+
const expTime = result && (result.expirationTime || result.ttlTimestamp);
|
|
59
|
+
if (expTime && expTime > Math.floor(Date.now() / 1000)) {
|
|
59
60
|
return {
|
|
60
61
|
returnValue: {
|
|
61
62
|
permission: Boolean(result.permission),
|
|
@@ -75,14 +76,14 @@ async function _getL2Cache(_izContext, submittedByUserId, cacheSubKey) {
|
|
|
75
76
|
async function _setL2Cache(_izContext, submittedByUserId, cacheSubKey, value) {
|
|
76
77
|
try {
|
|
77
78
|
const tableName = dynamodbSharedLib.tableName(_izContext, 'RbacPermissionCaches');
|
|
78
|
-
const
|
|
79
|
+
const expirationTime = Math.floor(Date.now() / 1000) + L2_TTL_SECONDS;
|
|
79
80
|
await dynamodbSharedLib.putItem(_izContext, tableName, {
|
|
80
81
|
userId: submittedByUserId,
|
|
81
82
|
cacheKey: cacheSubKey,
|
|
82
83
|
permission: value.returnValue ? value.returnValue.permission : value.permission,
|
|
83
84
|
roleIdKey: value.returnValue ? value.returnValue.roleIdKey : value.roleIdKey,
|
|
84
85
|
rbacType: 'target',
|
|
85
|
-
|
|
86
|
+
expirationTime
|
|
86
87
|
});
|
|
87
88
|
} catch (error) {
|
|
88
89
|
// Safely ignore L2 write errors if table is not configured
|
|
@@ -27,7 +27,8 @@ export const main = middleware.wrap(async (event, context) => {
|
|
|
27
27
|
await dynamodbSharedLib.tableName(event._izContext, 'WebSocketTask'),
|
|
28
28
|
{
|
|
29
29
|
taskKey: correlationId,
|
|
30
|
-
connectionId: connectionId
|
|
30
|
+
connectionId: connectionId,
|
|
31
|
+
expirationTime: Math.floor(Date.now() / 1000) + 7200
|
|
31
32
|
}
|
|
32
33
|
);
|
|
33
34
|
|
|
@@ -181,6 +181,7 @@ export function renderS3FunctionYaml({
|
|
|
181
181
|
handlerPath,
|
|
182
182
|
roleName,
|
|
183
183
|
bucketName,
|
|
184
|
+
s3KeyPrefix,
|
|
184
185
|
flowTag,
|
|
185
186
|
flowStepName
|
|
186
187
|
}) {
|
|
@@ -189,6 +190,7 @@ export function renderS3FunctionYaml({
|
|
|
189
190
|
handlerPath,
|
|
190
191
|
roleName,
|
|
191
192
|
bucketName,
|
|
193
|
+
s3KeyPrefix,
|
|
192
194
|
environmentSetting: _environmentSetting(
|
|
193
195
|
functionName,
|
|
194
196
|
flowTag,
|
|
@@ -342,15 +344,8 @@ function _sqsFilterPatterns(
|
|
|
342
344
|
- body:
|
|
343
345
|
MessageAttributes:
|
|
344
346
|
callingFlow:
|
|
345
|
-
|
|
347
|
+
StringValue:
|
|
346
348
|
- \${self:custom.iz_resourcePrefix}${callingFlowName}
|
|
347
|
-
- body:
|
|
348
|
-
MessageAttributes:
|
|
349
|
-
callingFlow:
|
|
350
|
-
- exists: false
|
|
351
|
-
- body:
|
|
352
|
-
MessageAttributes:
|
|
353
|
-
- exists: false
|
|
354
349
|
#<#/${hookFunctionName}FilterPatterns#>
|
|
355
350
|
`;
|
|
356
351
|
}
|
|
@@ -8,9 +8,8 @@
|
|
|
8
8
|
bucket: <%= bucketName %>
|
|
9
9
|
event: s3:ObjectCreated:*
|
|
10
10
|
existing: true
|
|
11
|
-
rules
|
|
11
|
+
rules:<% if (s3KeyPrefix) { %>
|
|
12
|
+
- prefix: <%- s3KeyPrefix %><% } %>
|
|
12
13
|
#<#<%= functionName %>rules#>
|
|
13
14
|
#<#/<%= functionName %>rules#>
|
|
14
|
-
- prefix: uploads/
|
|
15
|
-
- suffix: .csv
|
|
16
15
|
forceDeploy: true
|
|
@@ -139,6 +139,25 @@ export function roleStatements(state, roleName) {
|
|
|
139
139
|
resource: ['arn:aws:s3:::${self:custom.iz_serviceSchemaBucketName}']
|
|
140
140
|
});
|
|
141
141
|
}
|
|
142
|
+
const s3FlowsForRole = (state.flows || []).filter((candidate) => {
|
|
143
|
+
const roleCandidate = customFlowRoleName(candidate?.flowTag ?? '');
|
|
144
|
+
return (
|
|
145
|
+
roleCandidate === roleName &&
|
|
146
|
+
includesEvent(candidate, 's3') &&
|
|
147
|
+
Boolean(candidate.bucketName)
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
if (s3FlowsForRole.length > 0) {
|
|
151
|
+
const bucketResources = [
|
|
152
|
+
...new Set(
|
|
153
|
+
s3FlowsForRole.map((flow) => `arn:aws:s3:::${flow.bucketName}/*`)
|
|
154
|
+
)
|
|
155
|
+
].sort();
|
|
156
|
+
statements.push({
|
|
157
|
+
action: ['s3:GetObject'],
|
|
158
|
+
resource: bucketResources
|
|
159
|
+
});
|
|
160
|
+
}
|
|
142
161
|
addStatement(statements, dynamoActionsForRole(roleName), [
|
|
143
162
|
...dynamoArns(roleTableNames(state, roleName)),
|
|
144
163
|
...externalDynamoArns(roleExternalDynamoTables(state, roleName))
|
|
@@ -479,6 +498,9 @@ export function roleTopicNames(state, roleName) {
|
|
|
479
498
|
if (flowHasOwnTopic(flow)) {
|
|
480
499
|
topics.push(`${name}_In`);
|
|
481
500
|
}
|
|
501
|
+
if (includesEvent(flow, 's3')) {
|
|
502
|
+
topics.push('Create_In');
|
|
503
|
+
}
|
|
482
504
|
return topics;
|
|
483
505
|
}
|
|
484
506
|
return [];
|
|
@@ -4,7 +4,8 @@ import {
|
|
|
4
4
|
flowExtTopicSteps,
|
|
5
5
|
generatedDsqFlows,
|
|
6
6
|
generatedOwnTopicFlows,
|
|
7
|
-
supportedCustomFlows
|
|
7
|
+
supportedCustomFlows,
|
|
8
|
+
resolveS3Parts
|
|
8
9
|
} from '../../application/custom/customFlows.js';
|
|
9
10
|
import { hasSystemWebSocket } from '../../application/system/emitSystemWebSocketArtifacts.js';
|
|
10
11
|
import { rbacFlowTag } from '../../application/rbac/rbacFlowTag.js';
|
|
@@ -161,9 +162,12 @@ export function createResourceState({ objects, relationships, flows }) {
|
|
|
161
162
|
|
|
162
163
|
for (const flow of flows.filter((flow) => includesEvent(flow, 's3'))) {
|
|
163
164
|
const tag = upperFirst(flow.flowTag);
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
const parts = resolveS3Parts(flow);
|
|
166
|
+
for (const part of parts) {
|
|
167
|
+
if (part.type === 'sqs') {
|
|
168
|
+
customSqsQueues.push(`${tag}${part.action}HdrSqs`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
167
171
|
}
|
|
168
172
|
|
|
169
173
|
const customDsqQueues = flows
|
|
@@ -281,8 +285,14 @@ export function createResourceState({ objects, relationships, flows }) {
|
|
|
281
285
|
topicName = `Out${upperFirst(flow.outputTopic)}`;
|
|
282
286
|
}
|
|
283
287
|
}
|
|
288
|
+
let targetAction = 'ReservedLimit';
|
|
289
|
+
const parts = resolveS3Parts(flow);
|
|
290
|
+
const firstSqsPart = parts.find((p) => p.type === 'sqs');
|
|
291
|
+
if (firstSqsPart && firstSqsPart.action) {
|
|
292
|
+
targetAction = firstSqsPart.action;
|
|
293
|
+
}
|
|
284
294
|
const queueName =
|
|
285
|
-
flow.s3Subscription?.queueName || `${tag}
|
|
295
|
+
flow.s3Subscription?.queueName || `${tag}${targetAction}HdrSqs`;
|
|
286
296
|
const subscriptionName =
|
|
287
297
|
flow.s3Subscription?.subscriptionName ||
|
|
288
298
|
`Subscription${topicName}To${queueName}`;
|
|
@@ -1,13 +1,29 @@
|
|
|
1
1
|
import { indentYaml, renderResourceTemplate } from './resourceTemplate.js';
|
|
2
2
|
|
|
3
|
+
export function toAlphanumericPascalCase(str) {
|
|
4
|
+
if (!str) {
|
|
5
|
+
return '';
|
|
6
|
+
}
|
|
7
|
+
const parts = str.split(/[^a-zA-Z0-9]+/);
|
|
8
|
+
const validParts = parts.filter(Boolean);
|
|
9
|
+
const capitalizedParts = validParts.map((word) => {
|
|
10
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
11
|
+
});
|
|
12
|
+
return capitalizedParts.join('');
|
|
13
|
+
}
|
|
14
|
+
|
|
3
15
|
export async function renderS3(state) {
|
|
4
16
|
if (state.s3Buckets.length === 0) {
|
|
5
17
|
return '';
|
|
6
18
|
}
|
|
7
19
|
const renderedBuckets = await Promise.all(
|
|
8
|
-
state.s3Buckets.map((bucketName) =>
|
|
9
|
-
|
|
10
|
-
|
|
20
|
+
state.s3Buckets.map((bucketName) => {
|
|
21
|
+
const bucketLogicalId = `${toAlphanumericPascalCase(bucketName)}S3Bucket`;
|
|
22
|
+
return renderResourceTemplate('S3Bucket_Yaml.ejs', {
|
|
23
|
+
bucketName,
|
|
24
|
+
bucketLogicalId
|
|
25
|
+
});
|
|
26
|
+
})
|
|
11
27
|
);
|
|
12
28
|
const body = renderedBuckets.join('\n');
|
|
13
29
|
return `Resources:\n${indentYaml(body, 2)}`;
|
|
@@ -230,7 +230,7 @@ Resources:
|
|
|
230
230
|
- AttributeName: cacheKey
|
|
231
231
|
KeyType: RANGE
|
|
232
232
|
TimeToLiveSpecification:
|
|
233
|
-
AttributeName:
|
|
233
|
+
AttributeName: expirationTime
|
|
234
234
|
Enabled: true
|
|
235
235
|
|
|
236
236
|
RunningTotalUsageTable:
|
|
@@ -265,7 +265,7 @@ Resources:
|
|
|
265
265
|
- AttributeName: usedTimestampId
|
|
266
266
|
KeyType: RANGE
|
|
267
267
|
TimeToLiveSpecification:
|
|
268
|
-
AttributeName:
|
|
268
|
+
AttributeName: expirationTime
|
|
269
269
|
Enabled: true
|
|
270
270
|
StreamSpecification:
|
|
271
271
|
StreamViewType: NEW_AND_OLD_IMAGES
|