@izara_project/izara-core-generate-service-code 1.0.101 → 1.0.102
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/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/infrastructure/functions/templates/DssFunction_Yaml.ejs +1 -1
- package/src/emit/infrastructure/resources/dynamoResourceArtifacts.js +6 -2
- 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 +4 -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.102",
|
|
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": {
|
|
@@ -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
|
{
|
|
@@ -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
|
}
|
|
@@ -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,
|
|
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
|