dt-common-device 14.0.0 → 14.0.2
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/Integrations/twilio/twilio.service.d.ts +1 -2
- package/dist/Integrations/twilio/twilio.service.js +16 -20
- package/dist/alerts/alert.types.d.ts +5 -1
- package/dist/alerts/alert.types.js +4 -0
- package/dist/audit/AuditUtils.js +13 -0
- package/dist/audit/IAuditProperties.d.ts +7 -1
- package/dist/audit/IAuditProperties.js +1 -0
- package/dist/audit/PushAudit.d.ts +17 -1
- package/dist/audit/PushAudit.js +51 -9
- package/dist/config/config.d.ts +2 -1
- package/dist/config/config.js +14 -38
- package/dist/config/config.types.d.ts +3 -3
- package/dist/config/constants.js +1 -0
- package/dist/constants/ConnectionProviders.d.ts +4 -0
- package/dist/constants/ConnectionProviders.js +4 -0
- package/dist/constants/Event.d.ts +22 -0
- package/dist/constants/Event.js +22 -0
- package/dist/copilotQueue/utils/queueManager.js +2 -35
- package/dist/cronicle/Cronicle.service.d.ts +3 -3
- package/dist/cronicle/Cronicle.service.js +4 -3
- package/dist/cronicle/ICronicle.interface.d.ts +67 -0
- package/dist/db/db.js +32 -28
- package/dist/emails/emailService.js +23 -41
- package/dist/entities/admin/Admin.repository.d.ts +11 -2
- package/dist/entities/admin/Admin.repository.js +73 -2
- package/dist/entities/admin/Admin.service.d.ts +2 -1
- package/dist/entities/admin/Admin.service.js +12 -1
- package/dist/entities/admin/IAdmin.d.ts +40 -0
- package/dist/entities/connection/Connection.repository.d.ts +1 -0
- package/dist/entities/connection/Connection.repository.js +28 -1
- package/dist/entities/connection/Connection.service.d.ts +6 -0
- package/dist/entities/connection/Connection.service.js +15 -1
- package/dist/entities/connection/IConnection.d.ts +4 -1
- package/dist/entities/connection/IConnection.js +3 -0
- package/dist/entities/device/cloud/interfaces/IRawDevice.d.ts +3 -1
- package/dist/entities/device/cloud/interfaces/IRawDevice.js +2 -0
- package/dist/entities/device/local/interfaces/IDevice.d.ts +6 -0
- package/dist/entities/device/local/repository/Device.repository.d.ts +7 -0
- package/dist/entities/device/local/repository/Device.repository.js +11 -0
- package/dist/entities/device/local/services/Device.service.d.ts +8 -0
- package/dist/entities/device/local/services/Device.service.js +21 -0
- package/dist/entities/pms/IPms.d.ts +2 -1
- package/dist/entities/pms/IPms.js +1 -0
- package/dist/entities/pms/pms.service.js +22 -11
- package/dist/entities/property/Property.repository.d.ts +1 -0
- package/dist/entities/property/Property.repository.js +5 -0
- package/dist/entities/property/Property.service.d.ts +1 -0
- package/dist/entities/property/Property.service.js +6 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/issues/issue.types.d.ts +9 -2
- package/dist/issues/issue.types.js +8 -0
- package/dist/queue/entities/HybridHttpQueue.d.ts +0 -1
- package/dist/queue/entities/HybridHttpQueue.js +3 -5
- package/dist/queue/utils/queueUtils.d.ts +2 -2
- package/dist/queue/utils/queueUtils.js +16 -25
- package/dist/queue/utils/rateLimit.utils.js +19 -1
- package/dist/utils/http.utils.d.ts +3 -1
- package/dist/utils/http.utils.js +8 -0
- package/dist/webhookQueue/services/WebhookQueueService.js +3 -36
- package/package.json +3 -2
|
@@ -17,3 +17,70 @@ export interface ICronicle {
|
|
|
17
17
|
enabled?: boolean;
|
|
18
18
|
notes?: string;
|
|
19
19
|
}
|
|
20
|
+
export interface CronicleJob {
|
|
21
|
+
id: string;
|
|
22
|
+
title: string;
|
|
23
|
+
category: string;
|
|
24
|
+
plugin: string;
|
|
25
|
+
timeZone?: string;
|
|
26
|
+
timezone?: string;
|
|
27
|
+
enabled: number;
|
|
28
|
+
max_children: number;
|
|
29
|
+
target: string;
|
|
30
|
+
params: {
|
|
31
|
+
timeout: number;
|
|
32
|
+
headers: {
|
|
33
|
+
"Content-Type": string;
|
|
34
|
+
"x-api-key": string;
|
|
35
|
+
"User-Agent": string;
|
|
36
|
+
[key: string]: string;
|
|
37
|
+
};
|
|
38
|
+
url: string;
|
|
39
|
+
method: string;
|
|
40
|
+
data: string;
|
|
41
|
+
follow: number;
|
|
42
|
+
ssl_cert_bypass: number;
|
|
43
|
+
success_match: string;
|
|
44
|
+
error_match: string;
|
|
45
|
+
};
|
|
46
|
+
data: {
|
|
47
|
+
name: string;
|
|
48
|
+
cronJobId: string;
|
|
49
|
+
apiUrl: string;
|
|
50
|
+
method: string;
|
|
51
|
+
target: string;
|
|
52
|
+
schedule: {
|
|
53
|
+
years?: number[];
|
|
54
|
+
months?: number[];
|
|
55
|
+
days?: number[];
|
|
56
|
+
weekdays?: number[];
|
|
57
|
+
hours?: number[];
|
|
58
|
+
minutes?: number[];
|
|
59
|
+
};
|
|
60
|
+
notes: string;
|
|
61
|
+
};
|
|
62
|
+
timing: {
|
|
63
|
+
years?: number[];
|
|
64
|
+
months?: number[];
|
|
65
|
+
days?: number[];
|
|
66
|
+
weekdays?: number[];
|
|
67
|
+
hours?: number[];
|
|
68
|
+
minutes?: number[];
|
|
69
|
+
};
|
|
70
|
+
timeout: number;
|
|
71
|
+
notes: string;
|
|
72
|
+
modified: number;
|
|
73
|
+
created: number;
|
|
74
|
+
api_key: string;
|
|
75
|
+
}
|
|
76
|
+
export interface ListSchedules {
|
|
77
|
+
code: number;
|
|
78
|
+
list: {
|
|
79
|
+
page_size: number;
|
|
80
|
+
first_page: number;
|
|
81
|
+
last_page: number;
|
|
82
|
+
length: number;
|
|
83
|
+
type: string;
|
|
84
|
+
};
|
|
85
|
+
rows: CronicleJob[];
|
|
86
|
+
}
|
package/dist/db/db.js
CHANGED
|
@@ -11,45 +11,49 @@ exports.getWebhookPostgresClient = getWebhookPostgresClient;
|
|
|
11
11
|
const pg_1 = require("pg");
|
|
12
12
|
const config_1 = require("../config/config");
|
|
13
13
|
const mongoose_1 = __importDefault(require("mongoose"));
|
|
14
|
+
let adminPool = null;
|
|
15
|
+
let accessPool = null;
|
|
16
|
+
let pmsPool = null;
|
|
17
|
+
let webhookPool = null;
|
|
14
18
|
function getPostgresClient() {
|
|
15
19
|
const URI = (0, config_1.getAdminPostgresDbUri)();
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return
|
|
20
|
+
adminPool ?? (adminPool = new pg_1.Pool({
|
|
21
|
+
connectionString: URI,
|
|
22
|
+
connectionTimeoutMillis: 5000,
|
|
23
|
+
idleTimeoutMillis: 30000,
|
|
24
|
+
}).on("error", (err) => (0, config_1.getLogger)().error(err, `[Postgres] Admin Pool connection error`)));
|
|
25
|
+
(0, config_1.getLogger)().info(`[Postgres] Admin Pool initialized`);
|
|
26
|
+
return adminPool;
|
|
23
27
|
}
|
|
24
28
|
function getAccessPostgresClient() {
|
|
25
29
|
const URI = (0, config_1.getAccessPostgresDbUri)();
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return
|
|
30
|
+
accessPool ?? (accessPool = new pg_1.Pool({
|
|
31
|
+
connectionString: URI,
|
|
32
|
+
connectionTimeoutMillis: 5000,
|
|
33
|
+
idleTimeoutMillis: 30000,
|
|
34
|
+
}).on("error", (err) => (0, config_1.getLogger)().error(err, `[Postgres] Access Pool connection error`)));
|
|
35
|
+
(0, config_1.getLogger)().info(`[Postgres] Access Pool initialized`);
|
|
36
|
+
return accessPool;
|
|
33
37
|
}
|
|
34
38
|
function getPmsPostgresClient() {
|
|
35
39
|
const URI = (0, config_1.getPmsPostgresDbUri)();
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return
|
|
40
|
+
pmsPool ?? (pmsPool = new pg_1.Pool({
|
|
41
|
+
connectionString: URI,
|
|
42
|
+
connectionTimeoutMillis: 5000,
|
|
43
|
+
idleTimeoutMillis: 30000,
|
|
44
|
+
}).on("error", (err) => (0, config_1.getLogger)().error(err, `[Postgres] PMS Pool connection error`)));
|
|
45
|
+
(0, config_1.getLogger)().info(`[Postgres] PMS Pool initialized`);
|
|
46
|
+
return pmsPool;
|
|
43
47
|
}
|
|
44
48
|
function getWebhookPostgresClient() {
|
|
45
49
|
const URI = (0, config_1.getWebhookPostgresDbUri)();
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return
|
|
50
|
+
webhookPool ?? (webhookPool = new pg_1.Pool({
|
|
51
|
+
connectionString: URI,
|
|
52
|
+
connectionTimeoutMillis: 5000,
|
|
53
|
+
idleTimeoutMillis: 30000,
|
|
54
|
+
}).on("error", (err) => (0, config_1.getLogger)().error(err, `[Postgres] Webhook Pool connection error`)));
|
|
55
|
+
(0, config_1.getLogger)().info(`[Postgres] Webhook Pool initialized`);
|
|
56
|
+
return webhookPool;
|
|
53
57
|
}
|
|
54
58
|
const connectDatabase = async () => {
|
|
55
59
|
try {
|
|
@@ -236,61 +236,43 @@ let EmailService = (() => {
|
|
|
236
236
|
else {
|
|
237
237
|
imageURL = `https://api-sandbox-new.devicethread.com/dt-logo.png`;
|
|
238
238
|
}
|
|
239
|
-
const
|
|
240
|
-
const from = CONTACT_US_DETAILS?.FROM_EMAIL_ADDRESS;
|
|
241
|
-
if (!process.env.AWS_ACCESS_KEY_ID ||
|
|
242
|
-
!process.env.AWS_SECRET_ACCESS_KEY) {
|
|
243
|
-
throw new Error("AWS credentials are not set");
|
|
244
|
-
}
|
|
245
|
-
// Section below is replacing the global variables from template like contact us email, phone and current year.
|
|
239
|
+
const fromEmail = typeof mailData.fromEmail === "string" ? mailData.fromEmail.trim() : "";
|
|
246
240
|
const currentYear = new Date().getFullYear().toString();
|
|
247
|
-
|
|
248
|
-
let finalMessage = message
|
|
241
|
+
const finalMessage = message
|
|
249
242
|
.replaceAll("{{year}}", currentYear)
|
|
250
|
-
.replaceAll("{{
|
|
251
|
-
.replaceAll("{{contactUsEmail}}", `${CONTACT_US_DETAILS?.TO_EMAIL_ADDRESS}`)
|
|
243
|
+
.replaceAll("{{contactUsEmail}}", fromEmail)
|
|
252
244
|
.replaceAll("{{logo}}", `${imageURL}`);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
245
|
+
(0, config_1.getLogger)().info(`sending email (SES) with params: ${JSON.stringify(toAddr)}`);
|
|
246
|
+
if (ccAddr && Array.isArray(ccAddr) && ccAddr.length > 0) {
|
|
247
|
+
(0, config_1.getLogger)().info(`CC addresses: ${JSON.stringify(ccAddr)}`);
|
|
248
|
+
}
|
|
249
|
+
mailData.from = fromEmail;
|
|
250
|
+
if (pdfBuffer) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
await this.sesClient.send(new client_ses_1.SendEmailCommand({
|
|
254
|
+
Source: fromEmail,
|
|
256
255
|
Destination: {
|
|
257
256
|
ToAddresses: toAddr,
|
|
257
|
+
...(ccAddr && Array.isArray(ccAddr) && ccAddr.length > 0
|
|
258
|
+
? {
|
|
259
|
+
CcAddresses: ccAddr.filter((e) => typeof e === "string" && e.trim()),
|
|
260
|
+
}
|
|
261
|
+
: {}),
|
|
258
262
|
},
|
|
259
263
|
Message: {
|
|
264
|
+
Subject: { Data: subject, Charset: "UTF-8" },
|
|
260
265
|
Body: {
|
|
261
|
-
Html: {
|
|
262
|
-
Charset: "UTF-8",
|
|
263
|
-
Data: finalMessage,
|
|
264
|
-
},
|
|
265
|
-
},
|
|
266
|
-
Subject: {
|
|
267
|
-
Charset: "UTF-8",
|
|
268
|
-
Data: subject,
|
|
266
|
+
Html: { Data: finalMessage, Charset: "UTF-8" },
|
|
269
267
|
},
|
|
270
268
|
},
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
};
|
|
274
|
-
// Add CC addresses if present
|
|
275
|
-
if (ccAddr && Array.isArray(ccAddr) && ccAddr.length > 0) {
|
|
276
|
-
params.Destination.CcAddresses = ccAddr;
|
|
277
|
-
}
|
|
278
|
-
(0, config_1.getLogger)().info(`sending email with params: ${JSON.stringify(toAddr)}`);
|
|
279
|
-
if (ccAddr && ccAddr.length > 0) {
|
|
280
|
-
(0, config_1.getLogger)().info(`CC addresses: ${JSON.stringify(ccAddr)}`);
|
|
281
|
-
}
|
|
282
|
-
mailData.from = CONTACT_US_DETAILS?.FROM_EMAIL_ADDRESS;
|
|
283
|
-
if (pdfBuffer) {
|
|
284
|
-
// this.sendEmailWithAttachments(this.sesClient, mailData, finalMessage, pdfBuffer)
|
|
285
|
-
}
|
|
286
|
-
else {
|
|
287
|
-
await this.sesClient.send(new client_ses_1.SendEmailCommand(params));
|
|
288
|
-
}
|
|
269
|
+
...(fromEmail ? { ReplyToAddresses: [fromEmail] } : {}),
|
|
270
|
+
}));
|
|
289
271
|
const maskedEmailsList = toAddr.map((email) => (0, Email_1.GetMaskedEmail)(email));
|
|
290
272
|
(0, config_1.getLogger)().info(`Sending email to: ${maskedEmailsList.join(", ")}`);
|
|
291
273
|
}
|
|
292
274
|
catch (error) {
|
|
293
|
-
(0, config_1.getLogger)().error("sendMail: Error", error);
|
|
275
|
+
(0, config_1.getLogger)().error("sendMail: Error", JSON.stringify(error));
|
|
294
276
|
}
|
|
295
277
|
}
|
|
296
278
|
};
|
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
import { IAccessGroup, IUser, IZone, IZoneAccessGroup } from "./IAdmin";
|
|
1
|
+
import { IAccessGroup, IDeliverableLocksByAccessGroupResult, IUser, IZone, IZoneAccessGroup, ILocksAndZonesByAccessGroup } from "./IAdmin";
|
|
2
2
|
export declare class AdminRepository {
|
|
3
3
|
private readonly deviceRepository;
|
|
4
4
|
private readonly postgres;
|
|
5
5
|
private readonly localDeviceService;
|
|
6
6
|
private readonly redisUtils;
|
|
7
7
|
constructor();
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Raw zones + devices for access groups (all non-HUB devices). Used when callers need the full list (e.g. UI).
|
|
10
|
+
*/
|
|
11
|
+
private loadCollectionZonesDevicesByAccessGroupIds;
|
|
12
|
+
getZonesByAccessGroupIds(accessGroupIds: string[], propertyId: string): Promise<ILocksAndZonesByAccessGroup[]>;
|
|
13
|
+
/**
|
|
14
|
+
* ZN-2: same data as {@link getZonesByAccessGroupIds}, but `devices` are only LOCKs that are not
|
|
15
|
+
* under maintenance (zone or device). `maintenanceSkips` is structured data for audit emission.
|
|
16
|
+
*/
|
|
17
|
+
getDeliverableLockDevicesByAccessGroupIds(accessGroupIds: string[], propertyId: string): Promise<IDeliverableLocksByAccessGroupResult>;
|
|
9
18
|
getZonesByAccessGroups(accessGroupIds: string[], type?: string[]): Promise<any[]>;
|
|
10
19
|
getAccessGroup(accessGroupId: string, propertyId?: string): Promise<IAccessGroup | null>;
|
|
11
20
|
getZoneAccessGroupByZoneId(zoneId: string): Promise<IZoneAccessGroup[] | null>;
|
|
@@ -91,7 +91,13 @@ let AdminRepository = (() => {
|
|
|
91
91
|
this.localDeviceService = typedi_1.default.get(services_1.LocalDeviceService);
|
|
92
92
|
this.redisUtils = typedi_1.default.get(utils_1.RedisUtils);
|
|
93
93
|
}
|
|
94
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Raw zones + devices for access groups (all non-HUB devices). Used when callers need the full list (e.g. UI).
|
|
96
|
+
*/
|
|
97
|
+
async loadCollectionZonesDevicesByAccessGroupIds(accessGroupIds, propertyId) {
|
|
98
|
+
if (!accessGroupIds?.length) {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
95
101
|
// If not cached, get the result from the database
|
|
96
102
|
const result = await this.postgres.query(`SELECT
|
|
97
103
|
"zc"."id" AS "zoneCollectionMapId",
|
|
@@ -100,7 +106,8 @@ let AdminRepository = (() => {
|
|
|
100
106
|
"z"."id" AS "zoneId",
|
|
101
107
|
"z"."name" AS "zoneName",
|
|
102
108
|
"z"."zoneTypeId",
|
|
103
|
-
"zt"."name" AS "zoneTypeName"
|
|
109
|
+
"zt"."name" AS "zoneTypeName",
|
|
110
|
+
"z"."isUnderMaintenance"
|
|
104
111
|
FROM "dt_zones_collection_map" AS "zc"
|
|
105
112
|
INNER JOIN "dt_zones" AS "z" ON "zc"."zoneId" = "z"."id"
|
|
106
113
|
LEFT JOIN "dt_zoneTypes" AS "zt" ON "z"."zoneTypeId" = "zt"."id"
|
|
@@ -171,6 +178,14 @@ let AdminRepository = (() => {
|
|
|
171
178
|
zoneIds: _zoneIds,
|
|
172
179
|
excludeDeviceType: interfaces_1.DeviceType.HUB,
|
|
173
180
|
});
|
|
181
|
+
// Fetch isUnderMaintenance for ALL zone IDs (including child zones not in the collection map)
|
|
182
|
+
const allCollectionIds = collectionZone.map((e) => e.collectionId);
|
|
183
|
+
const [allZoneMaintenanceResult, collectionMaintenanceResult] = await Promise.all([
|
|
184
|
+
this.postgres.query(`SELECT "id", "isUnderMaintenance" FROM "dt_zones" WHERE "id" = ANY($1)`, [_zoneIds]),
|
|
185
|
+
this.postgres.query(`SELECT "id", "isUnderMaintenance" FROM "dt_collections" WHERE "id" = ANY($1)`, [allCollectionIds]),
|
|
186
|
+
]);
|
|
187
|
+
const zoneMaintenanceMap = new Map(allZoneMaintenanceResult.rows.map((r) => [r.id, r.isUnderMaintenance ?? false]));
|
|
188
|
+
const collectionMaintenanceMap = new Map(collectionMaintenanceResult.rows.map((r) => [r.id, r.isUnderMaintenance ?? false]));
|
|
174
189
|
const _collectionZone = collectionZone.map((e) => {
|
|
175
190
|
const zones = e.zoneIds;
|
|
176
191
|
let devices = [];
|
|
@@ -184,18 +199,21 @@ let AdminRepository = (() => {
|
|
|
184
199
|
const _devices = devices.concat(device);
|
|
185
200
|
devices = _devices;
|
|
186
201
|
// Create zone data for each zone
|
|
202
|
+
// Use zoneMaintenanceMap for isUnderMaintenance so child zones are also covered
|
|
187
203
|
const zoneInfo = response.find((r) => r.zoneId === element);
|
|
188
204
|
const zoneData = {
|
|
189
205
|
zoneId: element,
|
|
190
206
|
deviceIds: device?.map((d) => d.deviceId), // Get first device ID if available
|
|
191
207
|
zoneName: zoneInfo?.zoneName || "",
|
|
192
208
|
zoneType: zoneInfo?.zoneTypeName || null,
|
|
209
|
+
isUnderMaintenance: zoneMaintenanceMap.get(element) ?? false,
|
|
193
210
|
};
|
|
194
211
|
zonesData.push(zoneData);
|
|
195
212
|
});
|
|
196
213
|
e.devices = devices;
|
|
197
214
|
e.zones = zonesData;
|
|
198
215
|
e.parentZone = response;
|
|
216
|
+
e.isUnderMaintenance = collectionMaintenanceMap.get(e.collectionId) ?? false;
|
|
199
217
|
return e;
|
|
200
218
|
});
|
|
201
219
|
const collectionZoneDevices = Array.from(new Set(_collectionZone));
|
|
@@ -207,6 +225,59 @@ let AdminRepository = (() => {
|
|
|
207
225
|
// );
|
|
208
226
|
return collectionZoneDevices;
|
|
209
227
|
}
|
|
228
|
+
async getZonesByAccessGroupIds(accessGroupIds, propertyId) {
|
|
229
|
+
return await this.loadCollectionZonesDevicesByAccessGroupIds(accessGroupIds, propertyId);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* ZN-2: same data as {@link getZonesByAccessGroupIds}, but `devices` are only LOCKs that are not
|
|
233
|
+
* under maintenance (zone or device). `maintenanceSkips` is structured data for audit emission.
|
|
234
|
+
*/
|
|
235
|
+
async getDeliverableLockDevicesByAccessGroupIds(accessGroupIds, propertyId) {
|
|
236
|
+
if (!accessGroupIds?.length) {
|
|
237
|
+
return { collections: [], maintenanceSkips: [] };
|
|
238
|
+
}
|
|
239
|
+
const raw = await this.loadCollectionZonesDevicesByAccessGroupIds(accessGroupIds, propertyId);
|
|
240
|
+
const maintenanceSkips = [];
|
|
241
|
+
const collections = raw.map((cz) => {
|
|
242
|
+
const inactiveIds = new Set();
|
|
243
|
+
for (const zone of cz.zones || []) {
|
|
244
|
+
if (zone.isUnderMaintenance) {
|
|
245
|
+
(zone.deviceIds || []).forEach((id) => {
|
|
246
|
+
if (id != null && id !== "")
|
|
247
|
+
inactiveIds.add(String(id));
|
|
248
|
+
});
|
|
249
|
+
maintenanceSkips.push({
|
|
250
|
+
accessGroupId: cz.collectionId,
|
|
251
|
+
zoneId: zone.zoneId,
|
|
252
|
+
deviceIds: (zone.deviceIds || [])
|
|
253
|
+
.map((x) => String(x))
|
|
254
|
+
.filter((x) => x !== ""),
|
|
255
|
+
reason: "zone inactive",
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
for (const dev of cz.devices || []) {
|
|
260
|
+
if (dev.deviceType?.type !== "LOCK" || !dev.isUnderMaintenance) {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
const did = String(dev.deviceId);
|
|
264
|
+
if (inactiveIds.has(did)) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
inactiveIds.add(did);
|
|
268
|
+
maintenanceSkips.push({
|
|
269
|
+
accessGroupId: cz.collectionId,
|
|
270
|
+
zoneId: dev.zoneId,
|
|
271
|
+
deviceIds: [did],
|
|
272
|
+
reason: "device under maintenance",
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
const deliverableDevices = (cz.devices || []).filter((d) => d.deviceType?.type === "LOCK" &&
|
|
276
|
+
!inactiveIds.has(String(d.deviceId)));
|
|
277
|
+
return { ...cz, devices: deliverableDevices, skippedDevices: cz.devices.filter((device) => device.deviceType?.type === "LOCK" && inactiveIds.has(String(device.deviceId))) };
|
|
278
|
+
});
|
|
279
|
+
return { collections, maintenanceSkips };
|
|
280
|
+
}
|
|
210
281
|
async getZonesByAccessGroups(accessGroupIds, type) {
|
|
211
282
|
// Fetch zone IDs associated with these access groups
|
|
212
283
|
const zonesIdsQuery = `
|
|
@@ -3,7 +3,8 @@ export declare class AdminService {
|
|
|
3
3
|
private readonly adminRepository;
|
|
4
4
|
private readonly redisUtils;
|
|
5
5
|
constructor();
|
|
6
|
-
getZonesByAccessGroupIds(accessGroupIds: string[], propertyId: string): Promise<
|
|
6
|
+
getZonesByAccessGroupIds(accessGroupIds: string[], propertyId: string): Promise<import("./IAdmin").ILocksAndZonesByAccessGroup[]>;
|
|
7
|
+
getDeliverableLockDevicesByAccessGroupIds(accessGroupIds: string[], propertyId: string): Promise<import("./IAdmin").IDeliverableLocksByAccessGroupResult>;
|
|
7
8
|
getZonesByAccessGroups(accessGroupIds: string[], type?: string[]): Promise<any[]>;
|
|
8
9
|
getAccessGroup(accessGroupId: string, propertyId?: string): Promise<IAccessGroup | null>;
|
|
9
10
|
getAccessGroupByZoneId(zoneId: string): Promise<IAccessGroup[] | []>;
|
|
@@ -95,6 +95,15 @@ let AdminService = (() => {
|
|
|
95
95
|
return [];
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
+
async getDeliverableLockDevicesByAccessGroupIds(accessGroupIds, propertyId) {
|
|
99
|
+
try {
|
|
100
|
+
return await this.adminRepository.getDeliverableLockDevicesByAccessGroupIds(accessGroupIds, propertyId);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
console.log(error);
|
|
104
|
+
return { collections: [], maintenanceSkips: [] };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
98
107
|
async getZonesByAccessGroups(accessGroupIds, type) {
|
|
99
108
|
try {
|
|
100
109
|
return await this.adminRepository.getZonesByAccessGroups(accessGroupIds, type);
|
|
@@ -307,7 +316,9 @@ let AdminService = (() => {
|
|
|
307
316
|
}
|
|
308
317
|
async propertyHasSaltoConnection(propertyId) {
|
|
309
318
|
const connections = await typedi_1.default.get(connection_1.LocalConnectionService).getConnectionsByPropertyId(propertyId);
|
|
310
|
-
|
|
319
|
+
// using 'some' to check, because property may have TV, Thermostats connections as well,
|
|
320
|
+
// In that case 'every' will return false and that breaks the SaltoKS check implementation
|
|
321
|
+
return connections.some((connection) => connection.connectionProvider === connection_1.ConnectionProvider.SaltoKS ||
|
|
311
322
|
connection.connectionProvider === connection_1.ConnectionProvider.SaltoSpace);
|
|
312
323
|
}
|
|
313
324
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { IDevice } from "../device/local/interfaces";
|
|
1
2
|
export interface IAccessGroup {
|
|
2
3
|
id: string;
|
|
3
4
|
propertyId: string;
|
|
@@ -48,3 +49,42 @@ export interface IUser {
|
|
|
48
49
|
deletedAt?: Date | null;
|
|
49
50
|
imageURL?: string | null;
|
|
50
51
|
}
|
|
52
|
+
/** ZN-2 skip metadata for callers that emit maintenance audits (e.g. smart-access-node). */
|
|
53
|
+
export type IZn2MaintenanceSkipReason = "zone inactive" | "device under maintenance";
|
|
54
|
+
export interface IZn2MaintenanceSkip {
|
|
55
|
+
accessGroupId: string;
|
|
56
|
+
zoneId: string;
|
|
57
|
+
deviceIds: string[];
|
|
58
|
+
reason: IZn2MaintenanceSkipReason;
|
|
59
|
+
}
|
|
60
|
+
/** LOCK devices eligible for guest programming after zone + device maintenance rules. */
|
|
61
|
+
export interface IDeliverableLocksByAccessGroupResult {
|
|
62
|
+
collections: {
|
|
63
|
+
collectionId: string;
|
|
64
|
+
devices: IDevice[];
|
|
65
|
+
skippedDevices: IDevice[];
|
|
66
|
+
zones: {
|
|
67
|
+
zoneId: string;
|
|
68
|
+
deviceIds: string[];
|
|
69
|
+
zoneName: string;
|
|
70
|
+
zoneTypeName: string;
|
|
71
|
+
isUnderMaintenance: boolean;
|
|
72
|
+
}[];
|
|
73
|
+
parentZone: any;
|
|
74
|
+
isUnderMaintenance: boolean;
|
|
75
|
+
}[];
|
|
76
|
+
maintenanceSkips: IZn2MaintenanceSkip[];
|
|
77
|
+
}
|
|
78
|
+
export interface ILocksAndZonesByAccessGroup {
|
|
79
|
+
collectionId: string;
|
|
80
|
+
devices: IDevice[];
|
|
81
|
+
zones: {
|
|
82
|
+
zoneId: string;
|
|
83
|
+
deviceIds: string[];
|
|
84
|
+
zoneName: string;
|
|
85
|
+
zoneTypeName: string;
|
|
86
|
+
isUnderMaintenance: boolean;
|
|
87
|
+
}[];
|
|
88
|
+
parentZone: any;
|
|
89
|
+
isUnderMaintenance: boolean;
|
|
90
|
+
}
|
|
@@ -7,4 +7,5 @@ export declare class ConnectionRepository {
|
|
|
7
7
|
getConnectionsByPropertyId(propertyId: string): Promise<IConnection[]>;
|
|
8
8
|
queryConnections(query: Partial<IConnection>): Promise<IConnection[]>;
|
|
9
9
|
updateConnection(connectionId: string, data: Partial<IConnection>): Promise<any>;
|
|
10
|
+
update(connectionId: string, propertyId: string, data: Partial<IConnection>): Promise<any>;
|
|
10
11
|
}
|
|
@@ -83,7 +83,15 @@ let ConnectionRepository = (() => {
|
|
|
83
83
|
// Build conditions dynamically based on provided query parameters
|
|
84
84
|
Object.keys(query).forEach((key) => {
|
|
85
85
|
const value = query[key];
|
|
86
|
-
if (value
|
|
86
|
+
if (value === undefined || value === null)
|
|
87
|
+
return;
|
|
88
|
+
// ✅ Special handling for metaData (JSON)
|
|
89
|
+
if (key === "metaData" && typeof value === "object") {
|
|
90
|
+
conditions.push(`"metaData" @> $${paramIndex}`);
|
|
91
|
+
values.push(JSON.stringify(value));
|
|
92
|
+
paramIndex++;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
87
95
|
conditions.push(`"${key}" = $${paramIndex}`);
|
|
88
96
|
values.push(value);
|
|
89
97
|
paramIndex++;
|
|
@@ -106,6 +114,25 @@ let ConnectionRepository = (() => {
|
|
|
106
114
|
const result = await this.pool.query(`UPDATE dt_connections SET ${setClause}, "updatedAt" = NOW() WHERE id = $1 RETURNING *`, [connectionId, ...values]);
|
|
107
115
|
return result.rows[0];
|
|
108
116
|
}
|
|
117
|
+
async update(connectionId, propertyId, data) {
|
|
118
|
+
// Prevent invalid query if no fields provided
|
|
119
|
+
if (!data || Object.keys(data).length === 0) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
// Build dynamic SET clause
|
|
123
|
+
const setClause = Object.keys(data)
|
|
124
|
+
.map((key, index) => `"${key}" = $${index + 3}`)
|
|
125
|
+
.join(", ");
|
|
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
|
|
130
|
+
RETURNING *`, [connectionId, propertyId, ...values]);
|
|
131
|
+
if (result.rowCount === 0) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
return result.rows[0];
|
|
135
|
+
}
|
|
109
136
|
};
|
|
110
137
|
__setFunctionName(_classThis, "ConnectionRepository");
|
|
111
138
|
(() => {
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { IConnection } from "./IConnection";
|
|
2
|
+
import { RedisUtils } from "../../utils";
|
|
2
3
|
export declare class LocalConnectionService {
|
|
3
4
|
private readonly connectionRepository;
|
|
5
|
+
protected readonly redisUtils: RedisUtils;
|
|
4
6
|
constructor();
|
|
5
7
|
createConnection(data: Partial<IConnection>): Promise<IConnection>;
|
|
6
8
|
getConnection(connectionId: string): Promise<IConnection>;
|
|
7
9
|
getConnectionsByPropertyId(propertyId: string): Promise<IConnection[]>;
|
|
8
10
|
queryConnections(query: Partial<IConnection>): Promise<IConnection[]>;
|
|
9
11
|
updateConnection(connectionId: string, data: Partial<IConnection>): Promise<IConnection>;
|
|
12
|
+
update(data: {
|
|
13
|
+
connectionId: string;
|
|
14
|
+
propertyId: string;
|
|
15
|
+
}, dataToUpdate: Partial<IConnection>): Promise<IConnection>;
|
|
10
16
|
}
|
|
@@ -41,6 +41,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
41
41
|
exports.LocalConnectionService = void 0;
|
|
42
42
|
const typedi_1 = require("typedi");
|
|
43
43
|
const Connection_repository_1 = require("./Connection.repository");
|
|
44
|
+
const utils_1 = require("../../utils");
|
|
44
45
|
let LocalConnectionService = (() => {
|
|
45
46
|
let _classDecorators = [(0, typedi_1.Service)()];
|
|
46
47
|
let _classDescriptor;
|
|
@@ -49,6 +50,7 @@ let LocalConnectionService = (() => {
|
|
|
49
50
|
var LocalConnectionService = _classThis = class {
|
|
50
51
|
constructor() {
|
|
51
52
|
this.connectionRepository = typedi_1.Container.get(Connection_repository_1.ConnectionRepository);
|
|
53
|
+
this.redisUtils = typedi_1.Container.get(utils_1.RedisUtils);
|
|
52
54
|
}
|
|
53
55
|
async createConnection(data) {
|
|
54
56
|
if (!data.connectionName ||
|
|
@@ -81,7 +83,19 @@ let LocalConnectionService = (() => {
|
|
|
81
83
|
if (!connectionId) {
|
|
82
84
|
throw new Error("Connection ID is required");
|
|
83
85
|
}
|
|
84
|
-
|
|
86
|
+
const connection = await this.connectionRepository.updateConnection(connectionId, data);
|
|
87
|
+
await this.redisUtils.del(`connection:${connectionId}`);
|
|
88
|
+
await this.redisUtils.del(`property_connections:${connection.propertyId}`);
|
|
89
|
+
return connection;
|
|
90
|
+
}
|
|
91
|
+
async update(data, dataToUpdate) {
|
|
92
|
+
if (!data.connectionId || !data.propertyId) {
|
|
93
|
+
throw new Error("Connection ID and Property ID are required");
|
|
94
|
+
}
|
|
95
|
+
const connection = await this.connectionRepository.update(data.connectionId, data.propertyId, dataToUpdate);
|
|
96
|
+
await this.redisUtils.del(`connection:${data.connectionId}`);
|
|
97
|
+
await this.redisUtils.del(`property_connections:${data.propertyId}`);
|
|
98
|
+
return connection;
|
|
85
99
|
}
|
|
86
100
|
};
|
|
87
101
|
__setFunctionName(_classThis, "LocalConnectionService");
|
|
@@ -20,4 +20,7 @@ var ConnectionProvider;
|
|
|
20
20
|
ConnectionProvider["Lockly"] = "Lockly";
|
|
21
21
|
ConnectionProvider["Sifely"] = "Sifely";
|
|
22
22
|
ConnectionProvider["Twilio"] = "Twilio";
|
|
23
|
+
ConnectionProvider["Daikin"] = "Daikin";
|
|
24
|
+
ConnectionProvider["HoneyWell"] = "HoneyWell";
|
|
25
|
+
ConnectionProvider["UltraLock"] = "UltraLock";
|
|
23
26
|
})(ConnectionProvider || (exports.ConnectionProvider = ConnectionProvider = {}));
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export interface IDevice {
|
|
2
2
|
deviceId: string;
|
|
3
3
|
propertyId: string;
|
|
4
|
+
collectionId?: string;
|
|
4
5
|
zoneId: string;
|
|
5
6
|
name: string;
|
|
6
7
|
hubId: string[];
|
|
@@ -48,6 +49,11 @@ export interface IDevice {
|
|
|
48
49
|
capabilities?: Record<string, any>;
|
|
49
50
|
isDeleted?: boolean;
|
|
50
51
|
deletedAt?: Date;
|
|
52
|
+
/** Persisted maintenance; ZONE-sourced rows mirror parent zone toggles (smart-cloud). */
|
|
53
|
+
isUnderMaintenance?: boolean;
|
|
54
|
+
underMaintenanceSince?: string | Date | null;
|
|
55
|
+
maintenanceReason?: string | null;
|
|
56
|
+
maintenanceSource?: "ZONE" | "DEVICE" | null;
|
|
51
57
|
}
|
|
52
58
|
export declare class IStatus {
|
|
53
59
|
online: boolean;
|
|
@@ -8,6 +8,13 @@ export declare class DeviceRepository {
|
|
|
8
8
|
getDevice(deviceId: string, withHubDetails?: boolean): Promise<IDevice>;
|
|
9
9
|
updateDevice(deviceId: string, body: any): Promise<IDevice>;
|
|
10
10
|
updateDevices(query: any, updateData: any): Promise<any>;
|
|
11
|
+
/** Sync Mongo `devices_v2` when smart-cloud toggles zone maintenance (operational device-service). */
|
|
12
|
+
syncDevicesZoneMaintenance(body: {
|
|
13
|
+
zoneId: string;
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
maintenanceReason?: string | null;
|
|
16
|
+
underMaintenanceSince?: string | null;
|
|
17
|
+
}): Promise<any>;
|
|
11
18
|
deleteDevice(deviceId: string): Promise<void>;
|
|
12
19
|
getDevices(deviceIds: string[], withHubDetails?: boolean): Promise<IDevice[]>;
|
|
13
20
|
getPropertyDevices(propertyId: string, selectDeviceId?: boolean, type?: string, withHubDetails?: boolean): Promise<IDevice[]>;
|