@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,46 @@
|
|
|
1
|
+
import { SharedServicesDependencies } from '../types/dependencies';
|
|
2
|
+
export declare class DeviceCacheService {
|
|
3
|
+
private redisClient;
|
|
4
|
+
private logger;
|
|
5
|
+
constructor(deps: SharedServicesDependencies);
|
|
6
|
+
/**
|
|
7
|
+
* Store individual device as hash with indexes
|
|
8
|
+
*/
|
|
9
|
+
storeDevice(device: any): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Get device by ID - O(1) lookup
|
|
12
|
+
*/
|
|
13
|
+
getDeviceById(id: number): Promise<any | null>;
|
|
14
|
+
/**
|
|
15
|
+
* Get device by device_id
|
|
16
|
+
*/
|
|
17
|
+
getDeviceByDeviceId(device_id: string): Promise<any | null>;
|
|
18
|
+
/**
|
|
19
|
+
* Get device by serial number
|
|
20
|
+
*/
|
|
21
|
+
getDeviceBySerialNumber(serial_number: string): Promise<any | null>;
|
|
22
|
+
/**
|
|
23
|
+
* Get multiple devices efficiently using pipeline
|
|
24
|
+
*/
|
|
25
|
+
getDevicesByIds(ids: number[]): Promise<any[]>;
|
|
26
|
+
/**
|
|
27
|
+
* Query by police station
|
|
28
|
+
*/
|
|
29
|
+
getDevicesByStation(stationId: number): Promise<any[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Get active devices
|
|
32
|
+
*/
|
|
33
|
+
getActiveDevices(): Promise<any[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Get all devices from cache
|
|
36
|
+
*/
|
|
37
|
+
getAllDevices(): Promise<any[]>;
|
|
38
|
+
/**
|
|
39
|
+
* Clear all device cache
|
|
40
|
+
*/
|
|
41
|
+
clearCache(): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Remove single device from cache
|
|
44
|
+
*/
|
|
45
|
+
removeDevice(id: number): Promise<void>;
|
|
46
|
+
}
|
|
@@ -0,0 +1,432 @@
|
|
|
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.DeviceCacheService = void 0;
|
|
13
|
+
class DeviceCacheService {
|
|
14
|
+
constructor(deps) {
|
|
15
|
+
this.redisClient = deps.redisClient;
|
|
16
|
+
this.logger = deps.logger;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Store individual device as hash with indexes
|
|
20
|
+
*/
|
|
21
|
+
storeDevice(device) {
|
|
22
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
23
|
+
var _a, _b;
|
|
24
|
+
try {
|
|
25
|
+
if (!device || !device.id) {
|
|
26
|
+
throw new Error('Invalid device: missing id');
|
|
27
|
+
}
|
|
28
|
+
// Check if Redis is connected
|
|
29
|
+
if (!this.redisClient.isOpen) {
|
|
30
|
+
this.logger.warn('Redis client not connected, attempting to connect...');
|
|
31
|
+
yield this.redisClient.connect();
|
|
32
|
+
}
|
|
33
|
+
const key = `device:${device.id}`;
|
|
34
|
+
// Check if key exists and delete it if it's the wrong type (from old cache implementation)
|
|
35
|
+
try {
|
|
36
|
+
const keyType = yield this.redisClient.type(key);
|
|
37
|
+
if (keyType !== 'hash' && keyType !== 'none') {
|
|
38
|
+
this.logger.warn(`Deleting key ${key} with wrong type: ${keyType}`);
|
|
39
|
+
yield this.redisClient.del(key);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (typeError) {
|
|
43
|
+
// If type check fails, try to delete the key anyway
|
|
44
|
+
this.logger.warn(`Error checking key type for ${key}, attempting to delete`, {
|
|
45
|
+
error: typeError instanceof Error ? typeError.message : String(typeError)
|
|
46
|
+
});
|
|
47
|
+
try {
|
|
48
|
+
yield this.redisClient.del(key);
|
|
49
|
+
}
|
|
50
|
+
catch (delError) {
|
|
51
|
+
// Ignore delete errors, continue with store
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Helper function to safely convert dates
|
|
55
|
+
const safeDateToString = (date) => {
|
|
56
|
+
if (!date)
|
|
57
|
+
return '';
|
|
58
|
+
if (date instanceof Date) {
|
|
59
|
+
return isNaN(date.getTime()) ? '' : date.toISOString();
|
|
60
|
+
}
|
|
61
|
+
if (typeof date === 'string') {
|
|
62
|
+
return date;
|
|
63
|
+
}
|
|
64
|
+
return '';
|
|
65
|
+
};
|
|
66
|
+
// Prepare hash data - ensure all values are strings
|
|
67
|
+
const hashData = {
|
|
68
|
+
id: device.id.toString(),
|
|
69
|
+
device_id: (device.device_id || '').toString(),
|
|
70
|
+
device_name: (device.device_name || '').toString(),
|
|
71
|
+
device_type: (device.device_type || '').toString(),
|
|
72
|
+
serial_number: (device.serial_number || '').toString(),
|
|
73
|
+
imei: (device.imei || '').toString(),
|
|
74
|
+
is_active: (((_a = device.is_active) === null || _a === void 0 ? void 0 : _a.toString()) || 'false').toString(),
|
|
75
|
+
policeStationId: (((_b = device.policeStationId) === null || _b === void 0 ? void 0 : _b.toString()) || '').toString(),
|
|
76
|
+
created_at: safeDateToString(device.created_at),
|
|
77
|
+
updated_at: safeDateToString(device.updated_at),
|
|
78
|
+
};
|
|
79
|
+
// Stringify nested objects safely
|
|
80
|
+
try {
|
|
81
|
+
hashData.policeStation = JSON.stringify(device.policeStation || null);
|
|
82
|
+
}
|
|
83
|
+
catch (jsonError) {
|
|
84
|
+
this.logger.warn('Failed to stringify policeStation', { deviceId: device.id });
|
|
85
|
+
hashData.policeStation = 'null';
|
|
86
|
+
}
|
|
87
|
+
// Store as hash with JSON-stringified complex fields
|
|
88
|
+
yield this.redisClient.hSet(key, hashData);
|
|
89
|
+
// Create indexes for common queries
|
|
90
|
+
// Helper to safely add to set (delete if wrong type first)
|
|
91
|
+
const safeSAdd = (setKey, value) => __awaiter(this, void 0, void 0, function* () {
|
|
92
|
+
try {
|
|
93
|
+
const keyType = yield this.redisClient.type(setKey);
|
|
94
|
+
if (keyType !== 'set' && keyType !== 'none') {
|
|
95
|
+
yield this.redisClient.del(setKey);
|
|
96
|
+
}
|
|
97
|
+
yield this.redisClient.sAdd(setKey, value);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
// If it fails, try to delete and retry
|
|
101
|
+
try {
|
|
102
|
+
yield this.redisClient.del(setKey);
|
|
103
|
+
yield this.redisClient.sAdd(setKey, value);
|
|
104
|
+
}
|
|
105
|
+
catch (retryError) {
|
|
106
|
+
this.logger.warn(`Failed to add to set ${setKey}`, {
|
|
107
|
+
error: retryError instanceof Error ? retryError.message : String(retryError)
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
// Helper to safely set string value (delete if wrong type first)
|
|
113
|
+
const safeSet = (key, value) => __awaiter(this, void 0, void 0, function* () {
|
|
114
|
+
try {
|
|
115
|
+
const keyType = yield this.redisClient.type(key);
|
|
116
|
+
if (keyType !== 'string' && keyType !== 'none') {
|
|
117
|
+
yield this.redisClient.del(key);
|
|
118
|
+
}
|
|
119
|
+
yield this.redisClient.set(key, value);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
// If it fails, try to delete and retry
|
|
123
|
+
try {
|
|
124
|
+
yield this.redisClient.del(key);
|
|
125
|
+
yield this.redisClient.set(key, value);
|
|
126
|
+
}
|
|
127
|
+
catch (retryError) {
|
|
128
|
+
this.logger.warn(`Failed to set key ${key}`, {
|
|
129
|
+
error: retryError instanceof Error ? retryError.message : String(retryError)
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
if (device.policeStationId) {
|
|
135
|
+
yield safeSAdd(`devices:station:${device.policeStationId}`, device.id.toString());
|
|
136
|
+
}
|
|
137
|
+
if (device.device_id) {
|
|
138
|
+
yield safeSet(`device:by_device_id:${device.device_id}`, device.id.toString());
|
|
139
|
+
}
|
|
140
|
+
if (device.serial_number) {
|
|
141
|
+
yield safeSet(`device:by_serial:${device.serial_number}`, device.id.toString());
|
|
142
|
+
}
|
|
143
|
+
if (device.is_active) {
|
|
144
|
+
yield safeSAdd('devices:active', device.id.toString());
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
yield safeSAdd('devices:inactive', device.id.toString());
|
|
148
|
+
}
|
|
149
|
+
// Maintain list of all device IDs
|
|
150
|
+
yield safeSAdd('devices:all', device.id.toString());
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
154
|
+
const errorStack = error instanceof Error ? error.stack : undefined;
|
|
155
|
+
const errorDetails = {
|
|
156
|
+
error: errorMessage,
|
|
157
|
+
errorStack: errorStack,
|
|
158
|
+
deviceId: device === null || device === void 0 ? void 0 : device.id,
|
|
159
|
+
deviceKeys: device ? Object.keys(device) : [],
|
|
160
|
+
device: device ? JSON.stringify(device, (key, value) => {
|
|
161
|
+
// Handle circular references and Date objects
|
|
162
|
+
if (value instanceof Date) {
|
|
163
|
+
return value.toISOString();
|
|
164
|
+
}
|
|
165
|
+
if (typeof value === 'object' && value !== null) {
|
|
166
|
+
try {
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
return '[Circular]';
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return value;
|
|
174
|
+
}, 2) : 'No device data'
|
|
175
|
+
};
|
|
176
|
+
// Log error (logger always logs, console only in dev)
|
|
177
|
+
this.logger.error('Error storing device in cache', errorDetails);
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Get device by ID - O(1) lookup
|
|
184
|
+
*/
|
|
185
|
+
getDeviceById(id) {
|
|
186
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
187
|
+
try {
|
|
188
|
+
const data = yield this.redisClient.hGetAll(`device:${id}`);
|
|
189
|
+
if (!data || !data.id)
|
|
190
|
+
return null;
|
|
191
|
+
// Parse JSON fields
|
|
192
|
+
return Object.assign(Object.assign({}, data), { id: parseInt(data.id), policeStationId: data.policeStationId ? parseInt(data.policeStationId) : null, is_active: data.is_active === 'true', created_at: data.created_at ? new Date(data.created_at) : null, updated_at: data.updated_at ? new Date(data.updated_at) : null, policeStation: data.policeStation ? JSON.parse(data.policeStation) : null });
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
this.logger.error('Error getting device from cache', {
|
|
196
|
+
error: error instanceof Error ? error.message : String(error),
|
|
197
|
+
deviceId: id
|
|
198
|
+
});
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Get device by device_id
|
|
205
|
+
*/
|
|
206
|
+
getDeviceByDeviceId(device_id) {
|
|
207
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
208
|
+
try {
|
|
209
|
+
const idStr = yield this.redisClient.get(`device:by_device_id:${device_id}`);
|
|
210
|
+
if (!idStr)
|
|
211
|
+
return null;
|
|
212
|
+
const id = parseInt(String(idStr));
|
|
213
|
+
return this.getDeviceById(id);
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
this.logger.error('Error getting device by device_id from cache', {
|
|
217
|
+
error: error instanceof Error ? error.message : String(error),
|
|
218
|
+
device_id
|
|
219
|
+
});
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Get device by serial number
|
|
226
|
+
*/
|
|
227
|
+
getDeviceBySerialNumber(serial_number) {
|
|
228
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
229
|
+
try {
|
|
230
|
+
const idStr = yield this.redisClient.get(`device:by_serial:${serial_number}`);
|
|
231
|
+
if (!idStr)
|
|
232
|
+
return null;
|
|
233
|
+
const id = parseInt(String(idStr));
|
|
234
|
+
return this.getDeviceById(id);
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
this.logger.error('Error getting device by serial from cache', {
|
|
238
|
+
error: error instanceof Error ? error.message : String(error),
|
|
239
|
+
serial_number
|
|
240
|
+
});
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Get multiple devices efficiently using pipeline
|
|
247
|
+
*/
|
|
248
|
+
getDevicesByIds(ids) {
|
|
249
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
250
|
+
if (ids.length === 0)
|
|
251
|
+
return [];
|
|
252
|
+
try {
|
|
253
|
+
const pipeline = this.redisClient.multi();
|
|
254
|
+
ids.forEach(id => pipeline.hGetAll(`device:${id}`));
|
|
255
|
+
const results = yield pipeline.exec();
|
|
256
|
+
return results
|
|
257
|
+
.map((result) => {
|
|
258
|
+
if (result[0])
|
|
259
|
+
return null; // Skip errors
|
|
260
|
+
const data = result[1];
|
|
261
|
+
if (!data || !data.id)
|
|
262
|
+
return null;
|
|
263
|
+
return Object.assign(Object.assign({}, data), { id: parseInt(data.id), policeStationId: data.policeStationId ? parseInt(data.policeStationId) : null, is_active: data.is_active === 'true', created_at: data.created_at ? new Date(data.created_at) : null, updated_at: data.updated_at ? new Date(data.updated_at) : null, policeStation: data.policeStation ? JSON.parse(data.policeStation) : null });
|
|
264
|
+
})
|
|
265
|
+
.filter(Boolean);
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
this.logger.error('Error getting devices from cache', {
|
|
269
|
+
error: error instanceof Error ? error.message : String(error)
|
|
270
|
+
});
|
|
271
|
+
return [];
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Query by police station
|
|
277
|
+
*/
|
|
278
|
+
getDevicesByStation(stationId) {
|
|
279
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
280
|
+
try {
|
|
281
|
+
const deviceIds = yield this.redisClient.sMembers(`devices:station:${stationId}`);
|
|
282
|
+
const ids = Array.isArray(deviceIds)
|
|
283
|
+
? deviceIds.map((id) => parseInt(String(id)))
|
|
284
|
+
: [];
|
|
285
|
+
return this.getDevicesByIds(ids);
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
this.logger.error('Error getting devices by station from cache', {
|
|
289
|
+
error: error instanceof Error ? error.message : String(error),
|
|
290
|
+
stationId
|
|
291
|
+
});
|
|
292
|
+
return [];
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Get active devices
|
|
298
|
+
*/
|
|
299
|
+
getActiveDevices() {
|
|
300
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
301
|
+
try {
|
|
302
|
+
const deviceIds = yield this.redisClient.sMembers('devices:active');
|
|
303
|
+
const ids = Array.isArray(deviceIds)
|
|
304
|
+
? deviceIds.map((id) => parseInt(String(id)))
|
|
305
|
+
: [];
|
|
306
|
+
return this.getDevicesByIds(ids);
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
this.logger.error('Error getting active devices from cache', {
|
|
310
|
+
error: error instanceof Error ? error.message : String(error)
|
|
311
|
+
});
|
|
312
|
+
return [];
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Get all devices from cache
|
|
318
|
+
*/
|
|
319
|
+
getAllDevices() {
|
|
320
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
321
|
+
try {
|
|
322
|
+
const deviceIds = yield this.redisClient.sMembers('devices:all');
|
|
323
|
+
const ids = Array.isArray(deviceIds)
|
|
324
|
+
? deviceIds.map((id) => parseInt(String(id)))
|
|
325
|
+
: [];
|
|
326
|
+
const devices = yield this.getDevicesByIds(ids);
|
|
327
|
+
// Sort by ID descending to match original behavior
|
|
328
|
+
return devices.sort((a, b) => b.id - a.id);
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
this.logger.error('Error getting all devices from cache', {
|
|
332
|
+
error: error instanceof Error ? error.message : String(error)
|
|
333
|
+
});
|
|
334
|
+
return [];
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Clear all device cache
|
|
340
|
+
*/
|
|
341
|
+
clearCache() {
|
|
342
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
343
|
+
try {
|
|
344
|
+
// Delete old cache key if it exists (from previous implementation)
|
|
345
|
+
try {
|
|
346
|
+
yield this.redisClient.del('devices');
|
|
347
|
+
}
|
|
348
|
+
catch (e) {
|
|
349
|
+
// Ignore if key doesn't exist
|
|
350
|
+
}
|
|
351
|
+
const deviceIds = yield this.redisClient.sMembers('devices:all');
|
|
352
|
+
const ids = Array.isArray(deviceIds) ? deviceIds : [];
|
|
353
|
+
if (ids.length > 0) {
|
|
354
|
+
const pipeline = this.redisClient.multi();
|
|
355
|
+
ids.forEach((id) => {
|
|
356
|
+
const idStr = String(id);
|
|
357
|
+
pipeline.del(`device:${idStr}`);
|
|
358
|
+
});
|
|
359
|
+
yield pipeline.exec();
|
|
360
|
+
}
|
|
361
|
+
// Clear indexes
|
|
362
|
+
const keys = yield this.redisClient.keys('devices:*');
|
|
363
|
+
if (keys.length > 0) {
|
|
364
|
+
yield this.redisClient.del(keys);
|
|
365
|
+
}
|
|
366
|
+
// Clear lookup keys
|
|
367
|
+
const lookupKeys = yield this.redisClient.keys('device:by_*');
|
|
368
|
+
if (lookupKeys.length > 0) {
|
|
369
|
+
yield this.redisClient.del(lookupKeys);
|
|
370
|
+
}
|
|
371
|
+
// Clear individual device hash keys (in case they exist from previous attempts)
|
|
372
|
+
const deviceKeys = yield this.redisClient.keys('device:[0-9]*');
|
|
373
|
+
const keysArray = Array.isArray(deviceKeys) ? deviceKeys : [];
|
|
374
|
+
if (keysArray.length > 0) {
|
|
375
|
+
// Filter to only get device:ID pattern (not device:by_*)
|
|
376
|
+
const hashKeys = keysArray
|
|
377
|
+
.map((key) => typeof key === 'string' ? key : key.toString())
|
|
378
|
+
.filter((key) => /^device:\d+$/.test(key));
|
|
379
|
+
if (hashKeys.length > 0) {
|
|
380
|
+
yield this.redisClient.del(hashKeys);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
this.logger.error('Error clearing device cache', {
|
|
386
|
+
error: error instanceof Error ? error.message : String(error)
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Remove single device from cache
|
|
393
|
+
*/
|
|
394
|
+
removeDevice(id) {
|
|
395
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
396
|
+
try {
|
|
397
|
+
const device = yield this.getDeviceById(id);
|
|
398
|
+
if (device) {
|
|
399
|
+
// Remove from indexes
|
|
400
|
+
if (device.policeStationId) {
|
|
401
|
+
yield this.redisClient.sRem(`devices:station:${device.policeStationId}`, id.toString());
|
|
402
|
+
}
|
|
403
|
+
if (device.device_id) {
|
|
404
|
+
const deviceIdStr = typeof device.device_id === 'string' ? device.device_id : device.device_id.toString();
|
|
405
|
+
yield this.redisClient.del(`device:by_device_id:${deviceIdStr}`);
|
|
406
|
+
}
|
|
407
|
+
if (device.serial_number) {
|
|
408
|
+
const serialStr = typeof device.serial_number === 'string' ? device.serial_number : device.serial_number.toString();
|
|
409
|
+
yield this.redisClient.del(`device:by_serial:${serialStr}`);
|
|
410
|
+
}
|
|
411
|
+
if (device.is_active) {
|
|
412
|
+
yield this.redisClient.sRem('devices:active', id.toString());
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
yield this.redisClient.sRem('devices:inactive', id.toString());
|
|
416
|
+
}
|
|
417
|
+
// Remove from all list
|
|
418
|
+
yield this.redisClient.sRem('devices:all', id.toString());
|
|
419
|
+
// Remove hash
|
|
420
|
+
yield this.redisClient.del(`device:${id}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
this.logger.error('Error removing device from cache', {
|
|
425
|
+
error: error instanceof Error ? error.message : String(error),
|
|
426
|
+
deviceId: id
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
exports.DeviceCacheService = DeviceCacheService;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { SharedServicesDependencies } from '../types/dependencies';
|
|
2
|
+
/**
|
|
3
|
+
* Device Service - Handles device retrieval with cache and auth service fallback
|
|
4
|
+
*
|
|
5
|
+
* This service:
|
|
6
|
+
* 1. First tries Redis cache (via DeviceCacheService)
|
|
7
|
+
* 2. Falls back to auth service API if cache miss
|
|
8
|
+
* 3. Stores fetched device in cache for future use
|
|
9
|
+
*/
|
|
10
|
+
export declare class DeviceService {
|
|
11
|
+
private deviceCacheService;
|
|
12
|
+
private logger;
|
|
13
|
+
private authHost?;
|
|
14
|
+
private authPort?;
|
|
15
|
+
constructor(deps: SharedServicesDependencies);
|
|
16
|
+
/**
|
|
17
|
+
* Get device by ID from Redis cache, fallback to auth service
|
|
18
|
+
* First tries Redis cache, then falls back to auth service API
|
|
19
|
+
*/
|
|
20
|
+
getDeviceById(deviceId: string | number): Promise<any | null>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
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.DeviceService = void 0;
|
|
13
|
+
const deviceCacheService_1 = require("./deviceCacheService");
|
|
14
|
+
/**
|
|
15
|
+
* Device Service - Handles device retrieval with cache and auth service fallback
|
|
16
|
+
*
|
|
17
|
+
* This service:
|
|
18
|
+
* 1. First tries Redis cache (via DeviceCacheService)
|
|
19
|
+
* 2. Falls back to auth service API if cache miss
|
|
20
|
+
* 3. Stores fetched device in cache for future use
|
|
21
|
+
*/
|
|
22
|
+
class DeviceService {
|
|
23
|
+
constructor(deps) {
|
|
24
|
+
this.deviceCacheService = new deviceCacheService_1.DeviceCacheService(deps);
|
|
25
|
+
this.logger = deps.logger;
|
|
26
|
+
this.authHost = deps.authHost || process.env.AUTH_HOST;
|
|
27
|
+
this.authPort = deps.authPort || process.env.AUTH_PORT;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get device by ID from Redis cache, fallback to auth service
|
|
31
|
+
* First tries Redis cache, then falls back to auth service API
|
|
32
|
+
*/
|
|
33
|
+
getDeviceById(deviceId) {
|
|
34
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
35
|
+
try {
|
|
36
|
+
const deviceIdNum = typeof deviceId === 'string' ? parseInt(deviceId) : deviceId;
|
|
37
|
+
if (isNaN(deviceIdNum)) {
|
|
38
|
+
this.logger.warn('Invalid device ID', { deviceId });
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
// Try cache first
|
|
42
|
+
try {
|
|
43
|
+
const cachedDevice = yield this.deviceCacheService.getDeviceById(deviceIdNum);
|
|
44
|
+
if (cachedDevice) {
|
|
45
|
+
this.logger.debug(`Device ${deviceIdNum} retrieved from Redis cache`);
|
|
46
|
+
return cachedDevice;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (cacheError) {
|
|
50
|
+
this.logger.warn('Cache read error, falling back to auth service', {
|
|
51
|
+
error: cacheError instanceof Error ? cacheError.message : String(cacheError),
|
|
52
|
+
deviceId: deviceIdNum
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// Fallback to auth service if host is configured
|
|
56
|
+
if (!this.authHost) {
|
|
57
|
+
this.logger.warn('Auth host not configured, cannot fetch device from auth service', {
|
|
58
|
+
deviceId: deviceIdNum
|
|
59
|
+
});
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const authUrl = this.authPort
|
|
63
|
+
? `http://${this.authHost}:${this.authPort}/api/v1/device/cache/${deviceIdNum}`
|
|
64
|
+
: `http://${this.authHost}/api/v1/device/cache/${deviceIdNum}`;
|
|
65
|
+
this.logger.debug(`Cache miss for device ${deviceIdNum}, fetching from auth service`);
|
|
66
|
+
const response = yield fetch(authUrl);
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
if (response.status === 404) {
|
|
69
|
+
this.logger.warn(`Device ${deviceIdNum} not found in auth service`);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
throw new Error(`Failed to fetch device: ${response.status} ${response.statusText}`);
|
|
73
|
+
}
|
|
74
|
+
const result = yield response.json();
|
|
75
|
+
const device = result.data || result;
|
|
76
|
+
// Store in cache for future use
|
|
77
|
+
if (device && device.id) {
|
|
78
|
+
try {
|
|
79
|
+
yield this.deviceCacheService.storeDevice(device);
|
|
80
|
+
this.logger.debug(`Device ${deviceIdNum} stored in cache after fetch from auth service`);
|
|
81
|
+
}
|
|
82
|
+
catch (cacheStoreError) {
|
|
83
|
+
// Don't fail if cache store fails, just log
|
|
84
|
+
this.logger.warn('Failed to store device in cache after fetch', {
|
|
85
|
+
error: cacheStoreError instanceof Error ? cacheStoreError.message : String(cacheStoreError),
|
|
86
|
+
deviceId: deviceIdNum
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
this.logger.debug(`Device ${deviceIdNum} fetched from auth service`);
|
|
91
|
+
return device;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
this.logger.error('Error fetching device by ID', {
|
|
95
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
96
|
+
deviceId: deviceId.toString()
|
|
97
|
+
});
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
exports.DeviceService = DeviceService;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { SharedServicesDependencies } from '../types/dependencies';
|
|
2
|
+
export declare class OfficerCacheService {
|
|
3
|
+
private redisClient;
|
|
4
|
+
private logger;
|
|
5
|
+
constructor(deps: SharedServicesDependencies);
|
|
6
|
+
/**
|
|
7
|
+
* Store individual officer as hash with indexes
|
|
8
|
+
*/
|
|
9
|
+
storeOfficer(officer: any): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Get officer by ID - O(1) lookup
|
|
12
|
+
*/
|
|
13
|
+
getOfficerById(id: number): Promise<any | null>;
|
|
14
|
+
/**
|
|
15
|
+
* Get multiple officers efficiently using pipeline
|
|
16
|
+
*/
|
|
17
|
+
getOfficersByIds(ids: number[]): Promise<any[]>;
|
|
18
|
+
/**
|
|
19
|
+
* Query by station
|
|
20
|
+
*/
|
|
21
|
+
getOfficersByStation(stationId: number): Promise<any[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Query by rank
|
|
24
|
+
*/
|
|
25
|
+
getOfficersByRank(rankId: number): Promise<any[]>;
|
|
26
|
+
/**
|
|
27
|
+
* Query by role
|
|
28
|
+
*/
|
|
29
|
+
getOfficersByRole(roleId: number): Promise<any[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Get all officers from cache
|
|
32
|
+
*/
|
|
33
|
+
getAllOfficers(): Promise<any[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Get officer by service_number
|
|
36
|
+
*/
|
|
37
|
+
getOfficerByServiceNumber(service_number: string): Promise<any | null>;
|
|
38
|
+
/**
|
|
39
|
+
* Cache all officers from auth service
|
|
40
|
+
*/
|
|
41
|
+
cacheAllOfficers(): Promise<any[]>;
|
|
42
|
+
/**
|
|
43
|
+
* Clear all officer cache
|
|
44
|
+
*/
|
|
45
|
+
clearCache(): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Remove single officer from cache
|
|
48
|
+
*/
|
|
49
|
+
removeOfficer(id: number): Promise<void>;
|
|
50
|
+
}
|