dt-common-device 13.0.9 → 13.0.13
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/dist/config/config.js +5 -0
- package/dist/config/config.types.d.ts +1 -1
- package/dist/config/constants.d.ts +8 -0
- package/dist/config/constants.js +8 -0
- package/dist/entities/admin/Admin.repository.d.ts +2 -0
- package/dist/entities/admin/Admin.repository.js +46 -0
- package/dist/entities/admin/Admin.service.d.ts +1 -0
- package/dist/entities/admin/Admin.service.js +46 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/notificationQueue/index.d.ts +1 -0
- package/dist/notificationQueue/index.js +17 -0
- package/dist/notificationQueue/queue.service.d.ts +21 -0
- package/dist/notificationQueue/queue.service.js +96 -0
- package/package.json +1 -1
package/dist/config/config.js
CHANGED
|
@@ -74,6 +74,11 @@ async function initialize(cfg) {
|
|
|
74
74
|
db_keys = constants_1.CONFIG_KEYS.OPERATION_NODE.db_keys;
|
|
75
75
|
await validateServiceConfig(cfg);
|
|
76
76
|
}
|
|
77
|
+
if (cfg.SOURCE === "NOTIFICATION_SERVICE") {
|
|
78
|
+
sourceKey = "NOTIFICATION";
|
|
79
|
+
db_keys = constants_1.CONFIG_KEYS.NOTIFICATION.db_keys;
|
|
80
|
+
await validateServiceConfig(cfg);
|
|
81
|
+
}
|
|
77
82
|
// Connect to databases
|
|
78
83
|
try {
|
|
79
84
|
await (0, db_1.connectDatabase)();
|
|
@@ -4,7 +4,7 @@ export interface ILogger {
|
|
|
4
4
|
warn(message: string, ...args: any[]): void;
|
|
5
5
|
error(message: string, ...args: any[]): void;
|
|
6
6
|
}
|
|
7
|
-
type AllowedSource = "ACCESS_SERVICE" | "ADMIN_SERVICE" | "ENERGY_SERVICE" | "REMOTE_SERVICE" | "SCHEDULE_SERVICE" | "MIGRATION" | "OPERATION_NODE_SERVICE";
|
|
7
|
+
type AllowedSource = "ACCESS_SERVICE" | "ADMIN_SERVICE" | "ENERGY_SERVICE" | "REMOTE_SERVICE" | "SCHEDULE_SERVICE" | "MIGRATION" | "OPERATION_NODE_SERVICE" | "NOTIFICATION_SERVICE";
|
|
8
8
|
export type IConfig = {
|
|
9
9
|
SOURCE: AllowedSource;
|
|
10
10
|
INTERNAL_EVENT_HANDLER?: IInternalEvent;
|
package/dist/config/constants.js
CHANGED
|
@@ -9,12 +9,14 @@ export declare class AdminRepository {
|
|
|
9
9
|
getZonesByAccessGroups(accessGroupIds: string[], type?: string[]): Promise<any[]>;
|
|
10
10
|
getAccessGroup(accessGroupId: string, propertyId?: string): Promise<IAccessGroup | null>;
|
|
11
11
|
getZoneAccessGroupByZoneId(zoneId: string): Promise<IZoneAccessGroup[] | null>;
|
|
12
|
+
getZoneIdsByAccessGroupIds(accessGroupIds: string[]): Promise<string[]>;
|
|
12
13
|
getAllParentZones(zoneId: string): Promise<string[]>;
|
|
13
14
|
getDirectChildZones(zoneId: string, propertyId?: string): Promise<string[]>;
|
|
14
15
|
getAllChildZones(zoneId: string, propertyId?: string): Promise<string[]>;
|
|
15
16
|
getAccessGroupsByZoneId(zoneId: string): Promise<IAccessGroup[]>;
|
|
16
17
|
getAccessGroupsByZoneIds(zoneIds: string[]): Promise<IAccessGroup[]>;
|
|
17
18
|
getZone(zoneId: string, propertyId?: string): Promise<IZone | null>;
|
|
19
|
+
getZonesByIds(zoneIds: string[]): Promise<IZone[]>;
|
|
18
20
|
getUser(userId: string): Promise<IUser | null>;
|
|
19
21
|
getZoneByDeviceId(deviceId: string): Promise<IZone | null>;
|
|
20
22
|
getAccessGroups(propertyId: string, accessibleBy?: string): Promise<IAccessGroup[]>;
|
|
@@ -278,6 +278,18 @@ let AdminRepository = (() => {
|
|
|
278
278
|
}
|
|
279
279
|
return null;
|
|
280
280
|
}
|
|
281
|
+
async getZoneIdsByAccessGroupIds(accessGroupIds) {
|
|
282
|
+
if (accessGroupIds.length === 0) {
|
|
283
|
+
return [];
|
|
284
|
+
}
|
|
285
|
+
const query = `
|
|
286
|
+
SELECT DISTINCT "zoneId"
|
|
287
|
+
FROM dt_zones_collection_map
|
|
288
|
+
WHERE "collectionId" = ANY($1)
|
|
289
|
+
`;
|
|
290
|
+
const result = await this.postgres.query(query, [accessGroupIds]);
|
|
291
|
+
return result.rows.map((row) => row.zoneId);
|
|
292
|
+
}
|
|
281
293
|
async getAllParentZones(zoneId) {
|
|
282
294
|
const allParentZoneIds = [];
|
|
283
295
|
let currentZoneId = zoneId;
|
|
@@ -416,6 +428,40 @@ let AdminRepository = (() => {
|
|
|
416
428
|
throw new Error("Failed to get zone");
|
|
417
429
|
}
|
|
418
430
|
}
|
|
431
|
+
async getZonesByIds(zoneIds) {
|
|
432
|
+
if (zoneIds.length === 0) {
|
|
433
|
+
return [];
|
|
434
|
+
}
|
|
435
|
+
try {
|
|
436
|
+
// Batch query to get all zones at once
|
|
437
|
+
const query = `SELECT * FROM dt_zones WHERE "id" = ANY($1)`;
|
|
438
|
+
const result = await this.postgres.query(query, [zoneIds]);
|
|
439
|
+
// Get all unique zone type IDs
|
|
440
|
+
const zoneTypeIds = [
|
|
441
|
+
...new Set(result.rows.map((row) => row.zoneTypeId)),
|
|
442
|
+
];
|
|
443
|
+
// Batch query for zone types
|
|
444
|
+
let zoneTypesMap = new Map();
|
|
445
|
+
if (zoneTypeIds.length > 0) {
|
|
446
|
+
const zoneTypesQuery = `SELECT * FROM "dt_zoneTypes" WHERE "id" = ANY($1)`;
|
|
447
|
+
const zoneTypesResult = await this.postgres.query(zoneTypesQuery, [
|
|
448
|
+
zoneTypeIds,
|
|
449
|
+
]);
|
|
450
|
+
zoneTypesResult.rows.forEach((zt) => {
|
|
451
|
+
zoneTypesMap.set(zt.id, { id: zt.id, name: zt.name });
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
// Map zone types to zones
|
|
455
|
+
return result.rows.map((zoneData) => {
|
|
456
|
+
zoneData.zoneType = zoneTypesMap.get(zoneData.zoneTypeId) || {};
|
|
457
|
+
return zoneData;
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
catch (error) {
|
|
461
|
+
console.error("Error in getZonesByIds:", error);
|
|
462
|
+
return [];
|
|
463
|
+
}
|
|
464
|
+
}
|
|
419
465
|
async getUser(userId) {
|
|
420
466
|
const user = await this.postgres.query(`SELECT * FROM dt_users WHERE "id" = $1`, [userId]);
|
|
421
467
|
if (user.rows.length > 0) {
|
|
@@ -8,6 +8,7 @@ export declare class AdminService {
|
|
|
8
8
|
getAccessGroup(accessGroupId: string, propertyId?: string): Promise<IAccessGroup | null>;
|
|
9
9
|
getAccessGroupByZoneId(zoneId: string): Promise<IAccessGroup[] | []>;
|
|
10
10
|
getAccessgroupBySubParentZoneId(zoneId: string): Promise<IAccessGroup[]>;
|
|
11
|
+
getAllParentSubZonesByAccessGroupIds(accessGroupIds: string[]): Promise<string[]>;
|
|
11
12
|
getZone(zoneId: string, propertyId?: string): Promise<IZone | null>;
|
|
12
13
|
getUser(userId: string): Promise<IUser | null>;
|
|
13
14
|
getZoneByDeviceId(deviceId: string): Promise<IZone | null>;
|
|
@@ -133,6 +133,7 @@ let AdminService = (() => {
|
|
|
133
133
|
}
|
|
134
134
|
return accessGroups;
|
|
135
135
|
}
|
|
136
|
+
//--------------------------USED FOR QUERY RESERVATION-----------------------
|
|
136
137
|
async getAccessgroupBySubParentZoneId(zoneId) {
|
|
137
138
|
if (!zoneId) {
|
|
138
139
|
throw new Error("Zone ID is required");
|
|
@@ -211,6 +212,51 @@ let AdminService = (() => {
|
|
|
211
212
|
return [];
|
|
212
213
|
}
|
|
213
214
|
}
|
|
215
|
+
// used for query reservation to find all the zones mapped with the access groups
|
|
216
|
+
async getAllParentSubZonesByAccessGroupIds(accessGroupIds) {
|
|
217
|
+
if (!accessGroupIds || accessGroupIds.length === 0) {
|
|
218
|
+
throw new Error("Access Group IDs are required");
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
// Step 1: Find all zones mapped with these access groups
|
|
222
|
+
const mappedZoneIds = await this.adminRepository.getZoneIdsByAccessGroupIds(accessGroupIds);
|
|
223
|
+
if (mappedZoneIds.length === 0) {
|
|
224
|
+
return [];
|
|
225
|
+
}
|
|
226
|
+
// OPTIMIZATION 1: Batch get all zones at once (reduces DB calls from O(n) to O(1))
|
|
227
|
+
const zones = await this.adminRepository.getZonesByIds(mappedZoneIds);
|
|
228
|
+
const zoneMap = new Map();
|
|
229
|
+
zones.forEach((zone) => zoneMap.set(zone.id, zone));
|
|
230
|
+
// Use Set to collect unique zone IDs from the start
|
|
231
|
+
const allZoneIds = new Set(mappedZoneIds);
|
|
232
|
+
// OPTIMIZATION 2: Process all zones in parallel (reduces time from O(n) sequential to O(1) parallel)
|
|
233
|
+
const zoneProcessingPromises = mappedZoneIds.map(async (zoneId) => {
|
|
234
|
+
const zone = zoneMap.get(zoneId);
|
|
235
|
+
if (!zone) {
|
|
236
|
+
return { parentIds: [], childIds: [] };
|
|
237
|
+
}
|
|
238
|
+
// OPTIMIZATION 3: Get parents and children in parallel for each zone
|
|
239
|
+
const [parentZoneIds, childZoneIds] = await Promise.all([
|
|
240
|
+
this.adminRepository.getAllParentZones(zoneId),
|
|
241
|
+
this.adminRepository.getAllChildZones(zoneId, zone.propertyId),
|
|
242
|
+
]);
|
|
243
|
+
return { parentIds: parentZoneIds, childIds: childZoneIds };
|
|
244
|
+
});
|
|
245
|
+
// Wait for all zone processing to complete
|
|
246
|
+
const results = await Promise.all(zoneProcessingPromises);
|
|
247
|
+
// OPTIMIZATION 4: Batch add all IDs to Set (O(n) operation)
|
|
248
|
+
results.forEach(({ parentIds, childIds }) => {
|
|
249
|
+
parentIds.forEach((id) => allZoneIds.add(id));
|
|
250
|
+
childIds.forEach((id) => allZoneIds.add(id));
|
|
251
|
+
});
|
|
252
|
+
return Array.from(allZoneIds);
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
console.error("Error in getAllParentSubZonesByAccessGroupIds:", error);
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
//----------------------------------------------------------------------------------
|
|
214
260
|
async getZone(zoneId, propertyId) {
|
|
215
261
|
if (!zoneId) {
|
|
216
262
|
throw new Error("Zone ID is required");
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -65,3 +65,5 @@ __exportStar(require("./webhookQueue"), exports);
|
|
|
65
65
|
__exportStar(require("./copilotQueue"), exports);
|
|
66
66
|
//Export Service Queue
|
|
67
67
|
__exportStar(require("./serviceQueue"), exports);
|
|
68
|
+
// Export Notification Queue (Redis pub/sub for notifications)
|
|
69
|
+
__exportStar(require("./notificationQueue"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./queue.service";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./queue.service"), exports);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface NotificationQueuePayload {
|
|
2
|
+
userId: string;
|
|
3
|
+
orgId?: string;
|
|
4
|
+
propertyId?: string;
|
|
5
|
+
data?: any;
|
|
6
|
+
title?: string;
|
|
7
|
+
message?: string;
|
|
8
|
+
url?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface NotificationQueueOptions {
|
|
11
|
+
channel?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Publish an event payload to Redis so any interested consumer can process it.
|
|
15
|
+
*/
|
|
16
|
+
export declare function enqueueEvent(event: NotificationQueuePayload, opts?: NotificationQueueOptions): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Start a single pub/sub consumer in this process.
|
|
19
|
+
* The `handler` is responsible for business logic (e.g. calling processEvent()).
|
|
20
|
+
*/
|
|
21
|
+
export declare function startConsumer(handler: (payload: NotificationQueuePayload) => Promise<void>, opts?: NotificationQueueOptions): Promise<void>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enqueueEvent = enqueueEvent;
|
|
4
|
+
exports.startConsumer = startConsumer;
|
|
5
|
+
const redis_1 = require("../db/redis");
|
|
6
|
+
const DEFAULT_CHANNEL = "notification:events";
|
|
7
|
+
let consumerStarted = false;
|
|
8
|
+
/**
|
|
9
|
+
* Publish an event payload to Redis so any interested consumer can process it.
|
|
10
|
+
*/
|
|
11
|
+
async function enqueueEvent(event, opts) {
|
|
12
|
+
const client = (0, redis_1.getRedisClient)();
|
|
13
|
+
const channel = opts?.channel ?? DEFAULT_CHANNEL;
|
|
14
|
+
try {
|
|
15
|
+
await client.publish(channel, JSON.stringify(event));
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
// eslint-disable-next-line no-console
|
|
19
|
+
console.error("Failed to publish notification event", {
|
|
20
|
+
error,
|
|
21
|
+
channel,
|
|
22
|
+
event,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Start a single pub/sub consumer in this process.
|
|
28
|
+
* The `handler` is responsible for business logic (e.g. calling processEvent()).
|
|
29
|
+
*/
|
|
30
|
+
async function startConsumer(handler, opts) {
|
|
31
|
+
if (consumerStarted) {
|
|
32
|
+
// eslint-disable-next-line no-console
|
|
33
|
+
console.warn("NotificationQueue consumer already started; skipping");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
consumerStarted = true;
|
|
37
|
+
const baseClient = (0, redis_1.getRedisClient)();
|
|
38
|
+
const subscriber = baseClient.duplicate();
|
|
39
|
+
const channel = opts?.channel ?? DEFAULT_CHANNEL;
|
|
40
|
+
try {
|
|
41
|
+
await subscriber.connect();
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
// eslint-disable-next-line no-console
|
|
45
|
+
console.error("Failed to connect Redis subscriber for notifications", {
|
|
46
|
+
error,
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
subscriber.on("error", (error) => {
|
|
51
|
+
// eslint-disable-next-line no-console
|
|
52
|
+
console.error("Redis notification subscriber error", { error });
|
|
53
|
+
});
|
|
54
|
+
try {
|
|
55
|
+
// ioredis `subscribe` expects an (err, count) callback, not a message handler.
|
|
56
|
+
// We subscribe first, then attach a separate "message" listener.
|
|
57
|
+
await subscriber.subscribe(channel);
|
|
58
|
+
subscriber.on("message", async (subscribedChannel, message) => {
|
|
59
|
+
// Only process messages for the intended channel (defensive in case of multiple subscriptions)
|
|
60
|
+
if (subscribedChannel !== channel) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
let payload;
|
|
64
|
+
try {
|
|
65
|
+
payload = JSON.parse(message);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
// eslint-disable-next-line no-console
|
|
69
|
+
console.error("Failed to parse pub/sub notification payload", {
|
|
70
|
+
error,
|
|
71
|
+
message,
|
|
72
|
+
channel: subscribedChannel,
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
await handler(payload);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
// eslint-disable-next-line no-console
|
|
81
|
+
console.error("Failed to process pub/sub notification event", {
|
|
82
|
+
error,
|
|
83
|
+
payload,
|
|
84
|
+
channel: subscribedChannel,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
// eslint-disable-next-line no-console
|
|
91
|
+
console.error("Error while subscribing to notification channel", {
|
|
92
|
+
error,
|
|
93
|
+
channel,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|