tango-app-api-client 3.4.3-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.
@@ -1,5 +1,5 @@
1
1
  import { billingDetailsUpdate, brandInfoUpdate, domainDetailsConfigurationUpdate, featureConfigurationUpdate, getClientData, signatoryDetailsUpdate, ticketConfigurationUpdate, documentsUpdate, getUserData, CsmUsersGet, OpsUsersGet, userConfigurationUpdate, findClient, aggregateClient, createAuditQueue, findOne, insert, update, findOneClient, updateOneClient } from '../service/client.service.js';
2
- import { checkFileExist, fileUpload, signedUrl, chunkArray, download, logger, getOpenSearchData, insertOpenSearchData, sendEmailWithSES, createCustomer, createVirtualAccount } from 'tango-app-api-middleware';
2
+ import { checkFileExist, signedUrl, chunkArray, download, logger, getOpenSearchData, insertOpenSearchData, sendEmailWithSES, createCustomer, createVirtualAccount, getUuid, fileRestrictedUpload, getXsscheck } from 'tango-app-api-middleware';
3
3
  import { countDocumentsUser, findOneAndUpdateUser, findOneUser, getUserNameEmailById, updateManyUser } from '../service/user.service.js';
4
4
  import { aggregateStore, countDocumentsStore, findStore, updateManyStore } from '../service/store.service.js';
5
5
  import { aggregateCamera, countDocumentsCamera } from '../service/camera.service.js';
@@ -10,16 +10,18 @@ import { aggregateTickets } from '../service/tangoticket.service.js';
10
10
  import { join } from 'path';
11
11
  import { readFileSync } from 'fs';
12
12
  import handlebars from 'handlebars';
13
- import { countDocumentsGroup, createGroupModel, findOneGroup } from '../service/group.service.js';
13
+ import { countDocumentsGroup, createGroupModel } from '../service/group.service.js';
14
14
  // import { deleteOneAuthentication } from '../service/authentication.service.js';
15
15
  import { createBilling } from '../service/billing.service.js';
16
16
  import { createPaymentAccount } from '../service/paymentAccount.service.js';
17
17
  import { countDocumentsClusters, createclusterModel } from '../service/cluster.service.js';
18
18
  import { countDocumentsTeams } from '../service/teams.service.js';
19
+ import { createauditConfig, updateauditConfig, aggregateAuditconfig } from '../service/auditConfig.service.js';
20
+ import { findOnerevopConfig, createrevopConfig, updaterevopConfig } from '../service/revopConfig.service.js';
19
21
 
20
22
  export async function create( req, res ) {
21
23
  try {
22
- const url = JSON.parse( process.env.URL );
24
+ // const url = JSON.parse( process.env.URL );
23
25
  const openSearch = JSON.parse( process.env.OPENSEARCH );
24
26
  const inputData = req.body;
25
27
  const countQuery = [
@@ -37,7 +39,6 @@ export async function create( req, res ) {
37
39
  ];
38
40
  const ID = await aggregateClient( countQuery );
39
41
  const count = ID[0]?.tangoId || 0;
40
- logger.info( { count: count } );
41
42
  const query = { clientName: inputData.clientName };
42
43
  const leadRecord = await findOne( query );
43
44
  const tangoId = count + 1;
@@ -71,14 +72,19 @@ export async function create( req, res ) {
71
72
  'planDetails.product': product,
72
73
  'profileDetails.website': leadRecord?.websiteUrl,
73
74
  'reportConfigs.reportName': generatedName,
74
- 'auditConfigs.queueName': generatedName,
75
75
  'auditConfigs.zoneQueueName': `${generatedName}-zone`,
76
76
  'auditConfigs.trafficQueueName': `${generatedName}-traffic`,
77
+ 'auditConfigs.trackQueueName': `${generatedName}-track`,
77
78
  'auditConfigs.traxQueueName.unattendedCustomer': `${generatedName}-unattendedCustomer`,
78
79
  'auditConfigs.traxQueueName.leftInMiddle': `${generatedName}-leftInMiddle`,
79
80
  'auditConfigs.traxQueueName.uniformDetection': `${generatedName}-uniformDetection`,
80
81
  'auditConfigs.traxQueueName.mobileDetection': `${generatedName}-mobileDetection`,
81
82
  'auditConfigs.traxQueueName.cameraAngleChange': `${generatedName}-cameraAngleChange`,
83
+ 'auditConfigs.traxQueueName.hygiene': `${generatedName}-hygiene`,
84
+ 'clientApi.apiKey': await getUuid(),
85
+ 'clientApi.status': true,
86
+ 'clientApi.allowedIps': [ '*' ],
87
+
82
88
  };
83
89
 
84
90
  record.featureConfigs = {};
@@ -94,7 +100,7 @@ export async function create( req, res ) {
94
100
  record.featureConfigs.isNewZone = true;
95
101
  }
96
102
  if ( data.productName === 'tangoTrax' ) {
97
- record.featureConfigs. isTrax= true;
103
+ record.featureConfigs.isTrax = true;
98
104
  }
99
105
  }
100
106
  }
@@ -140,7 +146,23 @@ export async function create( req, res ) {
140
146
  'isDefaults': true,
141
147
  };
142
148
 
143
- await createclusterModel( defaultcluster );
149
+ const cluster = await createclusterModel( defaultcluster );
150
+
151
+ const logClusterObj = {
152
+ clientId: insertedClientRecord.clientId,
153
+ userName: req.user?.userName,
154
+ userId: req?.user?._id,
155
+ email: req.user?.email,
156
+ clusterId: cluster._id,
157
+ date: new Date(),
158
+ logType: 'cluster',
159
+ logSubType: 'clusterCreated',
160
+ changes: [ 'All stores' ],
161
+ eventType: 'create',
162
+ showTo: [ 'client', 'tango' ],
163
+ };
164
+
165
+ await insertOpenSearchData( openSearch.activityLog, logClusterObj );
144
166
 
145
167
 
146
168
  const defaultGroup = {
@@ -160,128 +182,122 @@ export async function create( req, res ) {
160
182
 
161
183
  await createBilling( primaryBillingGroup );
162
184
 
163
- const createdGroup = await findOneGroup( { clientId: insertedClientRecord.clientId, isDefault: true }, {} );
164
-
165
- let oldGroup = {
166
- '_id': createdGroup._id,
167
- 'client_id': createdGroup.clientId,
168
- 'groupName': createdGroup.groupName,
169
- 'description': createdGroup.description,
170
- 'storeList': createdGroup.storeList,
171
- };
172
-
173
-
174
- await postApi( `${url.oldapidomain}/oldGroupAdd`, [ oldGroup ] );
175
-
176
185
  // For old dashboard insert
186
+ // const createdGroup = await findOneGroup( { clientId: insertedClientRecord.clientId, isDefault: true }, {} );
187
+ // let oldGroup = {
188
+ // '_id': createdGroup._id,
189
+ // 'client_id': createdGroup.clientId,
190
+ // 'groupName': createdGroup.groupName,
191
+ // 'description': createdGroup.description,
192
+ // 'storeList': createdGroup.storeList,
193
+ // };
194
+ // await postApi( `${url.oldapidomain}/oldGroupAdd`, [ oldGroup ] );
195
+
196
+ // const oldBrandInsertData = {
197
+ // '_id': insertedClientRecord._id,
198
+ // 'brandName': insertedClientRecord.clientName,
199
+ // 'brandIndex': insertedClientRecord.tangoId,
200
+ // 'client_id': insertedClientRecord.clientId,
201
+ // 'phone': user.mobileNumber,
202
+ // 'email': user.email,
203
+ // 'dialCode': user.countryCode,
204
+ // 'name': user.userName,
205
+ // 'cameraCount': insertedClientRecord.planDetails.totalCamera,
206
+ // 'apiKey': insertedClientRecord.clientApi.apiKey,
207
+ // 'clientStatus': insertedClientRecord.status,
208
+ // 'client_type': insertedClientRecord.planDetails.paymentStatus,
209
+ // 'activeMenu': [
210
+ // 'reports',
211
+ // 'birdsEye',
212
+ // 'tangoZone',
213
+ // 'tangoTraffic',
214
+ // 'support',
215
+ // ],
216
+ // 'planType': insertedClientRecord.planDetails.subscriptionPeriod,
217
+ // 'storeCount': insertedClientRecord.planDetails.totalStores,
218
+ // 'squareFeet': insertedClientRecord.planDetails.storeSize,
219
+ // 'subscription': insertedClientRecord.planDetails.subscriptionType,
220
+ // 'subscription_status': true,
221
+ // 'subscribed_features': {
222
+ // 'tango_traffic': false,
223
+ // 'tango_zone': false,
224
+ // 'tango_revop': false,
225
+ // 'tango_storeops': false,
226
+ // 'controlroom': false,
227
+ // 'live_data': false,
228
+ // 'normalization_features': false,
229
+ // 'actual_features': false,
230
+ // },
231
+ // 'store_added_status': true,
232
+ // 'terms_conditions': true,
233
+ // 'otp_verified': true,
234
+ // 'price': 6510,
235
+ // 'currency_type': 'rupees',
236
+ // 'domains': [],
237
+ // 'store_radius_config': 500,
238
+ // };
239
+
240
+ // insertedClientRecord.planDetails.product.forEach( ( element ) => {
241
+ // if ( element.productName === 'tangoTraffic' ) {
242
+ // oldBrandInsertData.subscribed_features.tango_traffic = true;
243
+ // }
244
+ // if ( element.productName === 'tangoZone' ) {
245
+ // oldBrandInsertData.subscribed_features.tango_zone = true;
246
+ // }
247
+ // if ( element.productName === 'prioritySupport' ) {
248
+ // oldBrandInsertData.subscribed_features.tango_storeops = true;
249
+ // }
250
+ // } );
251
+
252
+ // switch ( insertedClientRecord.status ) {
253
+ // case 'active':
254
+ // oldBrandInsertData.clientStatus = 'live';
255
+ // break;
256
+ // case 'hold':
257
+ // oldBrandInsertData.clientStatus = 'hold';
258
+ // break;
259
+ // case 'suspended':
260
+ // oldBrandInsertData.clientStatus = 'suspended';
261
+ // break;
262
+ // case 'deactive':
263
+ // oldBrandInsertData.clientStatus = 'deactivated';
264
+ // break;
265
+ // default:
266
+ // oldBrandInsertData.clientStatus = 'live';
267
+ // }
268
+
269
+ // switch ( insertedClientRecord.planDetails.paymentStatus ) {
270
+ // case 'free':
271
+ // oldBrandInsertData.client_type = 'free';
272
+ // break;
273
+ // case 'trial':
274
+ // oldBrandInsertData.client_type = 'trial';
275
+ // break;
276
+ // case 'paid':
277
+ // oldBrandInsertData.client_type = 'paid';
278
+ // break;
279
+ // default:
280
+ // oldBrandInsertData.client_type = 'trial';
281
+ // }
282
+ // await postApi( `${url.oldapidomain}/oldBrandAdd`, oldBrandInsertData );
283
+
284
+ // const oldDefaultRolesInsertData = {
285
+ // '_id': user._id,
286
+ // 'client_id': insertedClientRecord.clientId,
287
+ // 'brandId': insertedClientRecord._id,
288
+ // 'name': user.userName,
289
+ // 'email': user.email,
290
+ // 'phone': user.mobileNumber,
291
+ // 'password': user.password,
292
+ // 'role': 'storesuperadmin',
293
+ // };
294
+ // await postApi( `${url.oldapidomain}/oldDefaultRoleInsert`, oldDefaultRolesInsertData );
177
295
 
178
296
 
179
- const oldBrandInsertData = {
180
- '_id': insertedClientRecord._id,
181
- 'brandName': insertedClientRecord.clientName,
182
- 'brandIndex': insertedClientRecord.tangoId,
183
- 'client_id': insertedClientRecord.clientId,
184
- 'phone': user.mobileNumber,
185
- 'email': user.email,
186
- 'dialCode': user.countryCode,
187
- 'name': user.userName,
188
- 'cameraCount': insertedClientRecord.planDetails.totalCamera,
189
- 'apiKey': insertedClientRecord.clientApi.apiKey,
190
- 'clientStatus': insertedClientRecord.status,
191
- 'client_type': insertedClientRecord.planDetails.paymentStatus,
192
- 'activeMenu': [
193
- 'reports',
194
- 'birdsEye',
195
- 'tangoZone',
196
- 'tangoTraffic',
197
- 'support',
198
- ],
199
- 'planType': insertedClientRecord.planDetails.subscriptionPeriod,
200
- 'storeCount': insertedClientRecord.planDetails.totalStores,
201
- 'squareFeet': insertedClientRecord.planDetails.storeSize,
202
- 'subscription': insertedClientRecord.planDetails.subscriptionType,
203
- 'subscription_status': true,
204
- 'subscribed_features': {
205
- 'tango_traffic': false,
206
- 'tango_zone': false,
207
- 'tango_revop': false,
208
- 'tango_storeops': false,
209
- 'controlroom': false,
210
- 'live_data': false,
211
- 'normalization_features': false,
212
- 'actual_features': false,
213
- },
214
- 'store_added_status': true,
215
- 'terms_conditions': true,
216
- 'otp_verified': true,
217
- 'price': 6510,
218
- 'currency_type': 'rupees',
219
- 'domains': [],
220
- 'store_radius_config': 500,
221
- };
222
-
223
- insertedClientRecord.planDetails.product.forEach( ( element ) => {
224
- if ( element.productName === 'tangoTraffic' ) {
225
- oldBrandInsertData.subscribed_features.tango_traffic = true;
226
- }
227
- if ( element.productName === 'tangoZone' ) {
228
- oldBrandInsertData.subscribed_features.tango_zone = true;
229
- }
230
- if ( element.productName === 'prioritySupport' ) {
231
- oldBrandInsertData.subscribed_features.tango_storeops = true;
232
- }
233
- } );
234
-
235
- switch ( insertedClientRecord.status ) {
236
- case 'active':
237
- oldBrandInsertData.clientStatus = 'live';
238
- break;
239
- case 'hold':
240
- oldBrandInsertData.clientStatus = 'hold';
241
- break;
242
- case 'suspended':
243
- oldBrandInsertData.clientStatus = 'suspended';
244
- break;
245
- case 'deactive':
246
- oldBrandInsertData.clientStatus = 'deactivated';
247
- break;
248
- default:
249
- oldBrandInsertData.clientStatus = 'live';
250
- }
251
-
252
- switch ( insertedClientRecord.planDetails.paymentStatus ) {
253
- case 'free':
254
- oldBrandInsertData.client_type = 'free';
255
- break;
256
- case 'trial':
257
- oldBrandInsertData.client_type = 'trial';
258
- break;
259
- case 'paid':
260
- oldBrandInsertData.client_type = 'paid';
261
- break;
262
- default:
263
- oldBrandInsertData.client_type = 'trial';
264
- }
265
-
266
-
267
- const oldDefaultRolesInsertData = {
268
- '_id': user._id,
269
- 'client_id': insertedClientRecord.clientId,
270
- 'brandId': insertedClientRecord._id,
271
- 'name': user.userName,
272
- 'email': user.email,
273
- 'phone': user.mobileNumber,
274
- 'password': user.password,
275
- 'role': 'storesuperadmin',
276
- };
277
-
278
- await postApi( `${url.oldapidomain}/oldBrandAdd`, oldBrandInsertData );
279
-
280
- await postApi( `${url.oldapidomain}/oldDefaultRoleInsert`, oldDefaultRolesInsertData );
281
-
282
297
  const logObj = {
283
298
  clientId: insertedClientRecord.clientId,
284
299
  userName: req.user?.userName,
300
+ userId: req?.user?._id,
285
301
  email: req.user?.email,
286
302
  date: new Date(),
287
303
  logType: 'brandDetails',
@@ -330,15 +346,15 @@ export async function create( req, res ) {
330
346
  // await deleteOneAuthentication( { user: user?._id, type: 'retail' } );
331
347
  await findOneAndUpdateUser( userQuery, userRecord );
332
348
  await Promise.all( [
333
- createAuditQueue( generatedName ),
334
349
  createAuditQueue( `${generatedName}-zone` ),
335
350
  createAuditQueue( `${generatedName}-traffic` ),
351
+ createAuditQueue( `${generatedName}-track` ),
336
352
  createAuditQueue( `${generatedName}-unattendedCustomer` ),
337
353
  createAuditQueue( `${generatedName}-leftInMiddle` ),
338
354
  createAuditQueue( `${generatedName}-uniformDetection` ),
339
355
  createAuditQueue( `${generatedName}-mobileDetection` ),
340
356
  createAuditQueue( `${generatedName}-cameraAngleChange` ),
341
- ] );
357
+ createAuditQueue( `${generatedName}-hygiene` ) ] );
342
358
  return res.sendSuccess( { result: { clientId: String( tangoId ) } } );
343
359
  }
344
360
  } catch ( error ) {
@@ -395,7 +411,7 @@ export async function changeStatus( req, res, next ) {
395
411
 
396
412
  export async function getClients( req, res ) {
397
413
  try {
398
- const field = { clientName: 1, clientId: 1 };
414
+ const field = { clientName: 1, clientId: 1, traxDateRange: '$featureConfigs.traxDateRange', trafficDateRange: '$featureConfigs.trafficDateRange' };
399
415
  let result = [];
400
416
  if ( req?.user?.role === 'superadmin' ) {
401
417
  result = await findClient( {}, field );
@@ -405,18 +421,6 @@ export async function getClients( req, res ) {
405
421
  $match: {
406
422
  userEmail: req?.user?.email,
407
423
  assignedType: { $eq: 'client' },
408
- // $expr: {
409
- // $cond: {
410
- // if: {
411
- // $and: [
412
- // { $eq: [ '$userType', 'tango' ] },
413
- // { $eq: [ '$tangoUserType', 'csm' ] },
414
- // ],
415
- // },
416
- // then: { $eq: [ '$isClientApproved', true ] },
417
- // else: true,
418
- // },
419
- // },
420
424
  },
421
425
  },
422
426
  {
@@ -450,9 +454,9 @@ export async function getClients( req, res ) {
450
454
  },
451
455
  {
452
456
  $project: {
453
- _id: 0,
454
457
  clientId: 1,
455
458
  clientName: 1,
459
+ traxDateRange: '$featureConfigs.traxDateRange',
456
460
  },
457
461
  },
458
462
  ];
@@ -672,7 +676,7 @@ export async function detailedAllClientCount( req, res ) {
672
676
  if ( result.length == 0 ) {
673
677
  return res.sendError( 'No Data Found', 204 );
674
678
  }
675
- result[0].activeStoresCount = activeStores.length>0?Math.abs( activeStores.length - yettoInstallCount.length ):0;
679
+ result[0].activeStoresCount = activeStores.length > 0 ? Math.abs( activeStores.length - yettoInstallCount.length ) : 0;
676
680
  result[0].activeCameraCount = activeCameras;
677
681
  return res.sendSuccess( { result: result } );
678
682
  } catch ( error ) {
@@ -691,9 +695,8 @@ export async function updateBrandInfo( req, res ) {
691
695
  try {
692
696
  const bucket = JSON.parse( process.env.BUCKET );
693
697
  const openSearch = JSON.parse( process.env.OPENSEARCH );
694
- const url = JSON.parse( process.env.URL );
698
+ // const url = JSON.parse( process.env.URL );
695
699
  let updateKeys = [];
696
-
697
700
  if ( req.files?.logo ) {
698
701
  const uploadDataParams = {
699
702
  Bucket: bucket.assets,
@@ -702,8 +705,12 @@ export async function updateBrandInfo( req, res ) {
702
705
  ContentType: req.files.logo.mimetype,
703
706
  body: req.files.logo.data,
704
707
  };
705
- updateKeys.push( 'Logo' );
706
- await fileUpload( uploadDataParams );
708
+ updateKeys.push( 'Brand Logo' );
709
+ let uploadStatus= await fileRestrictedUpload( uploadDataParams );
710
+
711
+ if ( uploadStatus==false ) {
712
+ return res.sendError( 'Unsupported Media Type', 415 );
713
+ }
707
714
  }
708
715
 
709
716
  if ( Object.keys( req.body ).length > 0 ) {
@@ -712,32 +719,93 @@ export async function updateBrandInfo( req, res ) {
712
719
  } );
713
720
  }
714
721
 
715
- const user = await getUserNameEmailById( req.userId );
722
+ // Get updated client information before the update operation (fetch selected fields only)
723
+ const getPreCientInfo = await findOneClient(
724
+ { clientId: req.params.id }, // Filter by clientId
725
+ {
726
+ '_id': 0,
727
+ 'profileDetails.registeredCompanyName': 1,
728
+ 'profileDetails.industry': 1,
729
+ 'profileDetails.clientType': 1,
730
+ 'profileDetails.registeredAddress': 1,
731
+ 'profileDetails.headQuarters': 1,
732
+ 'profileDetails.website': 1,
733
+ 'status': 1,
734
+ 'averageTransactionValue': 1,
735
+ },
736
+ );
737
+
738
+
739
+ const updateAck = await brandInfoUpdate( {
740
+ clientId: req.params?.id, registeredCompanyName: req.body?.registeredCompanyName, industry: req.body?.industry,
741
+ clientType: req.body?.clientType, registeredAddress: req.body?.registeredAddress, headQuarters: req.body?.headQuarters,
742
+ website: req.body?.website, status: req.body?.status, logo: req.files?.logo ? `brandLogo.${req.files.logo.name.split( '.' )[1]}` : undefined, averageTransactionValue: req.body?.averageTransactionValue,
743
+ } );
716
744
 
745
+ // Get updated client information after the update operation (fetch selected fields only)
746
+ const getPosCientInfo = await findOneClient(
747
+ { clientId: req.params.id }, // Filter by clientId
748
+ {
749
+ '_id': 0,
750
+ 'profileDetails.registeredCompanyName': 1,
751
+ 'profileDetails.industry': 1,
752
+ 'profileDetails.clientType': 1,
753
+ 'profileDetails.registeredAddress': 1,
754
+ 'profileDetails.headQuarters': 1,
755
+ 'profileDetails.website': 1,
756
+ 'status': 1,
757
+ 'averageTransactionValue': 1,
758
+ },
759
+ );
760
+ // Map and rename keys from previous client info for UI display and logging
761
+ const oldData = {
762
+ RegisteredCompanyName: getPreCientInfo?.profileDetails?.registeredCompanyName,
763
+ IndustryType: getPreCientInfo?.profileDetails?.industry,
764
+ FirmType: getPreCientInfo?.profileDetails?.clientType,
765
+ RegisteredAddress: getPreCientInfo?.profileDetails?.registeredAddress,
766
+ HeadQuarters: getPreCientInfo?.profileDetails?.headQuarters,
767
+ CompanyWebsite: getPreCientInfo?.profileDetails?.website,
768
+ ProcessingStatus: getPreCientInfo?.status,
769
+ AverageTransactionValue: getPreCientInfo?.averageTransactionValue,
770
+ };
771
+
772
+ // Map and rename keys from current client info for UI display and logging
773
+ const newData = {
774
+ RegisteredCompanyName: getPosCientInfo?.profileDetails?.registeredCompanyName,
775
+ IndustryType: getPosCientInfo?.profileDetails?.industry,
776
+ FirmType: getPosCientInfo?.profileDetails?.clientType,
777
+ RegisteredAddress: getPosCientInfo?.profileDetails?.registeredAddress,
778
+ HeadQuarters: getPosCientInfo?.profileDetails?.headQuarters,
779
+ CompanyWebsite: getPosCientInfo?.profileDetails?.website,
780
+ ProcessingStatus: getPosCientInfo?.status,
781
+ AverageTransactionValue: getPosCientInfo?.averageTransactionValue,
717
782
 
783
+ };
784
+
785
+ // Prepare activity log object with all relevant details for OpenSearch logging
718
786
  const logObj = {
719
- clientId: req.params?.id,
720
- userName: user?.userName,
721
- email: user?.email,
722
- date: new Date(),
723
- logType: 'brandDetails',
724
- logSubType: 'brandInfo',
725
- changes: updateKeys,
726
- eventType: 'update',
727
- showTo: [ 'client', 'tango' ],
787
+ clientId: req.params?.id, // ID of the client whose data was updated
788
+ userName: req?.user?.userName, // Name of the user performing the update
789
+ email: req?.user?.email, // Email of the user performing the update
790
+ date: new Date(), // Timestamp of the update event
791
+ logType: 'brandDetails', // Type of log (e.g., related to brand details)
792
+ logSubType: 'brandInfo', // Subtype for more specific categorization
793
+ changes: updateKeys, // List of fields that were updated.these will shown in the UI as part of activity log
794
+ eventType: 'update', // Type of event (update operation)
795
+ showTo: [ 'client', 'tango' ], // Visibility of the log (who can see this log)
796
+ previous: getPreCientInfo, // Previous (old) client information before update based on DB keys (for internal reference and comparison).
797
+ current: getPosCientInfo, // Current (new) client information after update based on DB keys (for internal reference and comparison).
798
+ oldData: oldData, // Previous (old) client information before update (used for showing detailed changes in the UI).
799
+ newData: newData, // Current (new) client information after update (used for showing detailed changes in the UI).
728
800
  };
801
+ logger.info( { logObj: logObj, updateKeys: updateKeys, length: updateKeys?.length } );
729
802
 
803
+ // Insert activity log into OpenSearch if there are fields that were updated
730
804
  if ( updateKeys.length ) {
731
- await insertOpenSearchData( openSearch.activityLog, logObj );
805
+ await insertOpenSearchData( openSearch.activityLog, logObj ); // Insert activity log
732
806
  }
733
807
 
734
808
 
735
- const updateAck = await brandInfoUpdate( {
736
- clientId: req.params?.id, registeredCompanyName: req.body?.registeredCompanyName, industry: req.body?.industry,
737
- clientType: req.body?.clientType, registeredAddress: req.body?.registeredAddress, headQuarters: req.body?.headQuarters,
738
- website: req.body?.website, status: req.body?.status, logo: req.files?.logo ? `brandLogo.${req.files.logo.name.split( '.' )[1]}` : undefined, averageTransactionValue: req.body?.averageTransactionValue,
739
- } );
740
-
741
809
  if ( req.body?.status === 'active' ) {
742
810
  await updateManyStore( { clientId: req.params?.id }, { status: 'active' } );
743
811
  await updateManyUser( { clientId: req.params?.id }, { isActive: true } );
@@ -754,33 +822,30 @@ export async function updateBrandInfo( req, res ) {
754
822
 
755
823
  if ( req.body?.status === 'deactive' ) {
756
824
  await updateManyStore( { clientId: req.params?.id }, { status: 'deactive' } );
757
- // await updateManyCamera( { clientId: req.params?.id }, { isUp: false, isActivated: false } );
758
825
  await updateManyUser( { clientId: req.params?.id }, { isActive: false } );
759
826
  }
760
827
 
761
- const { data } = await getApi( `${url.oldapidomain}/oldBrandGet/${req.params?.id}` );
762
-
763
- switch ( req.body?.status ) {
764
- case 'active':
765
- data.clientStatus = 'live';
766
- break;
767
- case 'hold':
768
- data.clientStatus = 'hold';
769
- break;
770
- case 'suspended':
771
- data.clientStatus = 'suspended';
772
- break;
773
- case 'deactive':
774
- data.clientStatus = 'deactivated';
775
- break;
776
- default:
777
- data.clientStatus = 'live';
778
- }
779
-
780
- await postApi( `${url.oldapidomain}/oldBrandUpdate/${data?._id}`, { clientStatus: data.clientStatus } );
828
+ // const { data } = await getApi( `${url.oldapidomain}/oldBrandGet/${req.params?.id}` );
829
+ // switch ( req.body?.status ) {
830
+ // case 'active':
831
+ // data.clientStatus = 'live';
832
+ // break;
833
+ // case 'hold':
834
+ // data.clientStatus = 'hold';
835
+ // break;
836
+ // case 'suspended':
837
+ // data.clientStatus = 'suspended';
838
+ // break;
839
+ // case 'deactive':
840
+ // data.clientStatus = 'deactivated';
841
+ // break;
842
+ // default:
843
+ // data.clientStatus = 'live';
844
+ // }
845
+ // await postApi( `${url.oldapidomain}/oldBrandUpdate/${data?._id}`, { clientStatus: data.clientStatus } );
781
846
 
782
847
  if ( updateAck ) {
783
- res.sendSuccess( { result: 'Updated Successfully' } );
848
+ return res.sendSuccess( { result: 'Updated Successfully' } );
784
849
  }
785
850
  } catch ( error ) {
786
851
  logger.error( { error: error, message: req.params, function: 'updateBrandInfo' } );
@@ -803,7 +868,11 @@ export async function updateBillingDetails( req, res ) {
803
868
  body: req.files.gstCertificate.data,
804
869
  };
805
870
  updateKeys.push( 'GST certificate' );
806
- await fileUpload( uploadDataParams );
871
+ let uploadStatus= await fileRestrictedUpload( uploadDataParams );
872
+
873
+ if ( uploadStatus==false ) {
874
+ return res.sendError( 'Unsupported Media Type', 415 );
875
+ }
807
876
  }
808
877
 
809
878
  if ( Object.keys( req.body ).length > 0 ) {
@@ -889,6 +958,25 @@ export async function updateSignatoryDetails( req, res ) {
889
958
  export async function updateTicketConfiguration( req, res ) {
890
959
  try {
891
960
  const openSearch = JSON.parse( process.env.OPENSEARCH );
961
+ const fields = {
962
+ 'ticketConfigs.downTimeType': 1,
963
+ 'ticketConfigs.installationReAssign': 1,
964
+ 'ticketConfigs.infraDownTime': 1,
965
+ 'ticketConfigs.MinFilesCount': 1,
966
+ 'ticketConfigs.rcaTicketAssign': 1,
967
+ 'ticketConfigs.refreshAlert': 1,
968
+ 'ticketConfigs.statusCheckAlert': 1,
969
+ 'ticketConfigs.accuracyPercentage': 1,
970
+ 'ticketConfigs.reTrain': 1,
971
+ };
972
+
973
+ // Get updated client ticket configuration before the update operation (fetch selected fields only)
974
+ let findClient = await findOneClient(
975
+ { clientId: req.params?.id }, // Filter by clientId
976
+ fields,
977
+ );
978
+
979
+ // update the requested values in Mongo DB
892
980
  const updateAck = await ticketConfigurationUpdate( {
893
981
  clientId: req.params?.id, MinFilesCount: req.body?.MinFilesCount, accuracyPercentage: req.body?.accuracyPercentage, downTimeType: req.body?.downTimeType,
894
982
  infraDownTime: req.body?.infraDownTime, installationReAssign: req.body?.installationReAssign, isRcaTicketAssign: req.body?.isRcaTicketAssign,
@@ -896,44 +984,85 @@ export async function updateTicketConfiguration( req, res ) {
896
984
  refreshAlert: req.body?.refreshAlert, sendToAdmin: req.body?.sendToAdmin, sendToUser: req.body?.sendToUser, statusCheckAlert: req.body?.statusCheckAlert,
897
985
  } );
898
986
 
899
- let updateKeys = [];
987
+ let updateKeys = []; // List of updated field names in readable format
988
+
989
+ // Check if request body has any fields to update
900
990
  if ( Object.keys( req.body ).length > 0 ) {
901
991
  Object.keys( req.body ).forEach( ( element ) => {
902
- updateKeys.push( camelCaseToWords( element ) );
992
+ updateKeys.push( camelCaseToWords( element ) ); // Convert camelCase field name to readable format and add to updateKeys
903
993
  } );
904
994
  }
905
995
 
906
- const user = await getUserNameEmailById( req.userId );
907
996
 
997
+ // Get updated client ticket configuration after the update operation (fetch selected fields only)
998
+ let updatedClient = await findOneClient( { clientId: req.params?.id }, fields );
999
+
1000
+ // Map and rename keys from previous client info for UI display and logging
1001
+ const oldData = {
1002
+ InfraDowntimeType: findClient?.ticketConfigs?.downTimeType == 0 ? 'Single Camera' : 'All Cameras',
1003
+ AutoReAssignInstallation: findClient?.ticketConfigs?.installationReAssign,
1004
+ InfraDowntime: findClient?.ticketConfigs?.infraDownTime,
1005
+ InfraFiles: findClient?.ticketConfigs?.MinFilesCount,
1006
+ RcaTicket: findClient?.ticketConfigs?.rcaTicketAssign,
1007
+ RefreshTicketAlert: findClient?.ticketConfigs?.refreshAlert,
1008
+ StatusCheckAlert: findClient?.ticketConfigs?.statusCheckAlert,
1009
+ DataMismatchAccuracyPercentage: findClient?.ticketConfigs?.accuracyPercentage,
1010
+ DataMismatchReTraining: findClient?.ticketConfigs?.reTrain,
1011
+ };
1012
+
1013
+ // Map and rename keys from current client info for UI display and logging
1014
+ const newData = {
1015
+ InfraDowntimeType: updatedClient?.ticketConfigs?.downTimeType == 0 ? 'Single Camera' : 'All Cameras',
1016
+ AutoReAssignInstallation: updatedClient?.ticketConfigs?.installationReAssign,
1017
+ InfraDowntime: updatedClient?.ticketConfigs?.infraDownTime,
1018
+ InfraFiles: updatedClient?.ticketConfigs?.MinFilesCount,
1019
+ RcaTicket: updatedClient?.ticketConfigs?.rcaTicketAssign,
1020
+ RefreshTicketAlert: updatedClient?.ticketConfigs?.refreshAlert,
1021
+ StatusCheckAlert: updatedClient?.ticketConfigs?.statusCheckAlert,
1022
+ DataMismatchAccuracyPercentage: updatedClient?.ticketConfigs?.accuracyPercentage,
1023
+ DataMismatchReTraining: updatedClient?.ticketConfigs?.reTrain,
1024
+
1025
+ };
1026
+
1027
+ // Prepare activity log object with all relevant details for OpenSearch logging
908
1028
  const logObj = {
909
- clientId: req.params?.id,
910
- userName: user.userName,
911
- email: user.email,
912
- date: new Date(),
913
- logType: 'configuration',
914
- logSubType: 'ticketConfig',
915
- changes: updateKeys,
916
- eventType: 'update',
917
- showTo: [ 'tango' ],
1029
+ clientId: req.params?.id, // ID of the client whose data was updated
1030
+ userName: req?.user?.userName, // Name of the user performing the update
1031
+ email: req?.user?.email, // Email of the user performing the update
1032
+ date: new Date(), // Timestamp of the update event
1033
+ logType: 'configuration', // Type of log (e.g., related to brand details)
1034
+ logSubType: 'ticketConfig', // Subtype for more specific categorization
1035
+ changes: updateKeys, // List of fields that were updated.these will shown in the UI as part of activity log
1036
+ eventType: 'update', // Type of event (update operation)
1037
+ showTo: [ 'tango' ], // Visibility of the log (who can see this log)
1038
+ current: updatedClient.ticketConfigs, // Previous (old) client information before update based on DB keys (for internal reference and comparison).
1039
+ previous: findClient.ticketConfigs, // Current (new) client information after update based on DB keys (for internal reference and comparison).
1040
+ oldData: oldData, // Previous (old) client information before update (used for showing detailed changes in the UI).
1041
+ newData: newData, // Current (new) client information after update (used for showing detailed changes in the UI).
918
1042
  };
919
1043
 
1044
+ // Insert activity log into OpenSearch if there are fields that were updated
920
1045
  if ( updateKeys.length ) {
921
- await insertOpenSearchData( openSearch.activityLog, logObj );
1046
+ await insertOpenSearchData( openSearch.activityLog, logObj ); // Insert activity log
922
1047
  }
923
1048
 
924
1049
  if ( updateAck ) {
925
1050
  res.sendSuccess( { result: 'Updated Successfully' } );
926
1051
  }
927
1052
  } catch ( error ) {
1053
+ const err = error.message || 'Internal Server Error';
928
1054
  logger.error( { error: error, message: req.params, function: 'updateTicketConfiguration' } );
929
- return res.sendError( 'Internal Server Error', 500 );
1055
+ return res.sendError( err, 500 );
930
1056
  }
931
1057
  }
932
1058
 
933
1059
  export async function updateFeatureConfiguration( req, res ) {
934
1060
  try {
935
1061
  const openSearch = JSON.parse( process.env.OPENSEARCH );
936
- const url = JSON.parse( process.env.URL );
1062
+ // const url = JSON.parse( process.env.URL );
1063
+
1064
+ // Get updated client Feature configuration before the update operation (fetch selected fields only)
1065
+ const previousData = await findOneClient( { clientId: req.params?.id }, { featureConfigs: 1 } );
937
1066
  const inputData = req.body;
938
1067
  if ( inputData?.bouncedLimitValue ) {
939
1068
  inputData.missedOpportunityFromValue = inputData?.bouncedLimitValue;
@@ -944,74 +1073,156 @@ export async function updateFeatureConfiguration( req, res ) {
944
1073
  }
945
1074
  const updateAck = await featureConfigurationUpdate( { clientId: req.params?.id }, inputData );
946
1075
 
947
- let updateKeys = [];
1076
+ // Get updated client Feature configuration after the update operation (fetch selected fields only)
1077
+ const postData = await findOneClient( { clientId: req.params?.id }, { featureConfigs: 1 } );
1078
+ let updateKeys = []; // List of updated field names in readable format
948
1079
 
1080
+ // Check if request body has any fields to update
949
1081
  if ( Object.keys( inputData ).length > 0 ) {
950
1082
  Object.keys( inputData ).forEach( ( element ) => {
951
- updateKeys.push( camelCaseToWords( element ) );
1083
+ element === 'billableCalculation' ? 'potentialCalculation' : element; // Replace 'billableCalculation' with 'potentialCalculation' if present
1084
+ updateKeys.push( camelCaseToWords( element ) ); // Convert camelCase field name to readable format and add to updateKeys
952
1085
  } );
953
1086
  }
954
1087
 
955
1088
  const user = req.user;
956
1089
 
957
- const { data } = await getApi( `${url.oldapidomain}/oldBrandGet/${req.params?.id}` );
958
-
959
- if ( inputData?.open ) {
960
- data.brandConfigs.storeOpenTime = inputData?.open;
961
- }
962
-
963
- if ( inputData?.close ) {
964
- data.brandConfigs.storeCloseTime = inputData?.open;
965
- }
966
-
967
- if ( inputData?.missedOpportunityCalculation ) {
968
- data.brandConfigs.missedOpportunityCalculate = inputData?.missedOpportunityCalculation;
969
- }
970
-
971
- if ( inputData?.conversionCalculation ) {
972
- data.brandConfigs.conversionCalculate = inputData?.conversionCalculation;
973
- }
974
-
975
- if ( inputData?.billableCalculation ) {
976
- data.brandConfigs.billableCalculate = inputData?.billableCalculation;
977
- }
978
-
979
- if ( inputData?.bouncedLimitCondition ) {
980
- data.brandConfigs.bouncedConfigTime = data.brandConfigs.bouncedConfigTime.replace( /^.+(?=\d)/, inputData?.bouncedLimitCondition );
981
- data.brandConfigs.missedOpportunityEndTime = data.brandConfigs.missedOpportunityEndTime.replace( /^.+(?=\d)/, inputData?.bouncedLimitCondition );
982
- }
983
-
984
- if ( inputData?.bouncedLimitValue ) {
985
- data.brandConfigs.bouncedConfigTime = data.brandConfigs.bouncedConfigTime.replace( /\d+/, inputData?.bouncedLimitValue );
986
- data.brandConfigs.missedOpportunityStartTime = data.brandConfigs.missedOpportunityStartTime.replace( /\d+/, inputData?.bouncedLimitValue );
987
- }
988
-
989
- if ( inputData?.conversionCondition ) {
990
- data.brandConfigs.conversionConfigTime = data.brandConfigs.conversionConfigTime.replace( /^.+(?=\d)/, inputData?.conversionCondition );
991
- data.brandConfigs.missedOpportunityStartTime = data.brandConfigs.missedOpportunityStartTime.replace( /^.+(?=\d)/, inputData?.conversionCondition );
992
- }
993
-
994
- if ( inputData?.conversionValue ) {
995
- data.brandConfigs.conversionConfigTime = data.brandConfigs.conversionConfigTime.replace( /\d+/, inputData?.conversionValue );
996
- data.brandConfigs.missedOpportunityEndTime = data.brandConfigs.missedOpportunityEndTime.replace( /\d+/, inputData?.conversionValue );
997
- }
998
-
999
- await postApi( `${url.oldapidomain}/oldBrandUpdate/${data?._id}`, { brandConfigs: data.brandConfigs } );
1090
+ // const { data } = await getApi( `${url.oldapidomain}/oldBrandGet/${req.params?.id}` );
1091
+ // if ( inputData?.open ) {
1092
+ // data.brandConfigs.storeOpenTime = inputData?.open;
1093
+ // }
1094
+ // if ( inputData?.close ) {
1095
+ // data.brandConfigs.storeCloseTime = inputData?.open;
1096
+ // }
1097
+ // if ( inputData?.missedOpportunityCalculation ) {
1098
+ // data.brandConfigs.missedOpportunityCalculate = inputData?.missedOpportunityCalculation;
1099
+ // }
1100
+ // if ( inputData?.conversionCalculation ) {
1101
+ // data.brandConfigs.conversionCalculate = inputData?.conversionCalculation;
1102
+ // }
1103
+ // if ( inputData?.billableCalculation ) {
1104
+ // data.brandConfigs.billableCalculate = inputData?.billableCalculation;
1105
+ // }
1106
+ // if ( inputData?.bouncedLimitCondition ) {
1107
+ // data.brandConfigs.bouncedConfigTime = data.brandConfigs.bouncedConfigTime.replace( /^.+(?=\d)/, inputData?.bouncedLimitCondition );
1108
+ // data.brandConfigs.missedOpportunityEndTime = data.brandConfigs.missedOpportunityEndTime.replace( /^.+(?=\d)/, inputData?.bouncedLimitCondition );
1109
+ // }
1110
+ // if ( inputData?.bouncedLimitValue ) {
1111
+ // data.brandConfigs.bouncedConfigTime = data.brandConfigs.bouncedConfigTime.replace( /\d+/, inputData?.bouncedLimitValue );
1112
+ // data.brandConfigs.missedOpportunityStartTime = data.brandConfigs.missedOpportunityStartTime.replace( /\d+/, inputData?.bouncedLimitValue );
1113
+ // }
1114
+ // if ( inputData?.conversionCondition ) {
1115
+ // data.brandConfigs.conversionConfigTime = data.brandConfigs.conversionConfigTime.replace( /^.+(?=\d)/, inputData?.conversionCondition );
1116
+ // data.brandConfigs.missedOpportunityStartTime = data.brandConfigs.missedOpportunityStartTime.replace( /^.+(?=\d)/, inputData?.conversionCondition );
1117
+ // }
1118
+ // if ( inputData?.conversionValue ) {
1119
+ // data.brandConfigs.conversionConfigTime = data.brandConfigs.conversionConfigTime.replace( /\d+/, inputData?.conversionValue );
1120
+ // data.brandConfigs.missedOpportunityEndTime = data.brandConfigs.missedOpportunityEndTime.replace( /\d+/, inputData?.conversionValue );
1121
+ // }
1122
+ // await postApi( `${url.oldapidomain}/oldBrandUpdate/${data?._id}`, { brandConfigs: data.brandConfigs } );
1123
+
1124
+ const keysArray = [
1125
+ 'isExcludedArea', 'isPasserByData', 'isNormalized', 'isbillingDisabled', 'isCameraDisabled', 'isFootfallDirectory', 'isNOB', 'isNewTraffic', 'isTrax', 'isNewZone', 'isNewReports', 'isNewDashboard', 'streamBy', 'isFootfallView', 'isAIManager', 'isAIManagerAccessControl', 'isCameraEdit' ];
1126
+ // Map and rename keys from previous client info for UI display and logging
1127
+ const oldData = {
1128
+ StoreOpenTime: previousData?.featureConfigs?.open,
1129
+ StoreCloseTime: previousData?.featureConfigs?.close,
1130
+ ConversionCalculations: previousData?.featureConfigs?.conversionCalculation == 'footfall-count' ? 'Footfall Count' : postData?.featureConfigs?.conversionCalculation == 'engagers-count' ? 'Engagers Count' : 'Potential Entities',
1131
+ MissedOpportunityCalculation: previousData?.featureConfigs?.missedOpportunityCalculation == 'engagers-conversion' ? 'Engagers - Conversion' : 'Potential Entities - Conversion',
1132
+ PotentialCalculations: previousData?.featureConfigs?.billableCalculation == 'footfall-count' ? 'Footfall Count' : 'Engagers Count',
1133
+ ConversionCondition: previousData?.featureConfigs?.conversion.condition,
1134
+ ConversionValue: previousData?.featureConfigs?.conversion.value,
1135
+ MissedOpportunityFromCondition: previousData?.featureConfigs?.missedOpportunityFrom?.condition,
1136
+ MissedOpportunityToCondition: previousData?.featureConfigs?.missedOpportunityTo?.condition,
1137
+ MissedOpportunityFromValue: previousData?.featureConfigs?.missedOpportunityFrom?.value,
1138
+ MissedOpportunityToValue: previousData?.featureConfigs?.missedOpportunityTo?.value,
1139
+ BouncedLimitCondition: previousData?.featureConfigs?.bouncedLimit?.condition,
1140
+ BouncedLimitValue: previousData?.featureConfigs?.bouncedLimit?.value,
1141
+ InfraAlertValue: previousData?.featureConfigs?.infraAlert?.value,
1142
+ InfraAlertCondition: previousData?.featureConfigs?.infraAlert?.condition,
1143
+ ExcludedArea: previousData?.featureConfigs?.isExcludedArea == true ? 'Enabled' : 'Disabled',
1144
+ PasserBydata: previousData?.featureConfigs?.isPasserByData == true ? 'Enabled' : 'Disabled',
1145
+ NormalizedDataDuringDowntime: previousData?.featureConfigs?.isNormalized == true ? 'Enabled' : 'Disabled',
1146
+ Billing: previousData?.featureConfigs?.isbillingDisabled == true ? 'Enabled' : 'Disabled',
1147
+ CameraBlurring: previousData?.featureConfigs?.isCameraDisabled == true ? 'Enabled' : 'Disabled',
1148
+ FootfallDirectory: previousData?.featureConfigs?.isFootfallDirectory == true ? 'Enabled' : 'Disabled',
1149
+ NOBStatus: previousData?.featureConfigs?.isNOB == true ? 'Enabled' : 'Disabled',
1150
+ EnableAnalyze: previousData?.featureConfigs?.isNewDashboard == true ? 'Enabled' : 'Disabled',
1151
+ Traffic: previousData?.featureConfigs?.isNewTraffic == true ? 'Enabled' : 'Disabled',
1152
+ Zone: previousData?.featureConfigs?.isNewZone == true ? 'Enabled' : 'Disabled',
1153
+ Zonev2: previousData?.featureConfigs?.isNewZoneV2 == true ? 'Enabled' : 'Disabled',
1154
+ Reports: previousData?.featureConfigs?.isNewReports == true ? 'Enabled' : 'Disabled',
1155
+ Trax: previousData?.featureConfigs?.isTrax == true ? 'Enabled' : 'Disabled',
1156
+ Revops: previousData?.featureConfigs?.isRevops == true ? 'Enabled' : 'Disabled',
1157
+ StreamType: previousData?.featureConfigs?.streamBy == 'Edge' ? 'Edge App' : 'RTSP',
1158
+ FootfallDirectoryOnlyAudit: previousData?.featureConfigs?.isFootfallDirectoryAudit == true ? 'Enabled' : 'Disabled',
1159
+ FootfallDirectoryOnlyFew: previousData?.featureConfigs?.isFootfallDirectoryLimit == true ? 'Enabled' : 'Disabled',
1160
+ FootfallView: previousData?.featureConfigs?.isFootfallView == true ? 'Enabled' : 'Disabled',
1161
+ AIManager: previousData?.featureConfigs?.isAIManager == true ? 'Enabled' : 'Disabled',
1162
+ AIManagerAccessControl: previousData?.featureConfigs?.isAIManagerAccessControl == true ? 'Enabled' : 'Disabled',
1163
+ CameraEdit: previousData?.featureConfigs?.isCameraEdit == true ? 'Enabled' : 'Disabled',
1164
+ };
1000
1165
 
1166
+ // Map and rename keys from current client info for UI display and logging
1167
+ const newData = {
1168
+ StoreOpenTime: postData?.featureConfigs?.open,
1169
+ StoreCloseTime: postData?.featureConfigs?.close,
1170
+ ConversionCalculations: postData?.featureConfigs?.conversionCalculation == 'footfall-count' ? 'Footfall Count' : postData?.featureConfigs?.conversionCalculation == 'engagers-count' ? 'Engagers Count' : 'Potential Entities',
1171
+ MissedOpportunityCalculation: postData?.featureConfigs?.missedOpportunityCalculation == 'engagers-conversion' ? 'Engagers - Conversion' : 'Potential Entities - Conversion',
1172
+ PotentialCalculations: postData?.featureConfigs?.billableCalculation == 'footfall-count' ? 'Footfall Count' : 'Engagers Count',
1173
+ ConversionCondition: postData?.featureConfigs?.conversion.condition,
1174
+ ConversionValue: postData?.featureConfigs?.conversion.value,
1175
+ MissedOpportunityFromCondition: postData?.featureConfigs?.missedOpportunityFrom?.condition,
1176
+ MissedOpportunityToCondition: postData?.featureConfigs?.missedOpportunityTo?.condition,
1177
+ MissedOpportunityFromValue: postData?.featureConfigs?.missedOpportunityFrom?.value,
1178
+ MissedOpportunityToValue: postData?.featureConfigs?.missedOpportunityTo?.value,
1179
+ BouncedLimitCondition: postData?.featureConfigs?.bouncedLimit?.condition,
1180
+ BouncedLimitValue: postData?.featureConfigs?.bouncedLimit?.value,
1181
+ InfraAlertValue: postData?.featureConfigs?.infraAlert?.value,
1182
+ InfraAlertCondition: postData?.featureConfigs?.infraAlert?.condition,
1183
+ ExcludedArea: postData?.featureConfigs?.isExcludedArea == true ? 'Enabled' : 'Disabled',
1184
+ PasserBydata: postData?.featureConfigs?.isPasserByData == true ? 'Enabled' : 'Disabled',
1185
+ NormalizedDataDuringDowntime: postData?.featureConfigs?.isNormalized == true ? 'Enabled' : 'Disabled',
1186
+ Billing: postData?.featureConfigs?.isbillingDisabled == true ? 'Enabled' : 'Disabled',
1187
+ CameraBlurring: postData?.featureConfigs?.isCameraDisabled == true ? 'Enabled' : 'Disabled',
1188
+ FootfallDirectory: postData?.featureConfigs?.isFootfallDirectory == true ? 'Enabled' : 'Disabled',
1189
+ NOBStatus: postData?.featureConfigs?.isNOB == true ? 'Enabled' : 'Disabled',
1190
+ EnableAnalyze: postData?.featureConfigs?.isNewDashboard == true ? 'Enabled' : 'Disabled',
1191
+ Traffic: postData?.featureConfigs?.isNewTraffic == true ? 'Enabled' : 'Disabled',
1192
+ Zone: postData?.featureConfigs?.isNewZone == true ? 'Enabled' : 'Disabled',
1193
+ Zonev2: postData?.featureConfigs?.isNewZoneV2 == true ? 'Enabled' : 'Disabled',
1194
+ Reports: postData?.featureConfigs?.isNewReports == true ? 'Enabled' : 'Disabled',
1195
+ Trax: postData?.featureConfigs?.isTrax == true ? 'Enabled' : 'Disabled',
1196
+ Revops: postData?.featureConfigs?.isRevops == true ? 'Enabled' : 'Disabled',
1197
+ StreamType: postData?.featureConfigs?.streamBy == 'Edge' ? 'Edge App' : 'RTSP',
1198
+ FootfallDirectoryOnlyAudit: postData?.featureConfigs?.isFootfallDirectoryAudit == true ? 'Enabled' : 'Disabled',
1199
+ FootfallDirectoryOnlyFew: postData?.featureConfigs?.isFootfallDirectoryLimit == true ? 'Enabled' : 'Disabled',
1200
+ FootfallView: previousData?.featureConfigs?.isFootfallView == true ? 'Enabled' : 'Disabled',
1201
+ AIManager: previousData?.featureConfigs?.isAIManager == true ? 'Enabled' : 'Disabled',
1202
+ AIManagerAccessControl: previousData?.featureConfigs?.isAIManagerAccessControl == true ? 'Enabled' : 'Disabled',
1203
+ CameraEdit: previousData?.featureConfigs?.isCameraEdit == true ? 'Enabled' : 'Disabled',
1204
+ };
1001
1205
 
1206
+ // Prepare activity log object with all relevant details for OpenSearch logging
1002
1207
  const logObj = {
1003
- clientId: req.params?.id,
1004
- userName: user?.userName,
1005
- email: user?.email,
1006
- date: new Date(),
1007
- logType: 'configuration',
1008
- logSubType: 'featureConfig',
1009
- changes: updateKeys,
1010
- eventType: 'update',
1011
- showTo: [ 'client', 'tango' ],
1208
+ clientId: req.params?.id, // ID of the client whose data was updated
1209
+ userName: user?.userName, // Name of the user performing the update
1210
+ email: user?.email, // Email of the user performing the update
1211
+ date: new Date(), // Timestamp of the update event
1212
+ logType: 'configuration', // Type of log (e.g., related to brand details)
1213
+ logSubType: Object.keys( inputData ).some( ( key ) => keysArray.includes( key ) ) ? 'dashboardConfig' : 'featureConfig', // Subtype for more specific categorization
1214
+ changes: updateKeys, // List of fields that were updated.these will shown in the UI as part of activity log
1215
+ eventType: 'update', // Type of event (update operation)
1216
+ showTo: [ 'client', 'tango' ], // Visibility of the log (who can see this log)
1217
+ previous: previousData.featureConfigs, // Previous (old) client information before update based on DB keys (for internal reference and comparison).
1218
+ current: postData.featureConfigs, // Current (new) client information after update based on DB keys (for internal reference and comparison).
1219
+ oldData: oldData, // Previous (old) client information before update (used for showing detailed changes in the UI).
1220
+ newData: newData, // Current (new) client information after update (used for showing detailed changes in the UI).
1012
1221
  };
1222
+
1223
+ // Insert activity log into OpenSearch if there are fields that were updated
1013
1224
  if ( updateKeys.length ) {
1014
- await insertOpenSearchData( openSearch.activityLog, logObj );
1225
+ await insertOpenSearchData( openSearch.activityLog, logObj ); // Insert activity log
1015
1226
  }
1016
1227
 
1017
1228
  if ( updateAck ) {
@@ -1027,6 +1238,18 @@ export async function updateFeatureConfiguration( req, res ) {
1027
1238
  export async function domainDetailsConfiguration( req, res ) {
1028
1239
  try {
1029
1240
  const openSearch = JSON.parse( process.env.OPENSEARCH );
1241
+
1242
+ // Get updated client domain configuration before the update operation (fetch selected fields only)
1243
+ const getPreData = await findOneClient(
1244
+ { clientId: req.params?.id }, // filter by clientId
1245
+ {
1246
+ 'domainConfig.ssoLogin.domainName': 1,
1247
+ 'domainConfig.ssoLogin.isEnable': 1,
1248
+ 'domainConfig.ipWhitelisting.enableWhitelisting': 1,
1249
+ 'domainConfig.ipWhitelisting.allowedIps': 1,
1250
+ 'domainConfig.enableOtp': 1,
1251
+ },
1252
+ );
1030
1253
  const updateAck = await domainDetailsConfigurationUpdate( {
1031
1254
  clientId: req.params?.id, domainName: req.body?.domainName, isEnable: req.body?.isEnable,
1032
1255
  enableWhitelisting: req.body?.enableWhitelisting, allowedIps: req.body?.allowedIps, enableOtp: req.body?.enableOtp,
@@ -1034,30 +1257,66 @@ export async function domainDetailsConfiguration( req, res ) {
1034
1257
 
1035
1258
  let updateKeys = [];
1036
1259
 
1037
-
1260
+ // Check if request body has any fields to update
1038
1261
  if ( Object.keys( req.body ).length > 0 ) {
1039
1262
  Object.keys( req.body ).forEach( ( element ) => {
1040
1263
  updateKeys.push( camelCaseToWords( element ) );
1041
1264
  } );
1042
1265
  }
1043
1266
 
1044
- const user = await getUserNameEmailById( req.userId );
1267
+ // Get updated client domain configuration after the update operation (fetch selected fields only)
1268
+ const getPostData = await findOneClient(
1269
+ { clientId: req.params?.id }, // Filter by clientId
1270
+ {
1271
+ 'domainConfig.ssoLogin.domainName': 1,
1272
+ 'domainConfig.ssoLogin.isEnable': 1,
1273
+ 'domainConfig.ipWhitelisting.enableWhitelisting': 1,
1274
+ 'domainConfig.ipWhitelisting.allowedIps': 1,
1275
+ 'domainConfig.enableOtp': 1,
1276
+ },
1277
+ );
1045
1278
 
1046
1279
 
1280
+ // Map and rename keys from previous client info for UI display and logging
1281
+ const oldData = {
1282
+ DomainName: getPreData?.domainConfig?.ssoLogin?.domainName?.join( ', ' ),
1283
+ IsEnable: getPreData?.domainConfig?.ssoLogin?.isEnable == true ? 'Enable' : 'Disable',
1284
+ IPWhitelist: getPreData?.domainConfig?.ipWhitelisting?.enableWhitelisting == true ? 'Enable' : 'Disable',
1285
+ WhitelistedIps: getPreData?.domainConfig?.ipWhitelisting?.allowedIps?.join( ', ' ),
1286
+ TwoFactorAuthentication: getPreData?.domainConfig?.enableOtp == true ? 'Enable' : 'Disable',
1287
+
1288
+ };
1289
+
1290
+ // Map and rename keys from current client info for UI display and logging
1291
+ const newData = {
1292
+ DomainName: getPostData?.domainConfig?.ssoLogin?.domainName?.join( ', ' ),
1293
+ IsEnable: getPostData?.domainConfig?.ssoLogin?.isEnable == true ? 'Enable' : 'Disable',
1294
+ IPWhitelist: getPostData?.domainConfig?.ipWhitelisting?.enableWhitelisting == true ? 'Enable' : 'Disable',
1295
+ WhitelistedIps: getPostData?.domainConfig?.ipWhitelisting?.allowedIps?.join( ', ' ),
1296
+ TwoFactorAuthentication: getPostData?.domainConfig?.enableOtp == true ? 'Enable' : 'Disable',
1297
+ };
1298
+
1299
+ logger.info( { getPreData: getPreData, getPostData: getPostData, newData: newData, oldData: oldData } );
1300
+ // Prepare activity log object with all relevant details for OpenSearch logging
1047
1301
  const logObj = {
1048
- clientId: req.params?.id,
1049
- userName: user?.userName,
1050
- email: user?.email,
1051
- date: new Date(),
1052
- logType: 'configuration',
1053
- logSubType: 'domainDetails',
1054
- changes: updateKeys,
1055
- eventType: 'update',
1056
- showTo: [ 'client', 'tango' ],
1302
+ clientId: req.params?.id, // ID of the client whose data was updated
1303
+ userName: req?.user?.userName, // Name of the user performing the update
1304
+ email: req?.user?.email, // Email of the user performing the update
1305
+ date: new Date(), // Timestamp of the update event
1306
+ logType: 'configuration', // Type of log (e.g., related to brand details)
1307
+ logSubType: 'domainDetails', // Subtype for more specific categorization
1308
+ changes: updateKeys, // List of fields that were updated.these will shown in the UI as part of activity log
1309
+ eventType: 'update', // Type of event (update operation)
1310
+ showTo: [ 'client', 'tango' ], // Visibility of the log (who can see this log)
1311
+ previous: getPreData, // Previous (old) client information before update based on DB keys (for internal reference and comparison).
1312
+ current: getPostData, // Current (new) client information after update based on DB keys (for internal reference and comparison).
1313
+ oldData: oldData, // Previous (old) client information before update (used for showing detailed changes in the UI).
1314
+ newData: newData, // Current (new) client information after update (used for showing detailed changes in the UI).
1057
1315
  };
1058
1316
 
1317
+ // Insert activity log into OpenSearch if there are fields that were updated
1059
1318
  if ( updateKeys.length ) {
1060
- await insertOpenSearchData( openSearch.activityLog, logObj );
1319
+ await insertOpenSearchData( openSearch.activityLog, logObj ); // Insert activity log
1061
1320
  }
1062
1321
 
1063
1322
  if ( updateAck ) {
@@ -1099,8 +1358,17 @@ export async function updateDocuments( req, res ) {
1099
1358
  };
1100
1359
 
1101
1360
  updateKeys.push( 'Address certificate' );
1361
+ let xssCheckData=await getXsscheck( uploadDataParams.body );
1362
+
1363
+ if ( xssCheckData==true ) {
1364
+ return res.sendError( 'Unsupported Media Type', 415 );
1365
+ }
1366
+
1367
+ let uploadStatus= await fileRestrictedUpload( uploadDataParams );
1102
1368
 
1103
- await fileUpload( uploadDataParams );
1369
+ if ( uploadStatus==false ) {
1370
+ return res.sendError( 'Unsupported Media Type', 415 );
1371
+ }
1104
1372
  }
1105
1373
  if ( req.files?.gstDoc ) {
1106
1374
  const uploadDataParams = {
@@ -1113,7 +1381,17 @@ export async function updateDocuments( req, res ) {
1113
1381
 
1114
1382
  updateKeys.push( 'GST certificate' );
1115
1383
 
1116
- await fileUpload( uploadDataParams );
1384
+ let xssCheckData=await getXsscheck( uploadDataParams.body );
1385
+
1386
+ if ( xssCheckData==true ) {
1387
+ return res.sendError( 'Unsupported Media Type', 415 );
1388
+ }
1389
+
1390
+ let uploadStatus= await fileRestrictedUpload( uploadDataParams );
1391
+
1392
+ if ( uploadStatus==false ) {
1393
+ return res.sendError( 'Unsupported Media Type', 415 );
1394
+ }
1117
1395
  }
1118
1396
  if ( req.files?.panDoc ) {
1119
1397
  const uploadDataParams = {
@@ -1126,7 +1404,17 @@ export async function updateDocuments( req, res ) {
1126
1404
 
1127
1405
  updateKeys.push( 'PAN certificate' );
1128
1406
 
1129
- await fileUpload( uploadDataParams );
1407
+ let xssCheckData=await getXsscheck( uploadDataParams.body );
1408
+
1409
+ if ( xssCheckData==true ) {
1410
+ return res.sendError( 'Unsupported Media Type', 415 );
1411
+ }
1412
+
1413
+ let uploadStatus= await fileRestrictedUpload( uploadDataParams );
1414
+
1415
+ if ( uploadStatus==false ) {
1416
+ return res.sendError( 'Unsupported Media Type', 415 );
1417
+ }
1130
1418
  }
1131
1419
  if ( req.files?.cinDoc ) {
1132
1420
  const uploadDataParams = {
@@ -1139,7 +1427,11 @@ export async function updateDocuments( req, res ) {
1139
1427
 
1140
1428
  updateKeys.push( 'CIN certificate' );
1141
1429
 
1142
- await fileUpload( uploadDataParams );
1430
+ let uploadStatus= await fileRestrictedUpload( uploadDataParams );
1431
+
1432
+ if ( uploadStatus==false ) {
1433
+ return res.sendError( 'Unsupported Media Type', 415 );
1434
+ }
1143
1435
  }
1144
1436
 
1145
1437
  const updateAck = await documentsUpdate( {
@@ -1159,7 +1451,6 @@ export async function updateDocuments( req, res ) {
1159
1451
 
1160
1452
  const user = await getUserNameEmailById( req.userId );
1161
1453
 
1162
-
1163
1454
  const logObj = {
1164
1455
  clientId: req.params?.id,
1165
1456
  userName: user?.userName,
@@ -1221,14 +1512,31 @@ export async function auditConfiguration( req, res ) {
1221
1512
  auditConfigs: 1, _id: 0,
1222
1513
  };
1223
1514
  const previousClient = await findOneClient( query, fields );
1515
+ previousClient.auditConfigs.ratio = previousClient?.auditConfigs?.ratio ? ( previousClient.auditConfigs.ratio ) * 100 : null;
1224
1516
  const record = {
1225
1517
  'auditConfigs.count': inputData.count,
1226
1518
  'auditConfigs.audit': inputData.audit,
1227
1519
  'auditConfigs.ratio': Number( normalizeNumber( inputData.ratio, 0, 100 ) ),
1228
1520
  };
1229
- logger.info( { record: record, query: query } );
1230
- const a = await updateOneClient( query, record );
1231
- logger.info( { a: a } );
1521
+ await updateOneClient( query, record );
1522
+
1523
+ // Map and rename keys from previous client info for UI display and logging
1524
+ const oldData = {
1525
+ AuditStatus: previousClient?.auditConfigs?.audit == true ? 'Enable' : 'Disable',
1526
+ MappingPercentage: previousClient?.auditConfigs?.ratio,
1527
+ AuditCount: previousClient?.auditConfigs?.count,
1528
+ };
1529
+
1530
+ const currentClient = await findOneClient( query, fields );
1531
+ currentClient.auditConfigs.ratio = currentClient?.auditConfigs?.ratio ? ( currentClient.auditConfigs.ratio ) * 100 : null;
1532
+
1533
+ // Map and rename keys from current client info for UI display and logging
1534
+ const newData = {
1535
+ AuditStatus: currentClient?.auditConfigs?.audit == true ? 'Enable' : 'Disable',
1536
+ MappingPercentage: currentClient?.auditConfigs?.ratio,
1537
+ AuditCount: currentClient?.auditConfigs?.count,
1538
+
1539
+ };
1232
1540
  const logObj = {
1233
1541
  clientId: req.params?.id,
1234
1542
  userName: req.user?.userName,
@@ -1239,13 +1547,10 @@ export async function auditConfiguration( req, res ) {
1239
1547
  eventType: 'update',
1240
1548
  showTo: [ 'tango' ],
1241
1549
  changes: [ `Audit config for client id ${req.params?.id}` ],
1242
- previous: previousClient,
1243
- current: {
1244
- clientId: req.params?.id,
1245
- count: inputData.count,
1246
- audit: inputData.audit,
1247
- ratio: normalizeNumber( inputData.ratio, 0, 100 ),
1248
- },
1550
+ previous: previousClient.auditConfigs,
1551
+ current: currentClient.auditConfigs,
1552
+ oldData: oldData,
1553
+ newData: newData,
1249
1554
  };
1250
1555
 
1251
1556
  await insertOpenSearchData( openSearch.activityLog, logObj );
@@ -1288,17 +1593,6 @@ export async function clientList( req, res ) {
1288
1593
  $match: {
1289
1594
  userEmail: { $eq: req?.user?.email },
1290
1595
  userType: 'tango',
1291
- // $expr: {
1292
- // $cond: {
1293
- // if: {
1294
- // $and: [
1295
- // { $eq: [ '$tangoUserType', 'csm' ] },
1296
- // ],
1297
- // },
1298
- // then: { $eq: [ '$isClientApproved', true ] },
1299
- // else: true,
1300
- // },
1301
- // },
1302
1596
 
1303
1597
  },
1304
1598
  }, {
@@ -1614,17 +1908,6 @@ export async function clientListV1( req, res ) {
1614
1908
  $match: {
1615
1909
  userEmail: { $eq: req?.user?.email },
1616
1910
  userType: 'tango',
1617
- // $expr: {
1618
- // $cond: {
1619
- // if: {
1620
- // $and: [
1621
- // { $eq: [ '$tangoUserType', 'csm' ] },
1622
- // ],
1623
- // },
1624
- // then: { $eq: [ '$isClientApproved', true ] },
1625
- // else: true,
1626
- // },
1627
- // },
1628
1911
 
1629
1912
  },
1630
1913
  },
@@ -2020,13 +2303,41 @@ export async function detailedClientCount( req, res ) {
2020
2303
  }
2021
2304
  }
2022
2305
 
2023
- export async function getActivityLogs( req, res ) {
2306
+ export async function getActivityLogs1( req, res ) {
2024
2307
  try {
2308
+ const inputData = req.body;
2025
2309
  const openSearch = JSON.parse( process.env.OPENSEARCH );
2310
+ const clientQuery = [
2311
+ {
2312
+ $match: {
2313
+ $and: [
2314
+ {
2315
+ clientId: { $in: [ inputData?.clientId ] },
2316
+ },
2317
+ ],
2318
+ },
2319
+ },
2320
+ {
2321
+ $group: {
2322
+ _id: null,
2323
+ clientName: { $push: '$clientName' },
2324
+ },
2325
+ },
2326
+ {
2327
+ $project: {
2328
+ _id: 0,
2329
+ clientName: 1,
2330
+ },
2331
+ },
2332
+ ];
2333
+ const getClientName = await aggregateClient( clientQuery );
2334
+ if ( getClientName?.length == 0 || getClientName[0]?.clientName?.length === 0 ) {
2335
+ return res.sendError( 'No Data Found', 204 );
2336
+ }
2026
2337
  const query = {
2027
2338
  '_source': [
2028
2339
  'userId', 'userName', 'email', 'date', 'logType', 'logSubType',
2029
- 'changes', 'eventType',
2340
+ 'changes', 'eventType', 'previous', 'current', 'oldData', 'newData',
2030
2341
  ],
2031
2342
  'query': {
2032
2343
  'bool': {
@@ -2083,9 +2394,32 @@ export async function getActivityLogs( req, res ) {
2083
2394
 
2084
2395
  const hits = logs?.body?.hits?.hits;
2085
2396
  const totalDocuments = logs?.body?.hits?.total?.value;
2397
+
2398
+ let temp = [];
2086
2399
  if ( totalDocuments ) {
2087
- const dataArray = hits.map( ( hit ) => hit._source );
2088
- res.sendSuccess( { data: dataArray, count: totalDocuments } );
2400
+ hits.map( ( hit, i ) => {
2401
+ let respo = {};
2402
+ hit._source.logSubType = hit._source.logSubType === 'documentUpload' ? 'documentsUpload' : hit._source.logSubType === 'domainDetails' ? 'securityFeatures' : hit?._source?.logSubType;
2403
+ if ( ( ( hit?._source?.eventType ).match( /update/ ) || ( hit?._source?.eventType ).match( /edit/ ) ) && hit?._source?.previous ) {
2404
+ const previous = hit?._source?.logType == 'cameras' ? hit?._source?.oldData : hit?._source?.previous;
2405
+ const current = hit?._source?.logType == 'cameras' ? hit?._source?.newData : hit?._source?.current;
2406
+ const logType = hit?._source?.logType;
2407
+ const logSubType = hit?._source?.logSubType;
2408
+ if ( previous && current && hit?._source?.logType == 'cameras' ) {
2409
+ respo = findDifferences( previous, current );
2410
+ } else {
2411
+ respo = findDifferences1( previous, current, logType, logSubType );
2412
+ }
2413
+
2414
+ hit._source.updatedValue = respo;
2415
+ temp.push( hit?._source );
2416
+ hit._source.changes = logSubType === 'ticketConfig' ? Object.keys( respo ).map( ( item ) => item ) : hit._source.changes;
2417
+ } else {
2418
+ temp.push( hit?._source );
2419
+ }
2420
+ },
2421
+ );
2422
+ res.sendSuccess( { data: temp, count: totalDocuments } );
2089
2423
  } else {
2090
2424
  res.sendError( 'No data found', 204 );
2091
2425
  }
@@ -2096,42 +2430,334 @@ export async function getActivityLogs( req, res ) {
2096
2430
  }
2097
2431
 
2098
2432
 
2099
- async function postApi( url, data ) {
2100
- const requestOptions = {
2101
- method: 'POST',
2102
- headers: {
2103
- 'Content-Type': 'application/json',
2104
- 'Authorization': 'Bearer d47433f8-9a33-47c7-ba43-1a0fbac28f66',
2105
- },
2106
- body: JSON.stringify( data ),
2107
- };
2108
-
2109
- try {
2110
- const response = await fetch( url, requestOptions );
2111
- await response.json();
2112
- } catch ( error ) {
2113
- logger.error( { error: error, message: data, function: 'postApi' } );
2433
+ function findDifferences1( previous, current, logType, logSubType, path = '' ) {
2434
+ const dbKeys = JSON.parse( process.env.DB_KEYS );
2435
+ const ignoredKeys = new Set( [
2436
+ '_id', 'updatedAt', 'createdAt', 'password', 'clientId',
2437
+ 'storeId', 'refreshToken', 'employeeId', 'fcmToken', 'permission', 'updateFeatureConfig',
2438
+ ] );
2439
+ const documents = dbKeys.DOCUMENTS;
2440
+ // Get correct key mapping based on logType
2441
+ const keyMapping = logType == 'stores' ? documents.stores : logType == 'users' ? documents.users : logSubType == 'reportConfig' ? documents.reports : logType == 'brandInfo' ? documents.client : documents.client;
2442
+
2443
+
2444
+ let differences = {};
2445
+
2446
+ for ( const key in current ) {
2447
+ if ( !Object.prototype.hasOwnProperty.call( current, key ) || ignoredKeys.has( key ) ) continue;
2448
+
2449
+ const prevValue = previous[key];
2450
+ const currValue = current[key];
2451
+
2452
+ if ( typeof prevValue === 'object' && typeof currValue === 'object' && prevValue !== null && currValue !== null ) {
2453
+ if ( logSubType !== 'userUpdated' ) {
2454
+ if ( key == 'spocDetails' ) {
2455
+ if ( _.isEqual( prevValue, currValue ) ) {
2456
+ continue;
2457
+ } else {
2458
+ let result = compareArrayObjects( prevValue, currValue );
2459
+ differences = { ...differences, ...result };
2460
+ }
2461
+ } else if ( Array.isArray( prevValue ) && prevValue.every( ( item ) => typeof item === 'string' ) || Array.isArray( currValue ) && currValue.every( ( item ) => typeof item === 'string' ) ) {
2462
+ JSON.stringify( prevValue ) !== JSON.stringify( currValue ) ?
2463
+ differences[`${path}${key}`] = { previous: prevValue.length > 0 ? prevValue.join( ',' ) : null, current: currValue.length > 0 ? currValue.join( ',' ) : null } : null;
2464
+
2465
+ // differences[`${path}${key}`] = { previous: removed.length> 0 ? prevValue.join( ',' ): prevValue.join( ', ' ) :, current: added.length > 0? addedUsers.join( ', ' ) : currValue.join( ',' ) } : null;
2466
+ } else {
2467
+ const nestedDiffs = findDifferences1( prevValue, currValue, logType, logSubType, `${path}${key}.` );
2468
+ Object.assign( differences, nestedDiffs );
2469
+ }
2470
+ } else {
2471
+ const nestedDiffs = findDifferences1( prevValue, currValue, logType, logSubType, `${path}${key}.` );
2472
+ Object.assign( differences, nestedDiffs );
2473
+ }
2474
+ } else if ( prevValue !== currValue ) {
2475
+ if (
2476
+ ( prevValue === '' && currValue === '' ) ||
2477
+ ( Array.isArray( prevValue ) && Array.isArray( currValue ) && prevValue.length === 0 && currValue.length === 0 )
2478
+ ) {
2479
+ continue;
2480
+ }
2481
+ differences[`${path}${key}`] = { previous: prevValue, current: currValue };
2482
+ }
2114
2483
  }
2484
+
2485
+ // **Transform & Filter Differences**
2486
+ let updatedDifferences = {};
2487
+ Object.keys( differences ).forEach( ( key ) => {
2488
+ let newKey = key.replace( /spocDetails\.\d+\./, 'spocDetails.' ).replace( /WhitelistedIps\.\d+/, 'Whitelisted Ips' );
2489
+ let diff = differences[key];
2490
+ if ( newKey.toLowerCase().includes( 'isactive' ) ) {
2491
+ diff = {
2492
+ previous: diff.previous ? 'Active' : 'Deactive',
2493
+ current: diff.current ? 'Active' : 'Deactive',
2494
+ };
2495
+ }
2496
+
2497
+ const userFriendlyKey = getUserFriendlyKey( newKey, keyMapping );
2498
+ if ( userFriendlyKey ) {
2499
+ newKey = userFriendlyKey;
2500
+ }
2501
+ const binaryKeys = [ 'Two Factor Authentication', 'Mat Enabled', 'Audit Status', 'Infra Email Alert', 'Ip Whitelist', 'Excluded Area', 'Passer-by data', 'Normalized data during downtime', 'Billing', 'Camera Blurring', 'Footfall Directory', 'Footfall Directory Audit', 'Footfall Directory Limit', 'NOB Status', 'Traffic', 'Trax', 'Zone V1', 'Zone V2', 'Reports', 'Analyze' ];
2502
+ if ( binaryKeys.includes( newKey ) ) {
2503
+ diff.previous = ( diff.previous == true || diff.previous == 'Enabled' ) ? 'Enabled' : 'Disabled';
2504
+ diff.current = ( diff.current == true || diff.current == 'Enabled' ) ? 'Enabled' : 'Disabled';
2505
+ }
2506
+ if ( newKey === 'Server Type' ) {
2507
+ diff.previous = ( diff.previous == true || diff.previous == 'Server' ) ? 'Server' : 'Serverless';
2508
+ diff.current = ( diff.current == true || diff.current == 'Server' ) ? 'Server' : 'Serverless';
2509
+ }
2510
+
2511
+ if ( newKey === 'Stream Type' ) {
2512
+ diff.previous = ( diff.previous == 'Edge' || diff.previous == 'Edge App' ) ? 'Edge App' : 'RTSP';
2513
+ diff.current = ( diff.current == 'Edge' || diff.current == 'Edge App' ) ? 'Edge App' : 'RTSP';
2514
+ }
2515
+
2516
+ // **Transform Roles Permission Keys**
2517
+ const rolesPermissionMatch = newKey.match( /rolespermission\.(\d+)\.modules\.(\d+)\.(isAdd|isEdit)/ );
2518
+ if ( rolesPermissionMatch ) {
2519
+ const [ , roleIndex, moduleIndex, action ] = rolesPermissionMatch;
2520
+ const role = previous.rolespermission?.[roleIndex];
2521
+ const module = role?.modules?.[moduleIndex];
2522
+
2523
+ if ( module ) {
2524
+ newKey = `User's ${module.name} Module ${action === 'isAdd' ? 'Add' : 'Edit'}`;
2525
+ diff.previous = diff.previous ? 'Enabled' : 'Disabled';
2526
+ diff.current = diff.current ? 'Enabled' : 'Disabled';
2527
+ }
2528
+ }
2529
+
2530
+ if ( !newKey || ( diff.previous === '' && !diff.current ) ) return;
2531
+
2532
+ if (
2533
+ ( diff.previous === '' || diff.previous === null || ( Array.isArray( diff.previous ) && diff.previous.length === 0 ) ) &&
2534
+ diff.current === undefined
2535
+ ) {
2536
+ return;
2537
+ }
2538
+ if (
2539
+ ( diff.current === '' || diff.current === null || ( Array.isArray( diff.current ) && diff.current.length === 0 ) ) &&
2540
+ diff.previous === undefined
2541
+ ) {
2542
+ return;
2543
+ }
2544
+
2545
+ updatedDifferences[newKey] = diff;
2546
+ } );
2547
+
2548
+ return updatedDifferences;
2115
2549
  }
2116
2550
 
2117
- async function getApi( url ) {
2118
- const requestOptions = {
2119
- method: 'GET',
2120
- headers: {
2121
- 'Content-Type': 'application/json',
2122
- 'Authorization': 'Bearer d47433f8-9a33-47c7-ba43-1a0fbac28f66',
2123
- },
2124
- };
2125
2551
 
2552
+ function getUserFriendlyKey( keyPath, mapping ) {
2553
+ const keys = keyPath.split( '.' ); // Split the key path (e.g., storeProfile.pincode → ['storeProfile', 'pincode'])
2554
+ let value = mapping;
2555
+
2556
+ for ( const key of keys ) {
2557
+ if ( value && typeof value === 'object' && key in value ) {
2558
+ value = value[key]; // Traverse down the object
2559
+ } else {
2560
+ return null; // Return original if not found
2561
+ }
2562
+ }
2563
+
2564
+ return typeof value === 'string' ? value : keyPath; // Return mapped value or original key
2565
+ }
2566
+
2567
+ export async function getActivityLogs( req, res ) {
2126
2568
  try {
2127
- const response = await fetch( url, requestOptions );
2128
- const responseData = await response.json();
2129
- return responseData;
2569
+ const inputData = req.body;
2570
+ const openSearch = JSON.parse( process.env.OPENSEARCH );
2571
+ const clientQuery = [
2572
+ {
2573
+ $match: {
2574
+ $and: [
2575
+ {
2576
+ clientId: { $eq: inputData.clientId },
2577
+ },
2578
+ ],
2579
+ },
2580
+ },
2581
+ {
2582
+ $group: {
2583
+ _id: null,
2584
+ clientName: { $push: '$clientName' },
2585
+ },
2586
+ },
2587
+ {
2588
+ $project: {
2589
+ _id: 0,
2590
+ clientName: 1,
2591
+ },
2592
+ },
2593
+ ];
2594
+ const getClientName = await aggregateClient( clientQuery );
2595
+ if ( getClientName?.length == 0 || getClientName[0]?.clientName?.length === 0 ) {
2596
+ return res.sendError( 'No Data Found', 204 );
2597
+ }
2598
+ const query = {
2599
+ '_source': [
2600
+ 'userId', 'userName', 'email', 'date', 'logType', 'logSubType',
2601
+ 'changes', 'eventType', 'previous', 'current', 'oldData', 'newData',
2602
+ ],
2603
+ 'query': {
2604
+ 'bool': {
2605
+ 'must': [
2606
+ {
2607
+ 'terms': {
2608
+ 'clientId.keyword': [ inputData.clientId ],
2609
+ },
2610
+ },
2611
+
2612
+ ],
2613
+ 'should': [
2614
+ {
2615
+ 'terms': {
2616
+ 'clientName.keyword': getClientName[0].clientName, // Add clientName condition
2617
+ },
2618
+ },
2619
+ ],
2620
+ },
2621
+ },
2622
+ 'from': ( req.body.offset - 1 ) * req.body.limit,
2623
+ 'size': req.body.limit,
2624
+ 'sort': [
2625
+ {
2626
+ 'date': {
2627
+ 'order': 'desc',
2628
+ },
2629
+ },
2630
+ ],
2631
+ };
2632
+
2633
+ if ( req.body?.logTypeFilters?.length ) {
2634
+ query.query.bool.must.push(
2635
+ {
2636
+ 'terms': {
2637
+ 'logType.keyword': req.body.logTypeFilters,
2638
+ },
2639
+ },
2640
+ );
2641
+ }
2642
+
2643
+ if ( req.body?.startDate || req.body?.endDate ) {
2644
+ query.query.bool.must.push( {
2645
+ 'range': {
2646
+ 'date': {
2647
+ 'gte': req.body?.startDate ? req.body.startDate : '',
2648
+ 'lte': req.body?.endDate ? req.body.endDate : '',
2649
+ },
2650
+ },
2651
+ } );
2652
+ }
2653
+
2654
+ query.query.bool.must.push(
2655
+ {
2656
+ 'terms': {
2657
+ 'showTo.keyword': [ req.user.userType ],
2658
+ },
2659
+ },
2660
+ );
2661
+
2662
+ const logs = await getOpenSearchData( openSearch.activityLog, query );
2663
+
2664
+ const hits = logs?.body?.hits?.hits;
2665
+ const totalDocuments = logs?.body?.hits?.total?.value;
2666
+
2667
+ let temp = [];
2668
+ if ( totalDocuments ) {
2669
+ hits.map( ( hit, i ) => {
2670
+ let respo = {};
2671
+ hit._source.logSubType = hit._source.logSubType === 'documentUpload' ? 'documentsUpload' : hit._source.logSubType === 'domainDetails' ? 'securityFeatures' : hit?._source?.logSubType;
2672
+ if ( ( ( hit?._source?.eventType ).match( /update/ ) || ( hit?._source?.eventType ).match( /edit/ ) ) && hit?._source?.previous ) {
2673
+ const previous = hit?._source?.oldData;
2674
+ const current = hit?._source?.newData;
2675
+ if ( previous && current ) {
2676
+ respo = findDifferences( previous, current );
2677
+ }
2678
+ hit._source.updatedValue = respo;
2679
+ temp.push( hit?._source );
2680
+ // hit._source.changes =logSubType === 'ticketConfig'? Object.keys( respo ).map( ( item ) => item ) : hit._source.changes;
2681
+ } else {
2682
+ temp.push( hit?._source );
2683
+ }
2684
+ },
2685
+ );
2686
+ res.sendSuccess( { data: temp, count: totalDocuments } );
2687
+ } else {
2688
+ res.sendError( 'No data found', 204 );
2689
+ }
2130
2690
  } catch ( error ) {
2131
- logger.error( { error: error, message: 'Error in GET request', function: 'getApi' } );
2691
+ logger.error( { error: error, message: req.body, function: 'getActivityLogs' } );
2692
+ return res.sendError( 'Internal Server Error', 500 );
2132
2693
  }
2133
2694
  }
2134
2695
 
2696
+ function findDifferences( oldData, newData ) {
2697
+ logger.info( { oldData: oldData, newData: newData } );
2698
+ const allKeys = new Set( [ ...Object.keys( oldData ), ...Object.keys( newData ) ] );
2699
+ const diff = {};
2700
+
2701
+ allKeys.forEach( ( key ) => {
2702
+ const previous = key in oldData ? oldData[key] : null;
2703
+ const current = key in newData ? newData[key] : null;
2704
+
2705
+ // Only include fields that are added or updated (where previous !== current)
2706
+ if ( previous !== current ) {
2707
+ diff[splitCamelCase( key )] = {
2708
+ previous,
2709
+ current,
2710
+ };
2711
+ }
2712
+ } );
2713
+
2714
+ return diff;
2715
+ }
2716
+
2717
+ function splitCamelCase( text ) {
2718
+ return text.replace( /([a-z])([A-Z])/g, '$1 $2' ) // Add space between camelCase words
2719
+ .replace( /([a-zA-Z])(\d+)/g, '$1 $2' ) // Add space between letters and numbers
2720
+ .replace( /(\d+)([a-zA-Z])/g, '$1 $2' )
2721
+ .trim()
2722
+ .replace( /^./, ( match ) => match.toUpperCase() ); // Capitalize the first letter
2723
+ }
2724
+
2725
+ // async function postApi( url, data ) {
2726
+ // const requestOptions = {
2727
+ // method: 'POST',
2728
+ // headers: {
2729
+ // 'Content-Type': 'application/json',
2730
+ // 'Authorization': 'Bearer d47433f8-9a33-47c7-ba43-1a0fbac28f66',
2731
+ // },
2732
+ // body: JSON.stringify( data ),
2733
+ // };
2734
+
2735
+ // try {
2736
+ // const response = await fetch( url, requestOptions );
2737
+ // await response.json();
2738
+ // } catch ( error ) {
2739
+ // logger.error( { error: error, message: data, function: 'postApi' } );
2740
+ // }
2741
+ // }
2742
+
2743
+ // async function getApi( url ) {
2744
+ // const requestOptions = {
2745
+ // method: 'GET',
2746
+ // headers: {
2747
+ // 'Content-Type': 'application/json',
2748
+ // 'Authorization': 'Bearer d47433f8-9a33-47c7-ba43-1a0fbac28f66',
2749
+ // },
2750
+ // };
2751
+
2752
+ // try {
2753
+ // const response = await fetch( url, requestOptions );
2754
+ // const responseData = await response.json();
2755
+ // return responseData;
2756
+ // } catch ( error ) {
2757
+ // logger.error( { error: error, message: 'Error in GET request', function: 'getApi' } );
2758
+ // }
2759
+ // }
2760
+
2135
2761
 
2136
2762
  export async function csmAssignConfirmation( req, res ) {
2137
2763
  try {
@@ -2163,3 +2789,151 @@ export async function clientCsmAssignAction( req, res ) {
2163
2789
  return res.sendError( 'Internal Server Error', 500 );
2164
2790
  }
2165
2791
  }
2792
+
2793
+ function compareArrayObjects( prevArray, currArray ) {
2794
+ let changes = {};
2795
+ // logger.info( { prevArray: prevArray, currArray: currArray } );
2796
+ // Find the longest array length to avoid index mismatch
2797
+ let maxLength = Math.max( prevArray.length, currArray.length );
2798
+
2799
+ for ( let i = 0; i < maxLength; i++ ) {
2800
+ let prevObj = prevArray[i] || {}; // Default to empty object if missing
2801
+ let currObj = currArray[i] || {};
2802
+ let diff = {};
2803
+
2804
+ Object.keys( { ...prevObj, ...currObj } ).forEach( ( key ) => {
2805
+ if ( prevObj[key] !== currObj[key] && key !== '_id' ) {
2806
+ const key1 = key == 'contact' ? 'Contact Number' : key == 'email' ? 'Email ID' : key == 'name' ? 'Name' : key == 'designation' ? 'Designation' : key;
2807
+ const name = i == 0 ? 'Primary Spoc' : `Alternate Spoc ${i + 1}`;
2808
+ diff[`${name} ${key1}`] = {
2809
+ previous: prevObj[key] || 'null',
2810
+ current: currObj[key] || 'null',
2811
+ };
2812
+ }
2813
+ } );
2814
+
2815
+ if ( Object.keys( diff ).length > 0 ) {
2816
+ // changes.push( { index: i, changes: diff } );
2817
+ changes = { ...changes, ...diff };
2818
+ }
2819
+ }
2820
+
2821
+ return changes;
2822
+ }
2823
+
2824
+
2825
+ export async function createAuditConfig( req, res ) {
2826
+ try {
2827
+ await createauditConfig( req.body );
2828
+ return res.sendSuccess( 'created Sucessfully' );
2829
+ } catch ( error ) {
2830
+ logger.error( { error: error, message: req.body, function: 'createAuditConfig' } );
2831
+ return res.sendError( 'Internal Server Error', 500 );
2832
+ }
2833
+ }
2834
+ export async function updateAuditConfig( req, res ) {
2835
+ try {
2836
+ await updateauditConfig( { _id: req.params.id }, req.body );
2837
+ return res.sendSuccess( 'updated Sucessfully' );
2838
+ } catch ( error ) {
2839
+ logger.error( { error: error, message: req.body, function: 'createAuditConfig' } );
2840
+ return res.sendError( 'Internal Server Error', 500 );
2841
+ }
2842
+ }
2843
+ export async function AuditConfiglist( req, res ) {
2844
+ try {
2845
+ let Query = [ {
2846
+ $match: {
2847
+ '$and': [
2848
+ { 'clientId': { $in: req.body.clientId } },
2849
+ ],
2850
+ },
2851
+ } ];
2852
+ if ( req.body.searchValue && req.body.searchValue !== '' ) {
2853
+ const searchCondition = {
2854
+ $match: {
2855
+ $or: [
2856
+ { AuditName: { $regex: req.body.searchValue, $options: 'i' } },
2857
+ { keyWord: { $regex: req.body.searchValue, $options: 'i' } },
2858
+ ],
2859
+ },
2860
+ };
2861
+
2862
+ Query.push( searchCondition );
2863
+ }
2864
+ if ( req.body.sortColumName && req.body.sortColumName !== '' && req.body.sortBy ) {
2865
+ const sortOption = { $sort: { [req.body.sortColumName]: req.body.sortBy } };
2866
+ Query.push( sortOption );
2867
+ } else {
2868
+ const sortOption = { $sort: { 'AuditName': -1 } };
2869
+ Query.push( sortOption );
2870
+ }
2871
+
2872
+ let count = await aggregateAuditconfig( Query );
2873
+
2874
+ if ( req.body.limit && req.body.offset && !req.body.export ) {
2875
+ const skipValue = ( req.body.offset - 1 ) * req.body.limit;
2876
+ const limitValue = Number( req.body.limit );
2877
+
2878
+ const pagination = [
2879
+ { $skip: skipValue },
2880
+ { $limit: limitValue },
2881
+ ];
2882
+
2883
+ Query.push( ...pagination );
2884
+ }
2885
+
2886
+ let result = await aggregateAuditconfig( Query );
2887
+
2888
+ if ( req.body.export && result.length > 0 ) {
2889
+ const exportdata = result.map( ( element ) => {
2890
+ return {
2891
+ 'Audit Name': element.AuditName,
2892
+ 'Keyword': element.keyWord,
2893
+ 'File Type': element.fileType,
2894
+ 'Response Type': element.responseType,
2895
+ 'Status': element.status,
2896
+ };
2897
+ } );
2898
+ await download( exportdata, res );
2899
+ return;
2900
+ }
2901
+ return res.sendSuccess( { result: result, totalCount: count.length } );
2902
+ } catch ( error ) {
2903
+ logger.error( { error: error, message: req.body, function: 'createAuditConfig' } );
2904
+ return res.sendError( 'Internal Server Error', 500 );
2905
+ }
2906
+ }
2907
+
2908
+
2909
+ export async function revopconfig( req, res ) {
2910
+ try {
2911
+ let result = await findOnerevopConfig( { clientId: req.body.clientId } );
2912
+
2913
+ if ( result===null ) {
2914
+ await createrevopConfig( req.body );
2915
+ return res.sendSuccess( 'revopconfig created Sucessfully' );
2916
+ } else {
2917
+ await updaterevopConfig( { clientId: req.body.clientId }, req.body );
2918
+ return res.sendSuccess( 'revopconfig updated Sucessfully' );
2919
+ }
2920
+ } catch ( error ) {
2921
+ logger.error( { error: error, message: req.body, function: 'revopconfig' } );
2922
+ return res.sendError( 'Internal Server Error', 500 );
2923
+ }
2924
+ }
2925
+
2926
+ export async function getrevopconfig( req, res ) {
2927
+ try {
2928
+ let result = await findOnerevopConfig( { clientId: req.query.clientId } );
2929
+ if ( result===null ) {
2930
+ return res.sendError( 'no data found', 204 );
2931
+ }
2932
+ return res.sendSuccess( result );
2933
+ } catch ( error ) {
2934
+ logger.error( { error: error, message: req.query, function: 'revopconfig' } );
2935
+ return res.sendError( 'Internal Server Error', 500 );
2936
+ }
2937
+ }
2938
+
2939
+