@izara_project/izara-core-generate-service-code 1.0.83 → 1.0.85

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 (63) hide show
  1. package/package.json +4 -4
  2. package/src/compile/flowValidator.js +13 -2
  3. package/src/compile/loadSchemas.js +116 -7
  4. package/src/emit/attributeTree/emitAttributeTreeSchemas.js +388 -0
  5. package/src/emit/coreFunction/emitCoreFunctionArtifacts.js +34 -1
  6. package/src/emit/coreFunction/templates/InitialSetupConfig.ejs +29 -7
  7. package/src/emit/crud/crudActionPlan.js +41 -6
  8. package/src/emit/crud/emitCrudApplicationArtifacts.js +56 -8
  9. package/src/emit/crud/emitDynamicCrudFlowSchemas.js +153 -27
  10. package/src/emit/crud/generateCrudApplicationArtifacts.js +3 -2
  11. package/src/emit/crud/generateDynamicCrudFlowSchemas.js +3 -2
  12. package/src/emit/crud/loadLocalSchemaArrays.js +3 -3
  13. package/src/emit/crud/templates/CreateComplete_Main.ejs +1 -1
  14. package/src/emit/crud/templates/CreateConfirmComplete_Main.ejs +54 -0
  15. package/src/emit/crud/templates/CreateEndpoint_Main.ejs +252 -154
  16. package/src/emit/crud/templates/CreateLogicalLib.ejs +1163 -236
  17. package/src/emit/crud/templates/CreateNodeCallback_Main.ejs +221 -0
  18. package/src/emit/crud/templates/DeleteComplete_Main.ejs +1 -1
  19. package/src/emit/crud/templates/DeleteEndpoint_Main.ejs +33 -8
  20. package/src/emit/crud/templates/DeleteLogicalLib.ejs +6 -3
  21. package/src/emit/crud/templates/FlowEndpointRbacCallback_Main.ejs +20 -120
  22. package/src/emit/crud/templates/FlowEndpoint_HdrWbs.ejs +79 -10
  23. package/src/emit/crud/templates/FlowMain_Wbs.ejs +31 -35
  24. package/src/emit/crud/templates/GetEndpoint_Main.ejs +13 -0
  25. package/src/emit/crud/templates/UpdateComplete_Main.ejs +1 -1
  26. package/src/emit/crud/templates/UpdateEndpoint_Main.ejs +52 -3
  27. package/src/emit/crud/templates/UpdateLogicalLib.ejs +4 -3
  28. package/src/emit/custom/customFlows.js +47 -0
  29. package/src/emit/custom/emitCustomFlowApplicationArtifacts.js +168 -49
  30. package/src/emit/custom/generateCustomFlowApplicationArtifacts.js +1 -1
  31. package/src/emit/custom/templates/FlowEndpointRbacCallback_Main.ejs +194 -0
  32. package/src/emit/custom/templates/FlowEndpoint_Main.ejs +14 -22
  33. package/src/emit/custom/templates/FlowStep_Main.ejs +1 -1
  34. package/src/emit/function/templates/WbsFunction_Yaml.ejs +0 -2
  35. package/src/emit/rbac/emitDynamicRbacFlowSchemas.js +33 -8
  36. package/src/emit/rbac/emitRbacApplicationArtifacts.js +204 -7
  37. package/src/emit/rbac/templates/CancelUsage_Main.ejs +163 -0
  38. package/src/emit/rbac/templates/ConfirmUsage_Main.ejs +187 -0
  39. package/src/emit/rbac/templates/CreateTargetRole_Main.ejs +68 -6
  40. package/src/emit/rbac/templates/ListRbac_Main.ejs +9 -3
  41. package/src/emit/rbac/templates/RbacMutation_Main.ejs +1 -1
  42. package/src/emit/rbac/templates/ReservedDynamicUsage_Main.ejs +166 -0
  43. package/src/emit/rbac/templates/UserRbacFlow_Main.ejs +84 -23
  44. package/src/emit/rbac/templates/UserRoleMutation_Main.ejs +11 -8
  45. package/src/emit/relationship/emitDynamicRelationshipFlowSchemas.js +1 -1
  46. package/src/emit/relationship/templates/ChangeRelationship_Main.ejs +4 -4
  47. package/src/emit/resource/emitResourceArtifacts.js +351 -108
  48. package/src/emit/resource/templates/GeneratedLambdaRoleServiceConfig.ejs +3 -1
  49. package/src/emit/resource/templates/SystemDynamoDB_Yaml.ejs +55 -2
  50. package/src/emit/shared/templates/FlowHandler_HdrSqs.ejs +1 -1
  51. package/src/emit/system/emitSystemWebSocketArtifacts.js +6 -5
  52. package/src/emit/system/templates/WebSocketConnect.ejs +71 -12
  53. package/src/emit/system/templates/WebSocketConnect_Yaml.ejs +6 -0
  54. package/src/emit/systemText/emitSystemTextSchemas.js +1 -1
  55. package/src/generate.js +41 -10
  56. package/test/attributeTreeSchemas.test.js +219 -0
  57. package/test/createTargetRoleHandler.test.js +59 -4
  58. package/test/crudApplicationArtifacts.test.js +20 -3
  59. package/test/customFlowApplicationArtifacts.test.js +5 -5
  60. package/test/dynamicCrudFlowSchemas.test.js +107 -6
  61. package/test/dynamicRbacFlowSchemas.test.js +15 -2
  62. package/test/publicApi.test.js +6 -6
  63. package/test/resourceArtifacts.test.js +89 -27
@@ -0,0 +1,163 @@
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 { consts, NoRetryError } from '@izara_project/izara-core-library-core';
22
+
23
+ export default async function CancelUsage(_izContext, requestParams) {
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
+
34
+ const submittedByUserId =
35
+ requestParams?.submittedByUserId ||
36
+ _izContext.correlationIds?.get(consts.SUBMITTED_BY_USER_ID);
37
+ const flowTypeConcat =
38
+ requestParams?.flowTypeConcat ||
39
+ requestParams?.limitTag;
40
+
41
+ const targetIdentifiers = requestParams?.targetIdentifiers;
42
+ let identifiersConcat = requestParams?.identifiersConcat;
43
+ if (!identifiersConcat && targetIdentifiers && typeof targetIdentifiers === 'object') {
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
+ }
64
+ }
65
+ if (!identifiersConcat) {
66
+ identifiersConcat = submittedByUserId;
67
+ }
68
+
69
+ if (!runningTotalReservedId) {
70
+ if (identifiersConcat && flowTypeConcat) {
71
+ runningTotalReservedId = `${identifiersConcat}_${flowTypeConcat}`;
72
+ }
73
+ }
74
+
75
+ if (!runningTotalReservedId || !usedTimestampId) {
76
+ throw new NoRetryError(
77
+ 'runningTotalReservedId (or identifiersConcat + flowTypeConcat) and usedTimestampId (or uniqueIdentify) are required.'
78
+ );
79
+ }
80
+
81
+ const reservedRecord = await dynamodbSharedLib.getItem(
82
+ _izContext,
83
+ dynamodbSharedLib.tableName(_izContext, 'RunningTotalReservedUsage'),
84
+ {
85
+ runningTotalReservedId,
86
+ usedTimestampId
87
+ }
88
+ );
89
+
90
+ const usageCount = reservedRecord?.usageCount || 0;
91
+
92
+ await dynamodbSharedLib.updateItem(
93
+ _izContext,
94
+ dynamodbSharedLib.tableName(_izContext, 'RunningTotalReservedUsage'),
95
+ {
96
+ runningTotalReservedId,
97
+ usedTimestampId
98
+ },
99
+ {
100
+ runningTotalUsageStatus: 'cancelled'
101
+ }
102
+ );
103
+
104
+ if (usageCount > 0 && identifiersConcat && flowTypeConcat) {
105
+ const usageRecord = await dynamodbSharedLib.getItem(
106
+ _izContext,
107
+ dynamodbSharedLib.tableName(_izContext, 'RunningTotalUsage'),
108
+ { identifiersConcat, flowTypeConcat }
109
+ );
110
+ if (usageRecord) {
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(
132
+ _izContext,
133
+ dynamodbSharedLib.tableName(_izContext, 'RunningTotalUsage'),
134
+ { identifiersConcat, flowTypeConcat },
135
+ updateAttributes,
136
+ queryElementsWhenUpdate,
137
+ {
138
+ complexAttributes: true,
139
+ errorOnConditionalExpNotPass: false
140
+ }
141
+ ).catch(() => null);
142
+ }
143
+ }
144
+
145
+ await sns.publishMessage(_izContext, {
146
+ message: {
147
+ returnResult: {
148
+ status: true,
149
+ uniqueIdentify: usedTimestampId,
150
+ usedTimestampId,
151
+ runningTotalReservedId
152
+ }
153
+ },
154
+ TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
155
+ _izContext,
156
+ 'CancelUsage_Out'
157
+ )
158
+ });
159
+ } catch (error) {
160
+ _izContext.logger.error(`[${process.env.iz_serviceTag}:CancelUsage] error:`, error);
161
+ throw error;
162
+ }
163
+ }
@@ -0,0 +1,187 @@
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 { consts, NoRetryError } from '@izara_project/izara-core-library-core';
22
+
23
+ export default async function ConfirmUsage(_izContext, requestParams) {
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
+
34
+ const submittedByUserId =
35
+ requestParams?.submittedByUserId ||
36
+ _izContext.correlationIds?.get(consts.SUBMITTED_BY_USER_ID);
37
+ const flowTypeConcat =
38
+ requestParams?.flowTypeConcat ||
39
+ requestParams?.limitTag;
40
+
41
+ const targetIdentifiers = requestParams?.targetIdentifiers;
42
+ let identifiersConcat = requestParams?.identifiersConcat;
43
+ if (!identifiersConcat && targetIdentifiers && typeof targetIdentifiers === 'object') {
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
+ }
64
+ }
65
+ if (!identifiersConcat) {
66
+ identifiersConcat = submittedByUserId;
67
+ }
68
+
69
+ if (!runningTotalReservedId) {
70
+ if (identifiersConcat && flowTypeConcat) {
71
+ runningTotalReservedId = `${identifiersConcat}_${flowTypeConcat}`;
72
+ }
73
+ }
74
+
75
+ if (!runningTotalReservedId || !usedTimestampId) {
76
+ throw new NoRetryError(
77
+ 'runningTotalReservedId (or identifiersConcat + flowTypeConcat) and usedTimestampId (or uniqueIdentify) are required.'
78
+ );
79
+ }
80
+
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
+
98
+ await dynamodbSharedLib.updateItem(
99
+ _izContext,
100
+ reservedTable,
101
+ {
102
+ runningTotalReservedId,
103
+ usedTimestampId
104
+ },
105
+ {
106
+ runningTotalUsageStatus: 'confirmed',
107
+ confirmedAt: new Date().toISOString()
108
+ }
109
+ );
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
+
169
+ await sns.publishMessage(_izContext, {
170
+ message: {
171
+ returnResult: {
172
+ status: true,
173
+ uniqueIdentify: usedTimestampId,
174
+ usedTimestampId,
175
+ runningTotalReservedId
176
+ }
177
+ },
178
+ TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
179
+ _izContext,
180
+ 'ConfirmUsage_Out'
181
+ )
182
+ });
183
+ } catch (error) {
184
+ _izContext.logger.error(`[${process.env.iz_serviceTag}:ConfirmUsage] error:`, error);
185
+ throw error;
186
+ }
187
+ }
@@ -23,23 +23,85 @@ import { consts } from '@izara_project/izara-core-library-core';
23
23
 
24
24
  export default async function CreateTargetRole(_izContext, requestParams) {
25
25
  try {
26
- const userId = _izContext.correlationIds.get(consts.BASE_USER_ID);
27
- const { roleName } = requestParams;
28
- const roleId = identifierUuid();
26
+ const submittedByUserId =
27
+ requestParams.submittedByUserId ||
28
+ _izContext.correlationIds?.get(consts.SUBMITTED_BY_USER_ID);
29
+
30
+ const targetIdentifiers = requestParams.targetIdentifiers;
31
+ let identifiersConcat = requestParams.identifiersConcat;
32
+ if (!identifiersConcat && targetIdentifiers && typeof targetIdentifiers === 'object') {
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
+ }
53
+ }
54
+ if (!identifiersConcat) {
55
+ identifiersConcat = submittedByUserId;
56
+ }
57
+
58
+ const { roleName = 'owner', roleId: inputRoleId } = requestParams;
59
+ const roleId = inputRoleId || roleName || identifierUuid();
60
+ const roleIdKey = `${identifiersConcat}_${roleId}`;
61
+
29
62
  await dynamodbSharedLib.putItem(
30
63
  _izContext,
31
64
  dynamodbSharedLib.tableName(_izContext, 'TargetRoles'),
32
- { targetId: userId, roleIdKey: `${userId}_${roleId}`, roleName }
65
+ {
66
+ identifiersConcat,
67
+ roleIdKey,
68
+ roleName,
69
+ submittedByUserId
70
+ }
33
71
  );
72
+
73
+ if (submittedByUserId) {
74
+ try {
75
+ await dynamodbSharedLib.transactWriteItems(_izContext, [
76
+ {
77
+ action: 'put',
78
+ params: {
79
+ tableName: dynamodbSharedLib.tableName(_izContext, 'RoleUsers'),
80
+ attributes: { roleIdKey, submittedByUserId }
81
+ }
82
+ },
83
+ {
84
+ action: 'put',
85
+ params: {
86
+ tableName: dynamodbSharedLib.tableName(_izContext, 'UserRoles'),
87
+ attributes: { submittedByUserId, roleIdKey }
88
+ }
89
+ }
90
+ ]);
91
+ } catch (assignError) {
92
+ _izContext.logger.debug(`[${process.env.iz_serviceTag}:CreateTargetRole] UserRoles transactWrite ignored:`, assignError);
93
+ }
94
+ }
95
+
34
96
  await sns.publishMessage(_izContext, {
35
- message: { returnResult: { roleId } },
97
+ message: { returnResult: { roleId, roleIdKey } },
36
98
  TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
37
99
  _izContext,
38
100
  'CreateTargetRole_Out'
39
101
  )
40
102
  });
41
103
  } catch (error) {
42
- _izContext.logger.error('[Rbac:CreateTargetRole] error:', error);
104
+ _izContext.logger.error(`[${process.env.iz_serviceTag}:CreateTargetRole] error:`, error);
43
105
  throw error;
44
106
  }
45
107
  }
@@ -22,8 +22,14 @@ import { consts, NoRetryError } from '@izara_project/izara-core-library-core';
22
22
 
23
23
  export default async function <%- flowTag %>(_izContext, requestParams) {
24
24
  try {
25
- <% if (useContextUserId) { -%>
26
- const userId = _izContext.correlationIds.get(consts.BASE_USER_ID);
25
+ <% if (flowTag === 'ListTargetRole') { -%>
26
+ const userId = requestParams.submittedByUserId || _izContext.correlationIds.get(consts.SUBMITTED_BY_USER_ID);
27
+ const identifiersConcat =
28
+ requestParams.identifiersConcat || requestParams.targetId || userId;
29
+ if (!identifiersConcat) throw new NoRetryError('Identifier not found.');
30
+ const queryParams = { identifiersConcat };
31
+ <% } else if (useContextUserId) { -%>
32
+ const userId = requestParams.submittedByUserId || _izContext.correlationIds.get(consts.SUBMITTED_BY_USER_ID);
27
33
  if (!userId) throw new NoRetryError('User Id not found.');
28
34
  const queryParams = { userId };
29
35
  <% } else { -%>
@@ -43,7 +49,7 @@ export default async function <%- flowTag %>(_izContext, requestParams) {
43
49
  )
44
50
  });
45
51
  } catch (error) {
46
- _izContext.logger.error('[Rbac:<%- flowTag %>] error:', error);
52
+ _izContext.logger.error(`[${process.env.iz_serviceTag}:<%- flowTag %>] error:`, error);
47
53
  throw error;
48
54
  }
49
55
  }
@@ -36,7 +36,7 @@ export default async function <%- flowTag %>(_izContext, requestParams) {
36
36
  )
37
37
  });
38
38
  } catch (error) {
39
- _izContext.logger.error('[Rbac:<%- flowTag %>] error:', error);
39
+ _izContext.logger.error(`[${process.env.iz_serviceTag}:<%- flowTag %>] error:`, error);
40
40
  throw error;
41
41
  }
42
42
  }
@@ -0,0 +1,166 @@
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 { consts, NoRetryError } from '@izara_project/izara-core-library-core';
22
+
23
+ export default async function ReservedDynamicUsage(_izContext, requestParams) {
24
+ try {
25
+ const submittedByUserId =
26
+ requestParams.submittedByUserId ||
27
+ _izContext.correlationIds?.get(consts.SUBMITTED_BY_USER_ID);
28
+ const flowTypeConcat =
29
+ requestParams.flowTypeConcat ||
30
+ requestParams.limitTag;
31
+ const {
32
+ value = 1,
33
+ usedTimestamp = new Date().toISOString(),
34
+ roleLimitId
35
+ } = requestParams;
36
+
37
+ const targetIdentifiers = requestParams.targetIdentifiers;
38
+ let identifiersConcat = requestParams.identifiersConcat;
39
+ if (!identifiersConcat && targetIdentifiers && typeof targetIdentifiers === 'object') {
40
+ if (targetIdentifiers.identifierConcat) {
41
+ identifiersConcat = targetIdentifiers.identifierConcat;
42
+ } else {
43
+ const sortedKeys = Object.keys(targetIdentifiers).sort();
44
+ const parts = [];
45
+ for (const key of sortedKeys) {
46
+ const value = targetIdentifiers[key];
47
+ if (
48
+ key !== 'submittedByUserId' &&
49
+ key !== 'identifierConcat' &&
50
+ value !== undefined &&
51
+ value !== null
52
+ ) {
53
+ parts.push(`${key}#${value}`);
54
+ }
55
+ }
56
+ if (parts.length > 0) {
57
+ identifiersConcat = parts.join('|');
58
+ }
59
+ }
60
+ }
61
+ if (!identifiersConcat) {
62
+ identifiersConcat = submittedByUserId;
63
+ }
64
+
65
+ if (!identifiersConcat) {
66
+ throw new NoRetryError('Identifier not found.');
67
+ }
68
+ if (!flowTypeConcat) {
69
+ throw new NoRetryError('flowTypeConcat (or limitTag) is required.');
70
+ }
71
+
72
+ const uniqueIdentify = `${usedTimestamp}_${_izContext.uniqueRequestId || Date.now()}`;
73
+ const roleLimitKeys = Array.isArray(requestParams.roleLimitIds)
74
+ ? requestParams.roleLimitIds
75
+ : [roleLimitId || `${identifiersConcat}_${flowTypeConcat}`];
76
+
77
+ let maxLimit = Infinity;
78
+ const limitRecords = [];
79
+
80
+ for (const key of roleLimitKeys) {
81
+ const record = await dynamodbSharedLib.getItem(
82
+ _izContext,
83
+ dynamodbSharedLib.tableName(_izContext, 'RoleLimit'),
84
+ { roleLimitId: key, limitType: 'runningTotal' }
85
+ );
86
+ if (record && typeof record.limitValue === 'number') {
87
+ limitRecords.push(record);
88
+ }
89
+ }
90
+
91
+ if (limitRecords.length > 0) {
92
+ const isDecrease = (rec) =>
93
+ rec.effect === 'decrease' ||
94
+ rec.actionType === 'decrease' ||
95
+ rec.action === 'decrease';
96
+
97
+ const increaseLimits = limitRecords.filter((rec) => !isDecrease(rec));
98
+ const decreaseLimits = limitRecords.filter((rec) => isDecrease(rec));
99
+
100
+ const maxIncrease =
101
+ increaseLimits.length > 0
102
+ ? Math.max(...increaseLimits.map((r) => r.limitValue))
103
+ : Infinity;
104
+ const minDecrease =
105
+ decreaseLimits.length > 0
106
+ ? Math.min(...decreaseLimits.map((r) => r.limitValue))
107
+ : Infinity;
108
+
109
+ maxLimit = Math.min(maxIncrease, minDecrease);
110
+ }
111
+
112
+ const usageRecord = await dynamodbSharedLib.getItem(
113
+ _izContext,
114
+ dynamodbSharedLib.tableName(_izContext, 'RunningTotalUsage'),
115
+ { identifiersConcat, flowTypeConcat }
116
+ );
117
+ const currentTotal = usageRecord?.totalCount || 0;
118
+
119
+ let passStatus = false;
120
+ if (currentTotal + value <= maxLimit) {
121
+ passStatus = true;
122
+ const ttlTimestamp = Math.floor(Date.now() / 1000) + 3600;
123
+
124
+ await dynamodbSharedLib.putItem(
125
+ _izContext,
126
+ dynamodbSharedLib.tableName(_izContext, 'RunningTotalReservedUsage'),
127
+ {
128
+ runningTotalReservedId: `${identifiersConcat}_${flowTypeConcat}`,
129
+ usedTimestampId: uniqueIdentify,
130
+ usageCount: value,
131
+ runningTotalUsageStatus: 'reserved',
132
+ ttlTimestamp
133
+ }
134
+ );
135
+
136
+ await dynamodbSharedLib.putItem(
137
+ _izContext,
138
+ dynamodbSharedLib.tableName(_izContext, 'RunningTotalUsage'),
139
+ {
140
+ identifiersConcat,
141
+ flowTypeConcat,
142
+ totalCount: currentTotal + value,
143
+ updateUuid: _izContext.uniqueRequestId || `${Date.now()}`
144
+ }
145
+ );
146
+ }
147
+
148
+ const returnResult = {
149
+ passStatus,
150
+ uniqueIdentify,
151
+ flowTypeConcat,
152
+ identifiersConcat
153
+ };
154
+
155
+ await sns.publishMessage(_izContext, {
156
+ message: { returnResult },
157
+ TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
158
+ _izContext,
159
+ 'ReservedDynamicUsage_Out'
160
+ )
161
+ });
162
+ } catch (error) {
163
+ _izContext.logger.error(`[${process.env.iz_serviceTag}:ReservedDynamicUsage] error:`, error);
164
+ throw error;
165
+ }
166
+ }