tango-app-api-trax 3.9.38 → 3.9.39
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/internalTrax.controller.js +99 -221
- package/src/controllers/mobileTrax.controller.js +29 -69
- package/src/controllers/teaxFlag.controller.js +953 -15
- package/src/controllers/trax.controller.js +2 -3
- package/src/controllers/traxDashboard.controllers.js +55 -69
- 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 +91 -71
- package/src/routes/internalTraxApi.router.js +0 -1
- package/src/routes/trax.routes.js +3 -0
- package/src/routes/traxFlag.router.js +24 -0
- package/src/services/app.service.js +9 -15
- package/src/services/approver.service.js +15 -23
- package/src/services/authentication.service.js +3 -9
- package/src/services/camera.service.js +13 -19
- package/src/services/checklist.service.js +27 -35
- package/src/services/checklistAssign.service.js +38 -43
- package/src/services/checklistQuestion.service.js +34 -39
- package/src/services/checklistlog.service.js +34 -39
- package/src/services/clientRequest.service.js +2 -9
- package/src/services/clients.services.js +18 -23
- package/src/services/cluster.service.js +23 -31
- package/src/services/domain.service.js +18 -23
- package/src/services/download.services.js +25 -35
- package/src/services/group.service.js +17 -23
- package/src/services/lenskartEmployeeMapping.service.js +10 -15
- package/src/services/locus.service.js +28 -35
- package/src/services/notification.service.js +26 -35
- package/src/services/otp.service.js +13 -20
- package/src/services/planogram.service.js +2 -9
- package/src/services/processedTaskConfig.service.js +27 -35
- package/src/services/processedTaskList.service.js +26 -32
- package/src/services/processedchecklist.services.js +47 -55
- package/src/services/processedchecklistconfig.services.js +34 -39
- package/src/services/recurringFlagTracker.service.js +32 -39
- package/src/services/runAIFeatures.services.js +27 -32
- package/src/services/runAIRequest.services.js +38 -43
- package/src/services/store.service.js +27 -32
- package/src/services/tagging.service.js +2 -9
- package/src/services/taskConfig.service.js +27 -35
- package/src/services/teams.service.js +24 -35
- package/src/services/ticket.service.js +10 -15
- package/src/services/user.service.js +20 -27
- package/src/services/userAssignedstores.service.js +5 -12
- package/src/utils/visitChecklistPdf.utils.js +21 -449
- 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,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
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Post-process a steps array to compress repeated adjacent cycles into a single
|
|
3
|
-
* "batch" entry. Complements the push-time collapse of identical adjacent
|
|
4
|
-
* steps (which merges A,A,A into A×3) by merging repeated *sequences*
|
|
5
|
-
* (A,B,C,A,B,C,A,B,C → batch[A→B→C]×3).
|
|
6
|
-
*
|
|
7
|
-
* Rules:
|
|
8
|
-
* - A cycle is detected only if the same ordered sequence of step names/types
|
|
9
|
-
* appears ≥ 2 times back-to-back.
|
|
10
|
-
* - Failed steps are never included in cycles — each failure stands alone so
|
|
11
|
-
* you can see exactly which iteration broke.
|
|
12
|
-
* - Greedy: smallest cycle length (≥ 2) wins. This gives the most compact,
|
|
13
|
-
* most readable output.
|
|
14
|
-
* - Cycle length is bounded to keep the O(n·k) scan cheap.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
const MIN_CYCLE_LEN = 2;
|
|
18
|
-
const MAX_CYCLE_LEN = 10;
|
|
19
|
-
|
|
20
|
-
export function compressBatches( rawSteps ) {
|
|
21
|
-
if ( !Array.isArray( rawSteps ) || rawSteps.length < MIN_CYCLE_LEN * 2 ) return rawSteps;
|
|
22
|
-
|
|
23
|
-
const out = [];
|
|
24
|
-
let i = 0;
|
|
25
|
-
while ( i < rawSteps.length ) {
|
|
26
|
-
let absorbed = false;
|
|
27
|
-
const maxK = Math.min( MAX_CYCLE_LEN, Math.floor( ( rawSteps.length - i ) / 2 ) );
|
|
28
|
-
|
|
29
|
-
for ( let k = MIN_CYCLE_LEN; k <= maxK; k++ ) {
|
|
30
|
-
if ( !cycleEquals( rawSteps, i, k, i + k ) ) continue;
|
|
31
|
-
|
|
32
|
-
// At least 2 repeats confirmed. Count forward.
|
|
33
|
-
let repeats = 2;
|
|
34
|
-
let j = i + 2 * k;
|
|
35
|
-
while ( cycleEquals( rawSteps, i, k, j ) ) {
|
|
36
|
-
repeats++;
|
|
37
|
-
j += k;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const patternSteps = rawSteps.slice( i, i + k );
|
|
41
|
-
const totalMs = rawSteps.slice( i, j ).reduce( ( sum, s ) => sum + ( s.ms || 0 ), 0 );
|
|
42
|
-
|
|
43
|
-
out.push( {
|
|
44
|
-
stepIndex: out.length + 1,
|
|
45
|
-
name: patternSteps.map( ( s ) => s.count ? `${ s.name }×${ s.count }` : s.name ).join( ' → ' ),
|
|
46
|
-
type: 'batch',
|
|
47
|
-
status: 'success',
|
|
48
|
-
count: repeats,
|
|
49
|
-
ms: totalMs,
|
|
50
|
-
pattern: patternSteps.map( ( s ) => ( {
|
|
51
|
-
name: s.name,
|
|
52
|
-
type: s.type,
|
|
53
|
-
count: s.count ?? 1,
|
|
54
|
-
} ) ),
|
|
55
|
-
} );
|
|
56
|
-
|
|
57
|
-
i = j;
|
|
58
|
-
absorbed = true;
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if ( !absorbed ) {
|
|
63
|
-
out.push( { ...rawSteps[i], stepIndex: out.length + 1 } );
|
|
64
|
-
i++;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return out;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function cycleEquals( steps, patternStart, patternLen, checkStart ) {
|
|
72
|
-
if ( checkStart + patternLen > steps.length ) return false;
|
|
73
|
-
for ( let i = 0; i < patternLen; i++ ) {
|
|
74
|
-
const a = steps[patternStart + i];
|
|
75
|
-
const b = steps[checkStart + i];
|
|
76
|
-
if ( !a || !b ) return false;
|
|
77
|
-
if ( a.status === 'failed' || b.status === 'failed' ) return false;
|
|
78
|
-
if ( a.name !== b.name ) return false;
|
|
79
|
-
if ( a.type !== b.type ) return false;
|
|
80
|
-
if ( a.status !== b.status ) return false;
|
|
81
|
-
}
|
|
82
|
-
return true;
|
|
83
|
-
}
|
package/src/logging/config.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Self-contained configuration for the activity logging module.
|
|
3
|
-
*
|
|
4
|
-
* Read directly from process.env so this module has zero dependency on
|
|
5
|
-
* consumers' env.js layout. Getters (not cached values) so dotenv can load
|
|
6
|
-
* after this module is imported.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const DEFAULT_INDEX = 'trax-activity-logs-test';
|
|
10
|
-
const LOG_VERSION = 'v1';
|
|
11
|
-
|
|
12
|
-
export const loggingConfig = {
|
|
13
|
-
get enabled() {
|
|
14
|
-
return process.env.TRAX_ACTIVITY_LOGGING === 'true';
|
|
15
|
-
},
|
|
16
|
-
get index() {
|
|
17
|
-
try {
|
|
18
|
-
return JSON.parse( process.env.OPENSEARCH || '{}' )?.traxActivityLog || DEFAULT_INDEX;
|
|
19
|
-
} catch ( _e ) {
|
|
20
|
-
return DEFAULT_INDEX;
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
version: LOG_VERSION,
|
|
24
|
-
};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { getLogContext, pushStep, pushStepFailure } from './activityLogStore.js';
|
|
2
|
-
import { loggingConfig } from './config.js';
|
|
3
|
-
|
|
4
|
-
const WRITE_OPS = [
|
|
5
|
-
'create',
|
|
6
|
-
'updateOne',
|
|
7
|
-
'updateMany',
|
|
8
|
-
'deleteOne',
|
|
9
|
-
'deleteMany',
|
|
10
|
-
'upsertOne',
|
|
11
|
-
'insertMany',
|
|
12
|
-
'bulkUpsert',
|
|
13
|
-
'bulkWrite',
|
|
14
|
-
'removeKeys',
|
|
15
|
-
'findOneAndUpdate',
|
|
16
|
-
'findOneAndUpdate2',
|
|
17
|
-
'updateOneArrayItem',
|
|
18
|
-
'update',
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
export function createLoggableService( serviceModule, collectionName ) {
|
|
22
|
-
// Install the proxy unconditionally. The enabled-check happens at call time
|
|
23
|
-
// inside the `get` trap so env vars loaded after module import (via dotenv)
|
|
24
|
-
// are observed correctly.
|
|
25
|
-
return new Proxy( serviceModule, {
|
|
26
|
-
get( target, prop ) {
|
|
27
|
-
const original = target[prop];
|
|
28
|
-
if ( !WRITE_OPS.includes( prop ) || typeof original !== 'function' ) return original;
|
|
29
|
-
if ( !loggingConfig.enabled ) return original;
|
|
30
|
-
|
|
31
|
-
return async function( ...args ) {
|
|
32
|
-
const ctx = getLogContext();
|
|
33
|
-
const start = Date.now();
|
|
34
|
-
const stepName = `${ collectionName }.${ prop }`;
|
|
35
|
-
try {
|
|
36
|
-
const result = await original.apply( target, args );
|
|
37
|
-
if ( ctx ) pushStep( { name: stepName, type: 'db', status: 'success', ms: Date.now() - start } );
|
|
38
|
-
return result;
|
|
39
|
-
} catch ( e ) {
|
|
40
|
-
if ( ctx ) pushStepFailure( { name: stepName, type: 'db', ms: Date.now() - start }, e );
|
|
41
|
-
throw e;
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
},
|
|
45
|
-
} );
|
|
46
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { pushStep, pushStepFailure } from './activityLogStore.js';
|
|
2
|
-
|
|
3
|
-
export async function logExternalCall( serviceName, url, options = {} ) {
|
|
4
|
-
const start = Date.now();
|
|
5
|
-
const stepName = `external.${ serviceName }`;
|
|
6
|
-
try {
|
|
7
|
-
const res = await fetch( url, options );
|
|
8
|
-
pushStep( {
|
|
9
|
-
name: stepName,
|
|
10
|
-
type: 'external',
|
|
11
|
-
status: res.ok ? 'success' : 'failed',
|
|
12
|
-
ms: Date.now() - start,
|
|
13
|
-
} );
|
|
14
|
-
return res;
|
|
15
|
-
} catch ( e ) {
|
|
16
|
-
pushStepFailure( { name: stepName, type: 'external', ms: Date.now() - start }, e );
|
|
17
|
-
throw e;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export async function logQueueMessage( serviceName, queueUrl, messageBody, sendMessageToQueueFn ) {
|
|
22
|
-
const start = Date.now();
|
|
23
|
-
const stepName = `external.${ serviceName }`;
|
|
24
|
-
try {
|
|
25
|
-
const result = await sendMessageToQueueFn( queueUrl, messageBody );
|
|
26
|
-
pushStep( {
|
|
27
|
-
name: stepName,
|
|
28
|
-
type: 'external',
|
|
29
|
-
status: 'success',
|
|
30
|
-
ms: Date.now() - start,
|
|
31
|
-
} );
|
|
32
|
-
return result;
|
|
33
|
-
} catch ( e ) {
|
|
34
|
-
pushStepFailure( { name: stepName, type: 'external', ms: Date.now() - start }, e );
|
|
35
|
-
throw e;
|
|
36
|
-
}
|
|
37
|
-
}
|