tango-app-api-trax 3.9.32 → 3.9.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +1 -2
- package/package.json +2 -2
- package/src/controllers/gallery.controller.js +0 -2
- package/src/controllers/internalTrax.controller.js +78 -117
- package/src/controllers/mobileTrax.controller.js +29 -69
- package/src/controllers/teaxFlag.controller.js +5 -5
- package/src/controllers/trax.controller.js +3 -4
- package/src/controllers/traxDashboard.controllers.js +54 -63
- package/src/hbs/flag.hbs +1 -1
- package/src/hbs/login-otp.hbs +943 -943
- package/src/hbs/template.hbs +0 -7
- package/src/hbs/visit-checklist.hbs +1 -51
- package/src/services/app.service.js +14 -20
- package/src/services/approver.service.js +20 -28
- package/src/services/authentication.service.js +6 -12
- package/src/services/camera.service.js +18 -24
- package/src/services/checklist.service.js +31 -36
- package/src/services/checklistAssign.service.js +39 -44
- package/src/services/checklistQuestion.service.js +35 -40
- package/src/services/checklistlog.service.js +35 -40
- package/src/services/clientRequest.service.js +5 -12
- package/src/services/clients.services.js +18 -23
- package/src/services/cluster.service.js +28 -36
- package/src/services/domain.service.js +28 -33
- package/src/services/download.services.js +38 -48
- package/src/services/group.service.js +22 -28
- package/src/services/lenskartEmployeeMapping.service.js +15 -20
- package/src/services/locus.service.js +34 -41
- package/src/services/notification.service.js +31 -40
- package/src/services/otp.service.js +17 -24
- package/src/services/planogram.service.js +5 -12
- package/src/services/processedTaskConfig.service.js +32 -40
- package/src/services/processedTaskList.service.js +30 -36
- package/src/services/processedchecklist.services.js +48 -56
- package/src/services/processedchecklistconfig.services.js +35 -40
- package/src/services/recurringFlagTracker.service.js +33 -40
- package/src/services/runAIFeatures.services.js +31 -36
- package/src/services/runAIRequest.services.js +39 -44
- package/src/services/store.service.js +31 -36
- package/src/services/tagging.service.js +5 -12
- package/src/services/taskConfig.service.js +32 -40
- package/src/services/teams.service.js +30 -41
- package/src/services/ticket.service.js +15 -20
- package/src/services/user.service.js +25 -32
- package/src/services/userAssignedstores.service.js +10 -17
- package/src/utils/visitChecklistPdf.utils.js +11 -114
- package/src/logging/activityLogFlusher.js +0 -59
- package/src/logging/activityLogMiddleware.js +0 -45
- package/src/logging/activityLogStore.js +0 -91
- package/src/logging/compressBatches.js +0 -83
- package/src/logging/config.js +0 -24
- package/src/logging/createLoggableService.js +0 -46
- package/src/logging/logExternalCall.js +0 -37
|
@@ -1,41 +1,30 @@
|
|
|
1
|
-
import teamsModel from 'tango-api-schema/schema/teams.model.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
},
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const _svc = createLoggableService( _methods, 'teams' );
|
|
33
|
-
|
|
34
|
-
export const createTeamsModel = ( ...args ) => _svc.createTeamsModel( ...args );
|
|
35
|
-
export const updateOneTeams = ( ...args ) => _svc.updateOneTeams( ...args );
|
|
36
|
-
export const aggregateTeams = ( ...args ) => _svc.aggregateTeams( ...args );
|
|
37
|
-
export const deleteTeams = ( ...args ) => _svc.deleteTeams( ...args );
|
|
38
|
-
export const findOneTeams = ( ...args ) => _svc.findOneTeams( ...args );
|
|
39
|
-
export const findteams = ( ...args ) => _svc.findteams( ...args );
|
|
40
|
-
export const updateOneTeamModel = ( ...args ) => _svc.updateOneTeamModel( ...args );
|
|
41
|
-
export const countDocumentsTeams = ( ...args ) => _svc.countDocumentsTeams( ...args );
|
|
1
|
+
import teamsModel from 'tango-api-schema/schema/teams.model.js';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export async function createTeamsModel( data ) {
|
|
5
|
+
return await teamsModel.create( data );
|
|
6
|
+
};
|
|
7
|
+
export async function updateOneTeams( query, record ) {
|
|
8
|
+
return await teamsModel.updateOne( query, { $set: record }, { upsert: true } );
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export async function aggregateTeams( query ) {
|
|
12
|
+
return await teamsModel.aggregate( query );
|
|
13
|
+
};
|
|
14
|
+
export async function deleteTeams( query ={} ) {
|
|
15
|
+
return await teamsModel.deleteOne( query );
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export async function findOneTeams( query ={}, field={} ) {
|
|
19
|
+
return await teamsModel.findOne( query, field );
|
|
20
|
+
};
|
|
21
|
+
export async function findteams( query ={}, field={} ) {
|
|
22
|
+
return await teamsModel.find( query, field );
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export async function updateOneTeamModel( query, record ) {
|
|
26
|
+
return await teamsModel.updateOne( query, record );
|
|
27
|
+
}
|
|
28
|
+
export function countDocumentsTeams( query ) {
|
|
29
|
+
return teamsModel.countDocuments( query );
|
|
30
|
+
}
|
|
@@ -1,20 +1,15 @@
|
|
|
1
|
-
import model from 'tango-api-schema';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const _svc = createLoggableService( _methods, 'tickets' );
|
|
17
|
-
|
|
18
|
-
export const findOne = ( ...args ) => _svc.findOne( ...args );
|
|
19
|
-
export const create = ( ...args ) => _svc.create( ...args );
|
|
20
|
-
export const deleteOne = ( ...args ) => _svc.deleteOne( ...args );
|
|
1
|
+
import model from 'tango-api-schema';
|
|
2
|
+
|
|
3
|
+
export const findOne = async ( query={}, field={} ) => {
|
|
4
|
+
return model.aiTicketConfigModel.findOne( query, field );
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const create = async ( document = {} ) => {
|
|
8
|
+
return model.aiTicketConfigModel.create( document );
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const deleteOne = async ( query = {} ) => {
|
|
12
|
+
return model.aiTicketConfigModel.deleteOne( query );
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
|
|
@@ -1,32 +1,25 @@
|
|
|
1
|
-
import model from 'tango-api-schema';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
export const findOne = ( ...args ) => _svc.findOne( ...args );
|
|
28
|
-
export const find = ( ...args ) => _svc.find( ...args );
|
|
29
|
-
export const create = ( ...args ) => _svc.create( ...args );
|
|
30
|
-
export const deleteOne = ( ...args ) => _svc.deleteOne( ...args );
|
|
31
|
-
export const updateOne = ( ...args ) => _svc.updateOne( ...args );
|
|
32
|
-
export const aggregate = ( ...args ) => _svc.aggregate( ...args );
|
|
1
|
+
import model from 'tango-api-schema';
|
|
2
|
+
|
|
3
|
+
export const findOne = async ( query={}, field={} ) => {
|
|
4
|
+
return model.userModel.findOne( query, field ).sort( { userName: 1 } );
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const find = async ( query={}, field={} ) => {
|
|
8
|
+
return model.userModel.find( query, field ).sort( { userName: 1 } );
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const create = async ( document = {} ) => {
|
|
12
|
+
return model.userModel.create( document );
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const deleteOne = async ( query = {} ) => {
|
|
16
|
+
return model.userModel.deleteOne( query );
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const updateOne = async ( query = {}, record={} ) => {
|
|
20
|
+
return model.userModel.updateOne( query, { $set: record } );
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const aggregate = async ( query = {} ) => {
|
|
24
|
+
return model.userModel.aggregate( query );
|
|
25
|
+
};
|
|
@@ -1,17 +1,10 @@
|
|
|
1
|
-
import userAssignedStoreModel from 'tango-api-schema/schema/userAssignedStore.model.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
},
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const _svc = createLoggableService( _methods, 'userassignedstores' );
|
|
15
|
-
|
|
16
|
-
export const aggregateUserAssignedStore = ( ...args ) => _svc.aggregateUserAssignedStore( ...args );
|
|
17
|
-
export const findUserAssignedStore = ( ...args ) => _svc.findUserAssignedStore( ...args );
|
|
1
|
+
import userAssignedStoreModel from 'tango-api-schema/schema/userAssignedStore.model.js';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export async function aggregateUserAssignedStore( query ) {
|
|
5
|
+
return await userAssignedStoreModel.aggregate( query );
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export async function findUserAssignedStore( query ) {
|
|
9
|
+
return await userAssignedStoreModel.find( query );
|
|
10
|
+
};
|
|
@@ -180,21 +180,11 @@ function buildQuestionAnswerEntries( question ) {
|
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
const validationImage = flattenImageRefs( userAnswer?.validationImage );
|
|
183
|
-
// Videos captured alongside 'Capture Multiple Image with description' validation (no runAI; shown as links).
|
|
184
|
-
const validationVideo = flattenImageRefs( userAnswer?.validationVideo );
|
|
185
|
-
|
|
186
|
-
const referenceImage = userAnswer?.referenceImage || matchedAnswer?.referenceImage || '';
|
|
187
|
-
// Left column shows a reference image only for non-image/video, non-multipleImage answer types.
|
|
188
|
-
// When there is no reference image, uploaded/validation media is rendered on the left instead of the right.
|
|
189
|
-
const hasReferenceImage = question?.answerType !== 'image/video' &&
|
|
190
|
-
question?.answerType !== 'multipleImage' &&
|
|
191
|
-
Boolean( referenceImage || multiReferenceImage.length );
|
|
192
183
|
|
|
193
184
|
return {
|
|
194
185
|
answer: userAnswer?.answer || '',
|
|
195
186
|
answerType: getMediaDisplayType( question?.answerType, userAnswer ),
|
|
196
|
-
referenceImage,
|
|
197
|
-
hasReferenceImage,
|
|
187
|
+
referenceImage: userAnswer?.referenceImage || matchedAnswer?.referenceImage || '',
|
|
198
188
|
multiReferenceImage,
|
|
199
189
|
remarks: userAnswer?.remarks || '',
|
|
200
190
|
sopFlag: userAnswer?.sopFlag ?? matchedAnswer?.sopFlag ?? false,
|
|
@@ -202,40 +192,12 @@ function buildQuestionAnswerEntries( question ) {
|
|
|
202
192
|
validationType,
|
|
203
193
|
validationAnswer,
|
|
204
194
|
validationImage,
|
|
205
|
-
validationVideo,
|
|
206
195
|
validationDisplayType: getValidationDisplayType( validationType ),
|
|
207
196
|
};
|
|
208
197
|
} );
|
|
209
198
|
}
|
|
210
199
|
|
|
211
200
|
|
|
212
|
-
/**
|
|
213
|
-
* Extracts every 'Matched/Not Matched' value from a userAnswer's runAIData.
|
|
214
|
-
* Handles both shapes:
|
|
215
|
-
* - [ { answerImage, results: [ { featureName, value } ] } ] (per-image nested)
|
|
216
|
-
* - [ { featureName, value } ] (flat)
|
|
217
|
-
* @param {Array} runAIData
|
|
218
|
-
* @return {string[]} list of 'True' / 'False' values (in source order)
|
|
219
|
-
*/
|
|
220
|
-
function getRunAIMatchValues( runAIData ) {
|
|
221
|
-
if ( !Array.isArray( runAIData ) ) return [];
|
|
222
|
-
|
|
223
|
-
const values = [];
|
|
224
|
-
|
|
225
|
-
runAIData.forEach( ( item ) => {
|
|
226
|
-
if ( Array.isArray( item?.results ) ) {
|
|
227
|
-
item.results.forEach( ( res ) => {
|
|
228
|
-
if ( res?.featureName === 'Matched/Not Matched' ) values.push( res.value );
|
|
229
|
-
} );
|
|
230
|
-
} else if ( item?.featureName === 'Matched/Not Matched' ) {
|
|
231
|
-
values.push( item.value );
|
|
232
|
-
}
|
|
233
|
-
} );
|
|
234
|
-
|
|
235
|
-
return values;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
|
|
239
201
|
/**
|
|
240
202
|
|
|
241
203
|
* Map section array { sectionName, questions[] } (view API or processed checklist) to scores / template sections.
|
|
@@ -285,18 +247,14 @@ function mapSectionsFromQuestionAnswer( questionAnswer ) {
|
|
|
285
247
|
let score = q.compliance ? Math.max( ...q?.userAnswer?.map( ( o ) => o?.complianceScore ?? 0 ) ) : 0;
|
|
286
248
|
|
|
287
249
|
|
|
288
|
-
if ( q.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
if ( matchValues.length ) {
|
|
297
|
-
// All images must be matched ('True'); if any one is not, treat as not matched.
|
|
298
|
-
const allMatched = matchValues.every( ( value ) => value === 'True' );
|
|
299
|
-
score = allMatched ? ( q?.answers?.[0]?.matchedCount ?? 0 ) : ( q?.answers?.[0]?.notMatchedCount ?? 0 );
|
|
250
|
+
if ( q.answerType == 'image' && q.compliance && q.userAnswer?.[0]?.runAIData ) {
|
|
251
|
+
let find = q.userAnswer?.[0]?.runAIData?.find( ( run ) => run?.featureName == 'Matched/Not Matched' );
|
|
252
|
+
if ( find ) {
|
|
253
|
+
if ( find?.value == 'True' ) {
|
|
254
|
+
score = q?.answers?.[0]?.matchedCount;
|
|
255
|
+
} else {
|
|
256
|
+
score = q?.answers?.[0]?.notMatchedCount;
|
|
257
|
+
}
|
|
300
258
|
}
|
|
301
259
|
}
|
|
302
260
|
|
|
@@ -523,9 +481,6 @@ export function buildVisitChecklistTemplateDataFromProcessed( processedDoc, bran
|
|
|
523
481
|
|
|
524
482
|
let referenceId = doc.coverage == 'store' ? doc?.storeName : doc?.userName;
|
|
525
483
|
|
|
526
|
-
const userImage = doc.userImage || '';
|
|
527
|
-
const userSignature = doc.userSignature || '';
|
|
528
|
-
|
|
529
484
|
return {
|
|
530
485
|
|
|
531
486
|
|
|
@@ -557,12 +512,6 @@ export function buildVisitChecklistTemplateDataFromProcessed( processedDoc, bran
|
|
|
557
512
|
|
|
558
513
|
country: doc.country || '--',
|
|
559
514
|
|
|
560
|
-
userImage,
|
|
561
|
-
|
|
562
|
-
userSignature,
|
|
563
|
-
|
|
564
|
-
showUserVerification: Boolean( userImage || userSignature ),
|
|
565
|
-
|
|
566
515
|
hasCompliancePage,
|
|
567
516
|
|
|
568
517
|
detailPageStart,
|
|
@@ -673,9 +622,6 @@ function buildFromViewChecklistApi( getchecklistData, viewchecklistData, brandIn
|
|
|
673
622
|
} );
|
|
674
623
|
}
|
|
675
624
|
|
|
676
|
-
const userImage = checklistAnswer?.userImage || checklistInfo?.userImage || '';
|
|
677
|
-
const userSignature = checklistAnswer?.userSignature || checklistInfo?.userSignature || '';
|
|
678
|
-
|
|
679
625
|
return {
|
|
680
626
|
|
|
681
627
|
brandLogo: brandInfo.brandLogo || '',
|
|
@@ -710,12 +656,6 @@ function buildFromViewChecklistApi( getchecklistData, viewchecklistData, brandIn
|
|
|
710
656
|
|
|
711
657
|
country: storeProfile?.Country || '--',
|
|
712
658
|
|
|
713
|
-
userImage,
|
|
714
|
-
|
|
715
|
-
userSignature,
|
|
716
|
-
|
|
717
|
-
showUserVerification: Boolean( userImage || userSignature ),
|
|
718
|
-
|
|
719
659
|
hasCompliancePage,
|
|
720
660
|
|
|
721
661
|
detailPageStart,
|
|
@@ -842,16 +782,6 @@ export function createImageCache() {
|
|
|
842
782
|
}
|
|
843
783
|
|
|
844
784
|
|
|
845
|
-
if ( resolvedData.userImage && resolvedData.userImage.startsWith( 'http' ) ) {
|
|
846
|
-
urls.add( resolvedData.userImage );
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
if ( resolvedData.userSignature && resolvedData.userSignature.startsWith( 'http' ) ) {
|
|
851
|
-
urls.add( resolvedData.userSignature );
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
|
|
855
785
|
const collectFromSection = ( section ) => {
|
|
856
786
|
section.questions?.forEach( ( q ) => {
|
|
857
787
|
if ( q.questionReferenceImage && q.questionReferenceImage.startsWith( 'http' ) ) urls.add( q.questionReferenceImage );
|
|
@@ -912,16 +842,6 @@ export function createImageCache() {
|
|
|
912
842
|
}
|
|
913
843
|
|
|
914
844
|
|
|
915
|
-
if ( resolvedData.userImage ) {
|
|
916
|
-
resolvedData.userImage = cache.get( resolvedData.userImage ) || resolvedData.userImage;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
if ( resolvedData.userSignature ) {
|
|
921
|
-
resolvedData.userSignature = cache.get( resolvedData.userSignature ) || resolvedData.userSignature;
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
|
|
925
845
|
const replaceInSection = ( section ) => {
|
|
926
846
|
section.questions?.forEach( ( q ) => {
|
|
927
847
|
if ( q.questionReferenceImage && cache.has( q.questionReferenceImage ) ) q.questionReferenceImage = cache.get( q.questionReferenceImage );
|
|
@@ -1016,10 +936,6 @@ export function resolveTemplateUrls( templateData, baseUrl = 'https://d1r0hc2ssk
|
|
|
1016
936
|
ua.validationImage = ua.validationImage.map( ( ele ) => resolveUrl( ele ) );
|
|
1017
937
|
}
|
|
1018
938
|
|
|
1019
|
-
if ( ua?.validationVideo?.length ) {
|
|
1020
|
-
ua.validationVideo = ua.validationVideo.map( ( ele ) => resolveUrl( ele ) );
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
939
|
|
|
1024
940
|
if ( ua.answer && ua.answerType !== 'text' ) {
|
|
1025
941
|
ua.answer = resolveUrl( ua.answer );
|
|
@@ -1038,16 +954,9 @@ export function resolveTemplateUrls( templateData, baseUrl = 'https://d1r0hc2ssk
|
|
|
1038
954
|
|
|
1039
955
|
resolvedData.sections?.forEach( resolveQuestionMedia );
|
|
1040
956
|
|
|
1041
|
-
if ( resolvedData?.brandLogo && !resolvedData?.brandLogo?.startsWith( 'http' ) ) {
|
|
1042
|
-
resolvedData.brandLogo = resolveUrl( resolvedData.brandLogo );
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
if ( resolvedData?.userImage && !resolvedData.userImage.startsWith( 'http' ) && !resolvedData.userImage.startsWith( 'data:' ) ) {
|
|
1046
|
-
resolvedData.userImage = resolveUrl( resolvedData.userImage );
|
|
1047
|
-
}
|
|
1048
957
|
|
|
1049
|
-
if ( resolvedData
|
|
1050
|
-
resolvedData.
|
|
958
|
+
if ( resolvedData.brandLogo && !resolvedData.brandLogo.startsWith( 'http' ) ) {
|
|
959
|
+
resolvedData.brandLogo = resolveUrl( resolvedData.brandLogo );
|
|
1051
960
|
}
|
|
1052
961
|
|
|
1053
962
|
|
|
@@ -1161,10 +1070,6 @@ export async function generateVisitChecklistPDF( templateData, baseUrl = 'https:
|
|
|
1161
1070
|
ua.validationImage = ua.validationImage.map( ( ele ) => resolveUrl( ele ) );
|
|
1162
1071
|
}
|
|
1163
1072
|
|
|
1164
|
-
if ( ua.validationVideo?.length ) {
|
|
1165
|
-
ua.validationVideo = ua.validationVideo.map( ( ele ) => resolveUrl( ele ) );
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
1073
|
if ( ua.answer && ua.answerType !== 'text' ) {
|
|
1169
1074
|
ua.answer = resolveUrl( ua.answer );
|
|
1170
1075
|
}
|
|
@@ -1182,14 +1087,6 @@ export async function generateVisitChecklistPDF( templateData, baseUrl = 'https:
|
|
|
1182
1087
|
resolvedData.brandLogo = resolveUrl( resolvedData.brandLogo );
|
|
1183
1088
|
}
|
|
1184
1089
|
|
|
1185
|
-
if ( resolvedData.userImage && !resolvedData.userImage.startsWith( 'http' ) && !resolvedData.userImage.startsWith( 'data:' ) ) {
|
|
1186
|
-
resolvedData.userImage = resolveUrl( resolvedData.userImage );
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
if ( resolvedData.userSignature && !resolvedData.userSignature.startsWith( 'http' ) && !resolvedData.userSignature.startsWith( 'data:' ) ) {
|
|
1190
|
-
resolvedData.userSignature = resolveUrl( resolvedData.userSignature );
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
1090
|
|
|
1194
1091
|
const html = template( resolvedData );
|
|
1195
1092
|
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { insertOpenSearchData, logger } from 'tango-app-api-middleware';
|
|
2
|
-
import { loggingConfig } from './config.js';
|
|
3
|
-
import { compressBatches } from './compressBatches.js';
|
|
4
|
-
|
|
5
|
-
export async function flushActivityLog( ctx, status ) {
|
|
6
|
-
if ( !loggingConfig.enabled ) return;
|
|
7
|
-
if ( !ctx ) return;
|
|
8
|
-
|
|
9
|
-
if ( ctx.steps.length === 0 && !ctx.error ) return;
|
|
10
|
-
|
|
11
|
-
const compressedSteps = safeCompress( ctx.steps );
|
|
12
|
-
|
|
13
|
-
const doc = {
|
|
14
|
-
version: loggingConfig.version,
|
|
15
|
-
requestId: ctx.requestId,
|
|
16
|
-
timestamp: new Date( ctx.startTime ).toISOString(),
|
|
17
|
-
duration: Date.now() - ctx.startTime,
|
|
18
|
-
user: ctx.user,
|
|
19
|
-
api: {
|
|
20
|
-
method: ctx.api.method,
|
|
21
|
-
path: ctx.api.path,
|
|
22
|
-
action: ctx.api.action,
|
|
23
|
-
body: stringifyBody( ctx.api.body ),
|
|
24
|
-
},
|
|
25
|
-
response: {
|
|
26
|
-
code: ctx.response?.code ?? null,
|
|
27
|
-
body: stringifyBody( ctx.response?.body ),
|
|
28
|
-
},
|
|
29
|
-
status,
|
|
30
|
-
steps: compressedSteps,
|
|
31
|
-
error: ctx.error,
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
await insertOpenSearchData( loggingConfig.index, doc );
|
|
36
|
-
} catch ( e ) {
|
|
37
|
-
logger.error( { functionName: 'flushActivityLog', error: e } );
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function safeCompress( steps ) {
|
|
42
|
-
try {
|
|
43
|
-
return compressBatches( steps );
|
|
44
|
-
} catch ( _e ) {
|
|
45
|
-
// Never let compression failure block log delivery — fall back to raw steps.
|
|
46
|
-
return steps;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function stringifyBody( body ) {
|
|
51
|
-
if ( body === null || body === undefined ) return null;
|
|
52
|
-
if ( typeof body === 'string' ) return body;
|
|
53
|
-
if ( body instanceof Error ) return body.message || String( body );
|
|
54
|
-
try {
|
|
55
|
-
return JSON.stringify( body );
|
|
56
|
-
} catch ( _e ) {
|
|
57
|
-
return '[unserializable]';
|
|
58
|
-
}
|
|
59
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { activityLogStore, createLogContext } from './activityLogStore.js';
|
|
2
|
-
import { flushActivityLog } from './activityLogFlusher.js';
|
|
3
|
-
import { loggingConfig } from './config.js';
|
|
4
|
-
|
|
5
|
-
function refreshUser( ctx, req ) {
|
|
6
|
-
if ( req?.user ) {
|
|
7
|
-
ctx.user = {
|
|
8
|
-
_id: req.user._id,
|
|
9
|
-
userName: req.user.userName,
|
|
10
|
-
email: req.user.email,
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export default function traxActivityLogMiddleware( req, res, next ) {
|
|
16
|
-
if ( !loggingConfig.enabled ) return next();
|
|
17
|
-
|
|
18
|
-
const WRITE_METHODS = [ 'POST', 'PUT', 'PATCH', 'DELETE' ];
|
|
19
|
-
if ( !WRITE_METHODS.includes( req.method ) ) return next();
|
|
20
|
-
|
|
21
|
-
const ctx = createLogContext( req );
|
|
22
|
-
|
|
23
|
-
activityLogStore.run( ctx, () => {
|
|
24
|
-
const originalSendSuccess = res.sendSuccess;
|
|
25
|
-
const originalSendError = res.sendError;
|
|
26
|
-
|
|
27
|
-
res.sendSuccess = ( data ) => {
|
|
28
|
-
refreshUser( ctx, req );
|
|
29
|
-
ctx.response.code = 200;
|
|
30
|
-
ctx.response.body = data;
|
|
31
|
-
originalSendSuccess( data );
|
|
32
|
-
setImmediate( () => flushActivityLog( ctx, 'success' ).catch( () => {} ) );
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
res.sendError = ( message, code ) => {
|
|
36
|
-
refreshUser( ctx, req );
|
|
37
|
-
ctx.response.code = code ?? 500;
|
|
38
|
-
ctx.response.body = message;
|
|
39
|
-
originalSendError( message, code );
|
|
40
|
-
setImmediate( () => flushActivityLog( ctx, 'failed' ).catch( () => {} ) );
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
next();
|
|
44
|
-
} );
|
|
45
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'async_hooks';
|
|
2
|
-
import { randomUUID } from 'crypto';
|
|
3
|
-
|
|
4
|
-
export const activityLogStore = new AsyncLocalStorage();
|
|
5
|
-
|
|
6
|
-
const SENSITIVE_KEYS = [ 'password', 'token', 'secret', 'authorization' ];
|
|
7
|
-
|
|
8
|
-
export function createLogContext( req ) {
|
|
9
|
-
return {
|
|
10
|
-
requestId: randomUUID(),
|
|
11
|
-
startTime: Date.now(),
|
|
12
|
-
user: req.user ? {
|
|
13
|
-
_id: req.user._id,
|
|
14
|
-
userName: req.user.userName,
|
|
15
|
-
email: req.user.email,
|
|
16
|
-
} : null,
|
|
17
|
-
api: {
|
|
18
|
-
method: req.method,
|
|
19
|
-
path: req.path,
|
|
20
|
-
action: `${ req.method } ${ req.path }`,
|
|
21
|
-
body: sanitizeBody( req.body ),
|
|
22
|
-
},
|
|
23
|
-
response: {
|
|
24
|
-
code: null,
|
|
25
|
-
body: null,
|
|
26
|
-
},
|
|
27
|
-
steps: [],
|
|
28
|
-
stepCounter: 0,
|
|
29
|
-
error: null,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function getLogContext() {
|
|
34
|
-
return activityLogStore.getStore();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function pushStep( entry ) {
|
|
38
|
-
const ctx = getLogContext();
|
|
39
|
-
if ( !ctx ) return;
|
|
40
|
-
|
|
41
|
-
const last = ctx.steps[ctx.steps.length - 1];
|
|
42
|
-
if (
|
|
43
|
-
last &&
|
|
44
|
-
last.name === entry.name &&
|
|
45
|
-
last.type === entry.type &&
|
|
46
|
-
last.status === 'success' &&
|
|
47
|
-
entry.status === 'success'
|
|
48
|
-
) {
|
|
49
|
-
last.count = ( last.count || 1 ) + 1;
|
|
50
|
-
last.ms += entry.ms;
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
ctx.stepCounter++;
|
|
55
|
-
ctx.steps.push( {
|
|
56
|
-
stepIndex: ctx.stepCounter,
|
|
57
|
-
name: entry.name,
|
|
58
|
-
type: entry.type,
|
|
59
|
-
status: entry.status,
|
|
60
|
-
ms: entry.ms,
|
|
61
|
-
} );
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function pushStepFailure( entry, error ) {
|
|
65
|
-
const ctx = getLogContext();
|
|
66
|
-
if ( !ctx ) return;
|
|
67
|
-
ctx.stepCounter++;
|
|
68
|
-
const message = error?.message || String( error );
|
|
69
|
-
ctx.steps.push( {
|
|
70
|
-
stepIndex: ctx.stepCounter,
|
|
71
|
-
name: entry.name,
|
|
72
|
-
type: entry.type,
|
|
73
|
-
status: 'failed',
|
|
74
|
-
ms: entry.ms,
|
|
75
|
-
error: message,
|
|
76
|
-
} );
|
|
77
|
-
ctx.error = {
|
|
78
|
-
message,
|
|
79
|
-
failedAtStepIndex: ctx.stepCounter,
|
|
80
|
-
failedStepName: entry.name,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function sanitizeBody( body ) {
|
|
85
|
-
if ( !body || typeof body !== 'object' ) return body;
|
|
86
|
-
const cleaned = { ...body };
|
|
87
|
-
for ( const key of SENSITIVE_KEYS ) {
|
|
88
|
-
if ( cleaned[key] !== undefined ) cleaned[key] = '[REDACTED]';
|
|
89
|
-
}
|
|
90
|
-
return cleaned;
|
|
91
|
-
}
|