@izara_project/izara-core-generate-service-code 1.0.100 → 1.0.101

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@izara_project/izara-core-generate-service-code",
3
- "version": "1.0.100",
3
+ "version": "1.0.101",
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": {
@@ -140,6 +140,12 @@ export async function resolveSchemaAndIdentity(_izContext, requestParams, foundE
140
140
  }
141
141
 
142
142
  requestParams.submittedByUserId = submittedByUserId;
143
+ if (submittedByUserId) {
144
+ _izContext.correlationIds?.set(
145
+ coreConsts.SUBMITTED_BY_USER_ID,
146
+ submittedByUserId
147
+ );
148
+ }
143
149
  return { objType, objectSchema, targetIdentifiers };
144
150
  }
145
151
 
@@ -27,7 +27,7 @@ export async function emitS3Flow(
27
27
  flow,
28
28
  source,
29
29
  handlerRoot,
30
- { objects = [] } = {}
30
+ { objects = [], relationships = [] } = {}
31
31
  ) {
32
32
  const tag = upperFirst(flow.flowTag);
33
33
  const roleName = customFlowRoleName(flow.flowTag);
@@ -71,7 +71,8 @@ export async function emitS3Flow(
71
71
  functionName,
72
72
  nextStepFunctionName,
73
73
  isSimplifiedS3,
74
- objects
74
+ objects,
75
+ relationships
75
76
  })
76
77
  );
77
78
  const handlerContent = await renderCustom(
@@ -260,13 +261,182 @@ export function resolveParentIdentifierInfo(flow, objects = []) {
260
261
  };
261
262
  }
262
263
 
264
+ export function resolveFlowRelationships(
265
+ flow,
266
+ { objects = [], relationships = [] } = {}
267
+ ) {
268
+ if (Array.isArray(flow.relationships) && flow.relationships.length > 0) {
269
+ const list = [];
270
+ for (const rel of flow.relationships) {
271
+ let targetObj = {
272
+ objectType: '',
273
+ serviceTag: ''
274
+ };
275
+ if (rel.targetObjType) {
276
+ targetObj = {
277
+ objectType: rel.targetObjType.objectType || '',
278
+ serviceTag: rel.targetObjType.serviceTag || ''
279
+ };
280
+ } else if (rel.objectType) {
281
+ targetObj = {
282
+ objectType: rel.objectType,
283
+ serviceTag: rel.serviceTag || ''
284
+ };
285
+ }
286
+
287
+ let relType = {
288
+ relationshipTag: '',
289
+ serviceTag: targetObj.serviceTag
290
+ };
291
+ if (rel.relType) {
292
+ relType = {
293
+ relationshipTag: rel.relType.relationshipTag || '',
294
+ serviceTag: rel.relType.serviceTag || targetObj.serviceTag
295
+ };
296
+ } else if (rel.relationshipTag) {
297
+ relType = {
298
+ relationshipTag: rel.relationshipTag,
299
+ serviceTag: rel.serviceTag || targetObj.serviceTag
300
+ };
301
+ }
302
+
303
+ let targetIdentifierKey = '';
304
+ if (rel.targetIdentifierKey) {
305
+ targetIdentifierKey = rel.targetIdentifierKey;
306
+ } else if (rel.targetIdentifiers) {
307
+ const idKeys = Object.keys(rel.targetIdentifiers);
308
+ if (idKeys.length > 0) {
309
+ targetIdentifierKey = idKeys[0];
310
+ }
311
+ }
312
+ if (!targetIdentifierKey && targetObj.objectType) {
313
+ targetIdentifierKey = `${targetObj.objectType}Id`;
314
+ }
315
+
316
+ let direction = 'to';
317
+ if (rel.relationshipDirection) {
318
+ direction = rel.relationshipDirection;
319
+ }
320
+
321
+ list.push({
322
+ targetObjType: targetObj,
323
+ relType,
324
+ targetIdentifierKey,
325
+ relationshipProperties: rel.relationshipProperties || {},
326
+ relationshipDirection: direction
327
+ });
328
+ }
329
+ return list;
330
+ }
331
+
332
+ let targetObjType = '';
333
+ if (typeof flow.objType === 'string') {
334
+ targetObjType = flow.objType;
335
+ } else if (flow.objType?.objectType) {
336
+ targetObjType = flow.objType.objectType;
337
+ } else if (flow.objectType) {
338
+ targetObjType = flow.objectType;
339
+ }
340
+
341
+ if (
342
+ !targetObjType ||
343
+ !Array.isArray(relationships) ||
344
+ relationships.length === 0
345
+ ) {
346
+ return [];
347
+ }
348
+
349
+ const resolved = [];
350
+ for (const relItem of relationships) {
351
+ if (!relItem || typeof relItem !== 'object') {
352
+ continue;
353
+ }
354
+
355
+ if (
356
+ relItem.objectType &&
357
+ relItem.objectType.toLowerCase() === targetObjType.toLowerCase() &&
358
+ relItem.relationshipTag &&
359
+ relItem.relationshipServiceTag
360
+ ) {
361
+ const parentService = relItem.relationshipServiceTag;
362
+ let parentObjType = '';
363
+ if (relItem.targetObjectType) {
364
+ parentObjType = relItem.targetObjectType;
365
+ } else if (parentService.length > 0) {
366
+ parentObjType =
367
+ parentService[0].toLowerCase() + parentService.slice(1);
368
+ }
369
+ let targetIdentifierKey = `${parentObjType}Id`;
370
+ if (relItem.targetIdentifierKey) {
371
+ targetIdentifierKey = relItem.targetIdentifierKey;
372
+ }
373
+ let direction = 'to';
374
+ if (relItem.relationshipDirection) {
375
+ direction = relItem.relationshipDirection;
376
+ }
377
+ resolved.push({
378
+ targetObjType: {
379
+ objectType: parentObjType,
380
+ serviceTag: parentService
381
+ },
382
+ relType: {
383
+ relationshipTag: relItem.relationshipTag,
384
+ serviceTag: parentService
385
+ },
386
+ targetIdentifierKey,
387
+ relationshipProperties: {},
388
+ relationshipDirection: direction
389
+ });
390
+ continue;
391
+ }
392
+
393
+ const relKeys = Object.keys(relItem);
394
+ for (const relTag of relKeys) {
395
+ const relDef = relItem[relTag];
396
+ if (!relDef || !Array.isArray(relDef.links)) {
397
+ continue;
398
+ }
399
+ for (const link of relDef.links) {
400
+ if (!link || !link.from || !link.to) {
401
+ continue;
402
+ }
403
+
404
+ const isToTarget =
405
+ link.to.objType?.objectType?.toLowerCase() ===
406
+ targetObjType.toLowerCase();
407
+ if (isToTarget && link.to.requiredOnCreate) {
408
+ const fromObj = link.from.objType || {};
409
+ const fromObjType = fromObj.objectType || '';
410
+ const targetIdentifierKey = `${fromObjType}Id`;
411
+ resolved.push({
412
+ targetObjType: {
413
+ objectType: fromObjType,
414
+ serviceTag: fromObj.serviceTag || ''
415
+ },
416
+ relType: {
417
+ relationshipTag: relTag,
418
+ serviceTag: fromObj.serviceTag || ''
419
+ },
420
+ targetIdentifierKey,
421
+ relationshipProperties: {},
422
+ relationshipDirection: 'to'
423
+ });
424
+ }
425
+ }
426
+ }
427
+ }
428
+
429
+ return resolved;
430
+ }
431
+
263
432
  function _s3TemplateData({
264
433
  action,
265
434
  flow,
266
435
  functionName,
267
436
  nextStepFunctionName,
268
437
  isSimplifiedS3,
269
- objects = []
438
+ objects = [],
439
+ relationships = []
270
440
  }) {
271
441
  let maxFileSizeMb = 50;
272
442
  if (flow.maxFileSizeMb) {
@@ -293,9 +463,15 @@ function _s3TemplateData({
293
463
  defaultImportType = flow.defaultImportType;
294
464
  } else if (flow.importType) {
295
465
  defaultImportType = flow.importType;
296
- } else if (typeof contentTypePrefix === 'string' && contentTypePrefix.startsWith('video/')) {
466
+ } else if (
467
+ typeof contentTypePrefix === 'string' &&
468
+ contentTypePrefix.startsWith('video/')
469
+ ) {
297
470
  defaultImportType = 'mp4';
298
- } else if (typeof contentTypePrefix === 'string' && contentTypePrefix.startsWith('image/')) {
471
+ } else if (
472
+ typeof contentTypePrefix === 'string' &&
473
+ contentTypePrefix.startsWith('image/')
474
+ ) {
299
475
  defaultImportType = 'jpg';
300
476
  }
301
477
 
@@ -313,6 +489,11 @@ function _s3TemplateData({
313
489
  rbacServiceTag = flow.objType?.serviceTag || flow.serviceTag || 'Rbac';
314
490
  }
315
491
 
492
+ const flowRelationships = resolveFlowRelationships(flow, {
493
+ objects,
494
+ relationships
495
+ });
496
+
316
497
  return {
317
498
  action,
318
499
  flowTag: flow.flowTag,
@@ -331,7 +512,8 @@ function _s3TemplateData({
331
512
  parentIdKeys,
332
513
  defaultImportType,
333
514
  isRbac,
334
- rbacServiceTag
515
+ rbacServiceTag,
516
+ flowRelationships
335
517
  };
336
518
  }
337
519
 
@@ -35,7 +35,7 @@ export { customFlowApplicationManagedPaths };
35
35
 
36
36
  export async function emitCustomFlowApplicationArtifacts(
37
37
  flows,
38
- { objects = [] } = {}
38
+ { objects = [], relationships = [] } = {}
39
39
  ) {
40
40
  const customFlows = supportedCustomFlows(flows);
41
41
  const generatedRoots = generatedOwnTopicFlows(flows);
@@ -51,7 +51,7 @@ export async function emitCustomFlowApplicationArtifacts(
51
51
  }
52
52
 
53
53
  const emitted = await Promise.all(
54
- customFlows.map((flow) => emitFlow(flow, objects))
54
+ customFlows.map((flow) => emitFlow(flow, objects, relationships))
55
55
  );
56
56
  const roots = await Promise.all(
57
57
  generatedRoots.map((flow) => emitOwnTopicRoot(flow, objects))
@@ -77,7 +77,7 @@ export async function emitCustomFlowApplicationArtifacts(
77
77
  ];
78
78
  }
79
79
 
80
- async function emitFlow(flow, objects) {
80
+ async function emitFlow(flow, objects, relationships = []) {
81
81
  const tag = upperFirst(flow.flowTag);
82
82
  const process = tag;
83
83
  const source = `app/src/generatedCode/FlowSchemas/${tag}/source`;
@@ -202,7 +202,8 @@ async function emitFlow(flow, objects) {
202
202
  }
203
203
  if (flow.event.includes('s3')) {
204
204
  const s3Artifacts = await emitS3Flow(flow, source, handlerRoot, {
205
- objects
205
+ objects,
206
+ relationships
206
207
  });
207
208
  artifacts.push(...s3Artifacts.artifacts);
208
209
  functions.push(...s3Artifacts.functions);
@@ -95,6 +95,7 @@ export const main = middleware.wrap(async (event, context) => {
95
95
  let returnPromise = await recordHandlerSharedLib.recordHandler(
96
96
  record,
97
97
  <%= functionName %>,
98
+ '<%= functionName %><%= handlerType %>',
98
99
  passOnProperties
99
100
  );
100
101
 
@@ -95,6 +95,7 @@ export const main = middleware.wrap(async (event, context) => {
95
95
  let returnPromise = await recordHandlerSharedLib.recordHandler(
96
96
  record,
97
97
  <%= functionName %>,
98
+ '<%= functionName %><%= handlerType %>',
98
99
  passOnProperties
99
100
  );
100
101
 
@@ -50,32 +50,43 @@ export default async function <%- functionName %>(
50
50
  passBackProperties
51
51
  });
52
52
 
53
- const targetId =
54
- objInstanceFull.identifiers?.<%- objectType %>Id ||
55
- Object.values(objInstanceFull.identifiers || {})[0];
53
+ let targetId =
54
+ passBackProperties.rawId ||
55
+ passBackProperties.<%- objectType %>Id ||
56
+ passBackProperties.parentPassBackProperties?.rawId ||
57
+ passBackProperties.parentPassBackProperties?.<%- objectType %>Id ||
58
+ objInstanceFull?.identifiers?.<%- objectType %>Id;
59
+
60
+ if (!targetId && objInstanceFull?.identifiers) {
61
+ targetId = Object.values(objInstanceFull.identifiers)[0];
62
+ }
56
63
 
57
64
  //(<confirmUsageHook>)
58
65
  //(</confirmUsageHook>)
59
66
 
60
67
  const taskKey = `${createFlowTypeConcat(_izContext, { flowTag: '<%- flowTag %>', serviceTag: process.env.iz_serviceTag })}${targetId}`;
61
- const taskRecord = await dynamodbSharedLib.getItem(
68
+ const tasks = await dynamodbSharedLib.query(
62
69
  _izContext,
63
70
  await dynamodbSharedLib.tableName(_izContext, 'WebSocketTask'),
64
- { taskKey: taskKey }
71
+ { taskKey }
65
72
  );
66
73
 
67
- if (taskRecord && taskRecord.connectionId) {
68
- await postToConnection(
69
- _izContext,
70
- {
71
- status: 'completed',
72
- data: {
73
- <%- objectType %>Id: targetId,
74
- objInstanceFull: objInstanceFull
75
- }
76
- },
77
- taskRecord.connectionId
78
- );
74
+ if (tasks && tasks.Items && tasks.Items.length > 0) {
75
+ for (const taskRecord of tasks.Items) {
76
+ if (taskRecord.connectionId) {
77
+ await postToConnection(
78
+ _izContext,
79
+ {
80
+ status: 'completed',
81
+ data: {
82
+ <%- objectType %>Id: targetId,
83
+ objInstanceFull: objInstanceFull
84
+ }
85
+ },
86
+ taskRecord.connectionId
87
+ );
88
+ }
89
+ }
79
90
  }
80
91
 
81
92
  return {
@@ -26,6 +26,7 @@ import snsSharedLib from '@izara_project/izara-core-library-sns';
26
26
  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
+ import { consts } from '@izara_project/izara-middleware';
29
30
 
30
31
  //(<optionalRequired>)
31
32
  //(</optionalRequired>)
@@ -33,7 +34,8 @@ import { sns } from '@izara_project/izara-core-library-external-request';
33
34
  /**
34
35
  * Step 2: ProcessAfterUpload (Deferred-Node S3 Pipeline - ADR-016)
35
36
  * Triggered by S3 ObjectCreated event. Inspects file, prepares entity payload,
36
- * and publishes to Create_In SNS topic with callingFlow targeting ConfirmAfterUpload.
37
+ * and publishes to Create_In SNS topic with callingFlow
38
+ * targeting ConfirmAfterUpload.
37
39
  *
38
40
  * @param {Object} _izContext
39
41
  * @param {Object} bucket - S3 bucket record
@@ -50,7 +52,15 @@ export default async function <%- functionName %>(
50
52
  passBackProperties = {}
51
53
  ) {
52
54
  try {
53
- _izContext.logger.debug('----- <%- functionName %> started -----', {
55
+ _izContext.logger.info(
56
+ '----- <%- functionName %> started -----',
57
+ {
58
+ bucketName: bucket.name,
59
+ key: field.key,
60
+ size: field.size
61
+ }
62
+ );
63
+ _izContext.logger.debug('<%- functionName %> incoming parameters:', {
54
64
  bucket,
55
65
  field,
56
66
  passBackProperties
@@ -60,6 +70,12 @@ export default async function <%- functionName %>(
60
70
  const actualByteSize = field.size;
61
71
  const actualSizeMb = actualByteSize / (1024 * 1024);
62
72
 
73
+ _izContext.logger.debug('S3 file details:', {
74
+ s3Key,
75
+ actualByteSize,
76
+ actualSizeMb
77
+ });
78
+
63
79
  let s3Metadata = {};
64
80
  try {
65
81
  const headResult = await s3.send(
@@ -68,13 +84,19 @@ export default async function <%- functionName %>(
68
84
  Key: s3Key
69
85
  })
70
86
  );
87
+
88
+ _izContext.logger.debug('HeadObjectCommand raw result:', headResult);
71
89
  s3Metadata = headResult.Metadata || {};
90
+ _izContext.logger.info('Parsed S3 Metadata:', { s3Metadata });
72
91
  } catch (headErr) {
73
- _izContext.logger.warn('Could not retrieve S3 object metadata via HeadObjectCommand:', {
74
- bucket: bucket.name,
75
- key: s3Key,
76
- error: headErr.message
77
- });
92
+ _izContext.logger.warn(
93
+ 'Could not retrieve S3 object metadata via HeadObjectCommand:',
94
+ {
95
+ bucket: bucket.name,
96
+ key: s3Key,
97
+ error: headErr.message
98
+ }
99
+ );
78
100
  }
79
101
 
80
102
  //(<extractIdentifiersFromKeyOrMetadata>)
@@ -102,13 +124,70 @@ export default async function <%- functionName %>(
102
124
  //(</endpointHook>)
103
125
 
104
126
  <% if (typeof nextStepFunctionName === 'string' && nextStepFunctionName) { -%>
105
- const metaUserId =
106
- s3Metadata.submittedbyuserid ||
107
- s3Metadata.submittedByUserId ||
108
- s3Metadata.userid ||
109
- s3Metadata.userId ||
110
- _izContext.userId ||
111
- '';
127
+ const metaUserId = s3Metadata.submittedbyuserid || '';
128
+ <% if (typeof flowRelationships !== 'undefined' && Array.isArray(flowRelationships) && flowRelationships.length > 0) { -%>
129
+ <% for (const rel of flowRelationships) { -%>
130
+ <% const targetIdKey = rel.targetIdentifierKey || `${rel.targetObjType.objectType}Id`; -%>
131
+ <% const metaKey = targetIdKey.toLowerCase(); -%>
132
+ const meta<%- targetIdKey.charAt(0).toUpperCase() + targetIdKey.slice(1) %> = s3Metadata.<%- metaKey %> || '';
133
+ <% } -%>
134
+ <% } -%>
135
+
136
+ _izContext.logger.info('Extracted identifiers and metadata:', {
137
+ <%- objectType %>Id: rawId,
138
+ parentId,
139
+ submittedByUserId: metaUserId,
140
+ <% if (typeof flowRelationships !== 'undefined' && Array.isArray(flowRelationships) && flowRelationships.length > 0) { -%>
141
+ <% for (const rel of flowRelationships) { -%>
142
+ <% const targetIdKey = rel.targetIdentifierKey || `${rel.targetObjType.objectType}Id`; -%>
143
+ <%- targetIdKey %>: meta<%- targetIdKey.charAt(0).toUpperCase() + targetIdKey.slice(1) %>,
144
+ <% } -%>
145
+ <% } -%>
146
+ fileName: fileNameWithExt
147
+ });
148
+
149
+ if (metaUserId) {
150
+ _izContext.correlationIds?.set(consts.SUBMITTED_BY_USER_ID, metaUserId);
151
+ }
152
+
153
+ //(<relationshipsHook>)
154
+ let relationships = [];
155
+ <% if (typeof flowRelationships !== 'undefined' && Array.isArray(flowRelationships) && flowRelationships.length > 0) { -%>
156
+ <% for (const rel of flowRelationships) { -%>
157
+ <% const targetIdKey = rel.targetIdentifierKey || `${rel.targetObjType.objectType}Id`; -%>
158
+ <% const targetObj = rel.targetObjType || {}; -%>
159
+ <% const relTypeObj = rel.relType || {}; -%>
160
+ <% const relDir = rel.relationshipDirection || 'to'; -%>
161
+ <% const varName = `meta${targetIdKey.charAt(0).toUpperCase() + targetIdKey.slice(1)}`; -%>
162
+ if (<%- varName %>) {
163
+ relationships.push({
164
+ targetObjType: {
165
+ objectType: '<%- targetObj.objectType %>',
166
+ serviceTag: '<%- targetObj.serviceTag %>'
167
+ },
168
+ targetIdentifiers: {
169
+ <%- targetIdKey %>: <%- varName %>
170
+ },
171
+ relType: {
172
+ relationshipTag: '<%- relTypeObj.relationshipTag %>',
173
+ serviceTag: '<%- relTypeObj.serviceTag %>'
174
+ },
175
+ relationshipProperties: <%- JSON.stringify(rel.relationshipProperties || {}) %>,
176
+ relationshipDirection: '<%- relDir %>'
177
+ });
178
+ _izContext.logger.info('Built relationship for <%- targetObj.objectType %>:', {
179
+ relationships
180
+ });
181
+ } else {
182
+ _izContext.logger.warn(
183
+ '<%- varName %> is empty! ' +
184
+ 'No <%- relTypeObj.relationshipTag %> relationship attached.'
185
+ );
186
+ }
187
+ <% } -%>
188
+ <% } -%>
189
+ //(</relationshipsHook>)
190
+
112
191
  let messageObject = {
113
192
  objectType: '<%- objectType %>',
114
193
  submittedByUserId: metaUserId,
@@ -127,37 +206,62 @@ export default async function <%- functionName %>(
127
206
  <%- parentIdKey %>: parentId
128
207
  },
129
208
  <% } -%>
209
+ relationships,
130
210
  fieldNames: {
131
211
  //(<fieldNamesHook>)
132
212
  //(</fieldNamesHook>)
213
+ fileName: fileNameWithExt
133
214
  }
134
215
  };
135
216
 
217
+ const confirmCallbackFunction =
218
+ await lambdaSharedLib.lambdaFunctionName(
219
+ _izContext,
220
+ '<%- nextStepFunctionName %>'
221
+ );
222
+ _izContext.logger.debug('Resolved confirm callback function:', {
223
+ confirmCallbackFunction
224
+ });
225
+
136
226
  messageObject =
137
227
  callingFlowSharedLib.addCallingFlowToSnsRequestMessageObject(
138
228
  messageObject,
139
229
  callingFlowSharedLib.addParentCallingFlowConfig(
140
230
  passBackProperties,
141
231
  callingFlowSharedLib.createCallingFlowConfig(
142
- await lambdaSharedLib.lambdaFunctionName(
143
- _izContext,
144
- '<%- nextStepFunctionName %>'
145
- ),
146
- {}
232
+ confirmCallbackFunction,
233
+ { rawId: rawId, <%- objectType %>Id: rawId }
147
234
  )
148
235
  )
149
236
  );
150
237
 
238
+ const topicArn =
239
+ snsSharedLib.snsTopicArnByFlowSchema(_izContext, 'Create_In');
240
+
151
241
  let sendMessageToCreateEndpoint = {
152
242
  Message: JSON.stringify(messageObject),
153
- TopicArn: snsSharedLib.snsTopicArnByFlowSchema(_izContext, 'Create_In')
243
+ TopicArn: topicArn
154
244
  };
155
245
 
246
+ _izContext.logger.info('Publishing to Create_In SNS topic:', {
247
+ topicArn,
248
+ <%- objectType %>Id: rawId,
249
+ 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
+ });
156
251
  _izContext.logger.debug(
157
- 'sendMessageToCreateEndpoint',
252
+ 'Full Create_In message payload:',
158
253
  sendMessageToCreateEndpoint
159
254
  );
160
- await sns.publishAsync(_izContext, sendMessageToCreateEndpoint);
255
+
256
+ const publishResult = await sns.publishAsync(
257
+ _izContext,
258
+ sendMessageToCreateEndpoint
259
+ );
260
+ _izContext.logger.info('Successfully published to Create_In SNS:', {
261
+ publishResult,
262
+ s3Key,
263
+ <%- objectType %>Id: rawId
264
+ });
161
265
 
162
266
  return {
163
267
  status: 'dispatched',
@@ -175,3 +279,4 @@ export default async function <%- functionName %>(
175
279
  throw err;
176
280
  }
177
281
  }
282
+
@@ -123,6 +123,13 @@ export default async function <%- functionName %>(
123
123
  //(<createKeyForUploadFile>)
124
124
  let importType = '<%- defaultImportType %>';
125
125
  let uploadPath = '';
126
+ <% if (typeof flowRelationships !== 'undefined' && Array.isArray(flowRelationships) && flowRelationships.length > 0) { -%>
127
+ <% for (const rel of flowRelationships) { -%>
128
+ <% const relIdKey = rel.targetIdentifierKey || `${rel.targetObjType.objectType}Id`; -%>
129
+ const <%- relIdKey %> =
130
+ requestParams.fieldNames?.<%- relIdKey %> || requestParams.file?.<%- relIdKey %>;
131
+ <% } -%>
132
+ <% } -%>
126
133
  //(</createKeyForUploadFile>)
127
134
  const Key = `<%- s3KeyPrefix %>${uploadPath}`;
128
135
  const maximumSize = maxFileSizeMb * 1048576;
@@ -139,7 +146,13 @@ export default async function <%- functionName %>(
139
146
  <% } -%>
140
147
  <% } -%>
141
148
  ['starts-with', '$x-amz-meta-submittedByUserId', ''],
142
- ['starts-with', '$Content-Type', '<%- contentTypePrefix %>']
149
+ ['starts-with', '$Content-Type', '<%- contentTypePrefix %>'],
150
+ <% if (typeof flowRelationships !== 'undefined' && Array.isArray(flowRelationships) && flowRelationships.length > 0) { -%>
151
+ <% for (const rel of flowRelationships) { -%>
152
+ <% const relIdKey = rel.targetIdentifierKey || `${rel.targetObjType.objectType}Id`; -%>
153
+ ['starts-with', '$x-amz-meta-<%- relIdKey %>', ''],
154
+ <% } -%>
155
+ <% } -%>
143
156
  //(<conditionCreateUrlLink>)
144
157
  //(</conditionCreateUrlLink>)
145
158
  ],
@@ -151,6 +164,12 @@ export default async function <%- functionName %>(
151
164
  <% } -%>
152
165
  <% } -%>
153
166
  'x-amz-meta-submittedByUserId': submittedByUserId,
167
+ <% if (typeof flowRelationships !== 'undefined' && Array.isArray(flowRelationships) && flowRelationships.length > 0) { -%>
168
+ <% for (const rel of flowRelationships) { -%>
169
+ <% const relIdKey = rel.targetIdentifierKey || `${rel.targetObjType.objectType}Id`; -%>
170
+ 'x-amz-meta-<%- relIdKey %>': <%- relIdKey %> || '',
171
+ <% } -%>
172
+ <% } -%>
154
173
  //(<fieldCreateUrlLink>)
155
174
  //(</fieldCreateUrlLink>)
156
175
  },
@@ -145,7 +145,8 @@ export async function resolveEmissionGroups(context) {
145
145
  ]);
146
146
 
147
147
  const custom = await emitCustomFlowApplicationArtifacts(flows, {
148
- objects
148
+ objects,
149
+ relationships
149
150
  });
150
151
 
151
152
  let systemFunctions = [];
@@ -725,6 +725,134 @@ test('emitS3Flow with ADR-016 pipeline without step-level event enum resolves co
725
725
  ]);
726
726
  });
727
727
 
728
+ test('emitS3Flow generates relationships with direction to in ProcessAfterUpload_Main and fields in Start_Main', async () => {
729
+ const flowWithRel = {
730
+ flowTag: 'FeedChunkRecordUrl',
731
+ initiateBy: 'user',
732
+ handleObj: 'one',
733
+ statusType: 'statusField',
734
+ event: ['s3'],
735
+ objType: {
736
+ objectType: 'feedChunk',
737
+ serviceTag: 'FeedChunk'
738
+ },
739
+ bucketName: 'video-record-temp',
740
+ flowSteps: [
741
+ { stepName: 'Start', event: ['wbs'] },
742
+ { stepName: 'ProcessAfterUpload', event: ['s3'] },
743
+ { stepName: 'ConfirmComplete', event: ['extTopic'] }
744
+ ],
745
+ relationships: [
746
+ {
747
+ targetObjType: { objectType: 'videoFeed', serviceTag: 'VideoFeed' },
748
+ targetIdentifierKey: 'videoFeedId',
749
+ relType: { relationshipTag: 'hasFeedChunk', serviceTag: 'VideoFeed' },
750
+ relationshipDirection: 'to'
751
+ }
752
+ ]
753
+ };
754
+
755
+ const source = 'app/src/generatedCode/FlowSchemas/FeedChunkRecordUrl/source';
756
+ const handlerRoot = 'src/generatedCode/FlowSchemas/FeedChunkRecordUrl/source';
757
+
758
+ const result = await emitS3Flow(flowWithRel, source, handlerRoot);
759
+
760
+ const startMain = result.artifacts.find(
761
+ (a) => a.path === `${source}/FeedChunkRecordUrl_Main.js`
762
+ );
763
+ assert.ok(
764
+ startMain.content.includes("requestParams.fieldNames?.videoFeedId"),
765
+ 'Start_Main should extract videoFeedId from requestParams'
766
+ );
767
+ assert.ok(
768
+ startMain.content.includes("['starts-with', '$x-amz-meta-videoFeedId', '']"),
769
+ 'Start_Main should include videoFeedId condition in Presigned POST'
770
+ );
771
+ assert.ok(
772
+ startMain.content.includes("'x-amz-meta-videoFeedId': videoFeedId || ''"),
773
+ 'Start_Main should include videoFeedId field in Presigned POST'
774
+ );
775
+
776
+ const procMain = result.artifacts.find(
777
+ (a) => a.path === `${source}/FeedChunkRecordUrlProcessAfterUpload_Main.js`
778
+ );
779
+ assert.ok(
780
+ procMain.content.includes("const metaVideoFeedId = s3Metadata.videofeedid || '';"),
781
+ 'ProcessAfterUpload_Main should extract metaVideoFeedId from s3Metadata'
782
+ );
783
+ assert.ok(
784
+ procMain.content.includes("videoFeedId: metaVideoFeedId"),
785
+ 'ProcessAfterUpload_Main should include videoFeedId in extracted metadata log'
786
+ );
787
+ assert.ok(
788
+ procMain.content.includes("objectType: 'videoFeed'"),
789
+ 'ProcessAfterUpload_Main should include targetObjType objectType'
790
+ );
791
+ assert.ok(
792
+ procMain.content.includes("relationshipTag: 'hasFeedChunk'"),
793
+ 'ProcessAfterUpload_Main should include relType relationshipTag'
794
+ );
795
+ assert.ok(
796
+ procMain.content.includes("relationshipDirection: 'to'"),
797
+ 'ProcessAfterUpload_Main must generate relationshipDirection to'
798
+ );
799
+ assert.ok(
800
+ procMain.content.includes('Built relationship for videoFeed'),
801
+ 'ProcessAfterUpload_Main should log relationship construction'
802
+ );
803
+ });
804
+
805
+ test('emitS3Flow resolves relationships from schemas with relationshipDirection to', async () => {
806
+ const flow = {
807
+ flowTag: 'FeedChunkRecordUrl',
808
+ initiateBy: 'user',
809
+ handleObj: 'one',
810
+ statusType: 'statusField',
811
+ event: ['s3'],
812
+ objType: {
813
+ objectType: 'feedChunk',
814
+ serviceTag: 'FeedChunk'
815
+ },
816
+ bucketName: 'video-record-temp',
817
+ flowSteps: [
818
+ { stepName: 'Start', event: ['wbs'] },
819
+ { stepName: 'ProcessAfterUpload', event: ['s3'] },
820
+ { stepName: 'ConfirmComplete', event: ['extTopic'] }
821
+ ]
822
+ };
823
+
824
+ const relationshipsSchema = [
825
+ {
826
+ objectType: 'feedChunk',
827
+ relationshipTag: 'hasFeedChunk',
828
+ relationshipServiceTag: 'VideoFeed'
829
+ }
830
+ ];
831
+
832
+ const source = 'app/src/generatedCode/FlowSchemas/FeedChunkRecordUrl/source';
833
+ const handlerRoot = 'src/generatedCode/FlowSchemas/FeedChunkRecordUrl/source';
834
+
835
+ const result = await emitS3Flow(flow, source, handlerRoot, {
836
+ relationships: relationshipsSchema
837
+ });
838
+
839
+ const procMain = result.artifacts.find(
840
+ (a) => a.path === `${source}/FeedChunkRecordUrlProcessAfterUpload_Main.js`
841
+ );
842
+ assert.ok(
843
+ procMain.content.includes("relationshipDirection: 'to'"),
844
+ 'ProcessAfterUpload_Main should infer relationshipDirection to from RefObjectRelationships'
845
+ );
846
+ assert.ok(
847
+ procMain.content.includes("relationshipTag: 'hasFeedChunk'"),
848
+ 'ProcessAfterUpload_Main should infer relationshipTag hasFeedChunk'
849
+ );
850
+ assert.ok(
851
+ procMain.content.includes("serviceTag: 'VideoFeed'"),
852
+ 'ProcessAfterUpload_Main should infer serviceTag VideoFeed'
853
+ );
854
+ });
855
+
728
856
 
729
857
 
730
858