@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 ADDED
@@ -0,0 +1,38 @@
1
+ # Changelog
2
+
3
+ ## Version 1.0.0
4
+
5
+ ### Added
6
+ - **OfficerCacheService**: Redis-based caching service for officers with efficient indexing
7
+ - **PenalCodeCacheService**: Redis-based caching service for penal codes
8
+ - **DeviceCacheService**: Redis-based caching service for devices with indexing
9
+ - **ApiKeyService**: Service for managing and validating API keys with Redis caching
10
+ - **AuditService**: Comprehensive audit logging service with Kafka integration
11
+ - **createAuthenticateDeviceOrOfficer**: Express middleware factory for multi-method authentication
12
+ - **createSafeRedisGet**: Factory function for safe Redis GET operations
13
+ - **createSafeRedisSet**: Factory function for safe Redis SET operations
14
+ - **createSafeRedisUtils**: Factory function that creates both Redis utilities
15
+
16
+ ### Features
17
+ - All services use dependency injection for testability and reusability
18
+ - Automatic Redis reconnection handling
19
+ - Efficient pipeline-based batch operations
20
+ - Comprehensive error handling and logging
21
+ - TypeScript type definitions included
22
+
23
+ ### Audit Service Enhancements
24
+ - **Internal Kafka Connection**: Audit service can manage its own Kafka connection via `kafkaBrokers` configuration
25
+ - **Custom Keys Support**: Add custom keys to audit event payloads dynamically
26
+ - **Automatic Event Type Detection**: Automatically determines event types based on request path and method
27
+ - **IP Address Detection**: Automatically detects client IP from multiple header sources
28
+ - **Debug Logging Control**: `enableDebugLogs` flag to control verbose logging (defaults to `false`)
29
+ - **Graceful Error Handling**: Silently handles Kafka unavailability without breaking requests
30
+ - **Comprehensive Event Tracking**: Tracks user, device, officer, and microservice information
31
+ - **Arrest-Specific Information**: Automatically extracts arrest-related data from requests
32
+
33
+ ### Configuration Options
34
+ - `kafkaBrokers`: Kafka broker URLs for internal connection management
35
+ - `kafkaClientId`: Custom Kafka client ID
36
+ - `auditTopic`: Configurable Kafka topic (defaults to `'audit-log'`)
37
+ - `enableDebugLogs`: Control verbose debug logging (defaults to `false`)
38
+ - `serverName`: Server/microservice name for audit events
package/README.md ADDED
@@ -0,0 +1,376 @@
1
+ # Govish Shared Services Package
2
+
3
+ This package contains shared services, middleware, and utilities that can be used across multiple microservices.
4
+
5
+ ## Quick Start
6
+
7
+ ```typescript
8
+ import {
9
+ AuditService,
10
+ createAuthenticateDeviceOrOfficer,
11
+ SharedServicesDependencies
12
+ } from '@govish/shared-services';
13
+
14
+ const dependencies: SharedServicesDependencies = {
15
+ redisClient,
16
+ logger,
17
+ kafkaBrokers: process.env.KAFKA_BROKER || 'localhost:9092',
18
+ auditTopic: 'audit-log',
19
+ enableDebugLogs: false, // Set to true for verbose logging
20
+ };
21
+
22
+ const auditService = new AuditService(dependencies);
23
+ const authenticateDeviceOrOfficer = createAuthenticateDeviceOrOfficer(dependencies);
24
+ ```
25
+
26
+ ## Services Included
27
+
28
+ - **OfficerCacheService**: Redis-based caching service for officers with indexing and efficient queries
29
+ - **PenalCodeCacheService**: Redis-based caching service for penal codes
30
+ - **DeviceCacheService**: Redis-based caching service for devices with indexing
31
+ - **ApiKeyService**: Service for managing and validating API keys with Redis caching
32
+ - **AuditService**: Service for logging audit events to Kafka with comprehensive event tracking
33
+ - **authenticateDeviceOrOfficer**: Express middleware for authenticating devices, officers, or microservices via API keys
34
+
35
+ ## Utilities Included
36
+
37
+ - **createSafeRedisGet**: Factory function to create a safe Redis GET operation with automatic reconnection
38
+ - **createSafeRedisSet**: Factory function to create a safe Redis SET operation with automatic reconnection
39
+ - **createSafeRedisUtils**: Factory function that creates both safeRedisGet and safeRedisSet functions
40
+
41
+ ## Installation
42
+
43
+ This is a local package. To use it in your project:
44
+
45
+ ```bash
46
+ cd packages/ob-shared-services
47
+ npm install
48
+ npm run build
49
+ ```
50
+
51
+ ## Usage
52
+
53
+ ### Initialization
54
+
55
+ All services require dependencies to be injected. Create a factory file in your project:
56
+
57
+ ```typescript
58
+ import {
59
+ OfficerCacheService,
60
+ PenalCodeCacheService,
61
+ DeviceCacheService,
62
+ ApiKeyService,
63
+ createAuthenticateDeviceOrOfficer,
64
+ SharedServicesDependencies
65
+ } from '@ob-main/shared-services';
66
+ import { redisClient } from './app';
67
+ import { logger } from './utils/logger';
68
+ // ... other dependencies
69
+
70
+ const dependencies: SharedServicesDependencies = {
71
+ redisClient,
72
+ logger,
73
+ authHost: process.env.AUTH_HOST,
74
+ authPort: process.env.AUTH_PORT,
75
+ serverName: process.env.SERVER_NAME,
76
+ auditTopic: process.env.AUDIT_TOPIC || 'audit-log',
77
+ enableDebugLogs: process.env.AUDIT_DEBUG_LOGS === 'true',
78
+ jwtConfig,
79
+
80
+ // Option 1: Use Kafka brokers (recommended - audit service manages its own connection)
81
+ kafkaBrokers: process.env.KAFKA_BROKER || process.env.KAFKA_BROKERS,
82
+ kafkaClientId: process.env.KAFKA_CLIENT_ID || 'audit-service',
83
+
84
+ // Option 2: Use existing producer (backward compatibility)
85
+ // kafkaProducer: producer,
86
+ // isProducerConnected: () => kafkaProducerService?.getIsConnected() || false,
87
+
88
+ ...createSafeRedisUtils(redisClient), // Spreads safeRedisGet and safeRedisSet
89
+ };
90
+
91
+ // Initialize services
92
+ export const officerCacheService = new OfficerCacheService(dependencies);
93
+ export const penalCodeCacheService = new PenalCodeCacheService(dependencies);
94
+ export const deviceCacheService = new DeviceCacheService(dependencies);
95
+ export const apiKeyService = new ApiKeyService(dependencies);
96
+ export const auditService = new AuditService(dependencies);
97
+
98
+ // Create middleware
99
+ export const authenticateDeviceOrOfficer = createAuthenticateDeviceOrOfficer(dependencies);
100
+
101
+ // Export Redis utilities (optional - can also use createSafeRedisUtils directly)
102
+ export const { safeRedisGet, safeRedisSet } = createSafeRedisUtils(redisClient);
103
+ ```
104
+
105
+ ### Using Services
106
+
107
+ ```typescript
108
+ import {
109
+ officerCacheService,
110
+ penalCodeCacheService,
111
+ deviceCacheService,
112
+ apiKeyService,
113
+ auditService,
114
+ safeRedisGet,
115
+ safeRedisSet
116
+ } from './services/sharedServicesFactory';
117
+
118
+ // Get officer by ID
119
+ const officer = await officerCacheService.getOfficerById(123);
120
+
121
+ // Get officers by station
122
+ const officers = await officerCacheService.getOfficersByStation(1);
123
+
124
+ // Store officer
125
+ await officerCacheService.storeOfficer(officerData);
126
+
127
+ // Get penal codes
128
+ const penalCodes = await penalCodeCacheService.getPenalCodes();
129
+
130
+ // Validate API key
131
+ const apiKey = await apiKeyService.validateApiKey('ak_xxx');
132
+
133
+ // Log audit event
134
+ await auditService.logAuditEvent(req, {
135
+ event_type: AuditEventType.ENDPOINT_ACCESSED
136
+ });
137
+
138
+ // Log audit event with custom keys
139
+ await auditService.logAuditEvent(req, {
140
+ event_type: AuditEventType.ENDPOINT_ACCESSED,
141
+ custom_field: 'custom_value',
142
+ metadata: {
143
+ action: 'special_action',
144
+ reason: 'user_requested'
145
+ }
146
+ });
147
+
148
+ // Use Redis utilities
149
+ const value = await safeRedisGet('some-key');
150
+ await safeRedisSet('some-key', 'some-value');
151
+ ```
152
+
153
+ ### Using Middleware
154
+
155
+ ```typescript
156
+ import { authenticateDeviceOrOfficer } from './services/sharedServicesFactory';
157
+ import { Router } from 'express';
158
+
159
+ const router = Router();
160
+ router.get('/api/v1/officers', authenticateDeviceOrOfficer, getOfficers);
161
+ ```
162
+
163
+ ## Dependencies
164
+
165
+ The package requires the following dependencies to be provided:
166
+
167
+ ### Required:
168
+ - `redisClient`: Redis client instance
169
+ - `logger`: Winston logger instance
170
+
171
+ ### Optional:
172
+ - `authHost`: Auth service host (defaults to `process.env.AUTH_HOST`)
173
+ - `authPort`: Auth service port (defaults to `process.env.AUTH_PORT`)
174
+ - `serverName`: Server/microservice name (defaults to `process.env.SERVER_NAME`)
175
+ - `auditTopic`: Kafka topic for audit logs (defaults to `process.env.AUDIT_TOPIC` or `'audit-log'`)
176
+ - `jwtConfig`: JWT configuration object (for authentication middleware)
177
+ - `enableDebugLogs`: Enable verbose debug logging (defaults to `false`, can be set via `AUDIT_DEBUG_LOGS=true`)
178
+ - **Kafka Configuration (choose one):**
179
+ - `kafkaBrokers`: Kafka broker URLs as string or array (e.g., `'localhost:9092'` or `['broker1:9092', 'broker2:9092']`) - **Recommended**: Audit service manages its own connection
180
+ - `kafkaClientId`: Kafka client ID (defaults to `'audit-service'`)
181
+ - **OR** (for backward compatibility):
182
+ - `kafkaProducer`: Existing Kafka producer instance
183
+ - `isProducerConnected`: Function that returns boolean indicating Kafka connection status
184
+ - `safeRedisGet`: Safe Redis get function (created via `createSafeRedisGet`)
185
+ - `safeRedisSet`: Safe Redis set function (created via `createSafeRedisSet`)
186
+
187
+ ## Audit Service
188
+
189
+ ### Features
190
+
191
+ #### Automatic Event Type Detection
192
+ The audit service automatically determines event types based on request path and method:
193
+ - **Login events**: `LOGIN_SUCCESS`, `LOGIN_FAILED`, `LOGIN_NOT_PERMITTED`
194
+ - **Officer events**: `OFFICER_RETRIEVED`, `OFFICERS_LISTED`
195
+ - **Device events**: `DEVICE_RETRIEVED`, `DEVICES_LISTED`
196
+ - **Arrest events**: `ARREST_CREATED`, `ARREST_UPDATED`, `ARREST_DELETED`, `ARREST_RETRIEVED`, `ARRESTS_LISTED`
197
+ - **General**: `ENDPOINT_ACCESSED`, `UNAUTHORIZED_ACCESS`, `FORBIDDEN_ACCESS`
198
+
199
+ #### Custom Keys Support
200
+ You can add custom keys to audit events - they will be included in the Kafka message:
201
+
202
+ ```typescript
203
+ await auditService.logAuditEvent(req, {
204
+ custom_field: 'custom_value',
205
+ metadata: { action: 'special_action' },
206
+ any_other_key: 'any_value'
207
+ });
208
+ ```
209
+
210
+ #### IP Address Detection
211
+ The audit service automatically detects client IP addresses from multiple sources (in order of priority):
212
+ 1. `x-forwarded-for` (most common behind proxies/load balancers)
213
+ 2. `cf-connecting-ip` (Cloudflare)
214
+ 3. `x-real-ip` (nginx)
215
+ 4. `true-client-ip` (Cloudflare Enterprise)
216
+ 5. `x-client-ip`
217
+ 6. `req.ip` (requires trust proxy)
218
+ 7. `req.socket.remoteAddress`
219
+ 8. `req.connection.remoteAddress`
220
+
221
+ #### Kafka Connection Management
222
+ The audit service can manage its own Kafka connection when `kafkaBrokers` is provided:
223
+ - Creates its own Kafka client and producer
224
+ - Handles connection lifecycle automatically
225
+ - Connects on first use (lazy connection)
226
+ - Gracefully handles connection failures
227
+ - Falls back silently if Kafka is unavailable (doesn't break requests)
228
+
229
+ #### User Information Tracking
230
+ Automatically extracts and includes:
231
+ - Officer information (id, name, service_number, email)
232
+ - Device information (id, device_id)
233
+ - Microservice information (for API key authentication)
234
+ - Authentication type (officer, device, both, api_key, microservice)
235
+
236
+ ### Usage Examples
237
+
238
+ ```typescript
239
+ import { auditService, AuditEventType } from './services/sharedServicesFactory';
240
+
241
+ // Basic usage - event type determined automatically
242
+ await auditService.logAuditEvent(req, {});
243
+
244
+ // Explicit event type
245
+ await auditService.logAuditEvent(req, {
246
+ event_type: AuditEventType.ARREST_CREATED
247
+ });
248
+
249
+ // With custom keys
250
+ await auditService.logAuditEvent(req, {
251
+ event_type: AuditEventType.ARREST_CREATED,
252
+ custom_action: 'manual_review',
253
+ review_reason: 'suspicious_pattern'
254
+ });
255
+
256
+ // With response information
257
+ await auditService.logAuditEvent(req, {
258
+ response_status: 200,
259
+ duration_ms: 150
260
+ });
261
+
262
+ // Log endpoint access (helper method)
263
+ await auditService.logEndpointAccess(req);
264
+
265
+ // Log with response status (helper method)
266
+ const startTime = Date.now();
267
+ // ... handle request ...
268
+ await auditService.logEndpointAccessWithResponse(req, res, startTime);
269
+ ```
270
+
271
+ ## Environment Variables
272
+
273
+ ### Audit Service Configuration
274
+ ```bash
275
+ # Kafka brokers (required for internal connection)
276
+ KAFKA_BROKER=localhost:9092
277
+ # or
278
+ KAFKA_BROKERS=broker1:9092,broker2:9092
279
+
280
+ # Kafka client ID (optional)
281
+ KAFKA_CLIENT_ID=audit-service
282
+
283
+ # Audit topic (optional, defaults to 'audit-log')
284
+ AUDIT_TOPIC=audit-log
285
+
286
+ # Enable debug logs (optional, defaults to false)
287
+ AUDIT_DEBUG_LOGS=true
288
+ ```
289
+
290
+ ### Other Configuration
291
+ ```bash
292
+ # Auth service (for device/officer service fallback)
293
+ AUTH_HOST=localhost
294
+ AUTH_PORT=3000
295
+
296
+ # Server name
297
+ SERVER_NAME=ob-main
298
+ ```
299
+
300
+ ## Environment Modes
301
+
302
+ The package respects the `NODE_ENV` environment variable to control logging behavior:
303
+
304
+ ### Development Mode (`NODE_ENV=development` or `NODE_ENV=dev`)
305
+ - All console logs are shown (debug, info, warnings, errors)
306
+ - Detailed error information is displayed
307
+ - Debug logging is enabled
308
+ - Full stack traces are shown
309
+
310
+ ### Production Mode (`NODE_ENV=production` or `NODE_ENV=prod`)
311
+ - Console logs are suppressed (only errors with minimal info)
312
+ - Only warnings and errors are logged via Winston logger
313
+ - Debug and info logs are hidden
314
+ - Reduced logging for better performance
315
+
316
+ ### Usage
317
+
318
+ The package provides utility functions for conditional logging:
319
+
320
+ ```typescript
321
+ import { devLog, devError, devWarn, devDebug, isDevelopmentMode } from '@govish/shared-services';
322
+
323
+ // Only logs in development mode
324
+ devLog('This will only show in dev mode');
325
+ devDebug('Debug information');
326
+ devWarn('Warning message');
327
+
328
+ // Errors always log, but with less detail in production
329
+ devError('Error occurred', errorDetails);
330
+
331
+ // Check mode programmatically
332
+ if (isDevelopmentMode()) {
333
+ // Development-only code
334
+ }
335
+ ```
336
+
337
+ ### Logger Configuration
338
+
339
+ The Winston logger automatically adjusts based on environment:
340
+ - **Development**: Logs all levels (debug, info, warn, error) to console and files
341
+ - **Production**: Only logs warnings and errors to files (console disabled unless `LOG_TO_CONSOLE=true`)
342
+
343
+ To enable console logging in production, set:
344
+ ```bash
345
+ LOG_TO_CONSOLE=true
346
+ ```
347
+
348
+ ### Debug Logging Control
349
+
350
+ The audit service has its own debug logging control via `enableDebugLogs`:
351
+
352
+ ```typescript
353
+ // Enable all debug logs (console.log, console.error for audit service)
354
+ const dependencies: SharedServicesDependencies = {
355
+ // ...
356
+ enableDebugLogs: true, // or set AUDIT_DEBUG_LOGS=true
357
+ };
358
+ ```
359
+
360
+ When `enableDebugLogs` is `false` (default):
361
+ - No `[AUDIT KAFKA]` debug logs
362
+ - No `[AUDIT]` user access logs
363
+ - Only errors are logged via Winston logger
364
+
365
+ When `enableDebugLogs` is `true`:
366
+ - All `[AUDIT KAFKA]` debug logs are shown
367
+ - `[AUDIT]` user access logs are shown
368
+ - Detailed Kafka connection and send logs
369
+
370
+ ## Building
371
+
372
+ ```bash
373
+ npm run build
374
+ ```
375
+
376
+ This will compile TypeScript to JavaScript in the `dist/` directory.
@@ -0,0 +1,15 @@
1
+ export { OfficerCacheService } from './services/officerCacheService';
2
+ export { OfficerService } from './services/officerService';
3
+ export { PenalCodeCacheService } from './services/penalCodeCacheService';
4
+ export { DeviceCacheService } from './services/deviceCacheService';
5
+ export { DeviceService } from './services/deviceService';
6
+ export { ApiKeyService } from './services/apiKeyService';
7
+ export type { ApiKeyData } from './services/apiKeyService';
8
+ export { AuditService } from './services/auditService';
9
+ export { AuditEventType } from './services/auditService';
10
+ export type { AuditEventPayload } from './services/auditService';
11
+ export { createAuthenticateDeviceOrOfficer } from './middleware/authenticateDevice';
12
+ export { createSafeRedisGet, createSafeRedisSet, createSafeRedisUtils } from './utils/redis';
13
+ export { getEnvironmentMode, isDevelopmentMode, isProductionMode, devLog, devError, devWarn, devDebug } from './utils/logMode';
14
+ export type { EnvironmentMode } from './utils/logMode';
15
+ export type { SharedServicesDependencies } from './types/dependencies';
package/dist/index.js ADDED
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.devDebug = exports.devWarn = exports.devError = exports.devLog = exports.isProductionMode = exports.isDevelopmentMode = exports.getEnvironmentMode = exports.createSafeRedisUtils = exports.createSafeRedisSet = exports.createSafeRedisGet = exports.createAuthenticateDeviceOrOfficer = exports.AuditEventType = exports.AuditService = exports.ApiKeyService = exports.DeviceService = exports.DeviceCacheService = exports.PenalCodeCacheService = exports.OfficerService = exports.OfficerCacheService = void 0;
4
+ // Export services
5
+ var officerCacheService_1 = require("./services/officerCacheService");
6
+ Object.defineProperty(exports, "OfficerCacheService", { enumerable: true, get: function () { return officerCacheService_1.OfficerCacheService; } });
7
+ var officerService_1 = require("./services/officerService");
8
+ Object.defineProperty(exports, "OfficerService", { enumerable: true, get: function () { return officerService_1.OfficerService; } });
9
+ var penalCodeCacheService_1 = require("./services/penalCodeCacheService");
10
+ Object.defineProperty(exports, "PenalCodeCacheService", { enumerable: true, get: function () { return penalCodeCacheService_1.PenalCodeCacheService; } });
11
+ var deviceCacheService_1 = require("./services/deviceCacheService");
12
+ Object.defineProperty(exports, "DeviceCacheService", { enumerable: true, get: function () { return deviceCacheService_1.DeviceCacheService; } });
13
+ var deviceService_1 = require("./services/deviceService");
14
+ Object.defineProperty(exports, "DeviceService", { enumerable: true, get: function () { return deviceService_1.DeviceService; } });
15
+ var apiKeyService_1 = require("./services/apiKeyService");
16
+ Object.defineProperty(exports, "ApiKeyService", { enumerable: true, get: function () { return apiKeyService_1.ApiKeyService; } });
17
+ var auditService_1 = require("./services/auditService");
18
+ Object.defineProperty(exports, "AuditService", { enumerable: true, get: function () { return auditService_1.AuditService; } });
19
+ var auditService_2 = require("./services/auditService");
20
+ Object.defineProperty(exports, "AuditEventType", { enumerable: true, get: function () { return auditService_2.AuditEventType; } });
21
+ // Export middleware
22
+ var authenticateDevice_1 = require("./middleware/authenticateDevice");
23
+ Object.defineProperty(exports, "createAuthenticateDeviceOrOfficer", { enumerable: true, get: function () { return authenticateDevice_1.createAuthenticateDeviceOrOfficer; } });
24
+ // Export utilities
25
+ var redis_1 = require("./utils/redis");
26
+ Object.defineProperty(exports, "createSafeRedisGet", { enumerable: true, get: function () { return redis_1.createSafeRedisGet; } });
27
+ Object.defineProperty(exports, "createSafeRedisSet", { enumerable: true, get: function () { return redis_1.createSafeRedisSet; } });
28
+ Object.defineProperty(exports, "createSafeRedisUtils", { enumerable: true, get: function () { return redis_1.createSafeRedisUtils; } });
29
+ var logMode_1 = require("./utils/logMode");
30
+ Object.defineProperty(exports, "getEnvironmentMode", { enumerable: true, get: function () { return logMode_1.getEnvironmentMode; } });
31
+ Object.defineProperty(exports, "isDevelopmentMode", { enumerable: true, get: function () { return logMode_1.isDevelopmentMode; } });
32
+ Object.defineProperty(exports, "isProductionMode", { enumerable: true, get: function () { return logMode_1.isProductionMode; } });
33
+ Object.defineProperty(exports, "devLog", { enumerable: true, get: function () { return logMode_1.devLog; } });
34
+ Object.defineProperty(exports, "devError", { enumerable: true, get: function () { return logMode_1.devError; } });
35
+ Object.defineProperty(exports, "devWarn", { enumerable: true, get: function () { return logMode_1.devWarn; } });
36
+ Object.defineProperty(exports, "devDebug", { enumerable: true, get: function () { return logMode_1.devDebug; } });
@@ -0,0 +1,15 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ import { SharedServicesDependencies } from '../types/dependencies';
3
+ /**
4
+ * Middleware to authenticate either a device, an officer, or a microservice (via API key)
5
+ * Checks JWT token and validates against Device or Officer table
6
+ * Also checks for API keys for microservice authentication
7
+ *
8
+ * Supports multiple authentication methods:
9
+ * - X-API-Key: <api_key> (for microservice authentication)
10
+ * - Device-Token: <device_jwt_token> (for device authentication)
11
+ * - Authorization: Bearer <user_jwt_token> (for officer/user authentication)
12
+ *
13
+ * Priority: API Key > Device Token > Authorization Token
14
+ */
15
+ export declare const createAuthenticateDeviceOrOfficer: (deps: SharedServicesDependencies) => (req: Request, res: Response, next: NextFunction) => Promise<void | Response<any, Record<string, any>>>;