@izara_project/izara-core-generate-service-code 1.0.84 → 1.0.86

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.
Files changed (46) hide show
  1. package/package.json +1 -1
  2. package/src/compile/compileProject.js +1 -2
  3. package/src/compile/loadSchemas.js +29 -17
  4. package/src/emit/coreFunction/emitCoreFunctionArtifacts.js +6 -0
  5. package/src/emit/coreFunction/templates/InitialSetupConfig.ejs +26 -6
  6. package/src/emit/crud/crudActionPlan.js +3 -1
  7. package/src/emit/crud/emitCrudApplicationArtifacts.js +47 -8
  8. package/src/emit/crud/emitDynamicCrudFlowSchemas.js +136 -25
  9. package/src/emit/crud/templates/CreateConfirmComplete_Main.ejs +54 -0
  10. package/src/emit/crud/templates/CreateEndpoint_Main.ejs +248 -181
  11. package/src/emit/crud/templates/CreateLogicalLib.ejs +1155 -293
  12. package/src/emit/crud/templates/CreateNodeCallback_Main.ejs +221 -0
  13. package/src/emit/crud/templates/DeleteEndpoint_Main.ejs +36 -4
  14. package/src/emit/crud/templates/FlowEndpointRbacCallback_Main.ejs +20 -120
  15. package/src/emit/crud/templates/FlowEndpoint_HdrWbs.ejs +76 -14
  16. package/src/emit/crud/templates/FlowMain_Wbs.ejs +31 -35
  17. package/src/emit/crud/templates/UpdateEndpoint_Main.ejs +35 -3
  18. package/src/emit/crud/templates/UpdateLogicalLib.ejs +754 -353
  19. package/src/emit/rbac/emitDynamicRbacFlowSchemas.js +5 -2
  20. package/src/emit/rbac/emitRbacApplicationArtifacts.js +102 -6
  21. package/src/emit/rbac/templates/CancelUsage_Main.ejs +82 -22
  22. package/src/emit/rbac/templates/ConfirmUsage_Main.ejs +130 -14
  23. package/src/emit/rbac/templates/CreateTargetRole_Main.ejs +61 -12
  24. package/src/emit/rbac/templates/DeleteTargetRole_Main.ejs +96 -0
  25. package/src/emit/rbac/templates/GetMyPermissions_Main.ejs +106 -0
  26. package/src/emit/rbac/templates/GetTargetRole_Main.ejs +62 -0
  27. package/src/emit/rbac/templates/ReservedDynamicUsage_Main.ejs +30 -11
  28. package/src/emit/rbac/templates/UpdateTargetRole_Main.ejs +107 -0
  29. package/src/emit/rbac/templates/UserRbacFlow_Main.ejs +40 -15
  30. package/src/emit/rbac/templates/UserRoleMutation_Main.ejs +22 -7
  31. package/src/emit/resource/emitResourceArtifacts.js +52 -2
  32. package/src/emit/resource/templates/GeneratedLambdaRoleServiceConfig.ejs +6 -2
  33. package/src/emit/resource/templates/SystemDynamoDB_Yaml.ejs +2 -0
  34. package/src/emit/shared/templates/FlowHandler_HdrSqs.ejs +1 -1
  35. package/src/emit/system/templates/WebSocketConnect_Yaml.ejs +0 -1
  36. package/src/emit/systemText/emitSystemTextSchemas.js +5 -2
  37. package/src/generate.js +18 -12
  38. package/src/generateResources/IntTest/generateResources/generateResources.js +1 -1
  39. package/src/generateResources/IntTest/generateTests/generateTemplate.js +1 -1
  40. package/src/generateResources/IntTest/upload/uploadIntTest.js +8 -2
  41. package/test/createTargetRoleHandler.test.js +42 -1
  42. package/test/crudApplicationArtifacts.test.js +53 -0
  43. package/test/dynamicCrudFlowSchemas.test.js +16 -2
  44. package/test/dynamicRbacFlowSchemas.test.js +5 -2
  45. package/test/publicApi.test.js +18 -14
  46. package/test/resourceArtifacts.test.js +24 -0
@@ -4,14 +4,17 @@ import { rbacFlowTag } from './rbacFlowTag.js';
4
4
 
5
5
  const RBAC_CORE_FLOW_DEFINITIONS = [
6
6
  { flowTag: 'CreateTargetRole', event: ['ownTopic'] },
7
- { flowTag: 'ListTargetRole', event: ['lambdaSyncApi'] },
7
+ { flowTag: 'UpdateTargetRole', event: ['ownTopic'] },
8
8
  { flowTag: 'DeleteTargetRole', event: ['ownTopic'] },
9
+ { flowTag: 'GetTargetRole', event: ['lambdaSyncApi'] },
10
+ { flowTag: 'ListTargetRole', event: ['lambdaSyncApi'] },
9
11
  { flowTag: 'CreateRolePermissions', event: ['ownTopic'] },
10
12
  { flowTag: 'ListRolePermissions', event: ['lambdaSyncApi'] },
11
13
  { flowTag: 'DeleteRolePermissions', event: ['ownTopic'] },
12
14
  { flowTag: 'CreateUserRole', event: ['ownTopic'] },
13
15
  { flowTag: 'ListUserInRoles', event: ['lambdaSyncApi'] },
14
- { flowTag: 'DeleteUserFromRole', event: ['ownTopic'] }
16
+ { flowTag: 'DeleteUserFromRole', event: ['ownTopic'] },
17
+ { flowTag: 'GetMyPermissions', event: ['lambdaSyncApi'] }
15
18
  ];
16
19
 
17
20
  const LIMIT_FLOW_DEFINITIONS = [
@@ -29,6 +29,19 @@ export async function emitRbacApplicationArtifacts(objects) {
29
29
  ...(hasSelfRbac
30
30
  ? [
31
31
  createTargetRoleHandler(),
32
+ createCustomRbacOwnTopicHandler(
33
+ 'UpdateTargetRole',
34
+ 'UpdateTargetRole_Main.ejs'
35
+ ),
36
+ createCustomRbacOwnTopicHandler(
37
+ 'DeleteTargetRole',
38
+ 'DeleteTargetRole_Main.ejs'
39
+ ),
40
+ createCustomRbacApiHandler('GetTargetRole', 'GetTargetRole_Main.ejs'),
41
+ createCustomRbacApiHandler(
42
+ 'GetMyPermissions',
43
+ 'GetMyPermissions_Main.ejs'
44
+ ),
32
45
  ...LIST_DEFINITIONS.map(createListHandler),
33
46
  ...MUTATION_DEFINITIONS.map(createMutationHandler),
34
47
  ...USER_ROLE_MUTATIONS.map(createUserRoleMutationHandler)
@@ -111,12 +124,6 @@ const MUTATION_DEFINITIONS = [
111
124
  tableName: 'RolePermissions',
112
125
  method: 'deleteItem',
113
126
  fields: ['roleIdKey', 'flowTypeConcat']
114
- },
115
- {
116
- flowTag: 'DeleteTargetRole',
117
- tableName: 'TargetRoles',
118
- method: 'deleteItem',
119
- fields: ['identifiersConcat', 'roleIdKey']
120
127
  }
121
128
  ];
122
129
 
@@ -151,6 +158,20 @@ const STATIC_RBAC_APPLICATION_PATHS = [
151
158
  'app/src/generatedCode/FlowRbac/CreateTargetRole/source/CreateTargetRole_HdrSqs.js',
152
159
  'app/src/generatedCode/FlowRbac/CreateTargetRole/source/CreateTargetRole_WbsMain.js',
153
160
  'app/src/generatedCode/FlowRbac/CreateTargetRole/source/CreateTargetRole_HdrWbs.js',
161
+ 'app/src/generatedCode/FlowRbac/UpdateTargetRole/source/UpdateTargetRole_Main.js',
162
+ 'app/src/generatedCode/FlowRbac/UpdateTargetRole/source/UpdateTargetRole_HdrSqs.js',
163
+ 'app/src/generatedCode/FlowRbac/UpdateTargetRole/source/UpdateTargetRole_WbsMain.js',
164
+ 'app/src/generatedCode/FlowRbac/UpdateTargetRole/source/UpdateTargetRole_HdrWbs.js',
165
+ 'app/src/generatedCode/FlowRbac/DeleteTargetRole/source/DeleteTargetRole_Main.js',
166
+ 'app/src/generatedCode/FlowRbac/DeleteTargetRole/source/DeleteTargetRole_HdrSqs.js',
167
+ 'app/src/generatedCode/FlowRbac/DeleteTargetRole/source/DeleteTargetRole_WbsMain.js',
168
+ 'app/src/generatedCode/FlowRbac/DeleteTargetRole/source/DeleteTargetRole_HdrWbs.js',
169
+ 'app/src/generatedCode/FlowRbac/GetTargetRole/source/GetTargetRole_Main.js',
170
+ 'app/src/generatedCode/FlowRbac/GetTargetRole/source/GetTargetRole_HdrSqs.js',
171
+ 'app/src/generatedCode/FlowRbac/GetTargetRole/source/GetTargetRole_HdrApi.js',
172
+ 'app/src/generatedCode/FlowRbac/GetMyPermissions/source/GetMyPermissions_Main.js',
173
+ 'app/src/generatedCode/FlowRbac/GetMyPermissions/source/GetMyPermissions_HdrSqs.js',
174
+ 'app/src/generatedCode/FlowRbac/GetMyPermissions/source/GetMyPermissions_HdrApi.js',
154
175
  'app/src/generatedCode/FlowRbac/ValidatorRbac/source/ValidatorRbac_Main.js',
155
176
  'app/src/generatedCode/FlowRbac/ValidatorRbac/source/ValidatorRbac_HdrSqs.js',
156
177
  'app/src/generatedCode/FlowRbac/ValidatorRbac/source/ValidatorRbac_WbsMain.js',
@@ -331,6 +352,81 @@ async function createLimitMutationHandler(definition) {
331
352
  };
332
353
  }
333
354
 
355
+ async function createCustomRbacOwnTopicHandler(flowTag, mainTemplateName) {
356
+ const sourcePath = `app/src/generatedCode/FlowRbac/${flowTag}/source`;
357
+ const handlerPath = `src/generatedCode/FlowRbac/${flowTag}/source`;
358
+ const webSocket = await createWebSocketEntry(
359
+ flowTag,
360
+ sourcePath,
361
+ handlerPath
362
+ );
363
+ return {
364
+ artifacts: [
365
+ {
366
+ path: `${sourcePath}/${flowTag}_Main.js`,
367
+ content: await renderRbacTemplate(mainTemplateName, { flowTag })
368
+ },
369
+ {
370
+ path: `${sourcePath}/${flowTag}_HdrSqs.js`,
371
+ content: await renderRbacTemplate('ListRbac_HdrSqs.ejs', { flowTag })
372
+ },
373
+ ...webSocket.artifacts
374
+ ],
375
+ functions: [
376
+ {
377
+ type: 'sqs',
378
+ functionName: `${flowTag}HdrSqs`,
379
+ handlerPath: `${handlerPath}/${flowTag}_HdrSqs.main`,
380
+ roleName: 'FlowRbacRole',
381
+ queueName: `${flowTag}HdrSqs`,
382
+ flowTag: flowTag,
383
+ flowStepName: ''
384
+ },
385
+ ...webSocket.functions
386
+ ]
387
+ };
388
+ }
389
+
390
+ async function createCustomRbacApiHandler(flowTag, mainTemplateName) {
391
+ const sourcePath = `app/src/generatedCode/FlowRbac/${flowTag}/source`;
392
+ const handlerPath = `src/generatedCode/FlowRbac/${flowTag}/source`;
393
+ return {
394
+ artifacts: [
395
+ {
396
+ path: `${sourcePath}/${flowTag}_Main.js`,
397
+ content: await renderRbacTemplate(mainTemplateName, { flowTag })
398
+ },
399
+ {
400
+ path: `${sourcePath}/${flowTag}_HdrSqs.js`,
401
+ content: await renderRbacTemplate('ListRbac_HdrSqs.ejs', { flowTag })
402
+ },
403
+ {
404
+ path: `${sourcePath}/${flowTag}_HdrApi.js`,
405
+ content: await renderRbacTemplate('ListRbac_HdrApi.ejs', { flowTag })
406
+ }
407
+ ],
408
+ functions: [
409
+ {
410
+ type: 'sqs',
411
+ functionName: `${flowTag}HdrSqs`,
412
+ handlerPath: `${handlerPath}/${flowTag}_HdrSqs.main`,
413
+ roleName: 'FlowRbacRole',
414
+ queueName: `${flowTag}HdrSqs`,
415
+ flowTag: flowTag,
416
+ flowStepName: ''
417
+ },
418
+ {
419
+ type: 'api',
420
+ functionName: `${flowTag}HdrApi`,
421
+ handlerPath: `${handlerPath}/${flowTag}_HdrApi.main`,
422
+ roleName: 'FlowRbacRole',
423
+ flowTag: flowTag,
424
+ flowStepName: ''
425
+ }
426
+ ]
427
+ };
428
+ }
429
+
334
430
  async function createTargetRoleHandler() {
335
431
  const sourcePath = 'app/src/generatedCode/FlowRbac/CreateTargetRole/source';
336
432
  const webSocket = await createWebSocketEntry(
@@ -22,35 +22,68 @@ import { consts, NoRetryError } from '@izara_project/izara-core-library-core';
22
22
 
23
23
  export default async function CancelUsage(_izContext, requestParams) {
24
24
  try {
25
+ let runningTotalReservedId =
26
+ requestParams?.runningTotalReservedId ||
27
+ requestParams?.reservedId ||
28
+ requestParams?.runningTotalReservedUsageId;
29
+
30
+ const usedTimestampId =
31
+ requestParams?.usedTimestampId ||
32
+ requestParams?.uniqueIdentify;
33
+
25
34
  const submittedByUserId =
26
- requestParams.submittedByUserId ||
35
+ requestParams?.submittedByUserId ||
27
36
  _izContext.correlationIds?.get(consts.SUBMITTED_BY_USER_ID);
28
- const { limitTag, uniqueIdentify } = requestParams;
37
+ const flowTypeConcat =
38
+ requestParams?.flowTypeConcat ||
39
+ requestParams?.limitTag;
29
40
 
30
- const targetIdentifiers = requestParams.targetIdentifiers;
31
- let identifiersConcat = requestParams.identifiersConcat;
41
+ const targetIdentifiers = requestParams?.targetIdentifiers;
42
+ let identifiersConcat = requestParams?.identifiersConcat;
32
43
  if (!identifiersConcat && targetIdentifiers && typeof targetIdentifiers === 'object') {
33
- identifiersConcat = Object.entries(targetIdentifiers)
34
- .map(([k, v]) => `${k}#${v}`)
35
- .join('_');
44
+ if (targetIdentifiers.identifierConcat) {
45
+ identifiersConcat = targetIdentifiers.identifierConcat;
46
+ } else {
47
+ const sortedKeys = Object.keys(targetIdentifiers).sort();
48
+ const parts = [];
49
+ for (const key of sortedKeys) {
50
+ const value = targetIdentifiers[key];
51
+ if (
52
+ key !== 'submittedByUserId' &&
53
+ key !== 'identifierConcat' &&
54
+ value !== undefined &&
55
+ value !== null
56
+ ) {
57
+ parts.push(`${key}#${value}`);
58
+ }
59
+ }
60
+ if (parts.length > 0) {
61
+ identifiersConcat = parts.join('|');
62
+ }
63
+ }
36
64
  }
37
65
  if (!identifiersConcat) {
38
66
  identifiersConcat = submittedByUserId;
39
67
  }
40
68
 
41
- if (!identifiersConcat || !limitTag || !uniqueIdentify) {
69
+ if (!runningTotalReservedId) {
70
+ if (identifiersConcat && flowTypeConcat) {
71
+ runningTotalReservedId = `${identifiersConcat}_${flowTypeConcat}`;
72
+ }
73
+ }
74
+
75
+ if (!runningTotalReservedId || !usedTimestampId) {
42
76
  throw new NoRetryError(
43
- 'identifiersConcat, limitTag, and uniqueIdentify are required.'
77
+ 'runningTotalReservedId (or identifiersConcat + flowTypeConcat) and usedTimestampId (or uniqueIdentify) are required.'
44
78
  );
45
79
  }
46
80
 
47
- const runningTotalReservedId = `${identifiersConcat}_${limitTag}`;
48
81
  const reservedRecord = await dynamodbSharedLib.getItem(
49
82
  _izContext,
50
83
  dynamodbSharedLib.tableName(_izContext, 'RunningTotalReservedUsage'),
51
84
  {
52
85
  runningTotalReservedId,
53
- usedTimestampId: uniqueIdentify
86
+ usedTimestampId
54
87
  }
55
88
  );
56
89
 
@@ -61,36 +94,63 @@ export default async function CancelUsage(_izContext, requestParams) {
61
94
  dynamodbSharedLib.tableName(_izContext, 'RunningTotalReservedUsage'),
62
95
  {
63
96
  runningTotalReservedId,
64
- usedTimestampId: uniqueIdentify
97
+ usedTimestampId
65
98
  },
66
99
  {
67
100
  runningTotalUsageStatus: 'cancelled'
68
101
  }
69
102
  );
70
103
 
71
- if (usageCount > 0) {
104
+ if (usageCount > 0 && identifiersConcat && flowTypeConcat) {
72
105
  const usageRecord = await dynamodbSharedLib.getItem(
73
106
  _izContext,
74
107
  dynamodbSharedLib.tableName(_izContext, 'RunningTotalUsage'),
75
- { identifiersConcat, limitTag }
108
+ { identifiersConcat, flowTypeConcat }
76
109
  );
77
110
  if (usageRecord) {
78
- const newTotal = Math.max(0, (usageRecord.totalCount || 0) - usageCount);
79
- await dynamodbSharedLib.putItem(
111
+ const currentTotal = usageRecord.totalCount || 0;
112
+ const newTotal = Math.max(0, currentTotal - usageCount);
113
+ const queryElementsWhenUpdate = {
114
+ additionalAttributes: { currentUuid: usageRecord.updateUuid },
115
+ logicalElements: [
116
+ {
117
+ type: 'logical',
118
+ comparison: 'equals',
119
+ lhsAttribute: 'updateUuid',
120
+ rhsReference: 'currentUuid'
121
+ }
122
+ ],
123
+ returnValues: 'ALL_NEW'
124
+ };
125
+
126
+ const updateAttributes = [
127
+ { attributeName: 'totalCount', action: 'set', value: newTotal },
128
+ { attributeName: 'updateUuid', action: 'set', value: _izContext.uniqueRequestId || `${Date.now()}` }
129
+ ];
130
+
131
+ await dynamodbSharedLib.updateItem(
80
132
  _izContext,
81
133
  dynamodbSharedLib.tableName(_izContext, 'RunningTotalUsage'),
134
+ { identifiersConcat, flowTypeConcat },
135
+ updateAttributes,
136
+ queryElementsWhenUpdate,
82
137
  {
83
- identifiersConcat,
84
- limitTag,
85
- totalCount: newTotal,
86
- updateUuid: _izContext.uniqueRequestId || `${Date.now()}`
138
+ complexAttributes: true,
139
+ errorOnConditionalExpNotPass: false
87
140
  }
88
- );
141
+ ).catch(() => null);
89
142
  }
90
143
  }
91
144
 
92
145
  await sns.publishMessage(_izContext, {
93
- message: { returnResult: { status: true, uniqueIdentify } },
146
+ message: {
147
+ returnResult: {
148
+ status: true,
149
+ uniqueIdentify: usedTimestampId,
150
+ usedTimestampId,
151
+ runningTotalReservedId
152
+ }
153
+ },
94
154
  TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
95
155
  _izContext,
96
156
  'CancelUsage_Out'
@@ -22,43 +22,159 @@ import { consts, NoRetryError } from '@izara_project/izara-core-library-core';
22
22
 
23
23
  export default async function ConfirmUsage(_izContext, requestParams) {
24
24
  try {
25
+ let runningTotalReservedId =
26
+ requestParams?.runningTotalReservedId ||
27
+ requestParams?.reservedId ||
28
+ requestParams?.runningTotalReservedUsageId;
29
+
30
+ const usedTimestampId =
31
+ requestParams?.usedTimestampId ||
32
+ requestParams?.uniqueIdentify;
33
+
25
34
  const submittedByUserId =
26
- requestParams.submittedByUserId ||
35
+ requestParams?.submittedByUserId ||
27
36
  _izContext.correlationIds?.get(consts.SUBMITTED_BY_USER_ID);
28
- const { limitTag, uniqueIdentify } = requestParams;
37
+ const flowTypeConcat =
38
+ requestParams?.flowTypeConcat ||
39
+ requestParams?.limitTag;
29
40
 
30
- const targetIdentifiers = requestParams.targetIdentifiers;
31
- let identifiersConcat = requestParams.identifiersConcat;
41
+ const targetIdentifiers = requestParams?.targetIdentifiers;
42
+ let identifiersConcat = requestParams?.identifiersConcat;
32
43
  if (!identifiersConcat && targetIdentifiers && typeof targetIdentifiers === 'object') {
33
- identifiersConcat = Object.entries(targetIdentifiers)
34
- .map(([k, v]) => `${k}#${v}`)
35
- .join('_');
44
+ if (targetIdentifiers.identifierConcat) {
45
+ identifiersConcat = targetIdentifiers.identifierConcat;
46
+ } else {
47
+ const sortedKeys = Object.keys(targetIdentifiers).sort();
48
+ const parts = [];
49
+ for (const key of sortedKeys) {
50
+ const value = targetIdentifiers[key];
51
+ if (
52
+ key !== 'submittedByUserId' &&
53
+ key !== 'identifierConcat' &&
54
+ value !== undefined &&
55
+ value !== null
56
+ ) {
57
+ parts.push(`${key}#${value}`);
58
+ }
59
+ }
60
+ if (parts.length > 0) {
61
+ identifiersConcat = parts.join('|');
62
+ }
63
+ }
36
64
  }
37
65
  if (!identifiersConcat) {
38
66
  identifiersConcat = submittedByUserId;
39
67
  }
40
68
 
41
- if (!identifiersConcat || !limitTag || !uniqueIdentify) {
69
+ if (!runningTotalReservedId) {
70
+ if (identifiersConcat && flowTypeConcat) {
71
+ runningTotalReservedId = `${identifiersConcat}_${flowTypeConcat}`;
72
+ }
73
+ }
74
+
75
+ if (!runningTotalReservedId || !usedTimestampId) {
42
76
  throw new NoRetryError(
43
- 'identifiersConcat, limitTag, and uniqueIdentify are required.'
77
+ 'runningTotalReservedId (or identifiersConcat + flowTypeConcat) and usedTimestampId (or uniqueIdentify) are required.'
44
78
  );
45
79
  }
46
80
 
47
- const runningTotalReservedId = `${identifiersConcat}_${limitTag}`;
81
+ const reservedTable = dynamodbSharedLib.tableName(
82
+ _izContext,
83
+ 'RunningTotalReservedUsage'
84
+ );
85
+
86
+ const reservedRecord = await dynamodbSharedLib
87
+ .getItem(_izContext, reservedTable, {
88
+ runningTotalReservedId,
89
+ usedTimestampId
90
+ })
91
+ .catch(() => null);
92
+
93
+ let usageCount = 1;
94
+ if (reservedRecord && typeof reservedRecord.usageCount === 'number') {
95
+ usageCount = reservedRecord.usageCount;
96
+ }
97
+
48
98
  await dynamodbSharedLib.updateItem(
49
99
  _izContext,
50
- dynamodbSharedLib.tableName(_izContext, 'RunningTotalReservedUsage'),
100
+ reservedTable,
51
101
  {
52
102
  runningTotalReservedId,
53
- usedTimestampId: uniqueIdentify
103
+ usedTimestampId
54
104
  },
55
105
  {
56
- runningTotalUsageStatus: 'confirmed'
106
+ runningTotalUsageStatus: 'confirmed',
107
+ confirmedAt: new Date().toISOString()
57
108
  }
58
109
  );
59
110
 
111
+ if (identifiersConcat && flowTypeConcat) {
112
+ const usageTable = dynamodbSharedLib.tableName(
113
+ _izContext,
114
+ 'RunningTotalUsage'
115
+ );
116
+
117
+ const usageRecord = await dynamodbSharedLib
118
+ .getItem(_izContext, usageTable, {
119
+ identifiersConcat,
120
+ flowTypeConcat
121
+ })
122
+ .catch(() => null);
123
+
124
+ let currentTotal = 0;
125
+ let queryElementsWhenUpdate = {};
126
+
127
+ if (usageRecord && typeof usageRecord.totalCount === 'number') {
128
+ currentTotal = usageRecord.totalCount;
129
+ queryElementsWhenUpdate = {
130
+ additionalAttributes: { currentUuid: usageRecord.updateUuid },
131
+ logicalElements: [
132
+ {
133
+ type: 'logical',
134
+ comparison: 'equals',
135
+ lhsAttribute: 'updateUuid',
136
+ rhsReference: 'currentUuid'
137
+ }
138
+ ],
139
+ returnValues: 'ALL_NEW'
140
+ };
141
+ } else {
142
+ queryElementsWhenUpdate = {
143
+ logicalElements: dynamodbSharedLib.generateNotExistLogicalElements({
144
+ identifiersConcat,
145
+ flowTypeConcat
146
+ }),
147
+ returnValues: 'ALL_NEW'
148
+ };
149
+ }
150
+
151
+ const updateAttributes = [
152
+ { attributeName: 'totalCount', action: 'set', value: currentTotal + usageCount },
153
+ { attributeName: 'updateUuid', action: 'set', value: _izContext.uniqueRequestId || `${Date.now()}` }
154
+ ];
155
+
156
+ await dynamodbSharedLib.updateItem(
157
+ _izContext,
158
+ usageTable,
159
+ { identifiersConcat, flowTypeConcat },
160
+ updateAttributes,
161
+ queryElementsWhenUpdate,
162
+ {
163
+ complexAttributes: true,
164
+ errorOnConditionalExpNotPass: false
165
+ }
166
+ ).catch(() => null);
167
+ }
168
+
60
169
  await sns.publishMessage(_izContext, {
61
- message: { returnResult: { status: true, uniqueIdentify } },
170
+ message: {
171
+ returnResult: {
172
+ status: true,
173
+ uniqueIdentify: usedTimestampId,
174
+ usedTimestampId,
175
+ runningTotalReservedId
176
+ }
177
+ },
62
178
  TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
63
179
  _izContext,
64
180
  'ConfirmUsage_Out'
@@ -30,15 +30,37 @@ export default async function CreateTargetRole(_izContext, requestParams) {
30
30
  const targetIdentifiers = requestParams.targetIdentifiers;
31
31
  let identifiersConcat = requestParams.identifiersConcat;
32
32
  if (!identifiersConcat && targetIdentifiers && typeof targetIdentifiers === 'object') {
33
- identifiersConcat = Object.entries(targetIdentifiers)
34
- .map(([k, v]) => `${k}#${v}`)
35
- .join('_');
33
+ if (targetIdentifiers.identifierConcat) {
34
+ identifiersConcat = targetIdentifiers.identifierConcat;
35
+ } else {
36
+ const sortedKeys = Object.keys(targetIdentifiers).sort();
37
+ const parts = [];
38
+ for (const key of sortedKeys) {
39
+ const value = targetIdentifiers[key];
40
+ if (
41
+ key !== 'submittedByUserId' &&
42
+ key !== 'identifierConcat' &&
43
+ value !== undefined &&
44
+ value !== null
45
+ ) {
46
+ parts.push(`${key}#${value}`);
47
+ }
48
+ }
49
+ if (parts.length > 0) {
50
+ identifiersConcat = parts.join('|');
51
+ }
52
+ }
36
53
  }
37
54
  if (!identifiersConcat) {
38
55
  identifiersConcat = submittedByUserId;
39
56
  }
40
57
 
41
- const { roleName = 'owner', roleId: inputRoleId } = requestParams;
58
+ const {
59
+ roleName = 'owner',
60
+ roleId: inputRoleId,
61
+ permissions = [],
62
+ assignUserIds = []
63
+ } = requestParams;
42
64
  const roleId = inputRoleId || roleName || identifierUuid();
43
65
  const roleIdKey = `${identifiersConcat}_${roleId}`;
44
66
 
@@ -53,24 +75,51 @@ export default async function CreateTargetRole(_izContext, requestParams) {
53
75
  }
54
76
  );
55
77
 
56
- if (submittedByUserId) {
78
+ if (Array.isArray(permissions) && permissions.length > 0) {
79
+ const rolePermissionsTable = dynamodbSharedLib.tableName(_izContext, 'RolePermissions');
80
+ for (const perm of permissions) {
81
+ const flowTypeConcat = typeof perm === 'string' ? perm : perm.flowTypeConcat;
82
+ if (flowTypeConcat) {
83
+ const permission =
84
+ typeof perm === 'object' && perm.permission !== undefined
85
+ ? perm.permission
86
+ : true;
87
+ const parameterGroups = (typeof perm === 'object' && perm.parameterGroups) || {};
88
+ await dynamodbSharedLib.putItem(_izContext, rolePermissionsTable, {
89
+ roleIdKey,
90
+ flowTypeConcat,
91
+ permission,
92
+ parameterGroups
93
+ });
94
+ }
95
+ }
96
+ }
97
+
98
+ const usersToAssign =
99
+ Array.isArray(assignUserIds) && assignUserIds.length > 0
100
+ ? assignUserIds
101
+ : (roleName === 'owner' && submittedByUserId ? [submittedByUserId] : []);
102
+
103
+ for (const userId of usersToAssign) {
57
104
  try {
58
105
  await dynamodbSharedLib.transactWriteItems(_izContext, [
59
106
  {
60
- Put: {
61
- TableName: dynamodbSharedLib.tableName(_izContext, 'RoleUsers'),
62
- Item: { roleIdKey, submittedByUserId }
107
+ action: 'put',
108
+ params: {
109
+ tableName: dynamodbSharedLib.tableName(_izContext, 'RoleUsers'),
110
+ attributes: { roleIdKey, userId, submittedByUserId }
63
111
  }
64
112
  },
65
113
  {
66
- Put: {
67
- TableName: dynamodbSharedLib.tableName(_izContext, 'UserRoles'),
68
- Item: { submittedByUserId, roleIdKey }
114
+ action: 'put',
115
+ params: {
116
+ tableName: dynamodbSharedLib.tableName(_izContext, 'UserRoles'),
117
+ attributes: { userId, roleIdKey, submittedByUserId }
69
118
  }
70
119
  }
71
120
  ]);
72
121
  } catch (assignError) {
73
- _izContext.logger.debug(`[${process.env.iz_serviceTag}:CreateTargetRole] UserRoles transactWrite ignored:', assignError);
122
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:CreateTargetRole] UserRoles transactWrite ignored:`, assignError);
74
123
  }
75
124
  }
76
125
 
@@ -0,0 +1,96 @@
1
+ /*
2
+ Copyright (C) 2020 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 dynamodbSharedLib from '@izara_project/izara-core-library-dynamodb';
19
+ import snsSharedLib from '@izara_project/izara-core-library-sns';
20
+ import { sns } from '@izara_project/izara-core-library-external-request';
21
+ import { NoRetryError } from '@izara_project/izara-core-library-core';
22
+
23
+ export default async function DeleteTargetRole(_izContext, requestParams) {
24
+ try {
25
+ const { identifiersConcat, roleIdKey } = requestParams;
26
+ if (!roleIdKey) {
27
+ throw new NoRetryError('roleIdKey is required');
28
+ }
29
+
30
+ if (roleIdKey.endsWith('_owner')) {
31
+ throw new NoRetryError('Cannot delete system owner role');
32
+ }
33
+
34
+ const targetRolesTable = dynamodbSharedLib.tableName(_izContext, 'TargetRoles');
35
+ const rolePermissionsTable = dynamodbSharedLib.tableName(_izContext, 'RolePermissions');
36
+ const roleUsersTable = dynamodbSharedLib.tableName(_izContext, 'RoleUsers');
37
+ const userRolesTable = dynamodbSharedLib.tableName(_izContext, 'UserRoles');
38
+
39
+ // 1. Delete from TargetRoles
40
+ await dynamodbSharedLib.deleteItem(
41
+ _izContext,
42
+ targetRolesTable,
43
+ { identifiersConcat, roleIdKey }
44
+ );
45
+
46
+ // 2. Cascade delete all permissions for this roleIdKey
47
+ const permsResult = await dynamodbSharedLib.query(_izContext, rolePermissionsTable, {
48
+ roleIdKey
49
+ });
50
+ for (const perm of (permsResult?.Items || [])) {
51
+ await dynamodbSharedLib.deleteItem(_izContext, rolePermissionsTable, {
52
+ roleIdKey,
53
+ flowTypeConcat: perm.flowTypeConcat
54
+ });
55
+ }
56
+
57
+ // 3. Cascade delete user assignments in RoleUsers and UserRoles
58
+ const usersResult = await dynamodbSharedLib.query(_izContext, roleUsersTable, {
59
+ roleIdKey
60
+ });
61
+ for (const userItem of (usersResult?.Items || [])) {
62
+ const userId = userItem.userId;
63
+ try {
64
+ await dynamodbSharedLib.transactWriteItems(_izContext, [
65
+ {
66
+ action: 'delete',
67
+ params: {
68
+ tableName: roleUsersTable,
69
+ keyValues: { roleIdKey, userId }
70
+ }
71
+ },
72
+ {
73
+ action: 'delete',
74
+ params: {
75
+ tableName: userRolesTable,
76
+ keyValues: { userId, roleIdKey }
77
+ }
78
+ }
79
+ ]);
80
+ } catch (err) {
81
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:DeleteTargetRole] Cascade user cleanup ignored:`, err);
82
+ }
83
+ }
84
+
85
+ await sns.publishMessage(_izContext, {
86
+ message: { returnResult: { identifiersConcat, roleIdKey, status: 'deleted' } },
87
+ TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
88
+ _izContext,
89
+ 'DeleteTargetRole_Out'
90
+ )
91
+ });
92
+ } catch (error) {
93
+ _izContext.logger.error(`[${process.env.iz_serviceTag}:DeleteTargetRole] error:`, error);
94
+ throw error;
95
+ }
96
+ }