tango-app-api-report 3.4.0-beta.5 → 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"
|
|
@@ -361,6 +361,7 @@ export async function generateReport( req, res ) {
|
|
|
361
361
|
const sqs = JSON.parse( process.env.SQS );
|
|
362
362
|
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
363
363
|
const inputData = req.body;
|
|
364
|
+
const revopType = req.report?.reportConfigs?.reportType || 'automate';
|
|
364
365
|
const sqsProduceQueue = {
|
|
365
366
|
QueueUrl: `${sqs.url}${sqs.generateReport}`,
|
|
366
367
|
MessageBody: JSON.stringify( {
|
|
@@ -368,6 +369,7 @@ export async function generateReport( req, res ) {
|
|
|
368
369
|
client_name: req.report?.reportConfigs?.reportName,
|
|
369
370
|
client_id: Number( inputData.clientId ),
|
|
370
371
|
message: 'good to trigger',
|
|
372
|
+
reportType: revopType,
|
|
371
373
|
} ),
|
|
372
374
|
};
|
|
373
375
|
const sqsQueue = await sendMessageToQueue( sqsProduceQueue.QueueUrl, sqsProduceQueue.MessageBody );
|
|
@@ -382,16 +384,17 @@ export async function generateReport( req, res ) {
|
|
|
382
384
|
reportName: req.report?.reportConfigs?.reportName,
|
|
383
385
|
clientId: inputData.clientId,
|
|
384
386
|
sqsMessageId: sqsQueue.MessageId,
|
|
387
|
+
reportType: revopType,
|
|
385
388
|
},
|
|
386
389
|
createdAt: Date.now(),
|
|
387
390
|
};
|
|
388
391
|
logger.info( { auditLog: openSearch.auditLog, openSearch: openSearch } );
|
|
389
392
|
await insertOpenSearchData( openSearch.auditLog, log );
|
|
390
393
|
|
|
391
|
-
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' } );
|
|
392
395
|
}
|
|
393
396
|
} catch ( error ) {
|
|
394
|
-
const err = error.
|
|
397
|
+
const err = error.message || 'Internal Server Error';
|
|
395
398
|
logger.error( { error: error, message: req.body, function: 'generateReport' } );
|
|
396
399
|
return res.sendError( err, 500 );
|
|
397
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
|
+
|