@govish/shared-services 1.1.0 → 1.2.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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## Version 1.2.0
4
+
5
+ ### Added
6
+ - **StationDutyBlockCacheService**: Read-only cache for station-duty blocks and sectors
7
+ - Keys: `station_duty_blocks:${stationDutyId}`, `station_duty_block:${blockId}`, `block_sectors:${blockId}`, `block_sector:${sectorId}`
8
+ - `getBlocks(stationDutyId)`, `getBlock(blockId, stationDutyId?)`, `getSectors(blockId)`, `getSector(sectorId, blockId?)`
9
+ - **RankLeaveDaysCacheService**: Read-only cache for rank leave days
10
+ - Keys: `rank_leave_days:${rankId}`, `rank_leave_day:${leaveDayId}`
11
+ - `getLeaveDays(rankId)`, `getLeaveDay(leaveDayId, rankId?)`
12
+
13
+ ---
14
+
3
15
  ## Version 1.1.0
4
16
 
5
17
  ### Added
package/README.md CHANGED
@@ -29,6 +29,8 @@ const authenticateDeviceOrOfficer = createAuthenticateDeviceOrOfficer(dependenci
29
29
  - **PenalCodeCacheService**: Redis-based caching service for penal codes
30
30
  - **DeviceCacheService**: Redis-based caching service for devices with indexing
31
31
  - **StationCacheService**: Redis-based caching service for police stations (JSON list, set of IDs, and per-station hashes)
32
+ - **StationDutyBlockCacheService**: Read-only cache for station-duty blocks and sectors (`getBlocks`, `getBlock`, `getSectors`, `getSector`)
33
+ - **RankLeaveDaysCacheService**: Read-only cache for rank leave days (`getLeaveDays`, `getLeaveDay`)
32
34
  - **ApiKeyService**: Service for managing and validating API keys with Redis caching
33
35
  - **AuditService**: Service for logging audit events to Kafka with comprehensive event tracking
34
36
  - **authenticateDeviceOrOfficer**: Express middleware for authenticating devices, officers, or microservices via API keys
package/dist/index.d.ts CHANGED
@@ -4,6 +4,8 @@ export { PenalCodeCacheService } from './services/penalCodeCacheService';
4
4
  export { DeviceCacheService } from './services/deviceCacheService';
5
5
  export { DeviceService } from './services/deviceService';
6
6
  export { StationCacheService } from './services/stationCacheService';
7
+ export { StationDutyBlockCacheService } from './services/stationDutyBlockCacheService';
8
+ export { RankLeaveDaysCacheService } from './services/rankLeaveDaysCacheService';
7
9
  export { ApiKeyService } from './services/apiKeyService';
8
10
  export type { ApiKeyData } from './services/apiKeyService';
9
11
  export { AuditService } from './services/auditService';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
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.StationCacheService = exports.DeviceService = exports.DeviceCacheService = exports.PenalCodeCacheService = exports.OfficerService = exports.OfficerCacheService = void 0;
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.RankLeaveDaysCacheService = exports.StationDutyBlockCacheService = exports.StationCacheService = exports.DeviceService = exports.DeviceCacheService = exports.PenalCodeCacheService = exports.OfficerService = exports.OfficerCacheService = void 0;
4
4
  // Export services
5
5
  var officerCacheService_1 = require("./services/officerCacheService");
6
6
  Object.defineProperty(exports, "OfficerCacheService", { enumerable: true, get: function () { return officerCacheService_1.OfficerCacheService; } });
@@ -14,6 +14,10 @@ var deviceService_1 = require("./services/deviceService");
14
14
  Object.defineProperty(exports, "DeviceService", { enumerable: true, get: function () { return deviceService_1.DeviceService; } });
15
15
  var stationCacheService_1 = require("./services/stationCacheService");
16
16
  Object.defineProperty(exports, "StationCacheService", { enumerable: true, get: function () { return stationCacheService_1.StationCacheService; } });
17
+ var stationDutyBlockCacheService_1 = require("./services/stationDutyBlockCacheService");
18
+ Object.defineProperty(exports, "StationDutyBlockCacheService", { enumerable: true, get: function () { return stationDutyBlockCacheService_1.StationDutyBlockCacheService; } });
19
+ var rankLeaveDaysCacheService_1 = require("./services/rankLeaveDaysCacheService");
20
+ Object.defineProperty(exports, "RankLeaveDaysCacheService", { enumerable: true, get: function () { return rankLeaveDaysCacheService_1.RankLeaveDaysCacheService; } });
17
21
  var apiKeyService_1 = require("./services/apiKeyService");
18
22
  Object.defineProperty(exports, "ApiKeyService", { enumerable: true, get: function () { return apiKeyService_1.ApiKeyService; } });
19
23
  var auditService_1 = require("./services/auditService");
@@ -0,0 +1,19 @@
1
+ import { SharedServicesDependencies } from '../types/dependencies';
2
+ /**
3
+ * Read-only cache service for rank leave days.
4
+ * Only provides get-from-cache; the app is responsible for storing and invalidating.
5
+ */
6
+ export declare class RankLeaveDaysCacheService {
7
+ private redisClient;
8
+ private logger;
9
+ constructor(deps: SharedServicesDependencies);
10
+ /**
11
+ * Get leave days for a rank from cache. Returns empty array on miss.
12
+ */
13
+ getLeaveDays(rankId: number): Promise<Array<Record<string, unknown>>>;
14
+ /**
15
+ * Get one leave day by ID from cache. Returns null on miss.
16
+ * If rankId is provided, returns null when cached leave day's rank_id does not match.
17
+ */
18
+ getLeaveDay(leaveDayId: number, rankId?: number): Promise<Record<string, unknown> | null>;
19
+ }
@@ -0,0 +1,89 @@
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.RankLeaveDaysCacheService = void 0;
13
+ /** Redis keys (must match app that writes: rank_leave_days, rank_leave_day) */
14
+ const RANK_LEAVE_DAYS_KEY = (rankId) => `rank_leave_days:${rankId}`;
15
+ const RANK_LEAVE_DAY_KEY = (leaveDayId) => `rank_leave_day:${leaveDayId}`;
16
+ function safeJsonParse(value, fallback, logger) {
17
+ if (!value || value === '' || value === 'null')
18
+ return fallback;
19
+ try {
20
+ return JSON.parse(value);
21
+ }
22
+ catch (e) {
23
+ logger.warn('Failed to parse rank leave days cache JSON', {
24
+ error: e instanceof Error ? e.message : String(e),
25
+ });
26
+ return fallback;
27
+ }
28
+ }
29
+ /**
30
+ * Read-only cache service for rank leave days.
31
+ * Only provides get-from-cache; the app is responsible for storing and invalidating.
32
+ */
33
+ class RankLeaveDaysCacheService {
34
+ constructor(deps) {
35
+ this.redisClient = deps.redisClient;
36
+ this.logger = deps.logger;
37
+ }
38
+ /**
39
+ * Get leave days for a rank from cache. Returns empty array on miss.
40
+ */
41
+ getLeaveDays(rankId) {
42
+ return __awaiter(this, void 0, void 0, function* () {
43
+ var _a;
44
+ try {
45
+ const key = RANK_LEAVE_DAYS_KEY(rankId);
46
+ const cached = yield this.redisClient.get(key);
47
+ if (!cached)
48
+ return [];
49
+ return (_a = safeJsonParse(cached, [], this.logger)) !== null && _a !== void 0 ? _a : [];
50
+ }
51
+ catch (error) {
52
+ this.logger.error('Error getting rank leave days from cache', {
53
+ error: error instanceof Error ? error.message : String(error),
54
+ rankId,
55
+ });
56
+ return [];
57
+ }
58
+ });
59
+ }
60
+ /**
61
+ * Get one leave day by ID from cache. Returns null on miss.
62
+ * If rankId is provided, returns null when cached leave day's rank_id does not match.
63
+ */
64
+ getLeaveDay(leaveDayId, rankId) {
65
+ return __awaiter(this, void 0, void 0, function* () {
66
+ try {
67
+ const key = RANK_LEAVE_DAY_KEY(leaveDayId);
68
+ const cached = yield this.redisClient.get(key);
69
+ if (!cached)
70
+ return null;
71
+ const parsed = safeJsonParse(cached, null, this.logger);
72
+ if (!parsed)
73
+ return null;
74
+ if (rankId !== undefined && Number(parsed.rank_id) !== rankId)
75
+ return null;
76
+ return parsed;
77
+ }
78
+ catch (error) {
79
+ this.logger.error('Error getting rank leave day from cache', {
80
+ error: error instanceof Error ? error.message : String(error),
81
+ leaveDayId,
82
+ rankId,
83
+ });
84
+ return null;
85
+ }
86
+ });
87
+ }
88
+ }
89
+ exports.RankLeaveDaysCacheService = RankLeaveDaysCacheService;
@@ -0,0 +1,28 @@
1
+ import { SharedServicesDependencies } from '../types/dependencies';
2
+ /**
3
+ * Read-only cache service for station-duty blocks and sectors.
4
+ * Only provides get-from-cache; the app is responsible for storing and invalidating.
5
+ */
6
+ export declare class StationDutyBlockCacheService {
7
+ private redisClient;
8
+ private logger;
9
+ constructor(deps: SharedServicesDependencies);
10
+ /**
11
+ * Get blocks for a station-duty from cache. Returns null on miss.
12
+ */
13
+ getBlocks(stationDutyId: number): Promise<Array<Record<string, unknown>> | null>;
14
+ /**
15
+ * Get one block by ID from cache. Returns null on miss.
16
+ * If stationDutyId is provided, returns null when cached block's station_duty_id does not match.
17
+ */
18
+ getBlock(blockId: number, stationDutyId?: number): Promise<Record<string, unknown> | null>;
19
+ /**
20
+ * Get sectors for a block from cache. Returns empty array on miss.
21
+ */
22
+ getSectors(blockId: number): Promise<Array<Record<string, unknown>>>;
23
+ /**
24
+ * Get one sector by ID from cache. Returns null on miss.
25
+ * If blockId is provided, returns null when cached sector's block_id does not match.
26
+ */
27
+ getSector(sectorId: number, blockId?: number): Promise<Record<string, unknown> | null>;
28
+ }
@@ -0,0 +1,138 @@
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.StationDutyBlockCacheService = void 0;
13
+ /** Redis keys (must match app that writes: station_duty_blocks, station_duty_block, block_sectors, block_sector) */
14
+ const BLOCKS_KEY = (stationDutyId) => `station_duty_blocks:${stationDutyId}`;
15
+ const BLOCK_KEY = (blockId) => `station_duty_block:${blockId}`;
16
+ const SECTORS_KEY = (blockId) => `block_sectors:${blockId}`;
17
+ const SECTOR_KEY = (sectorId) => `block_sector:${sectorId}`;
18
+ function safeJsonParse(value, fallback, logger) {
19
+ if (!value || value === '' || value === 'null')
20
+ return fallback;
21
+ try {
22
+ return JSON.parse(value);
23
+ }
24
+ catch (e) {
25
+ logger.warn('Failed to parse cache JSON', { error: e instanceof Error ? e.message : String(e) });
26
+ return fallback;
27
+ }
28
+ }
29
+ /**
30
+ * Read-only cache service for station-duty blocks and sectors.
31
+ * Only provides get-from-cache; the app is responsible for storing and invalidating.
32
+ */
33
+ class StationDutyBlockCacheService {
34
+ constructor(deps) {
35
+ this.redisClient = deps.redisClient;
36
+ this.logger = deps.logger;
37
+ }
38
+ /**
39
+ * Get blocks for a station-duty from cache. Returns null on miss.
40
+ */
41
+ getBlocks(stationDutyId) {
42
+ return __awaiter(this, void 0, void 0, function* () {
43
+ try {
44
+ const key = BLOCKS_KEY(stationDutyId);
45
+ const cached = yield this.redisClient.get(key);
46
+ if (!cached)
47
+ return null;
48
+ return safeJsonParse(cached, null, this.logger);
49
+ }
50
+ catch (error) {
51
+ this.logger.error('Error getting blocks from cache', {
52
+ error: error instanceof Error ? error.message : String(error),
53
+ stationDutyId,
54
+ });
55
+ return null;
56
+ }
57
+ });
58
+ }
59
+ /**
60
+ * Get one block by ID from cache. Returns null on miss.
61
+ * If stationDutyId is provided, returns null when cached block's station_duty_id does not match.
62
+ */
63
+ getBlock(blockId, stationDutyId) {
64
+ return __awaiter(this, void 0, void 0, function* () {
65
+ try {
66
+ const key = BLOCK_KEY(blockId);
67
+ const cached = yield this.redisClient.get(key);
68
+ if (!cached)
69
+ return null;
70
+ const parsed = safeJsonParse(cached, null, this.logger);
71
+ if (!parsed)
72
+ return null;
73
+ if (stationDutyId !== undefined && Number(parsed.station_duty_id) !== stationDutyId)
74
+ return null;
75
+ return parsed;
76
+ }
77
+ catch (error) {
78
+ this.logger.error('Error getting block from cache', {
79
+ error: error instanceof Error ? error.message : String(error),
80
+ blockId,
81
+ stationDutyId,
82
+ });
83
+ return null;
84
+ }
85
+ });
86
+ }
87
+ /**
88
+ * Get sectors for a block from cache. Returns empty array on miss.
89
+ */
90
+ getSectors(blockId) {
91
+ return __awaiter(this, void 0, void 0, function* () {
92
+ var _a;
93
+ try {
94
+ const key = SECTORS_KEY(blockId);
95
+ const cached = yield this.redisClient.get(key);
96
+ if (!cached)
97
+ return [];
98
+ return (_a = safeJsonParse(cached, [], this.logger)) !== null && _a !== void 0 ? _a : [];
99
+ }
100
+ catch (error) {
101
+ this.logger.error('Error getting sectors from cache', {
102
+ error: error instanceof Error ? error.message : String(error),
103
+ blockId,
104
+ });
105
+ return [];
106
+ }
107
+ });
108
+ }
109
+ /**
110
+ * Get one sector by ID from cache. Returns null on miss.
111
+ * If blockId is provided, returns null when cached sector's block_id does not match.
112
+ */
113
+ getSector(sectorId, blockId) {
114
+ return __awaiter(this, void 0, void 0, function* () {
115
+ try {
116
+ const key = SECTOR_KEY(sectorId);
117
+ const cached = yield this.redisClient.get(key);
118
+ if (!cached)
119
+ return null;
120
+ const parsed = safeJsonParse(cached, null, this.logger);
121
+ if (!parsed)
122
+ return null;
123
+ if (blockId !== undefined && Number(parsed.block_id) !== blockId)
124
+ return null;
125
+ return parsed;
126
+ }
127
+ catch (error) {
128
+ this.logger.error('Error getting sector from cache', {
129
+ error: error instanceof Error ? error.message : String(error),
130
+ sectorId,
131
+ blockId,
132
+ });
133
+ return null;
134
+ }
135
+ });
136
+ }
137
+ }
138
+ exports.StationDutyBlockCacheService = StationDutyBlockCacheService;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@govish/shared-services",
3
- "version": "1.1.0",
4
- "description": "Govish shared services package - includes officer, device, station, and penal code cache services, API key service, audit logging, and authentication middleware",
3
+ "version": "1.2.0",
4
+ "description": "Govish shared services package - officer, device, station, penal code, station-duty block, and rank leave days cache services; API key service; audit logging; authentication middleware",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "files": [