dt-common-device 13.10.5 → 13.10.7
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/README.md +452 -452
- package/dist/audit/IAuditProperties.d.ts +5 -0
- package/dist/audit/PushAudit.d.ts +12 -1
- package/dist/audit/PushAudit.js +27 -0
- package/dist/constants/ConnectionProviders.d.ts +1 -0
- package/dist/constants/ConnectionProviders.js +1 -0
- package/dist/entities/admin/Admin.repository.js +50 -50
- package/dist/entities/connection/Connection.repository.js +3 -3
- package/dist/entities/device/local/repository/Device.repository.js +2 -2
- package/dist/queue/utils/queueUtils.js +11 -1
- package/dist/queue/utils/rateLimit.utils.js +6 -0
- package/dist/webhooks/webhook.repository.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,452 +1,452 @@
|
|
|
1
|
-
# dt-common-device
|
|
2
|
-
|
|
3
|
-
A comprehensive TypeScript library for device management, supporting both cloud and local device operations with advanced event handling, queue management, and audit logging.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```sh
|
|
8
|
-
npm install dt-common-device
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Environment Variables Required
|
|
12
|
-
|
|
13
|
-
**The following environment variables are REQUIRED for the library to function:**
|
|
14
|
-
|
|
15
|
-
### AWS Configuration
|
|
16
|
-
|
|
17
|
-
- `AWS_SECRET_ACCESS_KEY` — Your AWS secret access key
|
|
18
|
-
- `AWS_REGION` — Your AWS region
|
|
19
|
-
- `AWS_ACCESS_KEY_ID` — Your AWS access key ID
|
|
20
|
-
- `EVENT_BUS_NAME` — Your AWS EventBridge event bus name
|
|
21
|
-
|
|
22
|
-
### Database Configuration
|
|
23
|
-
|
|
24
|
-
- `ADMIN_DB_URI` — PostgreSQL database connection URI
|
|
25
|
-
- `MONGODB_URI` — MongoDB database connection URI
|
|
26
|
-
|
|
27
|
-
### Redis Configuration
|
|
28
|
-
|
|
29
|
-
- `REDIS_HOST` — Redis server host
|
|
30
|
-
- `REDIS_PORT` — Redis server port
|
|
31
|
-
|
|
32
|
-
### Audit Logging
|
|
33
|
-
|
|
34
|
-
- `POSTHOG_API_KEY` — Your PostHog API key
|
|
35
|
-
- `POSTHOG_HOST` — The PostHog host URL
|
|
36
|
-
|
|
37
|
-
### SQS Configuration
|
|
38
|
-
|
|
39
|
-
- `SQS_QUEUE_URL` — AWS SQS queue URL (configured during initialization)
|
|
40
|
-
|
|
41
|
-
Example `.env` file:
|
|
42
|
-
|
|
43
|
-
```
|
|
44
|
-
# AWS Configuration
|
|
45
|
-
AWS_SECRET_ACCESS_KEY=your_secret_key
|
|
46
|
-
AWS_REGION=us-east-1
|
|
47
|
-
AWS_ACCESS_KEY_ID=your_access_key
|
|
48
|
-
EVENT_BUS_NAME=your-event-bus
|
|
49
|
-
|
|
50
|
-
# Database Configuration
|
|
51
|
-
ADMIN_DB_URI=postgres://username:password@host:port/database
|
|
52
|
-
MONGODB_URI=mongodb://username:password@host:port/database
|
|
53
|
-
|
|
54
|
-
# Redis Configuration
|
|
55
|
-
REDIS_HOST=localhost
|
|
56
|
-
REDIS_PORT=6379
|
|
57
|
-
|
|
58
|
-
# Audit Logging
|
|
59
|
-
POSTHOG_API_KEY=your_posthog_key
|
|
60
|
-
POSTHOG_HOST=https://app.posthog.com
|
|
61
|
-
|
|
62
|
-
# SQS Configuration (will be set during initialization)
|
|
63
|
-
SQS_QUEUE_URL=https://sqs.region.amazonaws.com/account/queue-name
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
The library will throw clear errors if any required environment variables are missing.
|
|
67
|
-
|
|
68
|
-
---
|
|
69
|
-
|
|
70
|
-
## Initialization (Required)
|
|
71
|
-
|
|
72
|
-
Before using any service, you **must** call `initialize()` in your main entry file with the required configuration:
|
|
73
|
-
|
|
74
|
-
```ts
|
|
75
|
-
import { initialize } from "dt-common-device";
|
|
76
|
-
|
|
77
|
-
// Create a logger instance
|
|
78
|
-
const logger = {
|
|
79
|
-
info: (message: string, ...args: any[]) => console.log(message, ...args),
|
|
80
|
-
warn: (message: string, ...args: any[]) => console.warn(message, ...args),
|
|
81
|
-
error: (message: string, ...args: any[]) => console.error(message, ...args),
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
// Initialize the library
|
|
85
|
-
await initialize({
|
|
86
|
-
SOURCE: "ADMIN_SERVICE", // or "ACCESS_SERVICE" or "ENERGY_SERVICE"
|
|
87
|
-
SQS_QUEUE_URL: "https://sqs.region.amazonaws.com/account/queue-name",
|
|
88
|
-
DEVICE_SERVICE: "https://api.example.com/device", // Optional
|
|
89
|
-
ADMIN_SERVICE: "https://api.example.com/admin", // Optional
|
|
90
|
-
ACCESS_SERVICE: "https://api.example.com/access", // Optional
|
|
91
|
-
ENERGY_SERVICE: "https://api.example.com/energy", // Optional
|
|
92
|
-
INTERNAL_EVENT_HANDLER: {
|
|
93
|
-
// Your event handler implementation
|
|
94
|
-
handleEvent: async (event) => {
|
|
95
|
-
// Handle internal events
|
|
96
|
-
console.log("Handling event:", event);
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
LOGGER: logger,
|
|
100
|
-
});
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
### Configuration Options
|
|
104
|
-
|
|
105
|
-
- `SOURCE`: Required. Must be one of: `"ADMIN_SERVICE"`, `"ACCESS_SERVICE"`, or `"ENERGY_SERVICE"`
|
|
106
|
-
- `SQS_QUEUE_URL`: Required. Your AWS SQS queue URL
|
|
107
|
-
- `DEVICE_SERVICE`, `ADMIN_SERVICE`, `ACCESS_SERVICE`, `ENERGY_SERVICE`: Optional service URLs
|
|
108
|
-
- `INTERNAL_EVENT_HANDLER`: Required. Your event handler implementation
|
|
109
|
-
- `LOGGER`: Required. Logger instance with info, warn, and error methods
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
113
|
-
## Available Services
|
|
114
|
-
|
|
115
|
-
### Local Device Service
|
|
116
|
-
|
|
117
|
-
```ts
|
|
118
|
-
import { LocalDeviceService } from "dt-common-device";
|
|
119
|
-
|
|
120
|
-
const deviceService = new LocalDeviceService();
|
|
121
|
-
|
|
122
|
-
// Create a device
|
|
123
|
-
const device = await deviceService.createDevice(deviceBody);
|
|
124
|
-
|
|
125
|
-
// Get a device
|
|
126
|
-
const device = await deviceService.getDevice(deviceId);
|
|
127
|
-
|
|
128
|
-
// Get multiple devices
|
|
129
|
-
const devices = await deviceService.getDevices(deviceIds);
|
|
130
|
-
|
|
131
|
-
// Get devices by property
|
|
132
|
-
const propertyDevices = await deviceService.getPropertyDevices(propertyId);
|
|
133
|
-
|
|
134
|
-
// Update a device
|
|
135
|
-
await deviceService.updateDevice(deviceId, updateBody);
|
|
136
|
-
|
|
137
|
-
// Delete a device
|
|
138
|
-
await deviceService.deleteDevice(deviceId);
|
|
139
|
-
|
|
140
|
-
// State management
|
|
141
|
-
const state = await deviceService.getState(deviceId);
|
|
142
|
-
await deviceService.setState(deviceId, newState);
|
|
143
|
-
|
|
144
|
-
// Status management
|
|
145
|
-
const status = await deviceService.getStatus(deviceId);
|
|
146
|
-
await deviceService.setStatus(
|
|
147
|
-
deviceId,
|
|
148
|
-
newStatus,
|
|
149
|
-
"heartbeat",
|
|
150
|
-
"Device went offline due to network connectivity issues"
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
// Battery management
|
|
154
|
-
const battery = await deviceService.getBatteryLevel(deviceId);
|
|
155
|
-
await deviceService.setBatteryLevel(deviceId, batteryLevel, "heartbeat");
|
|
156
|
-
|
|
157
|
-
// Metadata management
|
|
158
|
-
const metadata = await deviceService.getMetaData(deviceId);
|
|
159
|
-
await deviceService.setMetaData(deviceId, metadata);
|
|
160
|
-
|
|
161
|
-
// Query operations
|
|
162
|
-
const devices = await deviceService.queryDevices(query);
|
|
163
|
-
const count = await deviceService.queryCount(query);
|
|
164
|
-
await deviceService.deleteDevices(query);
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
### Local Hub Service
|
|
168
|
-
|
|
169
|
-
```ts
|
|
170
|
-
import { LocalHubService } from "dt-common-device";
|
|
171
|
-
|
|
172
|
-
const hubService = new LocalHubService();
|
|
173
|
-
|
|
174
|
-
// Add a hub
|
|
175
|
-
const hub = await hubService.addHub(hubBody);
|
|
176
|
-
|
|
177
|
-
// Get hubs
|
|
178
|
-
const hubs = await hubService.getHubs(hubIds);
|
|
179
|
-
|
|
180
|
-
// Get a single hub
|
|
181
|
-
const hub = await hubService.getHub(hubId);
|
|
182
|
-
|
|
183
|
-
// Update a hub
|
|
184
|
-
await hubService.updateHub(hubId, updateBody);
|
|
185
|
-
|
|
186
|
-
// Get hub status
|
|
187
|
-
const status = await hubService.getStatus(hubId);
|
|
188
|
-
|
|
189
|
-
// Delete a hub
|
|
190
|
-
await hubService.deleteHub(hubId);
|
|
191
|
-
|
|
192
|
-
// Delete multiple hubs
|
|
193
|
-
await hubService.deleteAllHubs(hubIds);
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
### Local Schedule Service
|
|
197
|
-
|
|
198
|
-
```ts
|
|
199
|
-
import { LocalScheduleService } from "dt-common-device";
|
|
200
|
-
|
|
201
|
-
const scheduleService = new LocalScheduleService();
|
|
202
|
-
|
|
203
|
-
// Get a schedule
|
|
204
|
-
const schedule = await scheduleService.getSchedule(scheduleId);
|
|
205
|
-
|
|
206
|
-
// Set a schedule
|
|
207
|
-
await scheduleService.setSchedule(scheduleId, schedule);
|
|
208
|
-
|
|
209
|
-
// Get schedule by zone
|
|
210
|
-
const zoneSchedule = await scheduleService.getScheduleByZone(zoneId);
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
### Local Connection Service
|
|
214
|
-
|
|
215
|
-
```ts
|
|
216
|
-
import { LocalConnectionService } from "dt-common-device";
|
|
217
|
-
|
|
218
|
-
const connectionService = new LocalConnectionService();
|
|
219
|
-
|
|
220
|
-
// Create a connection
|
|
221
|
-
const connection = await connectionService.createConnection({
|
|
222
|
-
connectionName: "My Connection",
|
|
223
|
-
connectionRefId: "ref-123",
|
|
224
|
-
propertyId: "prop-456",
|
|
225
|
-
connectionProvider: "Sensibo",
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
// Get a connection
|
|
229
|
-
const connection = await connectionService.getConnection(connectionId);
|
|
230
|
-
|
|
231
|
-
// Update a connection
|
|
232
|
-
await connectionService.updateConnection(connectionId, updateData);
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
### Cloud Device Service
|
|
236
|
-
|
|
237
|
-
```ts
|
|
238
|
-
import { CloudDeviceService, DeviceFactory } from "dt-common-device";
|
|
239
|
-
|
|
240
|
-
const cloudService = new CloudDeviceService();
|
|
241
|
-
const deviceFactory = new DeviceFactory();
|
|
242
|
-
|
|
243
|
-
// Get cloud devices (must implement in your project)
|
|
244
|
-
// await cloudService.getDevices(connection);
|
|
245
|
-
|
|
246
|
-
// Get device using factory
|
|
247
|
-
const device = await deviceFactory.getDevice(deviceId);
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
### Property Service
|
|
251
|
-
|
|
252
|
-
```ts
|
|
253
|
-
import { PropertyService } from "dt-common-device";
|
|
254
|
-
|
|
255
|
-
const propertyService = new PropertyService();
|
|
256
|
-
|
|
257
|
-
// Property operations (implementation specific)
|
|
258
|
-
// await propertyService.getProperty(propertyId);
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
---
|
|
262
|
-
|
|
263
|
-
## Event System
|
|
264
|
-
|
|
265
|
-
The library includes a comprehensive event handling system:
|
|
266
|
-
|
|
267
|
-
```ts
|
|
268
|
-
import {
|
|
269
|
-
EventHandler,
|
|
270
|
-
EventProcessingService,
|
|
271
|
-
DeviceEventHandler,
|
|
272
|
-
EventHandlerOrchestrator,
|
|
273
|
-
} from "dt-common-device";
|
|
274
|
-
|
|
275
|
-
// Event handler for device operations
|
|
276
|
-
const eventHandler = new EventHandler();
|
|
277
|
-
|
|
278
|
-
// Device-specific event handler
|
|
279
|
-
const deviceEventHandler = new DeviceEventHandler();
|
|
280
|
-
|
|
281
|
-
// Event processing service
|
|
282
|
-
const eventProcessingService = new EventProcessingService();
|
|
283
|
-
|
|
284
|
-
// Event handler orchestrator
|
|
285
|
-
const orchestrator = new EventHandlerOrchestrator();
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
---
|
|
289
|
-
|
|
290
|
-
## Queue System
|
|
291
|
-
|
|
292
|
-
The library provides a hybrid HTTP queue system for managing HTTP requests:
|
|
293
|
-
|
|
294
|
-
```ts
|
|
295
|
-
import { QueueService } from "dt-common-device";
|
|
296
|
-
|
|
297
|
-
const queueService = new QueueService();
|
|
298
|
-
|
|
299
|
-
// Make a rate-limited HTTP request
|
|
300
|
-
const response = await queueService.request({
|
|
301
|
-
method: "GET",
|
|
302
|
-
url: "https://api.example.com/data",
|
|
303
|
-
headers: { "Content-Type": "application/json" },
|
|
304
|
-
queueOptions: {
|
|
305
|
-
connectionId: "connection-123",
|
|
306
|
-
connectionProvider: "Sensibo",
|
|
307
|
-
microservice: "smart-energy",
|
|
308
|
-
},
|
|
309
|
-
});
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
### Features
|
|
313
|
-
|
|
314
|
-
- **Rate Limiting**: Automatic rate limiting per provider and connection
|
|
315
|
-
- **Retry Logic**: Exponential backoff with configurable retry attempts
|
|
316
|
-
- **Audit Logging**: Automatic audit logging for all requests
|
|
317
|
-
- **Queue Management**: Redis-based queue with BullMQ
|
|
318
|
-
- **Error Handling**: Comprehensive error handling and logging
|
|
319
|
-
|
|
320
|
-
---
|
|
321
|
-
|
|
322
|
-
## Alert and Issue Management
|
|
323
|
-
|
|
324
|
-
```ts
|
|
325
|
-
import {
|
|
326
|
-
AlertService,
|
|
327
|
-
IssueService,
|
|
328
|
-
AlertBuilder,
|
|
329
|
-
IssueBuilder,
|
|
330
|
-
} from "dt-common-device";
|
|
331
|
-
|
|
332
|
-
// Alert service
|
|
333
|
-
const alertService = new AlertService();
|
|
334
|
-
|
|
335
|
-
// Issue service
|
|
336
|
-
const issueService = new IssueService();
|
|
337
|
-
|
|
338
|
-
// Build alerts
|
|
339
|
-
const alert = new AlertBuilder()
|
|
340
|
-
.setTitle("Device Offline")
|
|
341
|
-
.setDescription("Device has been offline for more than 24 hours")
|
|
342
|
-
.setSeverity("HIGH")
|
|
343
|
-
.build();
|
|
344
|
-
|
|
345
|
-
// Build issues
|
|
346
|
-
const issue = new IssueBuilder()
|
|
347
|
-
.setTitle("Connection Failed")
|
|
348
|
-
.setDescription("Failed to connect to device")
|
|
349
|
-
.setPriority("HIGH")
|
|
350
|
-
.build();
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
---
|
|
354
|
-
|
|
355
|
-
## Graceful Shutdown
|
|
356
|
-
|
|
357
|
-
```ts
|
|
358
|
-
import { shutdown } from "dt-common-device";
|
|
359
|
-
|
|
360
|
-
// Gracefully shutdown the library
|
|
361
|
-
await shutdown();
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
---
|
|
365
|
-
|
|
366
|
-
## Importing Types and Interfaces
|
|
367
|
-
|
|
368
|
-
All types and interfaces are available as named exports:
|
|
369
|
-
|
|
370
|
-
```ts
|
|
371
|
-
import {
|
|
372
|
-
IDevice,
|
|
373
|
-
IHub,
|
|
374
|
-
IConnection,
|
|
375
|
-
ISchedule,
|
|
376
|
-
IProperty,
|
|
377
|
-
IAlert,
|
|
378
|
-
IIssue,
|
|
379
|
-
} from "dt-common-device";
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
---
|
|
383
|
-
|
|
384
|
-
## Dependencies
|
|
385
|
-
|
|
386
|
-
The library requires the following major dependencies:
|
|
387
|
-
|
|
388
|
-
- `axios`: HTTP client
|
|
389
|
-
- `bullmq`: Queue management
|
|
390
|
-
- `dt-audit-library`: Audit logging
|
|
391
|
-
- `dt-pub-sub`: Event publishing/subscribing
|
|
392
|
-
- `ioredis`: Redis client
|
|
393
|
-
- `mongoose`: MongoDB ODM
|
|
394
|
-
- `pg`: PostgreSQL client
|
|
395
|
-
- `typedi`: Dependency injection
|
|
396
|
-
|
|
397
|
-
---
|
|
398
|
-
|
|
399
|
-
## Notes
|
|
400
|
-
|
|
401
|
-
- You **must** call `initialize()` before using any service. If not, you will get a runtime error.
|
|
402
|
-
- **You must set `POSTHOG_API_KEY` and `POSTHOG_HOST` in your environment before using any local device service.**
|
|
403
|
-
- You do **not** need to call `initializeAudit()`
|
|
404
|
-
|
|
405
|
-
## Rate Limiting
|
|
406
|
-
|
|
407
|
-
The queue system now implements intelligent rate limiting that delays requests instead of dropping them when rate limits are exceeded.
|
|
408
|
-
|
|
409
|
-
### How it works
|
|
410
|
-
|
|
411
|
-
1. **Rate Limit Check**: Before processing each HTTP request, the system checks if the rate limit for the provider/connection combination has been exceeded.
|
|
412
|
-
|
|
413
|
-
2. **Delay Instead of Drop**: If the rate limit is exceeded, instead of immediately failing the request, the system:
|
|
414
|
-
|
|
415
|
-
- Calculates when the next request can be processed (after the rate limit window expires)
|
|
416
|
-
- Delays the job execution by the calculated time
|
|
417
|
-
- Re-checks the rate limit after the delay
|
|
418
|
-
- Only fails if still rate limited after the delay
|
|
419
|
-
|
|
420
|
-
3. **Example Scenario**:
|
|
421
|
-
|
|
422
|
-
```
|
|
423
|
-
Rate Limit: 5 requests per 60 seconds
|
|
424
|
-
|
|
425
|
-
Timeline:
|
|
426
|
-
0s: Request 1 (processed immediately)
|
|
427
|
-
10s: Request 2 (processed immediately)
|
|
428
|
-
20s: Request 3 (processed immediately)
|
|
429
|
-
30s: Request 4 (processed immediately)
|
|
430
|
-
40s: Request 5 (processed immediately)
|
|
431
|
-
45s: Request 6 (delayed by 15s, processed at 60s)
|
|
432
|
-
50s: Request 7 (delayed by 10s, processed at 60s)
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
### Configuration
|
|
436
|
-
|
|
437
|
-
Rate limits are configured per provider in `src/queue/utils/rateLimit.utils.ts`:
|
|
438
|
-
|
|
439
|
-
```typescript
|
|
440
|
-
configs.set("Sensibo", {
|
|
441
|
-
maxRequests: 5, // Maximum requests allowed
|
|
442
|
-
windowMs: 60000, // Time window in milliseconds
|
|
443
|
-
provider: "Sensibo",
|
|
444
|
-
});
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
### Benefits
|
|
448
|
-
|
|
449
|
-
- **No Lost Requests**: Requests are delayed rather than dropped
|
|
450
|
-
- **Automatic Recovery**: System automatically processes delayed requests when rate limits reset
|
|
451
|
-
- **Better User Experience**: Users don't see immediate failures due to rate limits
|
|
452
|
-
- **Audit Trail**: All rate limit events are logged for monitoring
|
|
1
|
+
# dt-common-device
|
|
2
|
+
|
|
3
|
+
A comprehensive TypeScript library for device management, supporting both cloud and local device operations with advanced event handling, queue management, and audit logging.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install dt-common-device
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Environment Variables Required
|
|
12
|
+
|
|
13
|
+
**The following environment variables are REQUIRED for the library to function:**
|
|
14
|
+
|
|
15
|
+
### AWS Configuration
|
|
16
|
+
|
|
17
|
+
- `AWS_SECRET_ACCESS_KEY` — Your AWS secret access key
|
|
18
|
+
- `AWS_REGION` — Your AWS region
|
|
19
|
+
- `AWS_ACCESS_KEY_ID` — Your AWS access key ID
|
|
20
|
+
- `EVENT_BUS_NAME` — Your AWS EventBridge event bus name
|
|
21
|
+
|
|
22
|
+
### Database Configuration
|
|
23
|
+
|
|
24
|
+
- `ADMIN_DB_URI` — PostgreSQL database connection URI
|
|
25
|
+
- `MONGODB_URI` — MongoDB database connection URI
|
|
26
|
+
|
|
27
|
+
### Redis Configuration
|
|
28
|
+
|
|
29
|
+
- `REDIS_HOST` — Redis server host
|
|
30
|
+
- `REDIS_PORT` — Redis server port
|
|
31
|
+
|
|
32
|
+
### Audit Logging
|
|
33
|
+
|
|
34
|
+
- `POSTHOG_API_KEY` — Your PostHog API key
|
|
35
|
+
- `POSTHOG_HOST` — The PostHog host URL
|
|
36
|
+
|
|
37
|
+
### SQS Configuration
|
|
38
|
+
|
|
39
|
+
- `SQS_QUEUE_URL` — AWS SQS queue URL (configured during initialization)
|
|
40
|
+
|
|
41
|
+
Example `.env` file:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
# AWS Configuration
|
|
45
|
+
AWS_SECRET_ACCESS_KEY=your_secret_key
|
|
46
|
+
AWS_REGION=us-east-1
|
|
47
|
+
AWS_ACCESS_KEY_ID=your_access_key
|
|
48
|
+
EVENT_BUS_NAME=your-event-bus
|
|
49
|
+
|
|
50
|
+
# Database Configuration
|
|
51
|
+
ADMIN_DB_URI=postgres://username:password@host:port/database
|
|
52
|
+
MONGODB_URI=mongodb://username:password@host:port/database
|
|
53
|
+
|
|
54
|
+
# Redis Configuration
|
|
55
|
+
REDIS_HOST=localhost
|
|
56
|
+
REDIS_PORT=6379
|
|
57
|
+
|
|
58
|
+
# Audit Logging
|
|
59
|
+
POSTHOG_API_KEY=your_posthog_key
|
|
60
|
+
POSTHOG_HOST=https://app.posthog.com
|
|
61
|
+
|
|
62
|
+
# SQS Configuration (will be set during initialization)
|
|
63
|
+
SQS_QUEUE_URL=https://sqs.region.amazonaws.com/account/queue-name
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The library will throw clear errors if any required environment variables are missing.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Initialization (Required)
|
|
71
|
+
|
|
72
|
+
Before using any service, you **must** call `initialize()` in your main entry file with the required configuration:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { initialize } from "dt-common-device";
|
|
76
|
+
|
|
77
|
+
// Create a logger instance
|
|
78
|
+
const logger = {
|
|
79
|
+
info: (message: string, ...args: any[]) => console.log(message, ...args),
|
|
80
|
+
warn: (message: string, ...args: any[]) => console.warn(message, ...args),
|
|
81
|
+
error: (message: string, ...args: any[]) => console.error(message, ...args),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Initialize the library
|
|
85
|
+
await initialize({
|
|
86
|
+
SOURCE: "ADMIN_SERVICE", // or "ACCESS_SERVICE" or "ENERGY_SERVICE"
|
|
87
|
+
SQS_QUEUE_URL: "https://sqs.region.amazonaws.com/account/queue-name",
|
|
88
|
+
DEVICE_SERVICE: "https://api.example.com/device", // Optional
|
|
89
|
+
ADMIN_SERVICE: "https://api.example.com/admin", // Optional
|
|
90
|
+
ACCESS_SERVICE: "https://api.example.com/access", // Optional
|
|
91
|
+
ENERGY_SERVICE: "https://api.example.com/energy", // Optional
|
|
92
|
+
INTERNAL_EVENT_HANDLER: {
|
|
93
|
+
// Your event handler implementation
|
|
94
|
+
handleEvent: async (event) => {
|
|
95
|
+
// Handle internal events
|
|
96
|
+
console.log("Handling event:", event);
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
LOGGER: logger,
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Configuration Options
|
|
104
|
+
|
|
105
|
+
- `SOURCE`: Required. Must be one of: `"ADMIN_SERVICE"`, `"ACCESS_SERVICE"`, or `"ENERGY_SERVICE"`
|
|
106
|
+
- `SQS_QUEUE_URL`: Required. Your AWS SQS queue URL
|
|
107
|
+
- `DEVICE_SERVICE`, `ADMIN_SERVICE`, `ACCESS_SERVICE`, `ENERGY_SERVICE`: Optional service URLs
|
|
108
|
+
- `INTERNAL_EVENT_HANDLER`: Required. Your event handler implementation
|
|
109
|
+
- `LOGGER`: Required. Logger instance with info, warn, and error methods
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Available Services
|
|
114
|
+
|
|
115
|
+
### Local Device Service
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { LocalDeviceService } from "dt-common-device";
|
|
119
|
+
|
|
120
|
+
const deviceService = new LocalDeviceService();
|
|
121
|
+
|
|
122
|
+
// Create a device
|
|
123
|
+
const device = await deviceService.createDevice(deviceBody);
|
|
124
|
+
|
|
125
|
+
// Get a device
|
|
126
|
+
const device = await deviceService.getDevice(deviceId);
|
|
127
|
+
|
|
128
|
+
// Get multiple devices
|
|
129
|
+
const devices = await deviceService.getDevices(deviceIds);
|
|
130
|
+
|
|
131
|
+
// Get devices by property
|
|
132
|
+
const propertyDevices = await deviceService.getPropertyDevices(propertyId);
|
|
133
|
+
|
|
134
|
+
// Update a device
|
|
135
|
+
await deviceService.updateDevice(deviceId, updateBody);
|
|
136
|
+
|
|
137
|
+
// Delete a device
|
|
138
|
+
await deviceService.deleteDevice(deviceId);
|
|
139
|
+
|
|
140
|
+
// State management
|
|
141
|
+
const state = await deviceService.getState(deviceId);
|
|
142
|
+
await deviceService.setState(deviceId, newState);
|
|
143
|
+
|
|
144
|
+
// Status management
|
|
145
|
+
const status = await deviceService.getStatus(deviceId);
|
|
146
|
+
await deviceService.setStatus(
|
|
147
|
+
deviceId,
|
|
148
|
+
newStatus,
|
|
149
|
+
"heartbeat",
|
|
150
|
+
"Device went offline due to network connectivity issues"
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// Battery management
|
|
154
|
+
const battery = await deviceService.getBatteryLevel(deviceId);
|
|
155
|
+
await deviceService.setBatteryLevel(deviceId, batteryLevel, "heartbeat");
|
|
156
|
+
|
|
157
|
+
// Metadata management
|
|
158
|
+
const metadata = await deviceService.getMetaData(deviceId);
|
|
159
|
+
await deviceService.setMetaData(deviceId, metadata);
|
|
160
|
+
|
|
161
|
+
// Query operations
|
|
162
|
+
const devices = await deviceService.queryDevices(query);
|
|
163
|
+
const count = await deviceService.queryCount(query);
|
|
164
|
+
await deviceService.deleteDevices(query);
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Local Hub Service
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
import { LocalHubService } from "dt-common-device";
|
|
171
|
+
|
|
172
|
+
const hubService = new LocalHubService();
|
|
173
|
+
|
|
174
|
+
// Add a hub
|
|
175
|
+
const hub = await hubService.addHub(hubBody);
|
|
176
|
+
|
|
177
|
+
// Get hubs
|
|
178
|
+
const hubs = await hubService.getHubs(hubIds);
|
|
179
|
+
|
|
180
|
+
// Get a single hub
|
|
181
|
+
const hub = await hubService.getHub(hubId);
|
|
182
|
+
|
|
183
|
+
// Update a hub
|
|
184
|
+
await hubService.updateHub(hubId, updateBody);
|
|
185
|
+
|
|
186
|
+
// Get hub status
|
|
187
|
+
const status = await hubService.getStatus(hubId);
|
|
188
|
+
|
|
189
|
+
// Delete a hub
|
|
190
|
+
await hubService.deleteHub(hubId);
|
|
191
|
+
|
|
192
|
+
// Delete multiple hubs
|
|
193
|
+
await hubService.deleteAllHubs(hubIds);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Local Schedule Service
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
import { LocalScheduleService } from "dt-common-device";
|
|
200
|
+
|
|
201
|
+
const scheduleService = new LocalScheduleService();
|
|
202
|
+
|
|
203
|
+
// Get a schedule
|
|
204
|
+
const schedule = await scheduleService.getSchedule(scheduleId);
|
|
205
|
+
|
|
206
|
+
// Set a schedule
|
|
207
|
+
await scheduleService.setSchedule(scheduleId, schedule);
|
|
208
|
+
|
|
209
|
+
// Get schedule by zone
|
|
210
|
+
const zoneSchedule = await scheduleService.getScheduleByZone(zoneId);
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Local Connection Service
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
import { LocalConnectionService } from "dt-common-device";
|
|
217
|
+
|
|
218
|
+
const connectionService = new LocalConnectionService();
|
|
219
|
+
|
|
220
|
+
// Create a connection
|
|
221
|
+
const connection = await connectionService.createConnection({
|
|
222
|
+
connectionName: "My Connection",
|
|
223
|
+
connectionRefId: "ref-123",
|
|
224
|
+
propertyId: "prop-456",
|
|
225
|
+
connectionProvider: "Sensibo",
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Get a connection
|
|
229
|
+
const connection = await connectionService.getConnection(connectionId);
|
|
230
|
+
|
|
231
|
+
// Update a connection
|
|
232
|
+
await connectionService.updateConnection(connectionId, updateData);
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Cloud Device Service
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
import { CloudDeviceService, DeviceFactory } from "dt-common-device";
|
|
239
|
+
|
|
240
|
+
const cloudService = new CloudDeviceService();
|
|
241
|
+
const deviceFactory = new DeviceFactory();
|
|
242
|
+
|
|
243
|
+
// Get cloud devices (must implement in your project)
|
|
244
|
+
// await cloudService.getDevices(connection);
|
|
245
|
+
|
|
246
|
+
// Get device using factory
|
|
247
|
+
const device = await deviceFactory.getDevice(deviceId);
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Property Service
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
import { PropertyService } from "dt-common-device";
|
|
254
|
+
|
|
255
|
+
const propertyService = new PropertyService();
|
|
256
|
+
|
|
257
|
+
// Property operations (implementation specific)
|
|
258
|
+
// await propertyService.getProperty(propertyId);
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Event System
|
|
264
|
+
|
|
265
|
+
The library includes a comprehensive event handling system:
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
import {
|
|
269
|
+
EventHandler,
|
|
270
|
+
EventProcessingService,
|
|
271
|
+
DeviceEventHandler,
|
|
272
|
+
EventHandlerOrchestrator,
|
|
273
|
+
} from "dt-common-device";
|
|
274
|
+
|
|
275
|
+
// Event handler for device operations
|
|
276
|
+
const eventHandler = new EventHandler();
|
|
277
|
+
|
|
278
|
+
// Device-specific event handler
|
|
279
|
+
const deviceEventHandler = new DeviceEventHandler();
|
|
280
|
+
|
|
281
|
+
// Event processing service
|
|
282
|
+
const eventProcessingService = new EventProcessingService();
|
|
283
|
+
|
|
284
|
+
// Event handler orchestrator
|
|
285
|
+
const orchestrator = new EventHandlerOrchestrator();
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Queue System
|
|
291
|
+
|
|
292
|
+
The library provides a hybrid HTTP queue system for managing HTTP requests:
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
import { QueueService } from "dt-common-device";
|
|
296
|
+
|
|
297
|
+
const queueService = new QueueService();
|
|
298
|
+
|
|
299
|
+
// Make a rate-limited HTTP request
|
|
300
|
+
const response = await queueService.request({
|
|
301
|
+
method: "GET",
|
|
302
|
+
url: "https://api.example.com/data",
|
|
303
|
+
headers: { "Content-Type": "application/json" },
|
|
304
|
+
queueOptions: {
|
|
305
|
+
connectionId: "connection-123",
|
|
306
|
+
connectionProvider: "Sensibo",
|
|
307
|
+
microservice: "smart-energy",
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Features
|
|
313
|
+
|
|
314
|
+
- **Rate Limiting**: Automatic rate limiting per provider and connection
|
|
315
|
+
- **Retry Logic**: Exponential backoff with configurable retry attempts
|
|
316
|
+
- **Audit Logging**: Automatic audit logging for all requests
|
|
317
|
+
- **Queue Management**: Redis-based queue with BullMQ
|
|
318
|
+
- **Error Handling**: Comprehensive error handling and logging
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Alert and Issue Management
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
import {
|
|
326
|
+
AlertService,
|
|
327
|
+
IssueService,
|
|
328
|
+
AlertBuilder,
|
|
329
|
+
IssueBuilder,
|
|
330
|
+
} from "dt-common-device";
|
|
331
|
+
|
|
332
|
+
// Alert service
|
|
333
|
+
const alertService = new AlertService();
|
|
334
|
+
|
|
335
|
+
// Issue service
|
|
336
|
+
const issueService = new IssueService();
|
|
337
|
+
|
|
338
|
+
// Build alerts
|
|
339
|
+
const alert = new AlertBuilder()
|
|
340
|
+
.setTitle("Device Offline")
|
|
341
|
+
.setDescription("Device has been offline for more than 24 hours")
|
|
342
|
+
.setSeverity("HIGH")
|
|
343
|
+
.build();
|
|
344
|
+
|
|
345
|
+
// Build issues
|
|
346
|
+
const issue = new IssueBuilder()
|
|
347
|
+
.setTitle("Connection Failed")
|
|
348
|
+
.setDescription("Failed to connect to device")
|
|
349
|
+
.setPriority("HIGH")
|
|
350
|
+
.build();
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## Graceful Shutdown
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
import { shutdown } from "dt-common-device";
|
|
359
|
+
|
|
360
|
+
// Gracefully shutdown the library
|
|
361
|
+
await shutdown();
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Importing Types and Interfaces
|
|
367
|
+
|
|
368
|
+
All types and interfaces are available as named exports:
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
import {
|
|
372
|
+
IDevice,
|
|
373
|
+
IHub,
|
|
374
|
+
IConnection,
|
|
375
|
+
ISchedule,
|
|
376
|
+
IProperty,
|
|
377
|
+
IAlert,
|
|
378
|
+
IIssue,
|
|
379
|
+
} from "dt-common-device";
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## Dependencies
|
|
385
|
+
|
|
386
|
+
The library requires the following major dependencies:
|
|
387
|
+
|
|
388
|
+
- `axios`: HTTP client
|
|
389
|
+
- `bullmq`: Queue management
|
|
390
|
+
- `dt-audit-library`: Audit logging
|
|
391
|
+
- `dt-pub-sub`: Event publishing/subscribing
|
|
392
|
+
- `ioredis`: Redis client
|
|
393
|
+
- `mongoose`: MongoDB ODM
|
|
394
|
+
- `pg`: PostgreSQL client
|
|
395
|
+
- `typedi`: Dependency injection
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## Notes
|
|
400
|
+
|
|
401
|
+
- You **must** call `initialize()` before using any service. If not, you will get a runtime error.
|
|
402
|
+
- **You must set `POSTHOG_API_KEY` and `POSTHOG_HOST` in your environment before using any local device service.**
|
|
403
|
+
- You do **not** need to call `initializeAudit()`
|
|
404
|
+
|
|
405
|
+
## Rate Limiting
|
|
406
|
+
|
|
407
|
+
The queue system now implements intelligent rate limiting that delays requests instead of dropping them when rate limits are exceeded.
|
|
408
|
+
|
|
409
|
+
### How it works
|
|
410
|
+
|
|
411
|
+
1. **Rate Limit Check**: Before processing each HTTP request, the system checks if the rate limit for the provider/connection combination has been exceeded.
|
|
412
|
+
|
|
413
|
+
2. **Delay Instead of Drop**: If the rate limit is exceeded, instead of immediately failing the request, the system:
|
|
414
|
+
|
|
415
|
+
- Calculates when the next request can be processed (after the rate limit window expires)
|
|
416
|
+
- Delays the job execution by the calculated time
|
|
417
|
+
- Re-checks the rate limit after the delay
|
|
418
|
+
- Only fails if still rate limited after the delay
|
|
419
|
+
|
|
420
|
+
3. **Example Scenario**:
|
|
421
|
+
|
|
422
|
+
```
|
|
423
|
+
Rate Limit: 5 requests per 60 seconds
|
|
424
|
+
|
|
425
|
+
Timeline:
|
|
426
|
+
0s: Request 1 (processed immediately)
|
|
427
|
+
10s: Request 2 (processed immediately)
|
|
428
|
+
20s: Request 3 (processed immediately)
|
|
429
|
+
30s: Request 4 (processed immediately)
|
|
430
|
+
40s: Request 5 (processed immediately)
|
|
431
|
+
45s: Request 6 (delayed by 15s, processed at 60s)
|
|
432
|
+
50s: Request 7 (delayed by 10s, processed at 60s)
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### Configuration
|
|
436
|
+
|
|
437
|
+
Rate limits are configured per provider in `src/queue/utils/rateLimit.utils.ts`:
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
configs.set("Sensibo", {
|
|
441
|
+
maxRequests: 5, // Maximum requests allowed
|
|
442
|
+
windowMs: 60000, // Time window in milliseconds
|
|
443
|
+
provider: "Sensibo",
|
|
444
|
+
});
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Benefits
|
|
448
|
+
|
|
449
|
+
- **No Lost Requests**: Requests are delayed rather than dropped
|
|
450
|
+
- **Automatic Recovery**: System automatically processes delayed requests when rate limits reset
|
|
451
|
+
- **Better User Experience**: Users don't see immediate failures due to rate limits
|
|
452
|
+
- **Audit Trail**: All rate limit events are logged for monitoring
|
|
@@ -1,10 +1,21 @@
|
|
|
1
|
-
import { IAuditProperties } from "./IAuditProperties";
|
|
1
|
+
import { IAuditProperties, PartialAuditProperties } from "./IAuditProperties";
|
|
2
2
|
/**
|
|
3
3
|
* Publishes an audit event. Failures are logged and swallowed so callers can
|
|
4
4
|
* fire-and-forget without causing unhandled promise rejections when the audit
|
|
5
5
|
* pipeline returns 5xx or times out (see e.g. Sentry DT-PMS-C).
|
|
6
|
+
* USE FOR PROPERTY LEVEL AUDITS
|
|
6
7
|
*/
|
|
7
8
|
export declare function pushAudit(data: {
|
|
8
9
|
auditType: string;
|
|
9
10
|
auditData: IAuditProperties;
|
|
10
11
|
}): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Publishes an audit event. Failures are logged and swallowed so callers can
|
|
14
|
+
* fire-and-forget without causing unhandled promise rejections when the audit
|
|
15
|
+
* pipeline returns 5xx or times out (see e.g. Sentry DT-PMS-C).
|
|
16
|
+
* USE FOR SYSTEM LEVEL AUDITS (If Property ID is not available)
|
|
17
|
+
*/
|
|
18
|
+
export declare function pushSystemAudit(data: {
|
|
19
|
+
auditType: string;
|
|
20
|
+
auditData: PartialAuditProperties;
|
|
21
|
+
}): Promise<void>;
|
package/dist/audit/PushAudit.js
CHANGED
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.pushAudit = pushAudit;
|
|
7
|
+
exports.pushSystemAudit = pushSystemAudit;
|
|
7
8
|
const dt_audit_library_1 = require("dt-audit-library");
|
|
8
9
|
const config_1 = require("../config/config");
|
|
9
10
|
const AuditUtils_1 = require("./AuditUtils");
|
|
@@ -12,6 +13,7 @@ const typedi_1 = __importDefault(require("typedi"));
|
|
|
12
13
|
* Publishes an audit event. Failures are logged and swallowed so callers can
|
|
13
14
|
* fire-and-forget without causing unhandled promise rejections when the audit
|
|
14
15
|
* pipeline returns 5xx or times out (see e.g. Sentry DT-PMS-C).
|
|
16
|
+
* USE FOR PROPERTY LEVEL AUDITS
|
|
15
17
|
*/
|
|
16
18
|
async function pushAudit(data) {
|
|
17
19
|
try {
|
|
@@ -33,3 +35,28 @@ async function pushAudit(data) {
|
|
|
33
35
|
: { auditType: data.auditType });
|
|
34
36
|
}
|
|
35
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Publishes an audit event. Failures are logged and swallowed so callers can
|
|
40
|
+
* fire-and-forget without causing unhandled promise rejections when the audit
|
|
41
|
+
* pipeline returns 5xx or times out (see e.g. Sentry DT-PMS-C).
|
|
42
|
+
* USE FOR SYSTEM LEVEL AUDITS (If Property ID is not available)
|
|
43
|
+
*/
|
|
44
|
+
async function pushSystemAudit(data) {
|
|
45
|
+
try {
|
|
46
|
+
await (0, dt_audit_library_1.publishAudit)({
|
|
47
|
+
eventType: data.auditType,
|
|
48
|
+
properties: {
|
|
49
|
+
...data.auditData,
|
|
50
|
+
timestamp: new Date().toISOString(),
|
|
51
|
+
env_type: process.env.NODE_ENV,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
const status = err?.response?.status;
|
|
57
|
+
const code = err?.code;
|
|
58
|
+
(0, config_1.getLogger)().error(`pushSystemAudit failed for ${data.auditType}: ${err?.message ?? err}`, status != null || code != null
|
|
59
|
+
? { auditType: data.auditType, status, code }
|
|
60
|
+
: { auditType: data.auditType });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -99,19 +99,19 @@ let AdminRepository = (() => {
|
|
|
99
99
|
return [];
|
|
100
100
|
}
|
|
101
101
|
// If not cached, get the result from the database
|
|
102
|
-
const result = await this.postgres.query(`SELECT
|
|
103
|
-
"zc"."id" AS "zoneCollectionMapId",
|
|
104
|
-
"zc"."collectionId",
|
|
105
|
-
"zc"."zoneId",
|
|
106
|
-
"z"."id" AS "zoneId",
|
|
107
|
-
"z"."name" AS "zoneName",
|
|
108
|
-
"z"."zoneTypeId",
|
|
109
|
-
"zt"."name" AS "zoneTypeName",
|
|
110
|
-
"z"."isUnderMaintenance"
|
|
111
|
-
FROM "dt_zones_collection_map" AS "zc"
|
|
112
|
-
INNER JOIN "dt_zones" AS "z" ON "zc"."zoneId" = "z"."id"
|
|
113
|
-
LEFT JOIN "dt_zoneTypes" AS "zt" ON "z"."zoneTypeId" = "zt"."id"
|
|
114
|
-
WHERE "zc"."collectionId" = ANY($1)
|
|
102
|
+
const result = await this.postgres.query(`SELECT
|
|
103
|
+
"zc"."id" AS "zoneCollectionMapId",
|
|
104
|
+
"zc"."collectionId",
|
|
105
|
+
"zc"."zoneId",
|
|
106
|
+
"z"."id" AS "zoneId",
|
|
107
|
+
"z"."name" AS "zoneName",
|
|
108
|
+
"z"."zoneTypeId",
|
|
109
|
+
"zt"."name" AS "zoneTypeName",
|
|
110
|
+
"z"."isUnderMaintenance"
|
|
111
|
+
FROM "dt_zones_collection_map" AS "zc"
|
|
112
|
+
INNER JOIN "dt_zones" AS "z" ON "zc"."zoneId" = "z"."id"
|
|
113
|
+
LEFT JOIN "dt_zoneTypes" AS "zt" ON "z"."zoneTypeId" = "zt"."id"
|
|
114
|
+
WHERE "zc"."collectionId" = ANY($1)
|
|
115
115
|
ORDER BY "zc"."id" ASC`, [accessGroupIds]);
|
|
116
116
|
const response = result.rows;
|
|
117
117
|
const _zones = (nestedZones, allZones = []) => {
|
|
@@ -280,10 +280,10 @@ let AdminRepository = (() => {
|
|
|
280
280
|
}
|
|
281
281
|
async getZonesByAccessGroups(accessGroupIds, type) {
|
|
282
282
|
// Fetch zone IDs associated with these access groups
|
|
283
|
-
const zonesIdsQuery = `
|
|
284
|
-
SELECT DISTINCT z."zoneId"
|
|
285
|
-
FROM dt_zones_collection_map z
|
|
286
|
-
WHERE z."collectionId" = ANY($1)
|
|
283
|
+
const zonesIdsQuery = `
|
|
284
|
+
SELECT DISTINCT z."zoneId"
|
|
285
|
+
FROM dt_zones_collection_map z
|
|
286
|
+
WHERE z."collectionId" = ANY($1)
|
|
287
287
|
`;
|
|
288
288
|
const zonesIdsResult = await this.postgres.query(zonesIdsQuery, [
|
|
289
289
|
accessGroupIds,
|
|
@@ -296,20 +296,20 @@ let AdminRepository = (() => {
|
|
|
296
296
|
const zoneTypesToFilter = type || ["Guest Room", "Room"];
|
|
297
297
|
// Fetch zone type IDs for the specified type
|
|
298
298
|
// Using IN with unnest() for better compatibility with string arrays
|
|
299
|
-
const zoneTypesQuery = `
|
|
300
|
-
SELECT id
|
|
301
|
-
FROM "dt_zoneTypes"
|
|
302
|
-
WHERE name IN (SELECT unnest($1::text[]))
|
|
299
|
+
const zoneTypesQuery = `
|
|
300
|
+
SELECT id
|
|
301
|
+
FROM "dt_zoneTypes"
|
|
302
|
+
WHERE name IN (SELECT unnest($1::text[]))
|
|
303
303
|
`;
|
|
304
304
|
const zoneTypes = await this.postgres.query(zoneTypesQuery, [
|
|
305
305
|
zoneTypesToFilter,
|
|
306
306
|
]);
|
|
307
307
|
const guestRoomZoneTypeIds = zoneTypes.rows.map((e) => e.id);
|
|
308
308
|
// Fetch zones matching both sets of IDs
|
|
309
|
-
const zonesQuery = `
|
|
310
|
-
SELECT id
|
|
311
|
-
FROM dt_zones
|
|
312
|
-
WHERE id = ANY($1) AND "zoneTypeId" = ANY($2)
|
|
309
|
+
const zonesQuery = `
|
|
310
|
+
SELECT id
|
|
311
|
+
FROM dt_zones
|
|
312
|
+
WHERE id = ANY($1) AND "zoneTypeId" = ANY($2)
|
|
313
313
|
`;
|
|
314
314
|
const zonesResult = await this.postgres.query(zonesQuery, [
|
|
315
315
|
zonesIds,
|
|
@@ -320,15 +320,15 @@ let AdminRepository = (() => {
|
|
|
320
320
|
async getAccessGroup(accessGroupId, propertyId) {
|
|
321
321
|
let query;
|
|
322
322
|
if (propertyId) {
|
|
323
|
-
query = `
|
|
324
|
-
SELECT * FROM dt_collections
|
|
325
|
-
WHERE "id" = $1 AND "propertyId" = $2
|
|
323
|
+
query = `
|
|
324
|
+
SELECT * FROM dt_collections
|
|
325
|
+
WHERE "id" = $1 AND "propertyId" = $2
|
|
326
326
|
`;
|
|
327
327
|
}
|
|
328
328
|
else {
|
|
329
|
-
query = `
|
|
330
|
-
SELECT * FROM dt_collections
|
|
331
|
-
WHERE "id" = $1
|
|
329
|
+
query = `
|
|
330
|
+
SELECT * FROM dt_collections
|
|
331
|
+
WHERE "id" = $1
|
|
332
332
|
`;
|
|
333
333
|
}
|
|
334
334
|
const params = propertyId ? [accessGroupId, propertyId] : [accessGroupId];
|
|
@@ -339,9 +339,9 @@ let AdminRepository = (() => {
|
|
|
339
339
|
return null;
|
|
340
340
|
}
|
|
341
341
|
async getZoneAccessGroupByZoneId(zoneId) {
|
|
342
|
-
const query = `
|
|
343
|
-
SELECT * FROM dt_zones_collection_map
|
|
344
|
-
WHERE "zoneId" = $1
|
|
342
|
+
const query = `
|
|
343
|
+
SELECT * FROM dt_zones_collection_map
|
|
344
|
+
WHERE "zoneId" = $1
|
|
345
345
|
`;
|
|
346
346
|
const result = await this.postgres.query(query, [zoneId]);
|
|
347
347
|
if (result.rows.length > 0) {
|
|
@@ -353,10 +353,10 @@ let AdminRepository = (() => {
|
|
|
353
353
|
if (accessGroupIds.length === 0) {
|
|
354
354
|
return [];
|
|
355
355
|
}
|
|
356
|
-
const query = `
|
|
357
|
-
SELECT DISTINCT "zoneId"
|
|
358
|
-
FROM dt_zones_collection_map
|
|
359
|
-
WHERE "collectionId" = ANY($1)
|
|
356
|
+
const query = `
|
|
357
|
+
SELECT DISTINCT "zoneId"
|
|
358
|
+
FROM dt_zones_collection_map
|
|
359
|
+
WHERE "collectionId" = ANY($1)
|
|
360
360
|
`;
|
|
361
361
|
const result = await this.postgres.query(query, [accessGroupIds]);
|
|
362
362
|
return result.rows.map((row) => row.zoneId);
|
|
@@ -446,12 +446,12 @@ let AdminRepository = (() => {
|
|
|
446
446
|
return [];
|
|
447
447
|
}
|
|
448
448
|
async getAccessGroupsByZoneId(zoneId) {
|
|
449
|
-
const query = `
|
|
450
|
-
SELECT DISTINCT c.*
|
|
451
|
-
FROM dt_collections c
|
|
452
|
-
INNER JOIN dt_zones_collection_map zcm ON c.id = zcm."collectionId"
|
|
453
|
-
WHERE zcm."zoneId" = $1
|
|
454
|
-
AND c."isDeleted" = false
|
|
449
|
+
const query = `
|
|
450
|
+
SELECT DISTINCT c.*
|
|
451
|
+
FROM dt_collections c
|
|
452
|
+
INNER JOIN dt_zones_collection_map zcm ON c.id = zcm."collectionId"
|
|
453
|
+
WHERE zcm."zoneId" = $1
|
|
454
|
+
AND c."isDeleted" = false
|
|
455
455
|
`;
|
|
456
456
|
const result = await this.postgres.query(query, [zoneId]);
|
|
457
457
|
return result.rows;
|
|
@@ -461,12 +461,12 @@ let AdminRepository = (() => {
|
|
|
461
461
|
return [];
|
|
462
462
|
}
|
|
463
463
|
// Get all access groups associated with any of these zones
|
|
464
|
-
const query = `
|
|
465
|
-
SELECT DISTINCT c.*
|
|
466
|
-
FROM dt_collections c
|
|
467
|
-
INNER JOIN dt_zones_collection_map zcm ON c.id = zcm."collectionId"
|
|
468
|
-
WHERE zcm."zoneId" = ANY($1)
|
|
469
|
-
AND c."isDeleted" = false
|
|
464
|
+
const query = `
|
|
465
|
+
SELECT DISTINCT c.*
|
|
466
|
+
FROM dt_collections c
|
|
467
|
+
INNER JOIN dt_zones_collection_map zcm ON c.id = zcm."collectionId"
|
|
468
|
+
WHERE zcm."zoneId" = ANY($1)
|
|
469
|
+
AND c."isDeleted" = false
|
|
470
470
|
`;
|
|
471
471
|
const result = await this.postgres.query(query, [zoneIds]);
|
|
472
472
|
return result.rows;
|
|
@@ -124,9 +124,9 @@ let ConnectionRepository = (() => {
|
|
|
124
124
|
.map((key, index) => `"${key}" = $${index + 3}`)
|
|
125
125
|
.join(", ");
|
|
126
126
|
const values = Object.values(data);
|
|
127
|
-
const result = await this.pool.query(`UPDATE dt_connections
|
|
128
|
-
SET ${setClause}, "updatedAt" = NOW()
|
|
129
|
-
WHERE id = $1 AND "propertyId" = $2
|
|
127
|
+
const result = await this.pool.query(`UPDATE dt_connections
|
|
128
|
+
SET ${setClause}, "updatedAt" = NOW()
|
|
129
|
+
WHERE id = $1 AND "propertyId" = $2
|
|
130
130
|
RETURNING *`, [connectionId, propertyId, ...values]);
|
|
131
131
|
if (result.rowCount === 0) {
|
|
132
132
|
return null;
|
|
@@ -235,8 +235,8 @@ let DeviceRepository = (() => {
|
|
|
235
235
|
}
|
|
236
236
|
async getDevicesByAccessGroup(accessGroupId) {
|
|
237
237
|
try {
|
|
238
|
-
const result = await this.postgres.query(`SELECT d.* FROM dt_devices d
|
|
239
|
-
INNER JOIN dt_zones_collection_map zcm ON d.zoneId = zcm.zoneId
|
|
238
|
+
const result = await this.postgres.query(`SELECT d.* FROM dt_devices d
|
|
239
|
+
INNER JOIN dt_zones_collection_map zcm ON d.zoneId = zcm.zoneId
|
|
240
240
|
WHERE zcm.collectionId = $1`, [accessGroupId]);
|
|
241
241
|
if (result.rows.length > 0) {
|
|
242
242
|
return result.rows;
|
|
@@ -35,6 +35,8 @@ class QueueUtils {
|
|
|
35
35
|
console.log(`[${new Date().toISOString()}] [getOrCreateWorker] Existing worker running status: ${isRunning}`);
|
|
36
36
|
if (!isRunning) {
|
|
37
37
|
console.log(`[${new Date().toISOString()}] [getOrCreateWorker] Worker not running, closing and recreating...`);
|
|
38
|
+
// Stale worker detected — surface at WARN level with count context
|
|
39
|
+
(0, config_1.getConfig)().LOGGER.warn(`Stale worker for queue ${queueKey} (not running), recreating. Active workers before recreate: ${workers.size}`);
|
|
38
40
|
try {
|
|
39
41
|
existingWorker.close();
|
|
40
42
|
}
|
|
@@ -116,7 +118,8 @@ class QueueUtils {
|
|
|
116
118
|
});
|
|
117
119
|
workers.set(queueKey, worker);
|
|
118
120
|
console.log(`[${new Date().toISOString()}] [getOrCreateWorker] Worker initialized and stored for queue: ${queueKey}`);
|
|
119
|
-
|
|
121
|
+
// Log total active workers — a growing count here signals a connection-proliferation leak
|
|
122
|
+
(0, config_1.getConfig)().LOGGER.info(`Worker initialized for queue: ${queueKey} (total active workers: ${workers.size})`);
|
|
120
123
|
}
|
|
121
124
|
static async waitForRateLimitExpiry(connectionId, provider, rateLimitConfigs) {
|
|
122
125
|
const key = `rate_limit:${provider}:${connectionId}`;
|
|
@@ -202,6 +205,10 @@ class QueueUtils {
|
|
|
202
205
|
queue.getActiveCount(),
|
|
203
206
|
]);
|
|
204
207
|
console.log(`[${new Date().toISOString()}] [addJobToQueue] Queue state - QueueKey: ${queueKey}, Waiting: ${waiting}, Delayed: ${delayed}, Active: ${active}`);
|
|
208
|
+
// Warn via structured logger when backlog is building — correlates with heap stats
|
|
209
|
+
if (waiting + delayed > 1) {
|
|
210
|
+
(0, config_1.getConfig)().LOGGER.warn(`Queue backlog [${queueKey}]: waiting=${waiting} delayed=${delayed} active=${active}`);
|
|
211
|
+
}
|
|
205
212
|
}
|
|
206
213
|
catch (e) {
|
|
207
214
|
// Ignore errors getting queue state
|
|
@@ -250,6 +257,9 @@ class QueueUtils {
|
|
|
250
257
|
// Timeout
|
|
251
258
|
setTimeout(() => {
|
|
252
259
|
clearInterval(checkInterval);
|
|
260
|
+
// Log via structured logger before rejecting so the timeout is visible in
|
|
261
|
+
// CloudWatch alongside the periodic heap stats — correlates queue stalls with memory pressure.
|
|
262
|
+
(0, config_1.getConfig)().LOGGER.error(`Job ${jobId} timed out after ${totalTimeout}ms [${queueKey}] (jobDelay=${jobDelay}ms)`);
|
|
253
263
|
reject(new Error(`Request timeout: Maximum wait time exceeded (${totalTimeout}ms). Job delay was ${jobDelay}ms.`));
|
|
254
264
|
}, totalTimeout);
|
|
255
265
|
});
|
|
@@ -147,6 +147,12 @@ class RateLimitUtils {
|
|
|
147
147
|
provider: constants_1.CONNECTION_PROVIDERS.DUSAW,
|
|
148
148
|
maxTimeoutWindowMs: 120000,
|
|
149
149
|
});
|
|
150
|
+
configs.set(constants_1.CONNECTION_PROVIDERS.ULTRALOCK, {
|
|
151
|
+
maxRequests: 60,
|
|
152
|
+
windowMs: 60000, // 1 minute — adjust once UltraLock API docs confirm their limit
|
|
153
|
+
provider: constants_1.CONNECTION_PROVIDERS.ULTRALOCK,
|
|
154
|
+
maxTimeoutWindowMs: 120000,
|
|
155
|
+
});
|
|
150
156
|
return configs;
|
|
151
157
|
}
|
|
152
158
|
static async isRateLimitAllowed(connectionId, provider, rateLimitConfigs) {
|
|
@@ -51,8 +51,8 @@ let WebHookRepository = (() => {
|
|
|
51
51
|
this.postgres = (0, db_1.getWebhookPostgresClient)();
|
|
52
52
|
}
|
|
53
53
|
async getWebhookAppByUid(uid) {
|
|
54
|
-
const query = `
|
|
55
|
-
SELECT * FROM application WHERE uid = $1
|
|
54
|
+
const query = `
|
|
55
|
+
SELECT * FROM application WHERE uid = $1
|
|
56
56
|
`;
|
|
57
57
|
const result = await this.postgres.query(query, [uid]);
|
|
58
58
|
if (result.rows.length > 0) {
|