@govish/shared-services 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/README.md +376 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +36 -0
- package/dist/middleware/authenticateDevice.d.ts +15 -0
- package/dist/middleware/authenticateDevice.js +310 -0
- package/dist/services/apiKeyService.d.ts +53 -0
- package/dist/services/apiKeyService.js +293 -0
- package/dist/services/auditService.d.ts +109 -0
- package/dist/services/auditService.js +785 -0
- package/dist/services/deviceCacheService.d.ts +46 -0
- package/dist/services/deviceCacheService.js +432 -0
- package/dist/services/deviceService.d.ts +21 -0
- package/dist/services/deviceService.js +103 -0
- package/dist/services/officerCacheService.d.ts +50 -0
- package/dist/services/officerCacheService.js +434 -0
- package/dist/services/officerService.d.ts +25 -0
- package/dist/services/officerService.js +177 -0
- package/dist/services/penalCodeCacheService.d.ts +20 -0
- package/dist/services/penalCodeCacheService.js +109 -0
- package/dist/types/dependencies.d.ts +23 -0
- package/dist/types/dependencies.js +2 -0
- package/dist/utils/logMode.d.ts +33 -0
- package/dist/utils/logMode.js +90 -0
- package/dist/utils/redis.d.ts +24 -0
- package/dist/utils/redis.js +122 -0
- package/package.json +52 -0
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.AuditService = exports.AuditEventType = void 0;
|
|
13
|
+
const kafkajs_1 = require("kafkajs");
|
|
14
|
+
/**
|
|
15
|
+
* Audit event types
|
|
16
|
+
*/
|
|
17
|
+
var AuditEventType;
|
|
18
|
+
(function (AuditEventType) {
|
|
19
|
+
// Authentication events
|
|
20
|
+
AuditEventType["LOGIN_SUCCESS"] = "login_success";
|
|
21
|
+
AuditEventType["LOGIN_FAILED"] = "login_failed";
|
|
22
|
+
AuditEventType["LOGIN_NOT_PERMITTED"] = "login_not_permitted";
|
|
23
|
+
AuditEventType["LOGOUT"] = "logout";
|
|
24
|
+
AuditEventType["TOKEN_GENERATED"] = "token_generated";
|
|
25
|
+
// Officer events (read-only consumption)
|
|
26
|
+
AuditEventType["OFFICER_RETRIEVED"] = "officer_retrieved";
|
|
27
|
+
AuditEventType["OFFICERS_LISTED"] = "officers_listed";
|
|
28
|
+
// Device events (read-only consumption)
|
|
29
|
+
AuditEventType["DEVICE_RETRIEVED"] = "device_retrieved";
|
|
30
|
+
AuditEventType["DEVICES_LISTED"] = "devices_listed";
|
|
31
|
+
// Arrest events
|
|
32
|
+
AuditEventType["ARREST_CREATED"] = "arrest_created";
|
|
33
|
+
AuditEventType["ARREST_UPDATED"] = "arrest_updated";
|
|
34
|
+
AuditEventType["ARREST_DELETED"] = "arrest_deleted";
|
|
35
|
+
AuditEventType["ARREST_RETRIEVED"] = "arrest_retrieved";
|
|
36
|
+
AuditEventType["ARRESTS_LISTED"] = "arrests_listed";
|
|
37
|
+
AuditEventType["ARREST_OB_GENERATED"] = "arrest_ob_generated";
|
|
38
|
+
// General access events
|
|
39
|
+
AuditEventType["ENDPOINT_ACCESSED"] = "endpoint_accessed";
|
|
40
|
+
AuditEventType["UNAUTHORIZED_ACCESS"] = "unauthorized_access";
|
|
41
|
+
AuditEventType["FORBIDDEN_ACCESS"] = "forbidden_access";
|
|
42
|
+
})(AuditEventType || (exports.AuditEventType = AuditEventType = {}));
|
|
43
|
+
class AuditService {
|
|
44
|
+
constructor(deps) {
|
|
45
|
+
this.isInternalConnection = false;
|
|
46
|
+
this.connectionAttempted = false;
|
|
47
|
+
this.logger = deps.logger;
|
|
48
|
+
this.serverName = deps.serverName;
|
|
49
|
+
this.auditTopic = deps.auditTopic;
|
|
50
|
+
this.enableDebugLogs = deps.enableDebugLogs || false;
|
|
51
|
+
// If Kafka brokers are provided, create internal connection
|
|
52
|
+
if (deps.kafkaBrokers) {
|
|
53
|
+
this.initializeInternalKafkaConnection(deps);
|
|
54
|
+
}
|
|
55
|
+
else if (deps.kafkaProducer) {
|
|
56
|
+
// Use provided producer (backward compatibility)
|
|
57
|
+
this.kafkaProducer = deps.kafkaProducer;
|
|
58
|
+
this.isProducerConnected = deps.isProducerConnected;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Initialize internal Kafka connection from brokers
|
|
63
|
+
*/
|
|
64
|
+
initializeInternalKafkaConnection(deps) {
|
|
65
|
+
try {
|
|
66
|
+
// Parse brokers
|
|
67
|
+
const brokers = Array.isArray(deps.kafkaBrokers)
|
|
68
|
+
? deps.kafkaBrokers
|
|
69
|
+
: typeof deps.kafkaBrokers === 'string'
|
|
70
|
+
? deps.kafkaBrokers.split(',').map(b => b.trim())
|
|
71
|
+
: [];
|
|
72
|
+
if (brokers.length === 0) {
|
|
73
|
+
this.logger.warn('No valid Kafka brokers provided');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const clientId = deps.kafkaClientId || 'audit-service';
|
|
77
|
+
// Create Kafka client
|
|
78
|
+
this.internalKafkaClient = new kafkajs_1.Kafka({
|
|
79
|
+
clientId,
|
|
80
|
+
brokers,
|
|
81
|
+
retry: {
|
|
82
|
+
retries: 3,
|
|
83
|
+
initialRetryTime: 1000,
|
|
84
|
+
multiplier: 2,
|
|
85
|
+
maxRetryTime: 5000,
|
|
86
|
+
},
|
|
87
|
+
connectionTimeout: 5000,
|
|
88
|
+
requestTimeout: 30000,
|
|
89
|
+
});
|
|
90
|
+
// Create producer
|
|
91
|
+
this.internalProducer = this.internalKafkaClient.producer({
|
|
92
|
+
createPartitioner: kafkajs_1.Partitioners.LegacyPartitioner,
|
|
93
|
+
retry: {
|
|
94
|
+
retries: 8,
|
|
95
|
+
initialRetryTime: 100,
|
|
96
|
+
multiplier: 2,
|
|
97
|
+
maxRetryTime: 30000,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
this.kafkaProducer = this.internalProducer;
|
|
101
|
+
this.isInternalConnection = true;
|
|
102
|
+
// Create connection check function
|
|
103
|
+
this.isProducerConnected = () => {
|
|
104
|
+
// For internal producer, we'll check connection status
|
|
105
|
+
// KafkaJS doesn't expose connection state directly, so we track it
|
|
106
|
+
return this.connectionAttempted && this.internalProducer !== undefined;
|
|
107
|
+
};
|
|
108
|
+
this.logger.info('Internal Kafka connection initialized', {
|
|
109
|
+
brokers,
|
|
110
|
+
clientId,
|
|
111
|
+
});
|
|
112
|
+
// Try to connect immediately (non-blocking)
|
|
113
|
+
this.ensureConnected().catch((error) => {
|
|
114
|
+
this.logger.warn('Initial connection attempt failed, will retry on first send', {
|
|
115
|
+
error: error instanceof Error ? error.message : String(error),
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
this.logger.error('Failed to initialize internal Kafka connection', {
|
|
121
|
+
error: error instanceof Error ? error.message : String(error),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Debug log helper - only logs if debug is enabled
|
|
127
|
+
*/
|
|
128
|
+
debugLog(message, data) {
|
|
129
|
+
if (this.enableDebugLogs) {
|
|
130
|
+
if (data) {
|
|
131
|
+
console.log(`[AUDIT KAFKA] ${message}`, data);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
console.log(`[AUDIT KAFKA] ${message}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Debug error helper - only logs if debug is enabled
|
|
140
|
+
*/
|
|
141
|
+
debugError(message, data) {
|
|
142
|
+
if (this.enableDebugLogs) {
|
|
143
|
+
if (data) {
|
|
144
|
+
console.error(`[AUDIT KAFKA] ${message}`, data);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
console.error(`[AUDIT KAFKA] ${message}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Ensure producer is connected (for internal connections)
|
|
153
|
+
*/
|
|
154
|
+
ensureConnected() {
|
|
155
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
156
|
+
if (!this.isInternalConnection || !this.internalProducer) {
|
|
157
|
+
this.debugLog('Not internal connection or no internal producer', {
|
|
158
|
+
isInternalConnection: this.isInternalConnection,
|
|
159
|
+
hasInternalProducer: !!this.internalProducer,
|
|
160
|
+
hasKafkaProducer: !!this.kafkaProducer,
|
|
161
|
+
});
|
|
162
|
+
return !!this.kafkaProducer;
|
|
163
|
+
}
|
|
164
|
+
// Always try to connect if not already connected
|
|
165
|
+
// Don't rely on connectionAttempted flag - actually check connection
|
|
166
|
+
try {
|
|
167
|
+
if (!this.connectionAttempted) {
|
|
168
|
+
this.connectionAttempted = true;
|
|
169
|
+
yield this.internalProducer.connect();
|
|
170
|
+
this.logger.info('Internal Kafka producer connected');
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
// Already attempted - assume connected
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
this.connectionAttempted = false;
|
|
180
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
181
|
+
this.debugError('Failed to connect internal Kafka producer', {
|
|
182
|
+
error: errorMessage,
|
|
183
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
184
|
+
});
|
|
185
|
+
this.logger.warn('Failed to connect internal Kafka producer', {
|
|
186
|
+
error: errorMessage,
|
|
187
|
+
});
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Send audit event to Kafka
|
|
194
|
+
*/
|
|
195
|
+
logAuditEvent(req, payload) {
|
|
196
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
197
|
+
var _a, _b, _c, _d;
|
|
198
|
+
try {
|
|
199
|
+
// Get IP address - try multiple sources in order of reliability
|
|
200
|
+
let ip_address = 'unknown';
|
|
201
|
+
// Log available IP sources for debugging
|
|
202
|
+
const ipSources = {
|
|
203
|
+
'x-forwarded-for': req.headers['x-forwarded-for'],
|
|
204
|
+
'x-real-ip': req.headers['x-real-ip'],
|
|
205
|
+
'cf-connecting-ip': req.headers['cf-connecting-ip'], // Cloudflare
|
|
206
|
+
'true-client-ip': req.headers['true-client-ip'], // Cloudflare Enterprise
|
|
207
|
+
'x-client-ip': req.headers['x-client-ip'],
|
|
208
|
+
'req.ip': req.ip,
|
|
209
|
+
'req.socket.remoteAddress': (_a = req.socket) === null || _a === void 0 ? void 0 : _a.remoteAddress,
|
|
210
|
+
'req.connection.remoteAddress': (_b = req.connection) === null || _b === void 0 ? void 0 : _b.remoteAddress,
|
|
211
|
+
};
|
|
212
|
+
this.debugLog('IP address sources', ipSources);
|
|
213
|
+
// Try x-forwarded-for first (most common behind proxies/load balancers)
|
|
214
|
+
// Format: "client, proxy1, proxy2" - we want the first (original client)
|
|
215
|
+
if (req.headers['x-forwarded-for']) {
|
|
216
|
+
const forwardedFor = String(req.headers['x-forwarded-for']).trim();
|
|
217
|
+
if (forwardedFor) {
|
|
218
|
+
// Split by comma and get first IP, then trim whitespace
|
|
219
|
+
const firstIp = forwardedFor.split(',')[0].trim();
|
|
220
|
+
if (firstIp && firstIp !== 'unknown') {
|
|
221
|
+
ip_address = firstIp;
|
|
222
|
+
this.logger.debug('Using IP from x-forwarded-for', { ip: ip_address });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Try Cloudflare specific header
|
|
227
|
+
if (ip_address === 'unknown' && req.headers['cf-connecting-ip']) {
|
|
228
|
+
const cfIp = String(req.headers['cf-connecting-ip']).trim();
|
|
229
|
+
if (cfIp) {
|
|
230
|
+
ip_address = cfIp;
|
|
231
|
+
this.logger.debug('Using IP from cf-connecting-ip', { ip: ip_address });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Try x-real-ip (common with nginx)
|
|
235
|
+
if (ip_address === 'unknown' && req.headers['x-real-ip']) {
|
|
236
|
+
const realIp = String(req.headers['x-real-ip']).trim();
|
|
237
|
+
if (realIp) {
|
|
238
|
+
ip_address = realIp;
|
|
239
|
+
this.logger.debug('Using IP from x-real-ip', { ip: ip_address });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Try true-client-ip (Cloudflare Enterprise)
|
|
243
|
+
if (ip_address === 'unknown' && req.headers['true-client-ip']) {
|
|
244
|
+
const trueClientIp = String(req.headers['true-client-ip']).trim();
|
|
245
|
+
if (trueClientIp) {
|
|
246
|
+
ip_address = trueClientIp;
|
|
247
|
+
this.logger.debug('Using IP from true-client-ip', { ip: ip_address });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Try x-client-ip
|
|
251
|
+
if (ip_address === 'unknown' && req.headers['x-client-ip']) {
|
|
252
|
+
const clientIp = String(req.headers['x-client-ip']).trim();
|
|
253
|
+
if (clientIp) {
|
|
254
|
+
ip_address = clientIp;
|
|
255
|
+
this.logger.debug('Using IP from x-client-ip', { ip: ip_address });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Try req.ip (requires trust proxy to be set)
|
|
259
|
+
if (ip_address === 'unknown' && req.ip) {
|
|
260
|
+
const reqIp = String(req.ip).trim();
|
|
261
|
+
if (reqIp && reqIp !== '::1' && reqIp !== '127.0.0.1') {
|
|
262
|
+
ip_address = reqIp;
|
|
263
|
+
this.logger.debug('Using IP from req.ip', { ip: ip_address });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Try socket.remoteAddress (direct connection)
|
|
267
|
+
if (ip_address === 'unknown' && ((_c = req.socket) === null || _c === void 0 ? void 0 : _c.remoteAddress)) {
|
|
268
|
+
const socketIp = String(req.socket.remoteAddress).trim();
|
|
269
|
+
// Remove IPv6 prefix if present
|
|
270
|
+
const cleanIp = socketIp.replace(/^::ffff:/, '');
|
|
271
|
+
if (cleanIp && cleanIp !== '::1' && cleanIp !== '127.0.0.1') {
|
|
272
|
+
ip_address = cleanIp;
|
|
273
|
+
this.logger.debug('Using IP from socket.remoteAddress', { ip: ip_address, original: socketIp });
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// Try connection.remoteAddress (fallback)
|
|
277
|
+
if (ip_address === 'unknown' && ((_d = req.connection) === null || _d === void 0 ? void 0 : _d.remoteAddress)) {
|
|
278
|
+
const connIp = String(req.connection.remoteAddress).trim();
|
|
279
|
+
const cleanIp = connIp.replace(/^::ffff:/, '');
|
|
280
|
+
if (cleanIp && cleanIp !== '::1' && cleanIp !== '127.0.0.1') {
|
|
281
|
+
ip_address = cleanIp;
|
|
282
|
+
this.logger.debug('Using IP from connection.remoteAddress', { ip: ip_address, original: connIp });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Log final IP address
|
|
286
|
+
this.logger.debug('Final IP address determined', {
|
|
287
|
+
ip_address,
|
|
288
|
+
all_sources: ipSources
|
|
289
|
+
});
|
|
290
|
+
// Get user agent
|
|
291
|
+
const user_agent = req.headers['user-agent'] || 'unknown';
|
|
292
|
+
// Extract user/device/microservice information from request
|
|
293
|
+
const authType = req.authType;
|
|
294
|
+
const officer = req.officer; // Use officer directly, as it has all the properties
|
|
295
|
+
const device = req.device;
|
|
296
|
+
const apiKey = req.apiKey;
|
|
297
|
+
const authenticatedMicroservice = req.microservice;
|
|
298
|
+
// Get microservice name from environment or default
|
|
299
|
+
const microserviceName = this.serverName || 'Package Name';
|
|
300
|
+
// Determine event type if not provided
|
|
301
|
+
let eventType = payload.event_type;
|
|
302
|
+
if (!eventType) {
|
|
303
|
+
eventType = AuditService.determineEventType(req);
|
|
304
|
+
}
|
|
305
|
+
// Build audit payload - start with base fields
|
|
306
|
+
const auditPayload = {
|
|
307
|
+
timestamp: new Date().toISOString(),
|
|
308
|
+
microservice: authenticatedMicroservice || microserviceName,
|
|
309
|
+
event_type: eventType,
|
|
310
|
+
endpoint: req.originalUrl || req.url,
|
|
311
|
+
method: req.method,
|
|
312
|
+
path: req.path,
|
|
313
|
+
query_params: Object.keys(req.query).length > 0 ? req.query : undefined,
|
|
314
|
+
ip_address,
|
|
315
|
+
user_agent,
|
|
316
|
+
};
|
|
317
|
+
// Add officer information if authenticated
|
|
318
|
+
if (officer) {
|
|
319
|
+
auditPayload.user_id = officer.id;
|
|
320
|
+
auditPayload.officer_id = officer.id;
|
|
321
|
+
auditPayload.officer_name = officer.name || undefined;
|
|
322
|
+
auditPayload.officer_service_number = officer.service_number || undefined;
|
|
323
|
+
auditPayload.officer_email = officer.email || undefined;
|
|
324
|
+
}
|
|
325
|
+
// Add device information if authenticated (minimal - just ID)
|
|
326
|
+
if (device) {
|
|
327
|
+
// Ensure device.id is a number
|
|
328
|
+
const deviceId = typeof device.id === 'string' ? parseInt(device.id, 10) : device.id;
|
|
329
|
+
// Set user_id to device_id if no officer is authenticated
|
|
330
|
+
if (!auditPayload.user_id && !isNaN(deviceId)) {
|
|
331
|
+
auditPayload.user_id = deviceId;
|
|
332
|
+
}
|
|
333
|
+
if (!isNaN(deviceId)) {
|
|
334
|
+
auditPayload.device_id = deviceId;
|
|
335
|
+
}
|
|
336
|
+
auditPayload.device_device_id = device.device_id;
|
|
337
|
+
}
|
|
338
|
+
// Set user type and add API Key / Microservice information
|
|
339
|
+
if (authType === 'api_key' || authType === 'microservice') {
|
|
340
|
+
auditPayload.user_type = 'microservice';
|
|
341
|
+
auditPayload.authenticated_microservice = authenticatedMicroservice || (apiKey === null || apiKey === void 0 ? void 0 : apiKey.microservice);
|
|
342
|
+
auditPayload.api_key_id = apiKey === null || apiKey === void 0 ? void 0 : apiKey.id;
|
|
343
|
+
// Set microservice name in the main microservice field
|
|
344
|
+
if (authenticatedMicroservice || (apiKey === null || apiKey === void 0 ? void 0 : apiKey.microservice)) {
|
|
345
|
+
auditPayload.microservice = authenticatedMicroservice || apiKey.microservice;
|
|
346
|
+
}
|
|
347
|
+
// For microservice authentication, we don't have a user_id
|
|
348
|
+
}
|
|
349
|
+
else if (authType === 'both') {
|
|
350
|
+
auditPayload.user_type = 'both';
|
|
351
|
+
// When both are authenticated, prioritize officer as user_id
|
|
352
|
+
if (officer) {
|
|
353
|
+
auditPayload.user_id = officer.id;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
else if (authType === 'device') {
|
|
357
|
+
auditPayload.user_type = 'device';
|
|
358
|
+
// Use device id as user_id when only device is authenticated
|
|
359
|
+
if (device) {
|
|
360
|
+
const deviceId = typeof device.id === 'string' ? parseInt(device.id, 10) : device.id;
|
|
361
|
+
if (!isNaN(deviceId)) {
|
|
362
|
+
auditPayload.user_id = deviceId;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
else if (authType === 'officer') {
|
|
367
|
+
auditPayload.user_type = 'officer';
|
|
368
|
+
// Use officer id as user_id when only officer is authenticated
|
|
369
|
+
if (officer) {
|
|
370
|
+
auditPayload.user_id = officer.id;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
// Extract arrest-specific information from request body or response
|
|
374
|
+
this.addArrestInformation(req, auditPayload, payload);
|
|
375
|
+
// Merge custom keys from payload
|
|
376
|
+
// Custom keys can override standard fields if explicitly provided
|
|
377
|
+
// This allows users to add custom metadata or override standard fields when needed
|
|
378
|
+
Object.keys(payload).forEach(key => {
|
|
379
|
+
if (payload[key] !== undefined) {
|
|
380
|
+
// Allow custom keys to override standard fields if explicitly provided
|
|
381
|
+
// This gives users full control over the audit payload
|
|
382
|
+
auditPayload[key] = payload[key];
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
// Build user info string for logging
|
|
386
|
+
const userInfo = auditPayload.officer_name
|
|
387
|
+
? `${auditPayload.officer_name} (Officer ID: ${auditPayload.officer_id})`
|
|
388
|
+
: auditPayload.device_device_id
|
|
389
|
+
? `Device: ${auditPayload.device_device_id} (Device ID: ${auditPayload.device_id})`
|
|
390
|
+
: auditPayload.authenticated_microservice
|
|
391
|
+
? `Microservice: ${auditPayload.authenticated_microservice}`
|
|
392
|
+
: 'Unknown user';
|
|
393
|
+
// Log to console for visibility (can be controlled by enableDebugLogs)
|
|
394
|
+
if (this.enableDebugLogs) {
|
|
395
|
+
console.log(`[AUDIT] ${userInfo} accessed ${auditPayload.method} ${auditPayload.endpoint}`, {
|
|
396
|
+
user_type: auditPayload.user_type,
|
|
397
|
+
user_id: auditPayload.user_id,
|
|
398
|
+
officer_id: auditPayload.officer_id,
|
|
399
|
+
device_id: auditPayload.device_id,
|
|
400
|
+
event_type: auditPayload.event_type,
|
|
401
|
+
response_status: auditPayload.response_status,
|
|
402
|
+
ip_address: auditPayload.ip_address,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
// Also log via logger (respects log level configuration)
|
|
406
|
+
this.logger.info(`Audit: ${userInfo} accessed ${auditPayload.method} ${auditPayload.endpoint}`, {
|
|
407
|
+
endpoint: auditPayload.endpoint,
|
|
408
|
+
method: auditPayload.method,
|
|
409
|
+
user_type: auditPayload.user_type,
|
|
410
|
+
user_id: auditPayload.user_id,
|
|
411
|
+
officer_id: auditPayload.officer_id,
|
|
412
|
+
device_id: auditPayload.device_id,
|
|
413
|
+
event_type: auditPayload.event_type,
|
|
414
|
+
response_status: auditPayload.response_status,
|
|
415
|
+
ip_address: auditPayload.ip_address,
|
|
416
|
+
topic: this.auditTopic || 'audit-log'
|
|
417
|
+
});
|
|
418
|
+
// Send to Kafka
|
|
419
|
+
// Check if Kafka is available before attempting to send
|
|
420
|
+
// Skip if Kafka producer is not available (but we've already logged to console)
|
|
421
|
+
if (!this.kafkaProducer) {
|
|
422
|
+
this.debugError('Kafka producer not available - skipping send', {
|
|
423
|
+
hasKafkaProducer: !!this.kafkaProducer,
|
|
424
|
+
hasIsProducerConnected: !!this.isProducerConnected,
|
|
425
|
+
});
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
try {
|
|
429
|
+
// For internal connections, ensure we're connected
|
|
430
|
+
if (this.isInternalConnection) {
|
|
431
|
+
const connected = yield this.ensureConnected();
|
|
432
|
+
if (!connected) {
|
|
433
|
+
this.debugError('Internal producer connection failed - skipping send');
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
// For external producer, check connection status
|
|
439
|
+
const isConnected = this.isProducerConnected ? this.isProducerConnected() : false;
|
|
440
|
+
this.debugLog('Producer connection check', {
|
|
441
|
+
hasIsProducerConnected: !!this.isProducerConnected,
|
|
442
|
+
isConnected: isConnected,
|
|
443
|
+
isInternalConnection: this.isInternalConnection,
|
|
444
|
+
});
|
|
445
|
+
if (!this.isProducerConnected || !isConnected) {
|
|
446
|
+
// Producer not connected (but we've already logged to console)
|
|
447
|
+
this.debugError('Producer not connected - skipping send', {
|
|
448
|
+
hasKafkaProducer: !!this.kafkaProducer,
|
|
449
|
+
hasIsProducerConnected: !!this.isProducerConnected,
|
|
450
|
+
isConnected: isConnected,
|
|
451
|
+
});
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (!this.kafkaProducer) {
|
|
456
|
+
this.debugError('Kafka producer not available (second check) - skipping send', {
|
|
457
|
+
hasKafkaProducer: !!this.kafkaProducer,
|
|
458
|
+
hasIsProducerConnected: !!this.isProducerConnected,
|
|
459
|
+
});
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
this.debugLog('All checks passed - proceeding to send');
|
|
463
|
+
const topic = this.auditTopic || 'audit-log';
|
|
464
|
+
const kafkaMessage = {
|
|
465
|
+
key: `${auditPayload.method}-${auditPayload.endpoint}-${Date.now()}`,
|
|
466
|
+
value: JSON.stringify(auditPayload),
|
|
467
|
+
headers: {
|
|
468
|
+
'microservice': auditPayload.microservice,
|
|
469
|
+
'event-type': 'audit-log',
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
// Debug log the payload and topic before sending
|
|
473
|
+
this.debugLog(`Sending to topic: ${topic}`);
|
|
474
|
+
this.debugLog('Payload:', JSON.stringify(auditPayload, null, 2));
|
|
475
|
+
this.debugLog(`Message key: ${kafkaMessage.key}`);
|
|
476
|
+
yield this.kafkaProducer.send({
|
|
477
|
+
topic: topic,
|
|
478
|
+
messages: [kafkaMessage]
|
|
479
|
+
})
|
|
480
|
+
.then((result) => {
|
|
481
|
+
this.debugLog('Audit event sent to Kafka', {
|
|
482
|
+
result: result,
|
|
483
|
+
endpoint: req.path,
|
|
484
|
+
method: req.method,
|
|
485
|
+
authType: req.authType
|
|
486
|
+
});
|
|
487
|
+
})
|
|
488
|
+
.catch((error) => {
|
|
489
|
+
this.debugError('Error sending audit event to Kafka', {
|
|
490
|
+
error: error.message,
|
|
491
|
+
errorStack: error.stack,
|
|
492
|
+
endpoint: req.path,
|
|
493
|
+
method: req.method,
|
|
494
|
+
authType: req.authType
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
catch (kafkaError) {
|
|
499
|
+
const errorMessage = (kafkaError === null || kafkaError === void 0 ? void 0 : kafkaError.message) || String(kafkaError);
|
|
500
|
+
this.debugError('Error sending audit event to Kafka', {
|
|
501
|
+
error: errorMessage,
|
|
502
|
+
errorStack: kafkaError instanceof Error ? kafkaError.stack : undefined,
|
|
503
|
+
errorName: kafkaError instanceof Error ? kafkaError.name : undefined,
|
|
504
|
+
endpoint: req.path,
|
|
505
|
+
method: req.method,
|
|
506
|
+
authType: req.authType
|
|
507
|
+
});
|
|
508
|
+
// If it's a DNS resolution error or connection error, skip silently (Kafka is not available)
|
|
509
|
+
if (errorMessage.includes('getaddrinfo EAI_AGAIN') ||
|
|
510
|
+
errorMessage.includes('ENOTFOUND') ||
|
|
511
|
+
errorMessage.includes('Connection error') ||
|
|
512
|
+
errorMessage.includes('not connected') ||
|
|
513
|
+
errorMessage.includes('ECONNREFUSED') ||
|
|
514
|
+
errorMessage.includes('ETIMEDOUT')) {
|
|
515
|
+
// Kafka is not available, skip audit logging silently
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
// If connection error, try to connect and retry once
|
|
519
|
+
if (kafkaError instanceof Error && errorMessage.includes('not connected') && this.kafkaProducer) {
|
|
520
|
+
try {
|
|
521
|
+
yield this.kafkaProducer.connect();
|
|
522
|
+
// Retry send after connection
|
|
523
|
+
const retryTopic = this.auditTopic || 'audit-log';
|
|
524
|
+
const retryMessage = {
|
|
525
|
+
key: `${auditPayload.method}-${auditPayload.endpoint}-${Date.now()}`,
|
|
526
|
+
value: JSON.stringify(auditPayload),
|
|
527
|
+
headers: {
|
|
528
|
+
'microservice': auditPayload.microservice,
|
|
529
|
+
'event-type': 'audit-log',
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
// Debug log the payload and topic before retry send
|
|
533
|
+
this.debugLog(`[RETRY] Sending to topic: ${retryTopic}`);
|
|
534
|
+
this.debugLog('[RETRY] Payload:', JSON.stringify(auditPayload, null, 2));
|
|
535
|
+
this.debugLog(`[RETRY] Message key: ${retryMessage.key}`);
|
|
536
|
+
yield this.kafkaProducer.send({
|
|
537
|
+
topic: retryTopic,
|
|
538
|
+
messages: [retryMessage]
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
catch (retryError) {
|
|
542
|
+
// If retry also fails with connection/DNS error, skip silently
|
|
543
|
+
const retryErrorMessage = retryError instanceof Error ? retryError.message : String(retryError);
|
|
544
|
+
if (retryErrorMessage.includes('getaddrinfo EAI_AGAIN') ||
|
|
545
|
+
retryErrorMessage.includes('ENOTFOUND') ||
|
|
546
|
+
retryErrorMessage.includes('Connection error') ||
|
|
547
|
+
retryErrorMessage.includes('ECONNREFUSED') ||
|
|
548
|
+
retryErrorMessage.includes('ETIMEDOUT')) {
|
|
549
|
+
return; // Skip silently
|
|
550
|
+
}
|
|
551
|
+
// Re-throw other errors to be caught by outer catch block
|
|
552
|
+
throw new Error(`Kafka send failed after retry: ${retryErrorMessage}`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
// Re-throw to be caught by outer catch block
|
|
557
|
+
throw new Error(`Kafka send failed: ${errorMessage}`);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
this.logger.debug('Audit event sent to Kafka', {
|
|
561
|
+
endpoint: auditPayload.endpoint,
|
|
562
|
+
method: auditPayload.method,
|
|
563
|
+
user_type: auditPayload.user_type,
|
|
564
|
+
topic: this.auditTopic || 'audit-log'
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
catch (error) {
|
|
568
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
569
|
+
// Skip logging DNS/connection errors - Kafka is simply not available
|
|
570
|
+
if (errorMessage.includes('getaddrinfo EAI_AGAIN') ||
|
|
571
|
+
errorMessage.includes('ENOTFOUND') ||
|
|
572
|
+
errorMessage.includes('Connection error') ||
|
|
573
|
+
errorMessage.includes('not connected') ||
|
|
574
|
+
errorMessage.includes('ECONNREFUSED') ||
|
|
575
|
+
errorMessage.includes('ETIMEDOUT')) {
|
|
576
|
+
// Silently skip - Kafka is not available
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
// Only log unexpected errors (but don't throw - audit logging should not break the request)
|
|
580
|
+
// Use debug level instead of error to reduce noise
|
|
581
|
+
this.logger.debug('Error sending audit event to Kafka', {
|
|
582
|
+
error: errorMessage,
|
|
583
|
+
errorStack: error instanceof Error ? error.stack : undefined,
|
|
584
|
+
errorName: error instanceof Error ? error.name : undefined,
|
|
585
|
+
endpoint: req.path,
|
|
586
|
+
method: req.method,
|
|
587
|
+
authType: req.authType
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Determine event type based on request path and method
|
|
594
|
+
*/
|
|
595
|
+
static determineEventType(req) {
|
|
596
|
+
// Use originalUrl or baseUrl + path to get the full path
|
|
597
|
+
// req.path is relative to the router mount point, so for "/" route it would be just "/"
|
|
598
|
+
const fullPath = (req.originalUrl || req.baseUrl + req.path || req.path).toLowerCase();
|
|
599
|
+
const path = req.path.toLowerCase();
|
|
600
|
+
const method = req.method.toUpperCase();
|
|
601
|
+
// Login events
|
|
602
|
+
if (fullPath.includes('/login') || path.includes('/login')) {
|
|
603
|
+
const status = req.statusCode || 200;
|
|
604
|
+
if (status === 200) {
|
|
605
|
+
return AuditEventType.LOGIN_SUCCESS;
|
|
606
|
+
}
|
|
607
|
+
else if (status === 401 || status === 403) {
|
|
608
|
+
return AuditEventType.LOGIN_NOT_PERMITTED;
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
return AuditEventType.LOGIN_FAILED;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
// Officer events (read-only consumption)
|
|
615
|
+
// Check both fullPath (for routes like /api/v1/officer) and path (for routes like /officer)
|
|
616
|
+
if (fullPath.includes('/officer') || fullPath.includes('/officers') ||
|
|
617
|
+
path.includes('/officer') || path.includes('/officers')) {
|
|
618
|
+
if (method === 'GET' && (fullPath.match(/\/officer\/\d+$/) || fullPath.match(/\/officers\/\d+$/) ||
|
|
619
|
+
path.match(/\/officer\/\d+$/) || path.match(/\/officers\/\d+$/))) {
|
|
620
|
+
return AuditEventType.OFFICER_RETRIEVED;
|
|
621
|
+
}
|
|
622
|
+
else if (method === 'GET') {
|
|
623
|
+
return AuditEventType.OFFICERS_LISTED;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
// Device events (read-only consumption)
|
|
627
|
+
if (fullPath.includes('/device') || fullPath.includes('/devices') ||
|
|
628
|
+
path.includes('/device') || path.includes('/devices')) {
|
|
629
|
+
if (method === 'GET' && (fullPath.match(/\/device\/\d+$/) || fullPath.match(/\/devices\/\d+$/) ||
|
|
630
|
+
path.match(/\/device\/\d+$/) || path.match(/\/devices\/\d+$/))) {
|
|
631
|
+
return AuditEventType.DEVICE_RETRIEVED;
|
|
632
|
+
}
|
|
633
|
+
else if (method === 'GET') {
|
|
634
|
+
return AuditEventType.DEVICES_LISTED;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
// Arrest events (sub_module_data endpoints)
|
|
638
|
+
if (fullPath.includes('/sub_module_data') || fullPath.includes('/arrest') ||
|
|
639
|
+
path.includes('/sub_module_data') || path.includes('/arrest')) {
|
|
640
|
+
if (method === 'POST') {
|
|
641
|
+
return AuditEventType.ARREST_CREATED;
|
|
642
|
+
}
|
|
643
|
+
else if (method === 'PUT') {
|
|
644
|
+
return AuditEventType.ARREST_UPDATED;
|
|
645
|
+
}
|
|
646
|
+
else if (method === 'DELETE') {
|
|
647
|
+
return AuditEventType.ARREST_DELETED;
|
|
648
|
+
}
|
|
649
|
+
else if (method === 'GET' && (fullPath.match(/\/sub_module_data\/\d+$/) || fullPath.match(/\/arrest\/\d+$/) ||
|
|
650
|
+
path.match(/\/sub_module_data\/\d+$/) || path.match(/\/arrest\/\d+$/))) {
|
|
651
|
+
return AuditEventType.ARREST_RETRIEVED;
|
|
652
|
+
}
|
|
653
|
+
else if (method === 'GET') {
|
|
654
|
+
return AuditEventType.ARRESTS_LISTED;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
// Default to endpoint accessed
|
|
658
|
+
return AuditEventType.ENDPOINT_ACCESSED;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Log endpoint access (called from middleware)
|
|
662
|
+
*/
|
|
663
|
+
logEndpointAccess(req) {
|
|
664
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
665
|
+
yield this.logAuditEvent(req, {
|
|
666
|
+
// Event type will be determined automatically
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Log endpoint access with response status
|
|
672
|
+
*/
|
|
673
|
+
logEndpointAccessWithResponse(req, res, startTime) {
|
|
674
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
675
|
+
const duration_ms = Date.now() - startTime;
|
|
676
|
+
yield this.logAuditEvent(req, {
|
|
677
|
+
response_status: res.statusCode,
|
|
678
|
+
duration_ms,
|
|
679
|
+
});
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Extract and add arrest-specific information to audit payload
|
|
684
|
+
*/
|
|
685
|
+
addArrestInformation(req, auditPayload, payload) {
|
|
686
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
687
|
+
// Check if this is an arrest-related endpoint
|
|
688
|
+
const path = req.path.toLowerCase();
|
|
689
|
+
if (!path.includes('/sub_module_data') && !path.includes('/arrest')) {
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
// Extract from request body (for POST/PUT)
|
|
693
|
+
const body = req.body || {};
|
|
694
|
+
const responseData = req.responseData; // If response data is attached to request
|
|
695
|
+
// Extract arrest ID from URL params or body
|
|
696
|
+
if ((_a = req.params) === null || _a === void 0 ? void 0 : _a.id) {
|
|
697
|
+
auditPayload.arrest_id = parseInt(req.params.id) || undefined;
|
|
698
|
+
}
|
|
699
|
+
else if (body.id) {
|
|
700
|
+
auditPayload.arrest_id = body.id;
|
|
701
|
+
}
|
|
702
|
+
else if (responseData === null || responseData === void 0 ? void 0 : responseData.id) {
|
|
703
|
+
auditPayload.arrest_id = responseData.id;
|
|
704
|
+
}
|
|
705
|
+
else if ((_b = responseData === null || responseData === void 0 ? void 0 : responseData.data) === null || _b === void 0 ? void 0 : _b.id) {
|
|
706
|
+
auditPayload.arrest_id = responseData.data.id;
|
|
707
|
+
}
|
|
708
|
+
// Extract OB number
|
|
709
|
+
if (body.ob_number) {
|
|
710
|
+
auditPayload.ob_number = body.ob_number;
|
|
711
|
+
}
|
|
712
|
+
else if (responseData === null || responseData === void 0 ? void 0 : responseData.ob_number) {
|
|
713
|
+
auditPayload.ob_number = responseData.ob_number;
|
|
714
|
+
}
|
|
715
|
+
else if ((_c = responseData === null || responseData === void 0 ? void 0 : responseData.data) === null || _c === void 0 ? void 0 : _c.ob_number) {
|
|
716
|
+
auditPayload.ob_number = responseData.data.ob_number;
|
|
717
|
+
}
|
|
718
|
+
// Extract sync_id
|
|
719
|
+
if (body.sync_id) {
|
|
720
|
+
auditPayload.sync_id = body.sync_id;
|
|
721
|
+
}
|
|
722
|
+
else if (responseData === null || responseData === void 0 ? void 0 : responseData.sync_id) {
|
|
723
|
+
auditPayload.sync_id = responseData.sync_id;
|
|
724
|
+
}
|
|
725
|
+
else if ((_d = responseData === null || responseData === void 0 ? void 0 : responseData.data) === null || _d === void 0 ? void 0 : _d.sync_id) {
|
|
726
|
+
auditPayload.sync_id = responseData.data.sync_id;
|
|
727
|
+
}
|
|
728
|
+
// Extract IPRS ID
|
|
729
|
+
if (body.iprsId || body.iprs_id) {
|
|
730
|
+
auditPayload.iprs_id = body.iprsId || body.iprs_id;
|
|
731
|
+
}
|
|
732
|
+
else if ((responseData === null || responseData === void 0 ? void 0 : responseData.iprsId) || (responseData === null || responseData === void 0 ? void 0 : responseData.iprs_id)) {
|
|
733
|
+
auditPayload.iprs_id = responseData.iprsId || responseData.iprs_id;
|
|
734
|
+
}
|
|
735
|
+
else if (((_e = responseData === null || responseData === void 0 ? void 0 : responseData.data) === null || _e === void 0 ? void 0 : _e.iprsId) || ((_f = responseData === null || responseData === void 0 ? void 0 : responseData.data) === null || _f === void 0 ? void 0 : _f.iprs_id)) {
|
|
736
|
+
auditPayload.iprs_id = responseData.data.iprsId || responseData.data.iprs_id;
|
|
737
|
+
}
|
|
738
|
+
// Extract arresting officer ID
|
|
739
|
+
if (body.arrestingOfficer || body.arresting_officer) {
|
|
740
|
+
auditPayload.arresting_officer_id = body.arrestingOfficer || body.arresting_officer;
|
|
741
|
+
}
|
|
742
|
+
else if ((responseData === null || responseData === void 0 ? void 0 : responseData.arrestingOfficer) || (responseData === null || responseData === void 0 ? void 0 : responseData.arresting_officer)) {
|
|
743
|
+
auditPayload.arresting_officer_id = responseData.arrestingOfficer || responseData.arresting_officer;
|
|
744
|
+
}
|
|
745
|
+
else if (((_g = responseData === null || responseData === void 0 ? void 0 : responseData.data) === null || _g === void 0 ? void 0 : _g.arrestingOfficer) || ((_h = responseData === null || responseData === void 0 ? void 0 : responseData.data) === null || _h === void 0 ? void 0 : _h.arresting_officer)) {
|
|
746
|
+
auditPayload.arresting_officer_id = responseData.data.arrestingOfficer || responseData.data.arresting_officer;
|
|
747
|
+
}
|
|
748
|
+
// Extract arresting station ID
|
|
749
|
+
if (body.arrestingStation || body.arresting_station) {
|
|
750
|
+
auditPayload.arresting_station_id = body.arrestingStation || body.arresting_station;
|
|
751
|
+
}
|
|
752
|
+
else if ((responseData === null || responseData === void 0 ? void 0 : responseData.arrestingStation) || (responseData === null || responseData === void 0 ? void 0 : responseData.arresting_station)) {
|
|
753
|
+
auditPayload.arresting_station_id = responseData.arrestingStation || responseData.arresting_station;
|
|
754
|
+
}
|
|
755
|
+
else if (((_j = responseData === null || responseData === void 0 ? void 0 : responseData.data) === null || _j === void 0 ? void 0 : _j.arrestingStation) || ((_k = responseData === null || responseData === void 0 ? void 0 : responseData.data) === null || _k === void 0 ? void 0 : _k.arresting_station)) {
|
|
756
|
+
auditPayload.arresting_station_id = responseData.data.arrestingStation || responseData.data.arresting_station;
|
|
757
|
+
}
|
|
758
|
+
// Extract sub_module_id
|
|
759
|
+
if (body.subModuleId || body.sub_module_id || body.sub_moduleId) {
|
|
760
|
+
auditPayload.sub_module_id = body.subModuleId || body.sub_module_id || body.sub_moduleId;
|
|
761
|
+
}
|
|
762
|
+
else if ((responseData === null || responseData === void 0 ? void 0 : responseData.subModuleId) || (responseData === null || responseData === void 0 ? void 0 : responseData.sub_module_id) || (responseData === null || responseData === void 0 ? void 0 : responseData.sub_moduleId)) {
|
|
763
|
+
auditPayload.sub_module_id = responseData.subModuleId || responseData.sub_module_id || responseData.sub_moduleId;
|
|
764
|
+
}
|
|
765
|
+
else if (((_l = responseData === null || responseData === void 0 ? void 0 : responseData.data) === null || _l === void 0 ? void 0 : _l.subModuleId) || ((_m = responseData === null || responseData === void 0 ? void 0 : responseData.data) === null || _m === void 0 ? void 0 : _m.sub_module_id) || ((_o = responseData === null || responseData === void 0 ? void 0 : responseData.data) === null || _o === void 0 ? void 0 : _o.sub_moduleId)) {
|
|
766
|
+
auditPayload.sub_module_id = responseData.data.subModuleId || responseData.data.sub_module_id || responseData.data.sub_moduleId;
|
|
767
|
+
}
|
|
768
|
+
// Override with explicit payload values if provided
|
|
769
|
+
if (payload.arrest_id)
|
|
770
|
+
auditPayload.arrest_id = payload.arrest_id;
|
|
771
|
+
if (payload.ob_number)
|
|
772
|
+
auditPayload.ob_number = payload.ob_number;
|
|
773
|
+
if (payload.sync_id)
|
|
774
|
+
auditPayload.sync_id = payload.sync_id;
|
|
775
|
+
if (payload.iprs_id)
|
|
776
|
+
auditPayload.iprs_id = payload.iprs_id;
|
|
777
|
+
if (payload.arresting_officer_id)
|
|
778
|
+
auditPayload.arresting_officer_id = payload.arresting_officer_id;
|
|
779
|
+
if (payload.arresting_station_id)
|
|
780
|
+
auditPayload.arresting_station_id = payload.arresting_station_id;
|
|
781
|
+
if (payload.sub_module_id)
|
|
782
|
+
auditPayload.sub_module_id = payload.sub_module_id;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
exports.AuditService = AuditService;
|