tango-app-api-report 3.4.0-beta.4 → 3.5.0
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,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tango-app-api-report",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"description": "report",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"start": "nodemon --exec \"eslint --fix . && node
|
|
8
|
+
"start": "nodemon --exec \"eslint --fix . && node app.js\""
|
|
9
9
|
},
|
|
10
10
|
"engines": {
|
|
11
11
|
"node": ">=18.10.0"
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"nodemailer": "^6.9.13",
|
|
26
26
|
"nodemon": "^3.1.0",
|
|
27
27
|
"swagger-ui-express": "^5.0.0",
|
|
28
|
-
"tango-api-schema": "^2.
|
|
28
|
+
"tango-api-schema": "^2.4.7",
|
|
29
29
|
"tango-app-api-middleware": "^3.1.45",
|
|
30
30
|
"winston": "^3.12.0",
|
|
31
31
|
"winston-daily-rotate-file": "^5.0.0"
|
|
@@ -266,10 +266,10 @@ export async function clientReportList( req, res ) {
|
|
|
266
266
|
return res.sendError( 'No Data Found', 204 );
|
|
267
267
|
}
|
|
268
268
|
let paginatedData = count;
|
|
269
|
+
|
|
269
270
|
if ( inputData?.reportStatus==='' ) {
|
|
270
271
|
paginatedData = await paginate( count, offset, limit );
|
|
271
272
|
}
|
|
272
|
-
|
|
273
273
|
if ( inputData.reportStatus&&inputData.reportStatus!=='' ) {
|
|
274
274
|
paginatedData = await splitArray( paginatedData );
|
|
275
275
|
paginatedData= paginatedData.filter( ( ele ) => {
|
|
@@ -291,7 +291,7 @@ export async function clientReportList( req, res ) {
|
|
|
291
291
|
}
|
|
292
292
|
|
|
293
293
|
export async function mapFunction( array ) {
|
|
294
|
-
for ( let
|
|
294
|
+
for ( let client of array ) {
|
|
295
295
|
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
296
296
|
const reportLog = await getOpenSearchData( openSearch.auditLog,
|
|
297
297
|
{
|
|
@@ -326,8 +326,10 @@ export async function mapFunction( array ) {
|
|
|
326
326
|
},
|
|
327
327
|
|
|
328
328
|
} );
|
|
329
|
+
|
|
329
330
|
client.reportStatus='notsent';
|
|
330
|
-
|
|
331
|
+
|
|
332
|
+
if ( reportLog?.body?.hits?.hits?.length > 0 ) {
|
|
331
333
|
const sourcesArray = reportLog.body.hits.hits.map( ( hit ) => hit._source );
|
|
332
334
|
if ( sourcesArray.length>0 ) {
|
|
333
335
|
client.reportStatus='sent';
|
|
@@ -349,9 +351,8 @@ export async function splitArray( array ) {
|
|
|
349
351
|
}
|
|
350
352
|
|
|
351
353
|
export async function paginate( array, pageNumber, pageSize ) {
|
|
352
|
-
const startIndex = pageNumber
|
|
354
|
+
const startIndex = pageNumber;
|
|
353
355
|
const endIndex = startIndex + pageSize;
|
|
354
|
-
|
|
355
356
|
return array.slice( startIndex, endIndex );
|
|
356
357
|
}
|
|
357
358
|
|
|
@@ -360,6 +361,7 @@ export async function generateReport( req, res ) {
|
|
|
360
361
|
const sqs = JSON.parse( process.env.SQS );
|
|
361
362
|
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
362
363
|
const inputData = req.body;
|
|
364
|
+
const revopType = req.report?.reportConfigs?.reportType || 'automate';
|
|
363
365
|
const sqsProduceQueue = {
|
|
364
366
|
QueueUrl: `${sqs.url}${sqs.generateReport}`,
|
|
365
367
|
MessageBody: JSON.stringify( {
|
|
@@ -367,6 +369,7 @@ export async function generateReport( req, res ) {
|
|
|
367
369
|
client_name: req.report?.reportConfigs?.reportName,
|
|
368
370
|
client_id: Number( inputData.clientId ),
|
|
369
371
|
message: 'good to trigger',
|
|
372
|
+
reportType: revopType,
|
|
370
373
|
} ),
|
|
371
374
|
};
|
|
372
375
|
const sqsQueue = await sendMessageToQueue( sqsProduceQueue.QueueUrl, sqsProduceQueue.MessageBody );
|
|
@@ -381,16 +384,17 @@ export async function generateReport( req, res ) {
|
|
|
381
384
|
reportName: req.report?.reportConfigs?.reportName,
|
|
382
385
|
clientId: inputData.clientId,
|
|
383
386
|
sqsMessageId: sqsQueue.MessageId,
|
|
387
|
+
reportType: revopType,
|
|
384
388
|
},
|
|
385
389
|
createdAt: Date.now(),
|
|
386
390
|
};
|
|
387
391
|
logger.info( { auditLog: openSearch.auditLog, openSearch: openSearch } );
|
|
388
392
|
await insertOpenSearchData( openSearch.auditLog, log );
|
|
389
393
|
|
|
390
|
-
return res.sendSuccess( { result: '
|
|
394
|
+
return res.sendSuccess( { result: revopType !== 'manual'? 'The report has been initiated and will be sent automatically.':'The report has been initiated Successfully' } );
|
|
391
395
|
}
|
|
392
396
|
} catch ( error ) {
|
|
393
|
-
const err = error.
|
|
397
|
+
const err = error.message || 'Internal Server Error';
|
|
394
398
|
logger.error( { error: error, message: req.body, function: 'generateReport' } );
|
|
395
399
|
return res.sendError( err, 500 );
|
|
396
400
|
}
|
|
@@ -3,7 +3,7 @@ import express from 'express';
|
|
|
3
3
|
import { accessVerification, isAllowedSessionHandler, validate } from 'tango-app-api-middleware';
|
|
4
4
|
import { addReportValid, clientListTableValid, deleteReportValid, downloadReportValid, generateReportValid, getReportListValid, getReportLogValid, getReportValid, getSingleReportValid, sendReportValid, updateReportValid } from '../dtos/report.dtos.js';
|
|
5
5
|
import { clientReportList, createReport, deleteReport, dowloadReport, generateReport, getReport, getReportList, getReportLog, getSingleReport, sendReport, updateBasePrice, updateReport, uploadManualReport } from '../controllers/report.controllers.js';
|
|
6
|
-
import { isFileExist, isFolderExist, isReportEnable, isSendReportEnable } from '../validations/report.validations.js';
|
|
6
|
+
import { getQueueCount, isAuditCompleted, isFeatureRunning, isFileExist, isFolderExist, isReportEnable, isSendReportEnable } from '../validations/report.validations.js';
|
|
7
7
|
|
|
8
8
|
export const reportRouter = express.Router();
|
|
9
9
|
// report setting configurations
|
|
@@ -30,7 +30,7 @@ reportRouter.post( '/generate-report', isAllowedSessionHandler,
|
|
|
30
30
|
accessVerification( { userType: [ 'tango' ], access: [
|
|
31
31
|
{ featureName: 'TangoAdmin', name: 'Reports', permissions: [ 'isEdit' ] },
|
|
32
32
|
] } ),
|
|
33
|
-
validate( generateReportValid ), isReportEnable, generateReport );
|
|
33
|
+
validate( generateReportValid ), isReportEnable, getQueueCount, isAuditCompleted, isFeatureRunning, generateReport );
|
|
34
34
|
|
|
35
35
|
reportRouter.post( '/upload-manual-report', isAllowedSessionHandler,
|
|
36
36
|
accessVerification( { userType: [ 'tango' ], access: [
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { checkFileExist, logger } from 'tango-app-api-middleware';
|
|
2
2
|
import { findOneClient } from '../service/client.service.js';
|
|
3
|
+
import { aggregateStoreAudit } from '../service/storeAudit.service.js';
|
|
4
|
+
import { getQueueUrl, receiveMessage } from 'tango-app-api-middleware/src/utils/aws/sqs.js';
|
|
5
|
+
import axios from 'axios';
|
|
3
6
|
|
|
4
7
|
|
|
5
8
|
export async function isReportEnable( req, res, next ) {
|
|
6
9
|
try {
|
|
7
10
|
const inputData = req.method !== 'GET'? req.body: req.query;
|
|
8
|
-
const getReportName = await findOneClient( { 'clientId': inputData.clientId, 'status': { $in: [ 'active', 'hold' ] }, 'reportConfigs.report': true }, { '_id': 0, 'reportConfigs
|
|
11
|
+
const getReportName = await findOneClient( { 'clientId': inputData.clientId, 'status': { $in: [ 'active', 'hold' ] }, 'reportConfigs.report': true }, { '_id': 0, 'reportConfigs': 1, 'clientName': 1, 'auditConfigs': 1 } );
|
|
9
12
|
if ( !getReportName ) {
|
|
10
13
|
return res.sendError( 'This action was forbidden', 403 );
|
|
11
14
|
}
|
|
@@ -74,3 +77,211 @@ export async function isFileExist( req, res, next ) {
|
|
|
74
77
|
return res.sendError( error, 500 );
|
|
75
78
|
}
|
|
76
79
|
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
export async function isAuditCompleted( req, res, next ) {
|
|
83
|
+
try {
|
|
84
|
+
const inputData = req.body;
|
|
85
|
+
if ( req?.report?.reportConfigs?.reportType !== 'manual' ) {
|
|
86
|
+
logger.info( { inputData: inputData } );
|
|
87
|
+
|
|
88
|
+
const getStoreAuditQuery = [
|
|
89
|
+
{
|
|
90
|
+
$match: {
|
|
91
|
+
$and: [
|
|
92
|
+
{
|
|
93
|
+
fileDate: { $eq: inputData.fileDate },
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
clientId: { $eq: inputData.clientId },
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
status: { $ne: 'completed' },
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
$group: {
|
|
106
|
+
_id: null,
|
|
107
|
+
storeId: { $addToSet: '$storeId' },
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
$project: {
|
|
112
|
+
storeId: 1,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
const getStoreAudit = await aggregateStoreAudit( getStoreAuditQuery );
|
|
118
|
+
if ( getStoreAudit && getStoreAudit?.[0]?.storeId?.length >0 ) {
|
|
119
|
+
return res.sendError(
|
|
120
|
+
'Some files are not completed in the audit level for the selected date. Please try again later.',
|
|
121
|
+
400,
|
|
122
|
+
);
|
|
123
|
+
} else {
|
|
124
|
+
return next();
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
return next();
|
|
128
|
+
}
|
|
129
|
+
} catch ( error ) {
|
|
130
|
+
logger.error( { error: error, message: req.body, function: 'isAuditCompleted' } );
|
|
131
|
+
return res.sendError( error.message || 'Internal Server Error', 500 );
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// -----------------------------
|
|
135
|
+
// getQueueCount endpoint
|
|
136
|
+
// -----------------------------
|
|
137
|
+
export async function getQueueCount( req, res, next ) {
|
|
138
|
+
try {
|
|
139
|
+
if ( req?.report?.reportConfigs?.reportType !== 'manual' ) {
|
|
140
|
+
if ( !req?.report?.auditConfigs ) return next();
|
|
141
|
+
|
|
142
|
+
const temp = req.report.auditConfigs;
|
|
143
|
+
|
|
144
|
+
// Config flags
|
|
145
|
+
const auditConfig = {
|
|
146
|
+
track: temp.audit,
|
|
147
|
+
traffic: temp.trackAudit,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
// Mapping of config flags to queue-check functions
|
|
152
|
+
const queueChecks = {
|
|
153
|
+
track: [ () => checkTrackingQueueCount( temp?.trackQueueName ) ],
|
|
154
|
+
traffic: [
|
|
155
|
+
() => checkTrafficQueueCount( temp?.trafficQueueName ),
|
|
156
|
+
() => checkZoneQueueCount( temp?.zoneQueueName ),
|
|
157
|
+
],
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Run all enabled checks, stop on first true
|
|
161
|
+
const isProcessing = await runQueueChecks( auditConfig, queueChecks );
|
|
162
|
+
|
|
163
|
+
if ( isProcessing ) {
|
|
164
|
+
return res.sendError(
|
|
165
|
+
'This client is already being processed in the Queue. Please try again later.',
|
|
166
|
+
400,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return next();
|
|
171
|
+
} else {
|
|
172
|
+
return next();
|
|
173
|
+
}
|
|
174
|
+
} catch ( error ) {
|
|
175
|
+
logger.error( { error, message: req.body, function: 'getQueueCount' } );
|
|
176
|
+
return res.sendError( error.message || 'Internal Server Error', 500 );
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
// -----------------------------
|
|
182
|
+
// Run queue checks in parallel, stop on first true
|
|
183
|
+
// -----------------------------
|
|
184
|
+
async function runQueueChecks( config, queueChecks ) {
|
|
185
|
+
const tasks = Object.entries( queueChecks )
|
|
186
|
+
.filter( ( [ flag ] ) => config[flag] )
|
|
187
|
+
.flatMap( ( [ , funcs ] ) => funcs.map( ( fn ) => fn() ) );
|
|
188
|
+
|
|
189
|
+
if ( tasks.length === 0 ) return false;
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
const result = await Promise.any( tasks );
|
|
193
|
+
return result === true;
|
|
194
|
+
} catch {
|
|
195
|
+
return false; // all checks returned false
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// -----------------------------
|
|
200
|
+
// Helper: add timeout to promises
|
|
201
|
+
// -----------------------------
|
|
202
|
+
function withTimeout( promise, ms, fallback = false ) {
|
|
203
|
+
return Promise.race( [
|
|
204
|
+
promise,
|
|
205
|
+
new Promise( ( resolve ) => setTimeout( () => resolve( fallback ), ms ) ),
|
|
206
|
+
] );
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// -----------------------------
|
|
210
|
+
// Generic safe queue check
|
|
211
|
+
// -----------------------------
|
|
212
|
+
async function safeQueueCheck( queueName ) {
|
|
213
|
+
if ( !queueName ) return false;
|
|
214
|
+
|
|
215
|
+
const [ queue, count ] = await Promise.all( [
|
|
216
|
+
withTimeout( getQueueUrl( queueName ), 5000 ),
|
|
217
|
+
withTimeout( receiveMessage( queueName ), 5000 ),
|
|
218
|
+
] );
|
|
219
|
+
|
|
220
|
+
if ( queue?.statusCode ) return false;
|
|
221
|
+
|
|
222
|
+
return !!count && count !== 'null';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// -----------------------------
|
|
226
|
+
// Queue check functions
|
|
227
|
+
// -----------------------------
|
|
228
|
+
async function checkTrackingQueueCount( queueName ) {
|
|
229
|
+
return safeQueueCheck( queueName );
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function checkTrafficQueueCount( queueName ) {
|
|
233
|
+
return safeQueueCheck( queueName );
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function checkZoneQueueCount( queueName ) {
|
|
237
|
+
return safeQueueCheck( queueName );
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export async function isFeatureRunning( req, res, next ) {
|
|
241
|
+
try {
|
|
242
|
+
const inputData = req.body;
|
|
243
|
+
if ( req?.report?.reportConfigs?.reportType !== 'manual' ) {
|
|
244
|
+
const url = JSON.parse( process.env.URL );
|
|
245
|
+
const REIDURL = url.REIDStatus;
|
|
246
|
+
const FeatureURL = url.FeatureStatus;
|
|
247
|
+
const data = { 'client_id': inputData.clientId, 'store_date': inputData?.fileDate };
|
|
248
|
+
|
|
249
|
+
const isFeature = await callLambdaWithData( data, FeatureURL );
|
|
250
|
+
const isREID = await callLambdaWithData( data, REIDURL );
|
|
251
|
+
if ( isFeature?.status_code !== '200' ||isREID?.status_code !== '200' ) {
|
|
252
|
+
return res.sendError(
|
|
253
|
+
'This client is already being processed in REID/Feature layer for the selected date. Please try again later.',
|
|
254
|
+
400,
|
|
255
|
+
);
|
|
256
|
+
} else if ( isFeature?.data === false || isREID?.data === false ) {
|
|
257
|
+
return res.sendError(
|
|
258
|
+
'This client is already being processed in REID/Feature layer for the selected date. Please try again later.',
|
|
259
|
+
400,
|
|
260
|
+
);
|
|
261
|
+
} else {
|
|
262
|
+
return next();
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
return next();
|
|
266
|
+
}
|
|
267
|
+
} catch ( error ) {
|
|
268
|
+
logger.error( { error: error, message: req.query, function: 'isFeatureRunning' } );
|
|
269
|
+
return res.sendError( error.message || 'Iternal Server Error', 500 );
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
export const callLambdaWithData = async ( data, LAMBDA_URL ) => {
|
|
275
|
+
try {
|
|
276
|
+
const response = await axios.post( LAMBDA_URL, data, {
|
|
277
|
+
headers: {
|
|
278
|
+
'Content-Type': 'application/json',
|
|
279
|
+
},
|
|
280
|
+
} );
|
|
281
|
+
return response.data;
|
|
282
|
+
} catch ( error ) {
|
|
283
|
+
console.error( 'Error calling Lambda with data:', error );
|
|
284
|
+
throw error;
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|