apf-node-common 2.0.1 → 3.0.1
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/index.js +12 -1
- package/package.json +4 -3
- package/utils/AccessInterceptor.js +26 -26
- package/utils/aws/DynamoDbService.js +289 -0
package/index.js
CHANGED
|
@@ -24,6 +24,7 @@ const AWSAPIKeyGenerator = require('./utils/aws/AWSAPIKeyGenerator');
|
|
|
24
24
|
const AESEncryptionUsingKMS = require('./utils/aws/AESEncryptionUsingKMS');
|
|
25
25
|
const AccessInterceptor = require('./utils/AccessInterceptor');
|
|
26
26
|
const AWSSMSUtils = require('./utils/aws/AWSSMSUtils');
|
|
27
|
+
const DynamoDbService = require('./utils/aws/DynamoDbService');
|
|
27
28
|
const HashIds = require('./utils/HashIds');
|
|
28
29
|
const modulePath = path.resolve(__dirname, 'node_modules');
|
|
29
30
|
const TimeZoneConverter = require('./utils/TimeZoneConverter');
|
|
@@ -154,7 +155,8 @@ module.exports.AWSUsagePlanAndApiKey = {
|
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
module.exports.AccessInterceptor = {
|
|
157
|
-
authorize: AccessInterceptor.authorize
|
|
158
|
+
// authorize: AccessInterceptor.authorize,
|
|
159
|
+
authorizeWithOpenApiJson: AccessInterceptor.authorizeWithOpenApiJson
|
|
158
160
|
}
|
|
159
161
|
|
|
160
162
|
module.exports.HashIds = {
|
|
@@ -185,4 +187,13 @@ module.exports.Internationalization = {
|
|
|
185
187
|
|
|
186
188
|
module.exports.URLShorteningService = {
|
|
187
189
|
shortenURL: URLShorteningService.shortenURL
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
module.exports.DynamoDbService = {
|
|
193
|
+
scanRecords: DynamoDbService.scanRecords,
|
|
194
|
+
getRecord: DynamoDbService.getRecord,
|
|
195
|
+
queryRecords: DynamoDbService.queryRecords,
|
|
196
|
+
putRecord: DynamoDbService.putRecord,
|
|
197
|
+
updateRecord: DynamoDbService.updateRecord,
|
|
198
|
+
deleteRecord: DynamoDbService.deleteRecord
|
|
188
199
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apf-node-common",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -21,9 +21,10 @@
|
|
|
21
21
|
"moment": "^2.24.0",
|
|
22
22
|
"moment-timezone": "^0.5.27",
|
|
23
23
|
"path-to-regexp": "^6.1.0",
|
|
24
|
-
"swagger-jsdoc": "^3.4.0",
|
|
25
24
|
"winston": "^3.2.1",
|
|
26
25
|
"winston-aws-cloudwatch": "^3.0.0",
|
|
27
|
-
"winston-daily-rotate-file": "^4.5.0"
|
|
26
|
+
"winston-daily-rotate-file": "^4.5.0",
|
|
27
|
+
"@aws-sdk/client-dynamodb": "^3.1021.0",
|
|
28
|
+
"@aws-sdk/lib-dynamodb": "^3.1021.0"
|
|
28
29
|
}
|
|
29
30
|
}
|
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
const { pathToRegexp } = require('path-to-regexp')
|
|
2
|
-
const swaggerJSDoc = require('swagger-jsdoc');
|
|
3
|
-
|
|
4
|
-
function authorize(req, res, options, httpContext, next) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
1
|
+
const { pathToRegexp } = require('path-to-regexp');
|
|
2
|
+
// const swaggerJSDoc = require('swagger-jsdoc');
|
|
3
|
+
|
|
4
|
+
// function authorize(req, res, options, httpContext, next) {
|
|
5
|
+
// console.log('AccessInterceptor -> authorize');
|
|
6
|
+
// /*var options = {
|
|
7
|
+
// swaggerDefinition: swaggerDefinition,
|
|
8
|
+
// apis: ['swagger/*.js']
|
|
9
|
+
// };*/
|
|
10
|
+
|
|
11
|
+
// const swaggerSpec = swaggerJSDoc(options)
|
|
12
|
+
// const swaggerJson = JSON.parse(JSON.stringify(swaggerSpec))
|
|
13
|
+
// try {
|
|
14
|
+
// const isAllowed = validateAccess(swaggerJson, req, httpContext, res);
|
|
15
|
+
// if (isAllowed === false) {
|
|
16
|
+
// return isAllowed
|
|
17
|
+
// }
|
|
18
|
+
// return true;
|
|
19
|
+
|
|
20
|
+
// } catch (error) {
|
|
21
|
+
// console.log(error);
|
|
22
|
+
// return error;
|
|
23
|
+
// }
|
|
24
|
+
// next();
|
|
25
|
+
// }
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Authorize request based on swagger specification
|
|
@@ -111,5 +111,5 @@ function getReplacePath(path) {
|
|
|
111
111
|
str = str.replace(/}/g, '');
|
|
112
112
|
return str
|
|
113
113
|
}
|
|
114
|
-
exports.authorize = authorize;
|
|
114
|
+
// exports.authorize = authorize;
|
|
115
115
|
exports.authorizeWithOpenApiJson = authorizeWithOpenApiJson;
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
const log = require('apf-node-common/Logger').getLogger();
|
|
2
|
+
const { DynamoDBClient } = require("@aws-sdk/client-dynamodb");
|
|
3
|
+
|
|
4
|
+
const { DynamoDBDocumentClient,
|
|
5
|
+
ScanCommand,
|
|
6
|
+
GetCommand,
|
|
7
|
+
PutCommand,
|
|
8
|
+
UpdateCommand,
|
|
9
|
+
DeleteCommand,
|
|
10
|
+
QueryCommand } = require("@aws-sdk/lib-dynamodb");
|
|
11
|
+
|
|
12
|
+
const client = new DynamoDBClient({});
|
|
13
|
+
const dynamoDb = DynamoDBDocumentClient.from(client);
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
module.exports = {
|
|
17
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
18
|
+
// SCAN — fetch multiple records with optional filters
|
|
19
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
20
|
+
/**
|
|
21
|
+
* @param {string} tableName
|
|
22
|
+
* @param {Object} filters - optional e.g. { IsDeleted: false }
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* // Fetch where IsDeleted <> true OR IsDeleted missing ✅
|
|
26
|
+
const items = await DynamoDbService.scanRecords(TABLE_NAME, {
|
|
27
|
+
FilterExpression: "(#IsDeleted <> :isDeleted OR attribute_not_exists(#IsDeleted))",
|
|
28
|
+
|
|
29
|
+
const records = await DynamoDbService.scanRecords("AllowedOriginUrls", {
|
|
30
|
+
FilterExpression: "#MerchantId > :minId AND (#IsDeleted <> :isDeleted OR attribute_not_exists(#IsDeleted)) AND #AllowedUrls = :url",
|
|
31
|
+
ExpressionAttributeNames: {
|
|
32
|
+
"#MerchantId": "MerchantId",
|
|
33
|
+
"#IsDeleted": "IsDeleted",
|
|
34
|
+
"#AllowedUrls": "AllowedUrls"
|
|
35
|
+
},
|
|
36
|
+
ExpressionAttributeValues: {
|
|
37
|
+
":minId": 0,
|
|
38
|
+
":isDeleted": true,
|
|
39
|
+
":url": "https://dcy2no2504rgw.cloudfront.net/#/login"
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
*
|
|
43
|
+
* // Fetch all records no filter
|
|
44
|
+
* const all = await scanRecords("AllowedOriginUrls");
|
|
45
|
+
*/
|
|
46
|
+
async scanRecords(tableName, params = {}) {
|
|
47
|
+
try {
|
|
48
|
+
const command = new ScanCommand({
|
|
49
|
+
TableName: tableName, // ← always set
|
|
50
|
+
...params // ← caller provides everything else (e.g. FilterExpression, ExpressionAttributeValues)
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const result = await dynamoDb.send(command);
|
|
54
|
+
log.info(`[DynamoDB] scanRecords(${tableName}) → ${result.Items.length} records`);
|
|
55
|
+
return result.Items;
|
|
56
|
+
|
|
57
|
+
} catch (error) {
|
|
58
|
+
log.error(`[DynamoDB] scanRecords failed on ${tableName}:`, error.message);
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Fetches a single record by primary key from DynamoDB.
|
|
65
|
+
*
|
|
66
|
+
* TABLE: AllowedOriginUrls
|
|
67
|
+
* Attributes: MerchantId (PK), IsDeleted, AllowedUrls
|
|
68
|
+
*
|
|
69
|
+
* EXAMPLES:
|
|
70
|
+
*
|
|
71
|
+
* const record = await DynamoDbService.getRecord("AllowedOriginUrls", { MerchantId: 1277 });
|
|
72
|
+
* // → { MerchantId: 1277, IsDeleted: false, AllowedUrls: "https://shop.com" }
|
|
73
|
+
*
|
|
74
|
+
* // Fetch specific attributes only (ProjectionExpression)
|
|
75
|
+
* const record = await DynamoDbService.getRecord("AllowedOriginUrls", { MerchantId: 1277 }, {
|
|
76
|
+
* ProjectionExpression: "MerchantId, AllowedUrls",
|
|
77
|
+
* });
|
|
78
|
+
* // → { MerchantId: 1277, AllowedUrls: "https://shop.com" } (IsDeleted not returned)
|
|
79
|
+
*
|
|
80
|
+
* // Returns null if record not found
|
|
81
|
+
* const record = await DynamoDbService.getRecord("AllowedOriginUrls", { MerchantId: 9999 });
|
|
82
|
+
* // → null
|
|
83
|
+
*/
|
|
84
|
+
async getRecord(tableName, key, params = {}) {
|
|
85
|
+
try {
|
|
86
|
+
const result = await dynamoDb.send(new GetCommand({
|
|
87
|
+
TableName: tableName,
|
|
88
|
+
Key: key,
|
|
89
|
+
...params // ← caller provides anything extra
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
log.info(`[DynamoDB] getRecord(${tableName}) → ${result.Item ? "found" : "not found"}`);
|
|
93
|
+
return result.Item || null;
|
|
94
|
+
|
|
95
|
+
} catch (error) {
|
|
96
|
+
log.error(`[DynamoDB] getRecord failed on ${tableName}:`, error.message);
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Fetches multiple records by partition key — faster than scan.
|
|
104
|
+
* Use when table has sort key and you need multiple records
|
|
105
|
+
* for the same partition key.
|
|
106
|
+
* Pl note QueryCommand REQUIRES KeyConditionExpression which must include the partition key and can optionally include the sort key.
|
|
107
|
+
* KeyConditionExpression only works on
|
|
108
|
+
* Partition Key → only supports = operator
|
|
109
|
+
* Sort Key → supports =, <, >, BETWEEN, begins_with
|
|
110
|
+
*
|
|
111
|
+
* TABLE: AllowedOriginUrls
|
|
112
|
+
* Attributes: MerchantId (PK), IsDeleted, AllowedUrls
|
|
113
|
+
*
|
|
114
|
+
* EXAMPLES:
|
|
115
|
+
*
|
|
116
|
+
* // Fetch all records for a merchant
|
|
117
|
+
* const records = await DynamoDbService.queryRecords("AllowedOriginUrls", {
|
|
118
|
+
* KeyConditionExpression: "#pk = :pkVal",
|
|
119
|
+
* ExpressionAttributeNames: { "#pk": "MerchantId" },
|
|
120
|
+
* ExpressionAttributeValues: { ":pkVal": 1277 }
|
|
121
|
+
* });
|
|
122
|
+
* // → [{ MerchantId: 1277, IsDeleted: false, AllowedUrls: "https://shop.com" }]
|
|
123
|
+
*
|
|
124
|
+
* // Fetch with additional filter
|
|
125
|
+
* const records = await DynamoDbService.queryRecords("AllowedOriginUrls", {
|
|
126
|
+
* KeyConditionExpression: "#pk = :pkVal",
|
|
127
|
+
* FilterExpression: "IsDeleted <> :isDeleted",
|
|
128
|
+
* ExpressionAttributeNames: { "#pk": "MerchantId" },
|
|
129
|
+
* ExpressionAttributeValues: { ":pkVal": 1277, ":isDeleted": true }
|
|
130
|
+
* });
|
|
131
|
+
*/
|
|
132
|
+
async queryRecords(tableName, params = {}) {
|
|
133
|
+
try {
|
|
134
|
+
const result = await dynamoDb.send(new QueryCommand({
|
|
135
|
+
TableName: tableName, // ← always set
|
|
136
|
+
...params // ← caller provides everything else (e.g. KeyConditionExpression, FilterExpression, ExpressionAttributeValues)
|
|
137
|
+
}));
|
|
138
|
+
|
|
139
|
+
log.info(`[DynamoDB] queryRecords(${tableName}) → ${result.Items.length} records`);
|
|
140
|
+
return result.Items;
|
|
141
|
+
|
|
142
|
+
} catch (error) {
|
|
143
|
+
log.error(`[DynamoDB] queryRecords failed on ${tableName}:`, error.message);
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Inserts or replaces a record in DynamoDB.
|
|
151
|
+
* If record with same key exists → replaces entire record.
|
|
152
|
+
*
|
|
153
|
+
* TABLE: AllowedOriginUrls
|
|
154
|
+
* Attributes: MerchantId (PK), IsDeleted, AllowedUrls
|
|
155
|
+
*
|
|
156
|
+
* EXAMPLES:
|
|
157
|
+
*
|
|
158
|
+
* // Insert new record
|
|
159
|
+
* await DynamoDbService.putRecord("AllowedOriginUrls", {
|
|
160
|
+
* MerchantId: 1277,
|
|
161
|
+
* AllowedUrls: "https://shop.com",
|
|
162
|
+
* IsDeleted: false
|
|
163
|
+
* });
|
|
164
|
+
*
|
|
165
|
+
* // Insert with condition — only if record does NOT already exist
|
|
166
|
+
* await DynamoDbService.putRecord("AllowedOriginUrls", {
|
|
167
|
+
* MerchantId: 1277,
|
|
168
|
+
* AllowedUrls: "https://shop.com",
|
|
169
|
+
* IsDeleted: false
|
|
170
|
+
* }, {
|
|
171
|
+
* ConditionExpression: "attribute_not_exists(MerchantId)"
|
|
172
|
+
* });
|
|
173
|
+
*/
|
|
174
|
+
async putRecord(tableName, item, params = {}) {
|
|
175
|
+
try {
|
|
176
|
+
await dynamoDb.send(new PutCommand({
|
|
177
|
+
TableName: tableName,
|
|
178
|
+
Item: item,
|
|
179
|
+
...params // ← caller provides anything extra (e.g. ConditionExpression, ExpressionAttributeValues for conditions)
|
|
180
|
+
}));
|
|
181
|
+
|
|
182
|
+
log.info(`[DynamoDB] putRecord(${tableName}) → saved successfully`);
|
|
183
|
+
return true;
|
|
184
|
+
|
|
185
|
+
} catch (error) {
|
|
186
|
+
log.error(`[DynamoDB] putRecord failed on ${tableName}:`, error.message);
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Updates specific fields of an existing record by primary key.
|
|
194
|
+
* Only provided fields are updated — rest remain unchanged.
|
|
195
|
+
*
|
|
196
|
+
* TABLE: AllowedOriginUrls
|
|
197
|
+
* Attributes: MerchantId (PK), IsDeleted, AllowedUrls
|
|
198
|
+
*
|
|
199
|
+
* EXAMPLES:
|
|
200
|
+
*
|
|
201
|
+
* // Soft delete a record
|
|
202
|
+
* await DynamoDbService.updateRecord("AllowedOriginUrls",
|
|
203
|
+
* { MerchantId: 1277 },
|
|
204
|
+
* {
|
|
205
|
+
* UpdateExpression: "SET #IsDeleted = :val",
|
|
206
|
+
* ExpressionAttributeNames: { "#IsDeleted": "IsDeleted" },
|
|
207
|
+
* ExpressionAttributeValues: { ":val": true }
|
|
208
|
+
* }
|
|
209
|
+
* );
|
|
210
|
+
*
|
|
211
|
+
* // Update AllowedUrls
|
|
212
|
+
* await DynamoDbService.updateRecord("AllowedOriginUrls",
|
|
213
|
+
* { MerchantId: 1277 },
|
|
214
|
+
* {
|
|
215
|
+
* UpdateExpression: "SET #AllowedUrls = :url, #IsDeleted = :isDeleted",
|
|
216
|
+
* ExpressionAttributeNames: { "#AllowedUrls": "AllowedUrls", "#IsDeleted": "IsDeleted" },
|
|
217
|
+
* ExpressionAttributeValues: { ":url": "https://newshop.com", ":isDeleted": false }
|
|
218
|
+
* }
|
|
219
|
+
* );
|
|
220
|
+
*
|
|
221
|
+
* // Update only if record exists
|
|
222
|
+
* await DynamoDbService.updateRecord("AllowedOriginUrls",
|
|
223
|
+
* { MerchantId: 1277 },
|
|
224
|
+
* {
|
|
225
|
+
* UpdateExpression: "SET #AllowedUrls = :url",
|
|
226
|
+
* ConditionExpression: "attribute_exists(MerchantId)",
|
|
227
|
+
* ExpressionAttributeNames: { "#AllowedUrls": "AllowedUrls" },
|
|
228
|
+
* ExpressionAttributeValues: { ":url": "https://newshop.com" }
|
|
229
|
+
* }
|
|
230
|
+
* );
|
|
231
|
+
*/
|
|
232
|
+
async updateRecord(tableName, key, params = {}) {
|
|
233
|
+
try {
|
|
234
|
+
const result = await dynamoDb.send(new UpdateCommand({
|
|
235
|
+
TableName: tableName,
|
|
236
|
+
Key: key,
|
|
237
|
+
ReturnValues: "ALL_NEW", // ← full updated item
|
|
238
|
+
...params // ← caller provides UpdateExpression etc
|
|
239
|
+
}));
|
|
240
|
+
|
|
241
|
+
log.info(`[DynamoDB] updateRecord(${tableName}) → updated:`, result.Attributes);
|
|
242
|
+
return result.Attributes;
|
|
243
|
+
|
|
244
|
+
} catch (error) {
|
|
245
|
+
log.error(`[DynamoDB] updateRecord failed on ${tableName}:`, error.message);
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Deletes a record by primary key from DynamoDB.
|
|
253
|
+
*
|
|
254
|
+
* TABLE: AllowedOriginUrls
|
|
255
|
+
* Attributes: MerchantId (PK), IsDeleted, AllowedUrls
|
|
256
|
+
*
|
|
257
|
+
* EXAMPLES:
|
|
258
|
+
*
|
|
259
|
+
* // Hard delete by partition key
|
|
260
|
+
* await DynamoDbService.deleteRecord("AllowedOriginUrls", { MerchantId: 1277 });
|
|
261
|
+
*
|
|
262
|
+
* // Delete only if IsDeleted is already true (conditional delete)
|
|
263
|
+
* await DynamoDbService.deleteRecord("AllowedOriginUrls",
|
|
264
|
+
* { MerchantId: 1277 },
|
|
265
|
+
* {
|
|
266
|
+
* ConditionExpression: "#IsDeleted = :val",
|
|
267
|
+
* ExpressionAttributeNames: { "#IsDeleted": "IsDeleted" },
|
|
268
|
+
* ExpressionAttributeValues: { ":val": true }
|
|
269
|
+
* }
|
|
270
|
+
* );
|
|
271
|
+
*/
|
|
272
|
+
async deleteRecord(tableName, key, params = {}) {
|
|
273
|
+
try {
|
|
274
|
+
await dynamoDb.send(new DeleteCommand({
|
|
275
|
+
TableName: tableName,
|
|
276
|
+
Key: key,
|
|
277
|
+
...params // ← caller provides anything extra ✅
|
|
278
|
+
}));
|
|
279
|
+
|
|
280
|
+
log.info(`[DynamoDB] deleteRecord(${tableName}) → deleted successfully`);
|
|
281
|
+
return true;
|
|
282
|
+
|
|
283
|
+
} catch (error) {
|
|
284
|
+
log.error(`[DynamoDB] deleteRecord failed on ${tableName}:`, error.message);
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
}
|