tango-app-api-analysis-traffic 3.8.18 → 3.8.20
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-analysis-traffic",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.20",
|
|
4
4
|
"description": "Traffic Analysis",
|
|
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"
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"mongodb": "^6.8.0",
|
|
24
24
|
"nodemon": "^3.1.4",
|
|
25
25
|
"swagger-ui-express": "^5.0.1",
|
|
26
|
-
"tango-api-schema": "^2.
|
|
26
|
+
"tango-api-schema": "^2.6.32",
|
|
27
27
|
"tango-app-api-middleware": "^3.6.5",
|
|
28
28
|
"winston": "^3.13.1",
|
|
29
29
|
"winston-daily-rotate-file": "^5.0.0"
|
|
@@ -3,7 +3,7 @@ import * as clientService from '../services/clients.services.js';
|
|
|
3
3
|
import {
|
|
4
4
|
findOneStore,
|
|
5
5
|
} from '../services/stores.service.js';
|
|
6
|
-
|
|
6
|
+
import { findOneUserAssignedStore } from '../services/userAssignedStore.service.js';
|
|
7
7
|
|
|
8
8
|
async function LamdaServiceCall( url, data ) {
|
|
9
9
|
try {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { logger, insertOpenSearchData, getOpenSearchData, updateOpenSearchData } from 'tango-app-api-middleware';
|
|
1
|
+
import { logger, insertOpenSearchData, getOpenSearchData, updateOpenSearchData, download } from 'tango-app-api-middleware';
|
|
2
2
|
import { findOnerevopConfig } from '../services/revopConfig.service.js';
|
|
3
3
|
import * as clientService from '../services/clients.services.js';
|
|
4
4
|
import { bulkUpdate, insertWithId, scrollResponse, searchOpenSearchData, upsertWithScript } from 'tango-app-api-middleware/src/utils/openSearch.js';
|
|
5
5
|
import { findOneVmsStoreRequest } from '../services/vmsStoreRequest.service.js';
|
|
6
|
+
import { aggregateUserAssignedStore } from '../services/userAssignedStore.service.js';
|
|
6
7
|
import dayjs from 'dayjs';
|
|
7
8
|
// Lamda Service Call //
|
|
8
9
|
async function LamdaServiceCall( url, data ) {
|
|
@@ -1011,6 +1012,133 @@ export async function revoptaggingcount( req, res ) {
|
|
|
1011
1012
|
}
|
|
1012
1013
|
}
|
|
1013
1014
|
|
|
1015
|
+
export async function exportRevopTagging( req, res ) {
|
|
1016
|
+
try {
|
|
1017
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
1018
|
+
const { storeId, fromDate, toDate, exportType } = req.body;
|
|
1019
|
+
|
|
1020
|
+
// Access control (clientId is derived from storeId.split('-')[0]):
|
|
1021
|
+
// - tango superadmin: full access to any store.
|
|
1022
|
+
// - other tango users: restricted to their assigned clients (userAssignedStore: assignedType 'client').
|
|
1023
|
+
// - client superadmin: access to all stores under their own client.
|
|
1024
|
+
// - restricted client users: only their assigned stores (getAssinedStore populates req.body.assignedStores).
|
|
1025
|
+
const storeClientId = `${storeId}`.split( '-' )[0];
|
|
1026
|
+
if ( req.user.userType === 'tango' ) {
|
|
1027
|
+
if ( req.user.role !== 'superadmin' ) {
|
|
1028
|
+
const assignedClients = await aggregateUserAssignedStore( [
|
|
1029
|
+
{ $match: { userEmail: req.user.email, assignedType: 'client', userType: 'tango' } },
|
|
1030
|
+
{ $project: { _id: 0, assignedValue: 1 } },
|
|
1031
|
+
] );
|
|
1032
|
+
const allowedClientIds = ( assignedClients || [] ).map( ( a ) => `${a.assignedValue}` );
|
|
1033
|
+
if ( !allowedClientIds.includes( String( storeClientId ) ) ) {
|
|
1034
|
+
return res.sendError( 'Store Not Assigned', 403 );
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
} else if ( req.user.role === 'superadmin' ) {
|
|
1038
|
+
if ( String( storeClientId ) !== String( req.user.clientId ) ) {
|
|
1039
|
+
return res.sendError( 'Store Not Assigned', 403 );
|
|
1040
|
+
}
|
|
1041
|
+
} else {
|
|
1042
|
+
const assignedStores = req.body.assignedStores;
|
|
1043
|
+
if ( !assignedStores || assignedStores.length === 0 || !assignedStores.includes( storeId ) ) {
|
|
1044
|
+
return res.sendError( 'Store Not Assigned', 403 );
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
const query = {
|
|
1049
|
+
size: 1000,
|
|
1050
|
+
sort: [ { createdAt: { order: 'asc' } } ],
|
|
1051
|
+
query: {
|
|
1052
|
+
bool: {
|
|
1053
|
+
must: [
|
|
1054
|
+
{ term: { 'storeId.keyword': storeId } },
|
|
1055
|
+
{ range: { dateString: { gte: fromDate, lte: toDate } } },
|
|
1056
|
+
],
|
|
1057
|
+
},
|
|
1058
|
+
},
|
|
1059
|
+
};
|
|
1060
|
+
|
|
1061
|
+
// Scroll through all matching documents for the store + date range
|
|
1062
|
+
const allHits = [];
|
|
1063
|
+
const firstResponse = await searchOpenSearchData( openSearch.revop, query );
|
|
1064
|
+
logger.info( { firstResponse: firstResponse } );
|
|
1065
|
+
let hitsBatch = firstResponse?.body?.hits?.hits || [];
|
|
1066
|
+
let scrollId = firstResponse?.body?._scroll_id;
|
|
1067
|
+
while ( hitsBatch.length > 0 ) {
|
|
1068
|
+
allHits.push( ...hitsBatch );
|
|
1069
|
+
const nextRes = await scrollResponse( scrollId );
|
|
1070
|
+
hitsBatch = nextRes?.body?.hits?.hits || [];
|
|
1071
|
+
scrollId = nextRes?.body?._scroll_id;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
const exportData = [];
|
|
1075
|
+
logger.info( { allHits: allHits } );
|
|
1076
|
+
for ( const hit of allHits ) {
|
|
1077
|
+
const src = hit._source || {};
|
|
1078
|
+
const dateValue = src.dateRange || src.dateString;
|
|
1079
|
+
const base = {
|
|
1080
|
+
'Date': dateValue ? dayjs( dateValue ).format( 'DD-MM-YYYY' ) : '',
|
|
1081
|
+
'Timestamp': src.customerInfo?.entryTime || '',
|
|
1082
|
+
'Customer ID': src.tempId ?? '',
|
|
1083
|
+
};
|
|
1084
|
+
|
|
1085
|
+
if ( exportType === 'purchaser' ) {
|
|
1086
|
+
// only export records tagged as purchaser
|
|
1087
|
+
if ( `${src.revopsType || ''}`.toLowerCase() !== 'purchaser' ) {
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
const customer = src.customerInfo || {};
|
|
1091
|
+
const contactNumber = [ customer.countryCode, customer.mobile ].filter( Boolean ).join( ' ' );
|
|
1092
|
+
exportData.push( {
|
|
1093
|
+
...base,
|
|
1094
|
+
'Name': customer.name || '',
|
|
1095
|
+
'Contact Number': contactNumber,
|
|
1096
|
+
'Email': customer.email || '',
|
|
1097
|
+
'location': customer.location || '',
|
|
1098
|
+
'Age': customer.age ?? '',
|
|
1099
|
+
'Gender': customer.gender || '',
|
|
1100
|
+
} );
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// summary
|
|
1105
|
+
const revopsType = src.revopsType || '';
|
|
1106
|
+
const typeLower = `${revopsType}`.toLowerCase();
|
|
1107
|
+
|
|
1108
|
+
if ( typeLower === 'missedopportunity' && Array.isArray( src.issueList ) && src.issueList.length > 0 ) {
|
|
1109
|
+
// one row per issueList entry: name -> Tagged Category, issue[] -> Tagged Sub Category
|
|
1110
|
+
for ( const issueItem of src.issueList ) {
|
|
1111
|
+
const subCategory = Array.isArray( issueItem?.issue ) ? issueItem.issue.join( ', ' ) : ( issueItem?.issue || '' );
|
|
1112
|
+
exportData.push( {
|
|
1113
|
+
...base,
|
|
1114
|
+
'Tag Type': revopsType,
|
|
1115
|
+
'Tagged Category': issueItem?.name || '',
|
|
1116
|
+
'Tagged Sub Category': subCategory,
|
|
1117
|
+
'Description': src.description || '',
|
|
1118
|
+
} );
|
|
1119
|
+
}
|
|
1120
|
+
} else {
|
|
1121
|
+
exportData.push( {
|
|
1122
|
+
...base,
|
|
1123
|
+
'Tag Type': revopsType,
|
|
1124
|
+
'Tagged Category': typeLower === 'nonshopper' ? ( src.nonshoperType || '' ) : '',
|
|
1125
|
+
'Tagged Sub Category': '',
|
|
1126
|
+
'Description': src.description || '',
|
|
1127
|
+
} );
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
logger.info( { exportDataLength: exportData } );
|
|
1131
|
+
if ( exportData.length === 0 ) {
|
|
1132
|
+
return res.sendError( 'No data found for the given criteria', 204 );
|
|
1133
|
+
}
|
|
1134
|
+
await download( exportData, res );
|
|
1135
|
+
return;
|
|
1136
|
+
} catch ( error ) {
|
|
1137
|
+
logger.error( { error: error, message: req.body, function: 'exportRevopTagging' } );
|
|
1138
|
+
return res.sendError( error.message || 'Internal Server Error', 500 );
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1014
1142
|
export async function storeProcessedData( req, res ) {
|
|
1015
1143
|
try {
|
|
1016
1144
|
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
package/src/dtos/revop.dtos.js
CHANGED
|
@@ -107,3 +107,25 @@ export const vmsDataMigrationValid = {
|
|
|
107
107
|
query: vmsDataMigrationSchema,
|
|
108
108
|
};
|
|
109
109
|
|
|
110
|
+
const dateValidator = ( value, helpers ) => {
|
|
111
|
+
const inputDate = dayjs( value, 'YYYY-MM-DD', true );
|
|
112
|
+
|
|
113
|
+
if ( !inputDate.isValid() ) {
|
|
114
|
+
return helpers.error( 'any.invalid' );
|
|
115
|
+
}
|
|
116
|
+
return value;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export const exportRevopTaggingSchema = joi.object( {
|
|
120
|
+
storeId: joi.string().required(),
|
|
121
|
+
fromDate: joi.string().required().custom( dateValidator ), // yyyy-mm-dd
|
|
122
|
+
toDate: joi.string().required().custom( dateValidator ), // yyyy-mm-dd
|
|
123
|
+
exportType: joi.string().valid( 'summary', 'purchaser' ).required().messages( {
|
|
124
|
+
'any.only': 'exportType must be one of [summary, purchaser]',
|
|
125
|
+
} ),
|
|
126
|
+
} );
|
|
127
|
+
|
|
128
|
+
export const exportRevopTaggingValid = {
|
|
129
|
+
body: exportRevopTaggingSchema,
|
|
130
|
+
};
|
|
131
|
+
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
import express from 'express';
|
|
3
|
-
import { storeProcessedData, getconfig, revoptagging, getrevoptagging, revoptaggingcount, footFallImages, tagTempId, getCategorizedImages, vmsDataMigration, migrateRevopIndex, expireReviewStatus, expireApproveStatus } from '../controllers/revop.controller.js';
|
|
4
|
-
import { isAllowedSessionHandler, validate } from 'tango-app-api-middleware';
|
|
5
|
-
import { footfallImagesValid, getCategorizedImagesValid, storeProcessedDataValid, tagTempIdValid, vmsDataMigrationValid } from '../dtos/revop.dtos.js';
|
|
3
|
+
import { storeProcessedData, getconfig, revoptagging, getrevoptagging, revoptaggingcount, footFallImages, tagTempId, getCategorizedImages, vmsDataMigration, migrateRevopIndex, expireReviewStatus, expireApproveStatus, exportRevopTagging } from '../controllers/revop.controller.js';
|
|
4
|
+
import { isAllowedSessionHandler, validate, getAssinedStore } from 'tango-app-api-middleware';
|
|
5
|
+
import { exportRevopTaggingValid, footfallImagesValid, getCategorizedImagesValid, storeProcessedDataValid, tagTempIdValid, vmsDataMigrationValid } from '../dtos/revop.dtos.js';
|
|
6
6
|
import { deleteTaggedDuplicate, getTaggingConfig, mappingConfig, validateDateAndFilePath } from '../validations/revop.validation.js';
|
|
7
7
|
|
|
8
8
|
|
|
@@ -16,6 +16,7 @@ revopRouter
|
|
|
16
16
|
.post( '/expire-review-status', expireReviewStatus )
|
|
17
17
|
.post( '/expire-approve-status', expireApproveStatus )
|
|
18
18
|
.post( '/revoptaggingcount', isAllowedSessionHandler, revoptaggingcount )
|
|
19
|
+
.post( '/export-revoptagging', isAllowedSessionHandler, validate( exportRevopTaggingValid ), getAssinedStore, exportRevopTagging )
|
|
19
20
|
|
|
20
21
|
// new enhancement (for footfall directory)
|
|
21
22
|
.get( '/store-processed-data', isAllowedSessionHandler, validate( storeProcessedDataValid ), storeProcessedData )
|