@izara_project/izara-core-generate-service-code 1.0.101 → 1.0.103
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/emit/application/coreFunction/templates/InitialSetupRunner_uploadSchema.ejs +7 -0
- package/src/emit/application/coreFunction/templates/UploadSchema_Main.ejs +7 -0
- package/src/emit/application/crud/templates/CreateEndpoint_Main.ejs +21 -0
- package/src/emit/application/custom/customFlowS3.js +38 -0
- package/src/emit/application/custom/customFlows.js +22 -14
- package/src/emit/application/custom/templates/s3/ConfirmComplete_Main.ejs +5 -5
- package/src/emit/application/custom/templates/s3/ProcessS3File_Main.ejs +112 -6
- package/src/emit/application/custom/templates/s3/Start_HdrWbs.ejs +65 -0
- package/src/emit/application/custom/templates/s3/Start_Main.ejs +20 -16
- package/src/emit/application/custom/templates/s3/{ConfirmAfterUpload_Main.ejs → legacy/ConfirmAfterUpload_Main.ejs} +1 -1
- package/src/emit/application/rbac/templates/CancelUsage_Main.ejs +7 -6
- package/src/emit/application/rbac/templates/ConfirmUsage_Main.ejs +7 -6
- package/src/emit/application/rbac/templates/CreateTargetRole_Main.ejs +2 -2
- package/src/emit/application/rbac/templates/DeleteTargetRole_Main.ejs +2 -2
- package/src/emit/application/rbac/templates/GetMyPermissions_Main.ejs +2 -2
- package/src/emit/application/rbac/templates/GetTargetRole_Main.ejs +2 -2
- package/src/emit/application/rbac/templates/ListRbac_Main.ejs +2 -2
- package/src/emit/application/rbac/templates/RbacMutation_Main.ejs +2 -2
- package/src/emit/application/rbac/templates/ReservedDynamicUsage_Main.ejs +7 -4
- package/src/emit/application/rbac/templates/UpdateTargetRole_Main.ejs +2 -2
- package/src/emit/application/rbac/templates/UserRoleMutation_Main.ejs +2 -2
- package/src/emit/infrastructure/functions/templates/DssFunction_Yaml.ejs +1 -1
- package/src/emit/infrastructure/resources/dynamoResourceArtifacts.js +6 -2
- package/src/emit/infrastructure/resources/sqsResourceArtifacts.js +2 -1
- package/src/emit/infrastructure/resources/templates/FlowOut_Yaml.ejs +2 -0
- package/test/unit/application/createTargetRoleHandler.test.js +1 -1
- package/test/unit/application/customFlowS3.test.js +156 -2
- package/test/unit/infrastructure/resourceArtifacts.test.js +24 -0
- package/test/unit/output/mergeHooks.test.js +49 -0
- /package/src/emit/application/custom/templates/s3/{ConfirmAfterUpload_HdrSqs.ejs → legacy/ConfirmAfterUpload_HdrSqs.ejs} +0 -0
- /package/src/emit/application/custom/templates/s3/{ConfirmReserved_HdrSqs.ejs → legacy/ConfirmReserved_HdrSqs.ejs} +0 -0
- /package/src/emit/application/custom/templates/s3/{ConfirmReserved_Main.ejs → legacy/ConfirmReserved_Main.ejs} +0 -0
- /package/src/emit/application/custom/templates/s3/{CreateObject_HdrWbs.ejs → legacy/CreateObject_HdrWbs.ejs} +0 -0
- /package/src/emit/application/custom/templates/s3/{CreateObject_Main.ejs → legacy/CreateObject_Main.ejs} +0 -0
- /package/src/emit/application/custom/templates/s3/{CreatePreSignUrl_HdrSqs.ejs → legacy/CreatePreSignUrl_HdrSqs.ejs} +0 -0
- /package/src/emit/application/custom/templates/s3/{CreatePreSignUrl_Main.ejs → legacy/CreatePreSignUrl_Main.ejs} +0 -0
- /package/src/emit/application/custom/templates/s3/{ReservedLimit_HdrSqs.ejs → legacy/ReservedLimit_HdrSqs.ejs} +0 -0
- /package/src/emit/application/custom/templates/s3/{ReservedLimit_Main.ejs → legacy/ReservedLimit_Main.ejs} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@izara_project/izara-core-generate-service-code",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.103",
|
|
4
4
|
"description": "Code for locally generating per service files",
|
|
5
5
|
"homepage": "https://bitbucket.org/izara-core-support-services/izara-core-support-services-generate-service-code#readme",
|
|
6
6
|
"repository": {
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import initialSetupLib from '@izara_project/izara-core-library-initial-setup';
|
|
2
|
+
import { basicValidatorSchema } from '@izara_project/izara-shared-service-schemas';
|
|
3
|
+
|
|
4
|
+
if (basicValidatorSchema?.basicFlowSchema?.properties?.event?.items?.enum) {
|
|
5
|
+
if (!basicValidatorSchema.basicFlowSchema.properties.event.items.enum.includes('dss')) {
|
|
6
|
+
basicValidatorSchema.basicFlowSchema.properties.event.items.enum.push('dss');
|
|
7
|
+
}
|
|
8
|
+
}
|
|
2
9
|
|
|
3
10
|
const SERVICE_TAG = {
|
|
4
11
|
TRANSLATIONS: 'Translations',
|
|
@@ -16,6 +16,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import initialSetupSharedLib from '@izara_project/izara-core-library-initial-setup';
|
|
19
|
+
import { basicValidatorSchema } from '@izara_project/izara-shared-service-schemas';
|
|
20
|
+
|
|
21
|
+
if (basicValidatorSchema?.basicFlowSchema?.properties?.event?.items?.enum) {
|
|
22
|
+
if (!basicValidatorSchema.basicFlowSchema.properties.event.items.enum.includes('dss')) {
|
|
23
|
+
basicValidatorSchema.basicFlowSchema.properties.event.items.enum.push('dss');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
19
26
|
|
|
20
27
|
const SERVICE_TAG = {
|
|
21
28
|
TRANSLATIONS: 'Translations',
|
|
@@ -153,6 +153,10 @@ export default async function <%- flowTag %>(
|
|
|
153
153
|
requestParams.billable = true;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
//(<rbacUsageValue_<%- flowTag %>>)
|
|
157
|
+
let usageValue = requestParams.value || requestParams.usageValue || 1;
|
|
158
|
+
//(</rbacUsageValue_<%- flowTag %>>)
|
|
159
|
+
|
|
156
160
|
const resultPermissionCheck = await checkRbacPermission(
|
|
157
161
|
_izContext,
|
|
158
162
|
{
|
|
@@ -168,6 +172,7 @@ export default async function <%- flowTag %>(
|
|
|
168
172
|
serviceTag: <%- rbacServiceTags.length === 1 ? rbacServiceTags[0] : `{ ${Object.entries(rbacServiceTagMap).map(([k, v]) => `'${k}': ${v}`).join(', ')} }[requestParams.objectType] || process.env.iz_serviceTag` %>
|
|
169
173
|
},
|
|
170
174
|
requestParams: requestParams,
|
|
175
|
+
value: usageValue,
|
|
171
176
|
sync: true
|
|
172
177
|
}
|
|
173
178
|
);
|
|
@@ -187,6 +192,14 @@ export default async function <%- flowTag %>(
|
|
|
187
192
|
);
|
|
188
193
|
}
|
|
189
194
|
|
|
195
|
+
if (resultPermissionCheck?.uniqueIdentify) {
|
|
196
|
+
requestParams.reservedUsageId = resultPermissionCheck.uniqueIdentify;
|
|
197
|
+
}
|
|
198
|
+
if (resultPermissionCheck?.runningTotalReservedId) {
|
|
199
|
+
requestParams.runningTotalReservedId =
|
|
200
|
+
resultPermissionCheck.runningTotalReservedId;
|
|
201
|
+
}
|
|
202
|
+
|
|
190
203
|
//(<rbacPublish_<%- flowTag %>>)
|
|
191
204
|
//(</rbacPublish_<%- flowTag %>>)
|
|
192
205
|
}
|
|
@@ -275,6 +288,7 @@ export default async function <%- flowTag %>(
|
|
|
275
288
|
//(</afterBeforeLogical_<%= objectType %>>)
|
|
276
289
|
|
|
277
290
|
<%_ if (hasOwnTopic) { _%>
|
|
291
|
+
/*
|
|
278
292
|
// ── ownTopic (async — dispatch to before-hook and await callback) ──────────
|
|
279
293
|
<%_ configs.filter(c => c?.generatedCodeConfig?.invocationType === 'ownTopic' || c?.event?.includes('ownTopic')).forEach(config => { _%>
|
|
280
294
|
let hookPayload_<%= config.flowTag %> = {
|
|
@@ -341,6 +355,13 @@ export default async function <%- flowTag %>(
|
|
|
341
355
|
status: 'pending',
|
|
342
356
|
message: 'Dispatched to BeforeHook'
|
|
343
357
|
};
|
|
358
|
+
*/
|
|
359
|
+
return await <%- flowTag %>LogicalLib(
|
|
360
|
+
_izContext,
|
|
361
|
+
requestParams,
|
|
362
|
+
callingFlowConfig,
|
|
363
|
+
foundErrors
|
|
364
|
+
);
|
|
344
365
|
<%_ } else { _%>
|
|
345
366
|
return await <%- flowTag %>LogicalLib(
|
|
346
367
|
_izContext,
|
|
@@ -261,6 +261,40 @@ export function resolveParentIdentifierInfo(flow, objects = []) {
|
|
|
261
261
|
};
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
export function resolveTargetIdentifierInfo(flow, objects = []) {
|
|
265
|
+
const targetObjType =
|
|
266
|
+
typeof flow.objType === 'string'
|
|
267
|
+
? flow.objType
|
|
268
|
+
: flow.objType?.objectType || flow.objectType;
|
|
269
|
+
const matchedObject = (objects || []).find(
|
|
270
|
+
(o) => o.objectType === targetObjType
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
let identifierKeys = [];
|
|
274
|
+
if (Array.isArray(flow.identifierKeys) && flow.identifierKeys.length > 0) {
|
|
275
|
+
identifierKeys = [...flow.identifierKeys];
|
|
276
|
+
} else if (flow.identifierKey) {
|
|
277
|
+
identifierKeys = [flow.identifierKey];
|
|
278
|
+
} else if (
|
|
279
|
+
Array.isArray(matchedObject?.identifiers) &&
|
|
280
|
+
matchedObject.identifiers.length > 0
|
|
281
|
+
) {
|
|
282
|
+
identifierKeys = extractIdentifierFieldNames(matchedObject.identifiers);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (identifierKeys.length === 0 && targetObjType) {
|
|
286
|
+
identifierKeys = [`${targetObjType}Id`];
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
let identifierKey =
|
|
290
|
+
identifierKeys.length > 0 ? identifierKeys[0] : `${targetObjType}Id`;
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
identifierKey,
|
|
294
|
+
identifierKeys
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
264
298
|
export function resolveFlowRelationships(
|
|
265
299
|
flow,
|
|
266
300
|
{ objects = [], relationships = [] } = {}
|
|
@@ -454,6 +488,8 @@ function _s3TemplateData({
|
|
|
454
488
|
}
|
|
455
489
|
const { parentObjectType, parentServiceTag, parentIdKey, parentIdKeys } =
|
|
456
490
|
resolveParentIdentifierInfo(flow, objects);
|
|
491
|
+
const { identifierKey, identifierKeys } =
|
|
492
|
+
resolveTargetIdentifierInfo(flow, objects);
|
|
457
493
|
let s3KeyPrefix = 'uploads/';
|
|
458
494
|
if (flow.s3KeyPrefix) {
|
|
459
495
|
s3KeyPrefix = flow.s3KeyPrefix;
|
|
@@ -498,6 +534,8 @@ function _s3TemplateData({
|
|
|
498
534
|
action,
|
|
499
535
|
flowTag: flow.flowTag,
|
|
500
536
|
objectType: flow.objType?.objectType,
|
|
537
|
+
identifierKey,
|
|
538
|
+
identifierKeys,
|
|
501
539
|
functionName,
|
|
502
540
|
nextStepFunctionName,
|
|
503
541
|
isSimplifiedS3,
|
|
@@ -350,22 +350,22 @@ export const LEGACY_S3_PARTS = [
|
|
|
350
350
|
action: 'CreateObject',
|
|
351
351
|
handler: 'HdrWbs',
|
|
352
352
|
type: 'wbs',
|
|
353
|
-
mainTemplate: 'CreateObject_Main.ejs',
|
|
354
|
-
handlerTemplate: 'CreateObject_HdrWbs.ejs'
|
|
353
|
+
mainTemplate: 'legacy/CreateObject_Main.ejs',
|
|
354
|
+
handlerTemplate: 'legacy/CreateObject_HdrWbs.ejs'
|
|
355
355
|
},
|
|
356
356
|
{
|
|
357
357
|
action: 'ReservedLimit',
|
|
358
358
|
handler: 'HdrSqs',
|
|
359
359
|
type: 'sqs',
|
|
360
|
-
mainTemplate: 'ReservedLimit_Main.ejs',
|
|
361
|
-
handlerTemplate: 'ReservedLimit_HdrSqs.ejs'
|
|
360
|
+
mainTemplate: 'legacy/ReservedLimit_Main.ejs',
|
|
361
|
+
handlerTemplate: 'legacy/ReservedLimit_HdrSqs.ejs'
|
|
362
362
|
},
|
|
363
363
|
{
|
|
364
364
|
action: 'CreatePreSignUrl',
|
|
365
365
|
handler: 'HdrSqs',
|
|
366
366
|
type: 'sqs',
|
|
367
|
-
mainTemplate: 'CreatePreSignUrl_Main.ejs',
|
|
368
|
-
handlerTemplate: 'CreatePreSignUrl_HdrSqs.ejs'
|
|
367
|
+
mainTemplate: 'legacy/CreatePreSignUrl_Main.ejs',
|
|
368
|
+
handlerTemplate: 'legacy/CreatePreSignUrl_HdrSqs.ejs'
|
|
369
369
|
},
|
|
370
370
|
{
|
|
371
371
|
action: 'ProcessAfterUpload',
|
|
@@ -378,8 +378,8 @@ export const LEGACY_S3_PARTS = [
|
|
|
378
378
|
action: 'ConfirmAfterUpload',
|
|
379
379
|
handler: 'HdrSqs',
|
|
380
380
|
type: 'sqs',
|
|
381
|
-
mainTemplate: 'ConfirmReserved_Main.ejs',
|
|
382
|
-
handlerTemplate: 'ConfirmReserved_HdrSqs.ejs'
|
|
381
|
+
mainTemplate: 'legacy/ConfirmReserved_Main.ejs',
|
|
382
|
+
handlerTemplate: 'legacy/ConfirmReserved_HdrSqs.ejs'
|
|
383
383
|
}
|
|
384
384
|
];
|
|
385
385
|
|
|
@@ -419,16 +419,18 @@ export function resolveS3Parts(flow) {
|
|
|
419
419
|
const eventType = Array.isArray(step.event) ? step.event[0] : 'sqs';
|
|
420
420
|
|
|
421
421
|
if (eventType === 'wbs' || action === 'Start' || action === 'CreateObject') {
|
|
422
|
-
let mainTemplate = 'CreateObject_Main.ejs';
|
|
422
|
+
let mainTemplate = 'legacy/CreateObject_Main.ejs';
|
|
423
|
+
let handlerTemplate = 'legacy/CreateObject_HdrWbs.ejs';
|
|
423
424
|
if (isDeferredNodeFlow) {
|
|
424
425
|
mainTemplate = 'Start_Main.ejs';
|
|
426
|
+
handlerTemplate = 'Start_HdrWbs.ejs';
|
|
425
427
|
}
|
|
426
428
|
return {
|
|
427
429
|
action,
|
|
428
430
|
handler: 'HdrWbs',
|
|
429
431
|
type: 'wbs',
|
|
430
432
|
mainTemplate,
|
|
431
|
-
handlerTemplate
|
|
433
|
+
handlerTemplate
|
|
432
434
|
};
|
|
433
435
|
}
|
|
434
436
|
|
|
@@ -453,12 +455,18 @@ export function resolveS3Parts(flow) {
|
|
|
453
455
|
}
|
|
454
456
|
|
|
455
457
|
if (action === 'ConfirmAfterUpload') {
|
|
458
|
+
let mainTemplate = 'legacy/ConfirmAfterUpload_Main.ejs';
|
|
459
|
+
let handlerTemplate = 'legacy/ConfirmAfterUpload_HdrSqs.ejs';
|
|
460
|
+
if (isDeferredNodeFlow) {
|
|
461
|
+
mainTemplate = 'ConfirmComplete_Main.ejs';
|
|
462
|
+
handlerTemplate = 'ConfirmComplete_HdrSqs.ejs';
|
|
463
|
+
}
|
|
456
464
|
return {
|
|
457
465
|
action,
|
|
458
466
|
handler: 'HdrSqs',
|
|
459
467
|
type: 'sqs',
|
|
460
|
-
mainTemplate
|
|
461
|
-
handlerTemplate
|
|
468
|
+
mainTemplate,
|
|
469
|
+
handlerTemplate
|
|
462
470
|
};
|
|
463
471
|
}
|
|
464
472
|
|
|
@@ -466,8 +474,8 @@ export function resolveS3Parts(flow) {
|
|
|
466
474
|
action,
|
|
467
475
|
handler: 'HdrSqs',
|
|
468
476
|
type: 'sqs',
|
|
469
|
-
mainTemplate: 'CreatePreSignUrl_Main.ejs',
|
|
470
|
-
handlerTemplate: 'CreatePreSignUrl_HdrSqs.ejs'
|
|
477
|
+
mainTemplate: 'legacy/CreatePreSignUrl_Main.ejs',
|
|
478
|
+
handlerTemplate: 'legacy/CreatePreSignUrl_HdrSqs.ejs'
|
|
471
479
|
};
|
|
472
480
|
});
|
|
473
481
|
}
|
|
@@ -52,10 +52,10 @@ export default async function <%- functionName %>(
|
|
|
52
52
|
|
|
53
53
|
let targetId =
|
|
54
54
|
passBackProperties.rawId ||
|
|
55
|
-
passBackProperties.<%- objectType %>
|
|
55
|
+
passBackProperties.<%- (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id` %> ||
|
|
56
56
|
passBackProperties.parentPassBackProperties?.rawId ||
|
|
57
|
-
passBackProperties.parentPassBackProperties?.<%- objectType %>
|
|
58
|
-
objInstanceFull?.identifiers?.<%- objectType
|
|
57
|
+
passBackProperties.parentPassBackProperties?.<%- (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id` %> ||
|
|
58
|
+
objInstanceFull?.identifiers?.<%- (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id` %>;
|
|
59
59
|
|
|
60
60
|
if (!targetId && objInstanceFull?.identifiers) {
|
|
61
61
|
targetId = Object.values(objInstanceFull.identifiers)[0];
|
|
@@ -73,13 +73,13 @@ export default async function <%- functionName %>(
|
|
|
73
73
|
|
|
74
74
|
if (tasks && tasks.Items && tasks.Items.length > 0) {
|
|
75
75
|
for (const taskRecord of tasks.Items) {
|
|
76
|
-
if (taskRecord.connectionId) {
|
|
76
|
+
if (taskRecord.connectionId && taskRecord.connectionId !== 'NONE') {
|
|
77
77
|
await postToConnection(
|
|
78
78
|
_izContext,
|
|
79
79
|
{
|
|
80
80
|
status: 'completed',
|
|
81
81
|
data: {
|
|
82
|
-
<%-
|
|
82
|
+
<%- (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id` %>: targetId,
|
|
83
83
|
objInstanceFull: objInstanceFull
|
|
84
84
|
}
|
|
85
85
|
},
|
|
@@ -27,6 +27,11 @@ import callingFlowSharedLib from '@izara_project/izara-core-library-calling-flow
|
|
|
27
27
|
import lambdaSharedLib from '@izara_project/izara-core-library-lambda';
|
|
28
28
|
import { sns } from '@izara_project/izara-core-library-external-request';
|
|
29
29
|
import { consts } from '@izara_project/izara-middleware';
|
|
30
|
+
<% if (isSimplifiedS3) { -%>
|
|
31
|
+
import dynamodbSharedLib from '@izara_project/izara-core-library-dynamodb';
|
|
32
|
+
import { utils } from '@izara_project/izara-core-library-service-schemas';
|
|
33
|
+
const { createFlowTypeConcat } = utils;
|
|
34
|
+
<% } -%>
|
|
30
35
|
|
|
31
36
|
//(<optionalRequired>)
|
|
32
37
|
//(</optionalRequired>)
|
|
@@ -68,6 +73,16 @@ export default async function <%- functionName %>(
|
|
|
68
73
|
|
|
69
74
|
const s3Key = field.key;
|
|
70
75
|
const actualByteSize = field.size;
|
|
76
|
+
|
|
77
|
+
if (!s3Key || s3Key.endsWith('/') || actualByteSize === 0) {
|
|
78
|
+
_izContext.logger.warn('Skipping directory marker or 0-byte object:', {
|
|
79
|
+
bucketName: bucket.name,
|
|
80
|
+
key: s3Key,
|
|
81
|
+
size: actualByteSize
|
|
82
|
+
});
|
|
83
|
+
return { status: 'skipped_empty_or_dir', s3Key };
|
|
84
|
+
}
|
|
85
|
+
|
|
71
86
|
const actualSizeMb = actualByteSize / (1024 * 1024);
|
|
72
87
|
|
|
73
88
|
_izContext.logger.debug('S3 file details:', {
|
|
@@ -120,6 +135,81 @@ export default async function <%- functionName %>(
|
|
|
120
135
|
<% } -%>
|
|
121
136
|
//(</extractIdentifiersFromKeyOrMetadata>)
|
|
122
137
|
|
|
138
|
+
<% if (isSimplifiedS3) { -%>
|
|
139
|
+
const flowTypeTag = createFlowTypeConcat(_izContext, {
|
|
140
|
+
flowTag: '<%- flowTag %>',
|
|
141
|
+
serviceTag: process.env.iz_serviceTag
|
|
142
|
+
});
|
|
143
|
+
const taskKey = `${flowTypeTag}${rawId}`;
|
|
144
|
+
const taskTableName = await dynamodbSharedLib.tableName(
|
|
145
|
+
_izContext,
|
|
146
|
+
'WebSocketTask'
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
let resolvedConnectionId =
|
|
150
|
+
s3Metadata.connectionid || s3Metadata.connectionId || '';
|
|
151
|
+
if (!resolvedConnectionId) {
|
|
152
|
+
const queryTasks = await dynamodbSharedLib.query(
|
|
153
|
+
_izContext,
|
|
154
|
+
taskTableName,
|
|
155
|
+
{ taskKey }
|
|
156
|
+
);
|
|
157
|
+
if (queryTasks && queryTasks.Items && queryTasks.Items.length > 0) {
|
|
158
|
+
resolvedConnectionId = queryTasks.Items[0].connectionId;
|
|
159
|
+
} else {
|
|
160
|
+
_izContext.logger.warn(
|
|
161
|
+
`[Idempotency] No task record found for <%- objectType %>Id: ${rawId}.` +
|
|
162
|
+
' Aborting dispatch to Create_In.'
|
|
163
|
+
);
|
|
164
|
+
return {
|
|
165
|
+
status: 'unauthorized_or_unknown_upload',
|
|
166
|
+
s3Key,
|
|
167
|
+
<%- objectType %>Id: rawId
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
await dynamodbSharedLib.updateItem(
|
|
174
|
+
_izContext,
|
|
175
|
+
taskTableName,
|
|
176
|
+
{
|
|
177
|
+
taskKey,
|
|
178
|
+
connectionId: resolvedConnectionId
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
UpdateExpression: 'SET uploadClaimed = :claimed, claimedAt = :now',
|
|
182
|
+
ConditionExpression:
|
|
183
|
+
'attribute_exists(taskKey) AND attribute_not_exists(uploadClaimed)',
|
|
184
|
+
ExpressionAttributeValues: {
|
|
185
|
+
':claimed': true,
|
|
186
|
+
':now': Date.now()
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
_izContext.logger.info(
|
|
191
|
+
`[Idempotency] Successfully claimed upload for <%- (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id` %>: ${rawId}`
|
|
192
|
+
);
|
|
193
|
+
} catch (claimErr) {
|
|
194
|
+
if (claimErr.name === 'ConditionalCheckFailedException') {
|
|
195
|
+
_izContext.logger.warn(
|
|
196
|
+
`[Idempotency] Duplicate or unauthorized S3 upload detected for` +
|
|
197
|
+
` <%- (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id` %>: ${rawId}. Aborting dispatch to Create_In.`
|
|
198
|
+
);
|
|
199
|
+
return {
|
|
200
|
+
status: 'duplicate_upload_ignored',
|
|
201
|
+
s3Key,
|
|
202
|
+
<%- (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id` %>: rawId
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
_izContext.logger.error(
|
|
206
|
+
'[Idempotency] Failed to execute atomic claim on WebSocketTask:',
|
|
207
|
+
claimErr
|
|
208
|
+
);
|
|
209
|
+
throw claimErr;
|
|
210
|
+
}
|
|
211
|
+
<% } -%>
|
|
212
|
+
|
|
123
213
|
//(<endpointHook>)
|
|
124
214
|
//(</endpointHook>)
|
|
125
215
|
|
|
@@ -134,7 +224,7 @@ export default async function <%- functionName %>(
|
|
|
134
224
|
<% } -%>
|
|
135
225
|
|
|
136
226
|
_izContext.logger.info('Extracted identifiers and metadata:', {
|
|
137
|
-
<%-
|
|
227
|
+
<%- (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id` %>: rawId,
|
|
138
228
|
parentId,
|
|
139
229
|
submittedByUserId: metaUserId,
|
|
140
230
|
<% if (typeof flowRelationships !== 'undefined' && Array.isArray(flowRelationships) && flowRelationships.length > 0) { -%>
|
|
@@ -191,9 +281,18 @@ export default async function <%- functionName %>(
|
|
|
191
281
|
let messageObject = {
|
|
192
282
|
objectType: '<%- objectType %>',
|
|
193
283
|
submittedByUserId: metaUserId,
|
|
284
|
+
<% if (typeof identifierKeys !== 'undefined' && identifierKeys && identifierKeys.length > 1) { -%>
|
|
285
|
+
identifiers: {
|
|
286
|
+
<%- identifierKeys[0] %>: rawId,
|
|
287
|
+
<% for (let i = 1; i < identifierKeys.length; i++) { -%>
|
|
288
|
+
<%- identifierKeys[i] %>: <%- identifierKeys[i] %><%- i < identifierKeys.length - 1 ? ',' : '' %>
|
|
289
|
+
<% } -%>
|
|
290
|
+
},
|
|
291
|
+
<% } else { -%>
|
|
194
292
|
identifiers: {
|
|
195
|
-
<%-
|
|
293
|
+
<%- (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id` %>: rawId
|
|
196
294
|
},
|
|
295
|
+
<% } -%>
|
|
197
296
|
<% if (typeof parentIdKeys !== 'undefined' && parentIdKeys && parentIdKeys.length > 1) { -%>
|
|
198
297
|
targetIdentifiers: {
|
|
199
298
|
<%- parentIdKeys[0] %>: parentId,
|
|
@@ -208,9 +307,16 @@ export default async function <%- functionName %>(
|
|
|
208
307
|
<% } -%>
|
|
209
308
|
relationships,
|
|
210
309
|
fieldNames: {
|
|
310
|
+
<% if (typeof identifierKeys !== 'undefined' && identifierKeys && identifierKeys.length > 1) { -%>
|
|
311
|
+
<%- identifierKeys[0] %>: rawId,
|
|
312
|
+
<% for (let i = 1; i < identifierKeys.length; i++) { -%>
|
|
313
|
+
<%- identifierKeys[i] %>: <%- identifierKeys[i] %>,
|
|
314
|
+
<% } -%>
|
|
315
|
+
<% } else { -%>
|
|
316
|
+
<%- (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id` %>: rawId,
|
|
317
|
+
<% } -%>
|
|
211
318
|
//(<fieldNamesHook>)
|
|
212
319
|
//(</fieldNamesHook>)
|
|
213
|
-
fileName: fileNameWithExt
|
|
214
320
|
}
|
|
215
321
|
};
|
|
216
322
|
|
|
@@ -230,7 +336,7 @@ export default async function <%- functionName %>(
|
|
|
230
336
|
passBackProperties,
|
|
231
337
|
callingFlowSharedLib.createCallingFlowConfig(
|
|
232
338
|
confirmCallbackFunction,
|
|
233
|
-
{ rawId: rawId, <%-
|
|
339
|
+
{ rawId: rawId, <%- (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id` %>: rawId }
|
|
234
340
|
)
|
|
235
341
|
)
|
|
236
342
|
);
|
|
@@ -245,7 +351,7 @@ export default async function <%- functionName %>(
|
|
|
245
351
|
|
|
246
352
|
_izContext.logger.info('Publishing to Create_In SNS topic:', {
|
|
247
353
|
topicArn,
|
|
248
|
-
<%-
|
|
354
|
+
<%- (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id` %>: rawId,
|
|
249
355
|
parentId<%- (typeof flowRelationships !== 'undefined' && Array.isArray(flowRelationships) && flowRelationships.length > 0) ? flowRelationships.map(r => { const k = r.targetIdentifierKey || `${r.targetObjType.objectType}Id`; return `,\n ${k}: meta${k.charAt(0).toUpperCase() + k.slice(1)}`; }).join('') : '' %>
|
|
250
356
|
});
|
|
251
357
|
_izContext.logger.debug(
|
|
@@ -260,7 +366,7 @@ export default async function <%- functionName %>(
|
|
|
260
366
|
_izContext.logger.info('Successfully published to Create_In SNS:', {
|
|
261
367
|
publishResult,
|
|
262
368
|
s3Key,
|
|
263
|
-
<%-
|
|
369
|
+
<%- (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id` %>: rawId
|
|
264
370
|
});
|
|
265
371
|
|
|
266
372
|
return {
|
|
@@ -0,0 +1,65 @@
|
|
|
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 {
|
|
19
|
+
middlewareHandler as middleware,
|
|
20
|
+
response
|
|
21
|
+
} from '@izara_project/izara-middleware';
|
|
22
|
+
import callingFlowSharedLib from '@izara_project/izara-core-library-calling-flow';
|
|
23
|
+
import { webSocket } from '@izara_project/izara-core-library-external-request';
|
|
24
|
+
const { postToConnection } = webSocket;
|
|
25
|
+
|
|
26
|
+
import <%= functionName %> from './<%= functionName %>_Main.js';
|
|
27
|
+
|
|
28
|
+
export const main = middleware.wrap(async (event, context) => {
|
|
29
|
+
event._izContext.logger.debug('Event:', event);
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
if (event.requestContext) {
|
|
33
|
+
const route = event.requestContext.routeKey;
|
|
34
|
+
|
|
35
|
+
switch (route) {
|
|
36
|
+
case '<%- route %>':
|
|
37
|
+
console.log('event in route webSocket', event);
|
|
38
|
+
let eventParams = JSON.parse(event.body);
|
|
39
|
+
|
|
40
|
+
let callingFlow = eventParams?.callingFlow
|
|
41
|
+
? eventParams.callingFlow
|
|
42
|
+
: {};
|
|
43
|
+
await <%= functionName %>(
|
|
44
|
+
event._izContext,
|
|
45
|
+
eventParams,
|
|
46
|
+
callingFlowSharedLib.addCallingFlowToPassOnProperties(callingFlow)
|
|
47
|
+
//(<additionalParams>)
|
|
48
|
+
//(</additionalParams>)
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
const connId = event.connId;
|
|
55
|
+
await postToConnection(event._izContext, { message: event.message }, connId);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return response.webSocketSuccess();
|
|
59
|
+
} catch (err) {
|
|
60
|
+
const connId = event.requestContext.connectionId;
|
|
61
|
+
event._izContext.logger.error('Error, WebSocket: ', err);
|
|
62
|
+
await postToConnection(event._izContext, { message: err.message }, connId);
|
|
63
|
+
return response.failure(err);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
@@ -15,7 +15,7 @@ You should have received a copy of the GNU Affero General Public License
|
|
|
15
15
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import { identifierUuid } from '@izara_project/izara-shared-core';
|
|
19
19
|
import { createPresignedPost } from '@aws-sdk/s3-presigned-post';
|
|
20
20
|
import { S3Client } from '@aws-sdk/client-s3';
|
|
21
21
|
import { consts } from '@izara_project/izara-middleware';
|
|
@@ -99,7 +99,8 @@ export default async function <%- functionName %>(
|
|
|
99
99
|
}
|
|
100
100
|
<% } -%>
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
<% const targetIdName = (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id`; -%>
|
|
103
|
+
const <%- targetIdName %> = identifierUuid();
|
|
103
104
|
|
|
104
105
|
<% if (typeof parentIdKeys !== 'undefined' && parentIdKeys && parentIdKeys.length > 0) { -%>
|
|
105
106
|
const targetParentId = requestParams.targetIdentifiers?.<%- parentIdKeys[0] %>;
|
|
@@ -139,7 +140,7 @@ export default async function <%- functionName %>(
|
|
|
139
140
|
Key: Key,
|
|
140
141
|
Conditions: [
|
|
141
142
|
['content-length-range', 0, maximumSize],
|
|
142
|
-
['eq', '$x-amz-meta-<%-
|
|
143
|
+
['eq', '$x-amz-meta-<%- targetIdName %>', <%- targetIdName %>],
|
|
143
144
|
<% if (typeof parentIdKeys !== 'undefined' && parentIdKeys && parentIdKeys.length > 0) { -%>
|
|
144
145
|
<% for (const key of parentIdKeys) { -%>
|
|
145
146
|
['eq', '$x-amz-meta-<%- key %>', target<%- key.charAt(0).toUpperCase() + key.slice(1) %>],
|
|
@@ -147,6 +148,7 @@ export default async function <%- functionName %>(
|
|
|
147
148
|
<% } -%>
|
|
148
149
|
['starts-with', '$x-amz-meta-submittedByUserId', ''],
|
|
149
150
|
['starts-with', '$Content-Type', '<%- contentTypePrefix %>'],
|
|
151
|
+
['starts-with', '$x-amz-meta-connectionId', ''],
|
|
150
152
|
<% if (typeof flowRelationships !== 'undefined' && Array.isArray(flowRelationships) && flowRelationships.length > 0) { -%>
|
|
151
153
|
<% for (const rel of flowRelationships) { -%>
|
|
152
154
|
<% const relIdKey = rel.targetIdentifierKey || `${rel.targetObjType.objectType}Id`; -%>
|
|
@@ -157,13 +159,14 @@ export default async function <%- functionName %>(
|
|
|
157
159
|
//(</conditionCreateUrlLink>)
|
|
158
160
|
],
|
|
159
161
|
Fields: {
|
|
160
|
-
'x-amz-meta-<%-
|
|
162
|
+
'x-amz-meta-<%- targetIdName %>': <%- targetIdName %>,
|
|
161
163
|
<% if (typeof parentIdKeys !== 'undefined' && parentIdKeys && parentIdKeys.length > 0) { -%>
|
|
162
164
|
<% for (const key of parentIdKeys) { -%>
|
|
163
165
|
'x-amz-meta-<%- key %>': target<%- key.charAt(0).toUpperCase() + key.slice(1) %>,
|
|
164
166
|
<% } -%>
|
|
165
167
|
<% } -%>
|
|
166
168
|
'x-amz-meta-submittedByUserId': submittedByUserId,
|
|
169
|
+
'x-amz-meta-connectionId': connectionId || 'NONE',
|
|
167
170
|
<% if (typeof flowRelationships !== 'undefined' && Array.isArray(flowRelationships) && flowRelationships.length > 0) { -%>
|
|
168
171
|
<% for (const rel of flowRelationships) { -%>
|
|
169
172
|
<% const relIdKey = rel.targetIdentifierKey || `${rel.targetObjType.objectType}Id`; -%>
|
|
@@ -181,24 +184,25 @@ export default async function <%- functionName %>(
|
|
|
181
184
|
|
|
182
185
|
const uploadUrl = await createPresignedPost(client, createPreSignUrlParam);
|
|
183
186
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
187
|
+
const resolvedConnectionId = connectionId || 'NONE';
|
|
188
|
+
await dynamodbSharedLib.putItem(
|
|
189
|
+
_izContext,
|
|
190
|
+
await dynamodbSharedLib.tableName(_izContext, 'WebSocketTask'),
|
|
191
|
+
{
|
|
192
|
+
taskKey: `${createFlowTypeConcat(_izContext, { flowTag: '<%- flowTag %>', serviceTag: process.env.iz_serviceTag })}${<%- targetIdName %>}`,
|
|
193
|
+
connectionId: resolvedConnectionId,
|
|
194
|
+
expirationTime: Math.floor(Date.now() / 1000) + 7200
|
|
195
|
+
}
|
|
196
|
+
);
|
|
194
197
|
|
|
198
|
+
if (connectionId) {
|
|
195
199
|
await postToConnection(
|
|
196
200
|
_izContext,
|
|
197
201
|
{
|
|
198
202
|
status: 'success',
|
|
199
203
|
data: {
|
|
200
204
|
uploadUrl: uploadUrl,
|
|
201
|
-
<%-
|
|
205
|
+
<%- targetIdName %>: <%- targetIdName %>
|
|
202
206
|
}
|
|
203
207
|
},
|
|
204
208
|
connectionId
|
|
@@ -207,7 +211,7 @@ export default async function <%- functionName %>(
|
|
|
207
211
|
|
|
208
212
|
return {
|
|
209
213
|
uploadUrl: uploadUrl,
|
|
210
|
-
<%-
|
|
214
|
+
<%- targetIdName %>: <%- targetIdName %>
|
|
211
215
|
};
|
|
212
216
|
} catch (err) {
|
|
213
217
|
_izContext.logger.error('Error in <%- functionName %>:', err);
|
|
@@ -64,7 +64,7 @@ export default async function <%- functionName %>(
|
|
|
64
64
|
{ taskKey: taskKey }
|
|
65
65
|
);
|
|
66
66
|
|
|
67
|
-
if (taskRecord && taskRecord.connectionId) {
|
|
67
|
+
if (taskRecord && taskRecord.connectionId && taskRecord.connectionId !== 'NONE') {
|
|
68
68
|
await postToConnection(
|
|
69
69
|
_izContext,
|
|
70
70
|
{
|
|
@@ -154,7 +154,7 @@ export default async function CancelUsage(_izContext, requestParams) {
|
|
|
154
154
|
};
|
|
155
155
|
|
|
156
156
|
const eventSourceTag =
|
|
157
|
-
_izContext
|
|
157
|
+
_izContext?.izEventSource?.eventSourceTag ||
|
|
158
158
|
requestParams?.izEventSource?.eventSourceTag;
|
|
159
159
|
|
|
160
160
|
let isSync = false;
|
|
@@ -162,16 +162,17 @@ export default async function CancelUsage(_izContext, requestParams) {
|
|
|
162
162
|
eventSourceTag === 'DirectInvoke' ||
|
|
163
163
|
eventSourceTag === 'ApiGateway' ||
|
|
164
164
|
eventSourceTag === 'ApiGatewayV2' ||
|
|
165
|
-
requestParams?.hasOwnProperty('__context__')
|
|
165
|
+
requestParams?.hasOwnProperty('__context__') ||
|
|
166
|
+
_izContext?.functionName?.endsWith('HdrInv') ||
|
|
167
|
+
_izContext?.functionName?.endsWith('HdrApi') ||
|
|
168
|
+
eventSourceTag !== 'Sqs'
|
|
166
169
|
) {
|
|
167
170
|
isSync = true;
|
|
168
171
|
}
|
|
169
172
|
|
|
170
173
|
if (!isSync) {
|
|
171
|
-
await sns.
|
|
172
|
-
|
|
173
|
-
returnResult
|
|
174
|
-
},
|
|
174
|
+
await sns.publishAsync(_izContext, {
|
|
175
|
+
Message: JSON.stringify({ returnResult }),
|
|
175
176
|
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
176
177
|
_izContext,
|
|
177
178
|
'CancelUsage_Out'
|
|
@@ -178,7 +178,7 @@ export default async function ConfirmUsage(_izContext, requestParams) {
|
|
|
178
178
|
};
|
|
179
179
|
|
|
180
180
|
const eventSourceTag =
|
|
181
|
-
_izContext
|
|
181
|
+
_izContext?.izEventSource?.eventSourceTag ||
|
|
182
182
|
requestParams?.izEventSource?.eventSourceTag;
|
|
183
183
|
|
|
184
184
|
let isSync = false;
|
|
@@ -186,16 +186,17 @@ export default async function ConfirmUsage(_izContext, requestParams) {
|
|
|
186
186
|
eventSourceTag === 'DirectInvoke' ||
|
|
187
187
|
eventSourceTag === 'ApiGateway' ||
|
|
188
188
|
eventSourceTag === 'ApiGatewayV2' ||
|
|
189
|
-
requestParams?.hasOwnProperty('__context__')
|
|
189
|
+
requestParams?.hasOwnProperty('__context__') ||
|
|
190
|
+
_izContext?.functionName?.endsWith('HdrInv') ||
|
|
191
|
+
_izContext?.functionName?.endsWith('HdrApi') ||
|
|
192
|
+
eventSourceTag !== 'Sqs'
|
|
190
193
|
) {
|
|
191
194
|
isSync = true;
|
|
192
195
|
}
|
|
193
196
|
|
|
194
197
|
if (!isSync) {
|
|
195
|
-
await sns.
|
|
196
|
-
|
|
197
|
-
returnResult
|
|
198
|
-
},
|
|
198
|
+
await sns.publishAsync(_izContext, {
|
|
199
|
+
Message: JSON.stringify({ returnResult }),
|
|
199
200
|
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
200
201
|
_izContext,
|
|
201
202
|
'ConfirmUsage_Out'
|
|
@@ -133,8 +133,8 @@ export default async function CreateTargetRole(_izContext, requestParams) {
|
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
await sns.
|
|
137
|
-
|
|
136
|
+
await sns.publishAsync(_izContext, {
|
|
137
|
+
Message: JSON.stringify({ returnResult: { roleId, roleIdKey } }),
|
|
138
138
|
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
139
139
|
_izContext,
|
|
140
140
|
'CreateTargetRole_Out'
|
|
@@ -82,8 +82,8 @@ export default async function DeleteTargetRole(_izContext, requestParams) {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
await sns.
|
|
86
|
-
|
|
85
|
+
await sns.publishAsync(_izContext, {
|
|
86
|
+
Message: JSON.stringify({ returnResult: { identifiersConcat, roleIdKey, status: 'deleted' } }),
|
|
87
87
|
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
88
88
|
_izContext,
|
|
89
89
|
'DeleteTargetRole_Out'
|
|
@@ -95,8 +95,8 @@ export default async function GetMyPermissions(_izContext, requestParams) {
|
|
|
95
95
|
permissions: permissionsMap
|
|
96
96
|
};
|
|
97
97
|
|
|
98
|
-
await sns.
|
|
99
|
-
|
|
98
|
+
await sns.publishAsync(_izContext, {
|
|
99
|
+
Message: JSON.stringify({ returnResult: result }),
|
|
100
100
|
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
101
101
|
_izContext,
|
|
102
102
|
'GetMyPermissions_Out'
|
|
@@ -47,8 +47,8 @@ export default async function GetTargetRole(_izContext, requestParams) {
|
|
|
47
47
|
users: usersResult?.Items || []
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
-
await sns.
|
|
51
|
-
|
|
50
|
+
await sns.publishAsync(_izContext, {
|
|
51
|
+
Message: JSON.stringify({ returnResult: result }),
|
|
52
52
|
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
53
53
|
_izContext,
|
|
54
54
|
'GetTargetRole_Out'
|
|
@@ -41,8 +41,8 @@ export default async function <%- flowTag %>(_izContext, requestParams) {
|
|
|
41
41
|
dynamodbSharedLib.tableName(_izContext, '<%- tableName %>'),
|
|
42
42
|
queryParams
|
|
43
43
|
);
|
|
44
|
-
await sns.
|
|
45
|
-
|
|
44
|
+
await sns.publishAsync(_izContext, {
|
|
45
|
+
Message: JSON.stringify({ returnResult: { records: resultQuery.Items || [] } }),
|
|
46
46
|
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
47
47
|
_izContext,
|
|
48
48
|
'<%- flowTag %>_Out'
|
|
@@ -28,8 +28,8 @@ export default async function <%- flowTag %>(_izContext, requestParams) {
|
|
|
28
28
|
dynamodbSharedLib.tableName(_izContext, '<%- tableName %>'),
|
|
29
29
|
{ <%- fieldList %> }
|
|
30
30
|
);
|
|
31
|
-
await sns.
|
|
32
|
-
|
|
31
|
+
await sns.publishAsync(_izContext, {
|
|
32
|
+
Message: JSON.stringify({ returnResult: { <%- fieldList %> } }),
|
|
33
33
|
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
34
34
|
_izContext,
|
|
35
35
|
'<%- flowTag %>_Out'
|
|
@@ -197,7 +197,7 @@ export default async function ReservedDynamicUsage(_izContext, requestParams) {
|
|
|
197
197
|
};
|
|
198
198
|
|
|
199
199
|
const eventSourceTag =
|
|
200
|
-
_izContext
|
|
200
|
+
_izContext?.izEventSource?.eventSourceTag ||
|
|
201
201
|
requestParams?.izEventSource?.eventSourceTag;
|
|
202
202
|
|
|
203
203
|
let isSync = false;
|
|
@@ -205,14 +205,17 @@ export default async function ReservedDynamicUsage(_izContext, requestParams) {
|
|
|
205
205
|
eventSourceTag === 'DirectInvoke' ||
|
|
206
206
|
eventSourceTag === 'ApiGateway' ||
|
|
207
207
|
eventSourceTag === 'ApiGatewayV2' ||
|
|
208
|
-
requestParams?.hasOwnProperty('__context__')
|
|
208
|
+
requestParams?.hasOwnProperty('__context__') ||
|
|
209
|
+
_izContext?.functionName?.endsWith('HdrInv') ||
|
|
210
|
+
_izContext?.functionName?.endsWith('HdrApi') ||
|
|
211
|
+
eventSourceTag !== 'Sqs'
|
|
209
212
|
) {
|
|
210
213
|
isSync = true;
|
|
211
214
|
}
|
|
212
215
|
|
|
213
216
|
if (!isSync) {
|
|
214
|
-
await sns.
|
|
215
|
-
|
|
217
|
+
await sns.publishAsync(_izContext, {
|
|
218
|
+
Message: JSON.stringify({ returnResult }),
|
|
216
219
|
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
217
220
|
_izContext,
|
|
218
221
|
'ReservedDynamicUsage_Out'
|
|
@@ -93,8 +93,8 @@ export default async function UpdateTargetRole(_izContext, requestParams) {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
await sns.
|
|
97
|
-
|
|
96
|
+
await sns.publishAsync(_izContext, {
|
|
97
|
+
Message: JSON.stringify({ returnResult: { roleIdKey, status: 'updated' } }),
|
|
98
98
|
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
99
99
|
_izContext,
|
|
100
100
|
'UpdateTargetRole_Out'
|
|
@@ -54,8 +54,8 @@ export default async function <%- flowTag %>(_izContext, requestParams) {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
]);
|
|
57
|
-
await sns.
|
|
58
|
-
|
|
57
|
+
await sns.publishAsync(_izContext, {
|
|
58
|
+
Message: JSON.stringify({ returnResult: { userId, roleIdKey } }),
|
|
59
59
|
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
60
60
|
_izContext,
|
|
61
61
|
'<%- flowTag %>_Out'
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
- stream:
|
|
8
8
|
type: dynamodb
|
|
9
9
|
arn:
|
|
10
|
-
Fn::
|
|
10
|
+
Fn::ImportValue: ${self:custom.iz_resourcePrefix}<%= tableName %>StreamArn
|
|
11
11
|
batchSize: <%= batchSize %>
|
|
12
12
|
startingPosition: <%= startingPosition %>
|
|
13
13
|
<% if (Array.isArray(filterPatterns) && filterPatterns.length > 0) { -%>
|
|
@@ -11,8 +11,12 @@ export async function renderDynamo(state) {
|
|
|
11
11
|
)
|
|
12
12
|
]);
|
|
13
13
|
const indentedTables = tables.map((t) => indentYaml(t, 2)).join('\n');
|
|
14
|
+
let content = system;
|
|
14
15
|
if (indentedTables) {
|
|
15
|
-
|
|
16
|
+
content = `${content}\n${indentedTables}`;
|
|
16
17
|
}
|
|
17
|
-
|
|
18
|
+
if (state.selfRbac) {
|
|
19
|
+
content = `${content}\n\nOutputs:\n RunningTotalReservedUsageStreamArn:\n Value:\n Fn::GetAtt: [RunningTotalReservedUsageTable, StreamArn]\n Export:\n Name: \${self:custom.iz_resourcePrefix}RunningTotalReservedUsageStreamArn\n`;
|
|
20
|
+
}
|
|
21
|
+
return content;
|
|
18
22
|
}
|
|
@@ -102,7 +102,8 @@ export async function renderOut(state) {
|
|
|
102
102
|
renderResourceTemplate('FlowOut_Yaml.ejs', { topicName })
|
|
103
103
|
)
|
|
104
104
|
);
|
|
105
|
-
const
|
|
105
|
+
const fileHook = '#<#customTopicOut#>\n#<#/customTopicOut#>';
|
|
106
|
+
const body = [...renderedTopics, fileHook].filter(Boolean).join('\n');
|
|
106
107
|
return `Resources:\n${indentYaml(body, 2)}`;
|
|
107
108
|
}
|
|
108
109
|
|
|
@@ -332,7 +332,7 @@ test('generates synchronous HdrInv and HdrApi handlers for FlowLimit flows', asy
|
|
|
332
332
|
/ReservedUsageTtlExpiredHdrDss:\n\s+handler: src\/generatedCode\/FlowLimit\/ReservedUsageTtlExpired\/source\/ReservedUsageTtlExpired_HdrDss\.main/
|
|
333
333
|
);
|
|
334
334
|
assert.match(limitYaml, /type: dynamodb/);
|
|
335
|
-
assert.match(limitYaml, /
|
|
335
|
+
assert.match(limitYaml, /RunningTotalReservedUsageStreamArn/);
|
|
336
336
|
assert.match(limitYaml, /eventName: \["REMOVE"\]/);
|
|
337
337
|
});
|
|
338
338
|
|
|
@@ -131,8 +131,8 @@ test('emitS3Flow with ADR-016 deferred-node pipeline generates correct 3 pairs o
|
|
|
131
131
|
'Start_Main should generate S3 presigned post URL'
|
|
132
132
|
);
|
|
133
133
|
assert.ok(
|
|
134
|
-
startMainArtifact.content.includes('
|
|
135
|
-
'Start_Main should generate identifier with
|
|
134
|
+
startMainArtifact.content.includes('identifierUuid'),
|
|
135
|
+
'Start_Main should generate identifier with identifierUuid'
|
|
136
136
|
);
|
|
137
137
|
assert.ok(
|
|
138
138
|
!startMainArtifact.content.includes("snsTopicArnByFlowSchema(_izContext, 'Create_In')"),
|
|
@@ -186,6 +186,10 @@ test('emitS3Flow with ADR-016 deferred-node pipeline generates correct 3 pairs o
|
|
|
186
186
|
procMainArtifact.content.includes('FeedChunkRecordUrlConfirmAfterUploadHdrSqs'),
|
|
187
187
|
'ProcessAfterUpload_Main must target ConfirmAfterUploadHdrSqs for callback'
|
|
188
188
|
);
|
|
189
|
+
assert.ok(
|
|
190
|
+
procMainArtifact.content.includes('feedChunkId: rawId'),
|
|
191
|
+
'ProcessAfterUpload_Main must include objectTypeId: rawId in fieldNames'
|
|
192
|
+
);
|
|
189
193
|
|
|
190
194
|
const confirmMainArtifact = result.artifacts.find(
|
|
191
195
|
(a) => a.path === `${source}/FeedChunkRecordUrlConfirmAfterUpload_Main.js`
|
|
@@ -853,6 +857,156 @@ test('emitS3Flow resolves relationships from schemas with relationshipDirection
|
|
|
853
857
|
);
|
|
854
858
|
});
|
|
855
859
|
|
|
860
|
+
test('emitS3Flow generates WebSocketTask atomic claim in ProcessAfterUpload and connectionId NONE guard in ConfirmComplete', async () => {
|
|
861
|
+
const flow = {
|
|
862
|
+
flowTag: 'FeedChunkRecordUrl',
|
|
863
|
+
initiateBy: 'user',
|
|
864
|
+
handleObj: 'one',
|
|
865
|
+
statusType: 'statusField',
|
|
866
|
+
event: ['s3'],
|
|
867
|
+
objType: {
|
|
868
|
+
objectType: 'feedChunk',
|
|
869
|
+
serviceTag: 'FeedChunk'
|
|
870
|
+
},
|
|
871
|
+
bucketName: 'video-record-temp',
|
|
872
|
+
flowSteps: [
|
|
873
|
+
{ stepName: 'Start', event: ['wbs'] },
|
|
874
|
+
{ stepName: 'ProcessAfterUpload', event: ['s3'] },
|
|
875
|
+
{ stepName: 'ConfirmComplete', event: ['extTopic'] }
|
|
876
|
+
]
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
const source = 'app/src/generatedCode/FlowSchemas/FeedChunkRecordUrl/source';
|
|
880
|
+
const handlerRoot = 'src/generatedCode/FlowSchemas/FeedChunkRecordUrl/source';
|
|
881
|
+
|
|
882
|
+
const result = await emitS3Flow(flow, source, handlerRoot);
|
|
883
|
+
|
|
884
|
+
const procMain = result.artifacts.find(
|
|
885
|
+
(a) => a.path === `${source}/FeedChunkRecordUrlProcessAfterUpload_Main.js`
|
|
886
|
+
);
|
|
887
|
+
assert.ok(
|
|
888
|
+
procMain.content.includes('attribute_exists(taskKey) AND attribute_not_exists(uploadClaimed)'),
|
|
889
|
+
'ProcessAfterUpload_Main must enforce attribute_exists(taskKey) AND attribute_not_exists(uploadClaimed)'
|
|
890
|
+
);
|
|
891
|
+
assert.ok(
|
|
892
|
+
procMain.content.includes("status: 'unauthorized_or_unknown_upload'"),
|
|
893
|
+
'ProcessAfterUpload_Main must abort when task record is missing'
|
|
894
|
+
);
|
|
895
|
+
assert.ok(
|
|
896
|
+
procMain.content.includes('dynamodbSharedLib'),
|
|
897
|
+
'ProcessAfterUpload_Main must import dynamodbSharedLib for simplified S3'
|
|
898
|
+
);
|
|
899
|
+
|
|
900
|
+
assert.ok(
|
|
901
|
+
procMain.content.includes("status: 'skipped_empty_or_dir'"),
|
|
902
|
+
'ProcessAfterUpload_Main must guard against directory markers or 0-byte files'
|
|
903
|
+
);
|
|
904
|
+
|
|
905
|
+
const confirmMain = result.artifacts.find(
|
|
906
|
+
(a) => a.path === `${source}/FeedChunkRecordUrlConfirmComplete_Main.js`
|
|
907
|
+
);
|
|
908
|
+
assert.ok(
|
|
909
|
+
confirmMain.content.includes("taskRecord.connectionId !== 'NONE'"),
|
|
910
|
+
'ConfirmComplete_Main must guard against connectionId NONE'
|
|
911
|
+
);
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
test('resolveTargetIdentifierInfo resolves both DynamoDB and Graph identifier formats', async () => {
|
|
915
|
+
const { resolveTargetIdentifierInfo } = await import(
|
|
916
|
+
'../../../src/emit/application/custom/customFlowS3.js'
|
|
917
|
+
);
|
|
918
|
+
|
|
919
|
+
// 1. Graph style: { type: 'identifier', fieldName: 'chunkUuid' }
|
|
920
|
+
const graphFlow = { objType: { objectType: 'feedChunk' } };
|
|
921
|
+
const graphObjects = [
|
|
922
|
+
{
|
|
923
|
+
objectType: 'feedChunk',
|
|
924
|
+
identifiers: [{ type: 'identifier', fieldName: 'chunkUuid' }]
|
|
925
|
+
}
|
|
926
|
+
];
|
|
927
|
+
const graphResult = resolveTargetIdentifierInfo(graphFlow, graphObjects);
|
|
928
|
+
assert.equal(graphResult.identifierKey, 'chunkUuid');
|
|
929
|
+
assert.deepEqual(graphResult.identifierKeys, ['chunkUuid']);
|
|
930
|
+
|
|
931
|
+
// 2. DynamoDB style: { type: 'partitionKey', fieldName: 'myFeedId' }
|
|
932
|
+
const dynamoFlow = { objType: { objectType: 'feedChunk' } };
|
|
933
|
+
const dynamoObjects = [
|
|
934
|
+
{
|
|
935
|
+
objectType: 'feedChunk',
|
|
936
|
+
identifiers: [{ type: 'partitionKey', fieldName: 'myFeedId' }]
|
|
937
|
+
}
|
|
938
|
+
];
|
|
939
|
+
const dynamoResult = resolveTargetIdentifierInfo(dynamoFlow, dynamoObjects);
|
|
940
|
+
assert.equal(dynamoResult.identifierKey, 'myFeedId');
|
|
941
|
+
assert.deepEqual(dynamoResult.identifierKeys, ['myFeedId']);
|
|
942
|
+
|
|
943
|
+
// 3. Fallback when no identifiers defined
|
|
944
|
+
const emptyFlow = { objType: { objectType: 'videoFeed' } };
|
|
945
|
+
const emptyResult = resolveTargetIdentifierInfo(emptyFlow, []);
|
|
946
|
+
assert.equal(emptyResult.identifierKey, 'videoFeedId');
|
|
947
|
+
assert.deepEqual(emptyResult.identifierKeys, ['videoFeedId']);
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
test('emitS3Flow generates dynamic identifier name for both Start_Main and ProcessAfterUpload_Main', async () => {
|
|
951
|
+
const flow = {
|
|
952
|
+
flowTag: 'FeedChunkRecordUrl',
|
|
953
|
+
flowType: 'custom',
|
|
954
|
+
event: ['s3'],
|
|
955
|
+
bucketName: 'video-record-temp',
|
|
956
|
+
contentTypePrefix: 'video/',
|
|
957
|
+
maxFileSizeMb: 50,
|
|
958
|
+
objType: {
|
|
959
|
+
objectType: 'feedChunk',
|
|
960
|
+
serviceTag: 'FeedChunk'
|
|
961
|
+
},
|
|
962
|
+
flowSteps: [
|
|
963
|
+
{ stepName: 'Start', event: ['wbs'] },
|
|
964
|
+
{ stepName: 'ProcessAfterUpload', event: ['s3'] },
|
|
965
|
+
{ stepName: 'ConfirmComplete', event: ['sqs'] }
|
|
966
|
+
]
|
|
967
|
+
};
|
|
968
|
+
|
|
969
|
+
const customObjects = [
|
|
970
|
+
{
|
|
971
|
+
objectType: 'feedChunk',
|
|
972
|
+
identifiers: [
|
|
973
|
+
{
|
|
974
|
+
type: 'identifier',
|
|
975
|
+
fieldName: 'customChunkId'
|
|
976
|
+
}
|
|
977
|
+
]
|
|
978
|
+
}
|
|
979
|
+
];
|
|
980
|
+
|
|
981
|
+
const source = 'app/src/generatedCode/FlowSchemas/FeedChunkRecordUrl/source';
|
|
982
|
+
const handlerRoot = 'src/generatedCode/FlowSchemas/FeedChunkRecordUrl/source';
|
|
983
|
+
|
|
984
|
+
const result = await emitS3Flow(flow, source, handlerRoot, {
|
|
985
|
+
objects: customObjects
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
const startMain = result.artifacts.find(
|
|
989
|
+
(a) => a.path === `${source}/FeedChunkRecordUrl_Main.js`
|
|
990
|
+
);
|
|
991
|
+
assert.ok(
|
|
992
|
+
startMain.content.includes('const customChunkId = identifierUuid()'),
|
|
993
|
+
'Start_Main must bind custom identifier key'
|
|
994
|
+
);
|
|
995
|
+
assert.ok(
|
|
996
|
+
startMain.content.includes("'x-amz-meta-customChunkId': customChunkId"),
|
|
997
|
+
'Start_Main must use custom identifier key in S3 metadata'
|
|
998
|
+
);
|
|
999
|
+
|
|
1000
|
+
const procMain = result.artifacts.find(
|
|
1001
|
+
(a) => a.path === `${source}/FeedChunkRecordUrlProcessAfterUpload_Main.js`
|
|
1002
|
+
);
|
|
1003
|
+
assert.ok(
|
|
1004
|
+
procMain.content.includes('customChunkId: rawId'),
|
|
1005
|
+
'ProcessAfterUpload_Main must use custom identifier key in fieldNames and identifiers'
|
|
1006
|
+
);
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
|
|
856
1010
|
|
|
857
1011
|
|
|
858
1012
|
|
|
@@ -97,6 +97,10 @@ test('emits queues, topics, tables, and IAM roles from schema data', async () =>
|
|
|
97
97
|
dynamoYaml,
|
|
98
98
|
/RunningTotalReservedUsageTable:\s+Type: 'AWS::DynamoDB::Table'[\s\S]+TableName: \${self:custom\.iz_resourcePrefix}RunningTotalReservedUsage/
|
|
99
99
|
);
|
|
100
|
+
assert.match(
|
|
101
|
+
dynamoYaml,
|
|
102
|
+
/Outputs:\s+RunningTotalReservedUsageStreamArn:\s+Value:\s+Fn::GetAtt: \[RunningTotalReservedUsageTable, StreamArn\]\s+Export:\s+Name: \${self:custom\.iz_resourcePrefix}RunningTotalReservedUsageStreamArn/
|
|
103
|
+
);
|
|
100
104
|
const iam = await readFile(
|
|
101
105
|
join(
|
|
102
106
|
rootPath,
|
|
@@ -960,3 +964,23 @@ test('roleStatements emits s3:GetObject for custom S3 flow bucket', async () =>
|
|
|
960
964
|
assert.match(uploadRole, /arn:aws:s3:::UploadBucket\/\*/);
|
|
961
965
|
});
|
|
962
966
|
|
|
967
|
+
test('emits per-topic hook tags in generated-sns-out.yml', async () => {
|
|
968
|
+
const rootPath = await createProject();
|
|
969
|
+
await generateResourceArtifacts(rootPath);
|
|
970
|
+
const snsOut = await readFile(
|
|
971
|
+
join(rootPath, 'resource/sls_yaml/generatedCode/source/generated-sns-out.yml'),
|
|
972
|
+
'utf8'
|
|
973
|
+
);
|
|
974
|
+
assert.match(snsOut, /#<#Out\w+TopicSetting#>\s*#<#\/Out\w+TopicSetting#>/);
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
test('emits customTopicOut file-level hook in generated-sns-out.yml', async () => {
|
|
978
|
+
const rootPath = await createProject();
|
|
979
|
+
await generateResourceArtifacts(rootPath);
|
|
980
|
+
const snsOut = await readFile(
|
|
981
|
+
join(rootPath, 'resource/sls_yaml/generatedCode/source/generated-sns-out.yml'),
|
|
982
|
+
'utf8'
|
|
983
|
+
);
|
|
984
|
+
assert.match(snsOut, /#<#customTopicOut#>\s*#<#\/customTopicOut#>/);
|
|
985
|
+
});
|
|
986
|
+
|
|
@@ -402,4 +402,53 @@ test('warns when source content contains custom code in hook missing from templa
|
|
|
402
402
|
}
|
|
403
403
|
});
|
|
404
404
|
|
|
405
|
+
test('preserves custom settings inside Out<topicName>TopicSetting and customTopicOut in generated-sns-out.yml', async () => {
|
|
406
|
+
const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'mergeHooks-test-'));
|
|
407
|
+
try {
|
|
408
|
+
const filePath = 'resource/sls_yaml/generatedCode/source/generated-sns-out.yml';
|
|
409
|
+
const diskContent = [
|
|
410
|
+
'Resources:',
|
|
411
|
+
' ##===== [Topic Out]',
|
|
412
|
+
' OutCreate:',
|
|
413
|
+
' Type: AWS::SNS::Topic',
|
|
414
|
+
' Properties:',
|
|
415
|
+
" DisplayName: 'SNS Message out'",
|
|
416
|
+
' TopicName: ${self:custom.iz_serviceTag}_${self:custom.iz_stage}_Create_Out',
|
|
417
|
+
' #<#OutCreateTopicSetting#>',
|
|
418
|
+
' FifoTopic: true',
|
|
419
|
+
' #<#/OutCreateTopicSetting#>',
|
|
420
|
+
'',
|
|
421
|
+
' #<#customTopicOut#>',
|
|
422
|
+
' OutCustomNotification:',
|
|
423
|
+
' Type: AWS::SNS::Topic',
|
|
424
|
+
' #<#/customTopicOut#>'
|
|
425
|
+
].join('\n') + '\n';
|
|
426
|
+
await writeTestFile(tmpDir, filePath, diskContent);
|
|
427
|
+
|
|
428
|
+
const generatedArtifact = {
|
|
429
|
+
path: filePath,
|
|
430
|
+
content: [
|
|
431
|
+
'Resources:',
|
|
432
|
+
' ##===== [Topic Out]',
|
|
433
|
+
' OutCreate:',
|
|
434
|
+
' Type: AWS::SNS::Topic',
|
|
435
|
+
' Properties:',
|
|
436
|
+
" DisplayName: 'SNS Message out'",
|
|
437
|
+
' TopicName: ${self:custom.iz_serviceTag}_${self:custom.iz_stage}_Create_Out',
|
|
438
|
+
' #<#OutCreateTopicSetting#>',
|
|
439
|
+
' #<#/OutCreateTopicSetting#>',
|
|
440
|
+
'',
|
|
441
|
+
' #<#customTopicOut#>',
|
|
442
|
+
' #<#/customTopicOut#>'
|
|
443
|
+
].join('\n') + '\n'
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
const [result] = await mergeHooks(tmpDir, [generatedArtifact]);
|
|
447
|
+
assert.match(result.content, /FifoTopic: true/);
|
|
448
|
+
assert.match(result.content, /OutCustomNotification:/);
|
|
449
|
+
} finally {
|
|
450
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
|
|
405
454
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|