dt-common-device 3.0.9 → 3.0.11
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.d.ts +1 -2
- package/dist/config/config.js +4 -5
- package/dist/device/cloud/interface.d.ts +101 -0
- package/dist/device/cloud/interface.js +3 -0
- package/dist/device/cloud/interfaces/IDeviceConnectionService.d.ts +7 -0
- package/dist/device/cloud/interfaces/IDeviceConnectionService.js +3 -0
- package/dist/device/cloud/interfaces/IDevicesService.d.ts +9 -0
- package/dist/device/cloud/services/Device.service.d.ts +39 -0
- package/dist/device/cloud/services/Device.service.js +9 -0
- package/dist/device/cloud/services/DeviceCloudService.d.ts +42 -0
- package/dist/device/cloud/services/DeviceCloudService.js +59 -0
- package/dist/device/cloud/services/DeviceHub.service.d.ts +3 -0
- package/dist/device/cloud/services/DeviceHub.service.js +6 -0
- package/dist/device/cloud/services/Hub.service.d.ts +25 -0
- package/dist/device/cloud/services/Hub.service.js +9 -0
- package/dist/device/cloud/services/SmartThingsDeviceService.d.ts +38 -0
- package/dist/device/cloud/services/SmartThingsDeviceService.js +52 -0
- package/dist/device/index.d.ts +4 -0
- package/dist/device/index.js +20 -0
- package/dist/device/local/events/EventHandler.js +6 -6
- package/dist/device/local/events/Events.d.ts +12 -33
- package/dist/device/local/events/Events.js +12 -33
- package/dist/device/local/interface.d.ts +0 -0
- package/dist/device/local/interface.js +1 -0
- package/dist/device/local/interfaces/IDevice.d.ts +1 -0
- package/dist/device/local/repository/Schedule.repository.d.ts +0 -1
- package/dist/device/local/repository/Schedule.repository.js +6 -6
- package/dist/device/local/services/DeviceHub.service.d.ts +11 -0
- package/dist/device/local/services/DeviceHub.service.js +40 -0
- package/dist/queue/entities/HybridHttpQueue.d.ts +4 -14
- package/dist/queue/entities/HybridHttpQueue.js +31 -119
- package/dist/queue/interfaces/IHybridHttpQueue.d.ts +2 -12
- package/dist/queue/interfaces/IJobResult.d.ts +1 -8
- package/dist/queue/interfaces/index.d.ts +0 -1
- package/dist/queue/interfaces/index.js +0 -1
- package/dist/queue/services/QueueService.d.ts +2 -12
- package/dist/queue/types/queue.types.d.ts +10 -29
- package/dist/queue/utils/jobUtils.d.ts +0 -3
- package/dist/queue/utils/jobUtils.js +0 -48
- package/dist/queue/utils/queueUtils.d.ts +7 -0
- package/dist/queue/utils/queueUtils.js +113 -4
- package/package.json +6 -1
- package/.eslintrc.js +0 -44
- package/dist/audit/AuditProperties.d.ts +0 -16
- package/dist/audit/AuditUtils.d.ts +0 -2
- package/dist/audit/AuditUtils.js +0 -36
- package/src/alerts/Alert.model.ts +0 -289
- package/src/alerts/Alert.repository.ts +0 -487
- package/src/alerts/Alert.service.ts +0 -711
- package/src/alerts/AlertBuilder.example.ts +0 -126
- package/src/alerts/AlertBuilder.ts +0 -208
- package/src/alerts/AlertService.example.ts +0 -232
- package/src/alerts/alert.types.ts +0 -64
- package/src/alerts/index.ts +0 -3
- package/src/audit/AuditProperties.ts +0 -16
- package/src/audit/AuditUtils.ts +0 -38
- package/src/config/config.ts +0 -202
- package/src/config/config.types.ts +0 -21
- package/src/connection/Connection.repository.ts +0 -52
- package/src/connection/Connection.service.ts +0 -39
- package/src/connection/IConnection.ts +0 -27
- package/src/connection/index.ts +0 -3
- package/src/constants/ConnectionProviders.ts +0 -11
- package/src/constants/Event.ts +0 -89
- package/src/constants/Service.ts +0 -17
- package/src/constants/index.ts +0 -3
- package/src/db/db.ts +0 -24
- package/src/db/index.ts +0 -2
- package/src/db/redis.ts +0 -20
- package/src/device/cloud/entities/CloudDevice.ts +0 -40
- package/src/device/cloud/entities/CloudDeviceService.ts +0 -8
- package/src/device/cloud/entities/DeviceFactory.ts +0 -27
- package/src/device/cloud/entities/index.ts +0 -3
- package/src/device/cloud/interfaces/ICloudDevice.ts +0 -14
- package/src/device/cloud/interfaces/ICloudDeviceService.ts +0 -6
- package/src/device/cloud/interfaces/IDeviceFactory.ts +0 -5
- package/src/device/cloud/interfaces/IRawDataTransformer.ts +0 -5
- package/src/device/cloud/interfaces/IRawDevice.ts +0 -19
- package/src/device/cloud/interfaces/index.ts +0 -5
- package/src/device/local/interfaces/IDevice.ts +0 -61
- package/src/device/local/interfaces/IDtDevice.ts +0 -16
- package/src/device/local/interfaces/ISchedule.ts +0 -40
- package/src/device/local/interfaces/index.ts +0 -3
- package/src/device/local/repository/Device.repository.ts +0 -368
- package/src/device/local/repository/Hub.repository.ts +0 -107
- package/src/device/local/repository/Schedule.repository.ts +0 -72
- package/src/device/local/services/Device.service.ts +0 -436
- package/src/device/local/services/Hub.service.ts +0 -57
- package/src/device/local/services/Schedule.service.ts +0 -26
- package/src/device/local/services/index.ts +0 -3
- package/src/docs/Alert.model.md +0 -319
- package/src/docs/Alerts&IssuesModel.md +0 -312
- package/src/docs/Issue.model.md +0 -386
- package/src/docs/SECURITY.md +0 -67
- package/src/docs/TROUBLESHOOTING.md +0 -184
- package/src/events/BaseEventHandler.ts +0 -145
- package/src/events/BaseEventTransformer.ts +0 -97
- package/src/events/DeviceEventHandler.ts +0 -213
- package/src/events/DeviceEventTransformerFactory.ts +0 -77
- package/src/events/EventHandler.ts +0 -124
- package/src/events/EventHandlerOrchestrator.ts +0 -119
- package/src/events/EventProcessingService.ts +0 -248
- package/src/events/InternalEventSubscription.ts +0 -194
- package/src/events/index.ts +0 -9
- package/src/events/interfaces/DeviceEvent.ts +0 -56
- package/src/events/interfaces/IEventHandler.ts +0 -28
- package/src/events/interfaces/IEventTransformer.ts +0 -8
- package/src/events/interfaces/IInternalEvent.ts +0 -33
- package/src/events/interfaces/index.ts +0 -4
- package/src/index.ts +0 -43
- package/src/issues/Issue.model.ts +0 -350
- package/src/issues/Issue.repository.ts +0 -517
- package/src/issues/Issue.service.ts +0 -932
- package/src/issues/IssueBuilder.example.ts +0 -210
- package/src/issues/IssueBuilder.ts +0 -263
- package/src/issues/IssueService.example.ts +0 -310
- package/src/issues/index.ts +0 -2
- package/src/issues/issue.types.ts +0 -98
- package/src/property/IProperty.ts +0 -30
- package/src/property/Property.repository.ts +0 -53
- package/src/property/Property.service.ts +0 -38
- package/src/property/index.ts +0 -2
- package/src/queue/entities/HybridHttpQueue.ts +0 -274
- package/src/queue/entities/index.ts +0 -1
- package/src/queue/index.ts +0 -6
- package/src/queue/interfaces/IHttpRequestJob.ts +0 -10
- package/src/queue/interfaces/IHybridHttpQueue.ts +0 -25
- package/src/queue/interfaces/IJobResult.ts +0 -15
- package/src/queue/interfaces/IRateLimitConfig.ts +0 -5
- package/src/queue/interfaces/index.ts +0 -4
- package/src/queue/services/QueueService.ts +0 -40
- package/src/queue/services/index.ts +0 -1
- package/src/queue/types/http.types.ts +0 -23
- package/src/queue/types/index.ts +0 -2
- package/src/queue/types/queue.types.ts +0 -21
- package/src/queue/utils/index.ts +0 -3
- package/src/queue/utils/jobUtils.ts +0 -79
- package/src/queue/utils/queueUtils.ts +0 -84
- package/src/queue/utils/rateLimit.utils.ts +0 -131
- package/src/utils/http.utils.ts +0 -143
- package/src/utils/index.ts +0 -2
- package/src/utils/redis.utils.ts +0 -74
- package/tsconfig.json +0 -20
- /package/dist/{audit/AuditProperties.js → device/cloud/interfaces/IDevicesService.js} +0 -0
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.QueueUtils = void 0;
|
|
7
|
+
const rateLimit_utils_1 = require("./rateLimit.utils");
|
|
8
|
+
const axios_1 = __importDefault(require("axios"));
|
|
9
|
+
const dt_audit_library_1 = require("dt-audit-library");
|
|
4
10
|
const config_1 = require("../../config/config");
|
|
5
11
|
const redis_1 = require("../../db/redis");
|
|
6
12
|
class QueueUtils {
|
|
7
13
|
static getQueueKey(microservice, connectionId, provider) {
|
|
8
14
|
return `${microservice}_${provider}_${connectionId}`;
|
|
9
15
|
}
|
|
16
|
+
static getRequestQueueKey(connectionId, provider) {
|
|
17
|
+
return `${connectionId}_${provider}`;
|
|
18
|
+
}
|
|
10
19
|
static getOrCreateQueue(queueKey, queues) {
|
|
11
20
|
return (queues.get(queueKey) ??
|
|
12
21
|
queues
|
|
@@ -27,10 +36,9 @@ class QueueUtils {
|
|
|
27
36
|
lockDuration: 300000,
|
|
28
37
|
stalledInterval: 60000,
|
|
29
38
|
});
|
|
30
|
-
//
|
|
39
|
+
// Event handlers for job tracking
|
|
31
40
|
worker.on("completed", (job) => {
|
|
32
41
|
(0, config_1.getConfig)().LOGGER.info(`HTTP request completed: ${job.id} [${queueKey}]`);
|
|
33
|
-
// job.returnvalue is the actual result, not a Promise
|
|
34
42
|
const result = job.returnvalue;
|
|
35
43
|
jobResults.set(job.id, {
|
|
36
44
|
result,
|
|
@@ -47,10 +55,111 @@ class QueueUtils {
|
|
|
47
55
|
});
|
|
48
56
|
});
|
|
49
57
|
worker.on("error", (err) => (0, config_1.getConfig)().LOGGER.error(`Worker error for ${queueKey}: ${err.message}`));
|
|
50
|
-
worker.on("stalled", (jobId) => (0, config_1.getConfig)().LOGGER.warn(`Job ${jobId} stalled in worker ${queueKey}`));
|
|
51
|
-
worker.on("active", (job) => (0, config_1.getConfig)().LOGGER.info(`HTTP request started: ${job.id} [${queueKey}]`));
|
|
52
58
|
workers.set(queueKey, worker);
|
|
53
59
|
(0, config_1.getConfig)().LOGGER.info(`Worker initialized for queue: ${queueKey}`);
|
|
54
60
|
}
|
|
61
|
+
static async waitForRateLimitExpiry(connectionId, provider, rateLimitConfigs) {
|
|
62
|
+
const key = `rate_limit:${provider}:${connectionId}`;
|
|
63
|
+
const config = rateLimitConfigs.get(provider);
|
|
64
|
+
if (!config)
|
|
65
|
+
return;
|
|
66
|
+
while (true) {
|
|
67
|
+
const timestamps = await rateLimit_utils_1.RateLimitUtils.getRawRequestTimestamps(key);
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
const windowStart = now - config.windowMs;
|
|
70
|
+
const recentRequests = timestamps.filter((t) => t > windowStart);
|
|
71
|
+
if (recentRequests.length < config.maxRequests) {
|
|
72
|
+
// Rate limit not exceeded, we can proceed
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
// Calculate when the earliest request will expire
|
|
76
|
+
const earliestRequest = recentRequests[0];
|
|
77
|
+
const nextAvailableTime = earliestRequest + config.windowMs;
|
|
78
|
+
const delay = Math.max(nextAvailableTime - now, 1000); // At least 1 second
|
|
79
|
+
(0, config_1.getConfig)().LOGGER.info(`Rate limit exceeded for ${provider} [${connectionId}]. Waiting ${delay}ms until next allowed request. Current requests in window: ${recentRequests.length}/${config.maxRequests}`);
|
|
80
|
+
// Wait for the calculated delay
|
|
81
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
static async executeHttpRequest(url, method, options, connectionId, provider) {
|
|
85
|
+
(0, config_1.getConfig)().LOGGER.info(`Executing: ${method} ${url} -> ${provider} [${connectionId}]`);
|
|
86
|
+
try {
|
|
87
|
+
// Record the request first
|
|
88
|
+
await rateLimit_utils_1.RateLimitUtils.recordRequest(connectionId, provider);
|
|
89
|
+
// Execute the HTTP request
|
|
90
|
+
const response = await (0, axios_1.default)({
|
|
91
|
+
method: method.toLowerCase(),
|
|
92
|
+
url: url,
|
|
93
|
+
headers: options.headers || {},
|
|
94
|
+
timeout: 30000,
|
|
95
|
+
...(options.body && { data: options.body }),
|
|
96
|
+
...(options.params && { params: options.params }),
|
|
97
|
+
});
|
|
98
|
+
(0, config_1.getConfig)().LOGGER.info(`HTTP request successful: ${method} ${url} for ${provider} [${connectionId}]`);
|
|
99
|
+
// Return only the response data
|
|
100
|
+
return response.data;
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
(0, config_1.getConfig)().LOGGER.error(`HTTP request failed: ${error.message}`);
|
|
104
|
+
await (0, dt_audit_library_1.publishAudit)({
|
|
105
|
+
eventType: "http.request.error",
|
|
106
|
+
properties: {
|
|
107
|
+
connectionId,
|
|
108
|
+
provider,
|
|
109
|
+
endpoint: url,
|
|
110
|
+
method,
|
|
111
|
+
timestamp: Date.now(),
|
|
112
|
+
reason: "execution_error",
|
|
113
|
+
errorMessage: error.message,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
// Throw the error instead of returning it
|
|
117
|
+
throw new Error(`HTTP request failed: ${error.message}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
static async addJobToQueue(queueKey, jobData, delay, queues) {
|
|
121
|
+
const queue = this.getOrCreateQueue(queueKey, queues);
|
|
122
|
+
const job = await queue.add("http-request", jobData, {
|
|
123
|
+
delay,
|
|
124
|
+
attempts: 1,
|
|
125
|
+
removeOnComplete: { age: 300, count: 1 },
|
|
126
|
+
removeOnFail: { age: 300, count: 1 },
|
|
127
|
+
});
|
|
128
|
+
return job.id;
|
|
129
|
+
}
|
|
130
|
+
static async waitForJobCompletion(jobId, queueKey, jobResults) {
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
let timeoutId;
|
|
133
|
+
let checkCount = 0;
|
|
134
|
+
const maxChecks = 600;
|
|
135
|
+
const checkJob = async () => {
|
|
136
|
+
if (++checkCount >= maxChecks) {
|
|
137
|
+
clearTimeout(timeoutId);
|
|
138
|
+
return reject(new Error("Job timeout: Request took too long"));
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
const memoryResult = jobResults.get(jobId);
|
|
142
|
+
if (memoryResult?.resolved) {
|
|
143
|
+
clearTimeout(timeoutId);
|
|
144
|
+
return memoryResult.result !== undefined
|
|
145
|
+
? resolve(memoryResult.result)
|
|
146
|
+
: reject(new Error(memoryResult.error || "Unknown error"));
|
|
147
|
+
}
|
|
148
|
+
// Continue checking
|
|
149
|
+
timeoutId = setTimeout(checkJob, checkCount < 10 ? 50 : 100);
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
clearTimeout(timeoutId);
|
|
153
|
+
reject(new Error(`Error checking job: ${error.message}`));
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
checkJob();
|
|
157
|
+
// Backup timeout
|
|
158
|
+
setTimeout(() => {
|
|
159
|
+
clearTimeout(timeoutId);
|
|
160
|
+
reject(new Error("Request timeout: Maximum wait time exceeded"));
|
|
161
|
+
}, 60000);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
55
164
|
}
|
|
56
165
|
exports.QueueUtils = QueueUtils;
|
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dt-common-device",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.11",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"README.md",
|
|
9
|
+
"LICENSE"
|
|
10
|
+
],
|
|
6
11
|
"scripts": {
|
|
7
12
|
"build": "tsc",
|
|
8
13
|
"patch": "npm version patch && npm run build && npm publish",
|
package/.eslintrc.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
parser: "@typescript-eslint/parser",
|
|
3
|
-
extends: ["eslint:recommended", "@typescript-eslint/recommended"],
|
|
4
|
-
plugins: ["@typescript-eslint"],
|
|
5
|
-
env: {
|
|
6
|
-
node: true,
|
|
7
|
-
es2020: true,
|
|
8
|
-
},
|
|
9
|
-
parserOptions: {
|
|
10
|
-
ecmaVersion: 2020,
|
|
11
|
-
sourceType: "module",
|
|
12
|
-
},
|
|
13
|
-
rules: {
|
|
14
|
-
// Security rules
|
|
15
|
-
"no-eval": "error",
|
|
16
|
-
"no-implied-eval": "error",
|
|
17
|
-
"no-new-func": "error",
|
|
18
|
-
"no-script-url": "error",
|
|
19
|
-
"no-unsafe-finally": "error",
|
|
20
|
-
|
|
21
|
-
// TypeScript security
|
|
22
|
-
"@typescript-eslint/no-explicit-any": "warn",
|
|
23
|
-
"@typescript-eslint/no-unsafe-assignment": "warn",
|
|
24
|
-
"@typescript-eslint/no-unsafe-call": "warn",
|
|
25
|
-
"@typescript-eslint/no-unsafe-member-access": "warn",
|
|
26
|
-
"@typescript-eslint/no-unsafe-return": "warn",
|
|
27
|
-
|
|
28
|
-
// Code quality
|
|
29
|
-
"@typescript-eslint/explicit-function-return-type": "warn",
|
|
30
|
-
"@typescript-eslint/no-unused-vars": "error",
|
|
31
|
-
"@typescript-eslint/prefer-const": "error",
|
|
32
|
-
"@typescript-eslint/no-var-requires": "error",
|
|
33
|
-
|
|
34
|
-
// Error handling
|
|
35
|
-
"no-console": "warn",
|
|
36
|
-
"no-throw-literal": "error",
|
|
37
|
-
|
|
38
|
-
// Best practices
|
|
39
|
-
"prefer-const": "error",
|
|
40
|
-
"no-var": "error",
|
|
41
|
-
"object-shorthand": "error",
|
|
42
|
-
},
|
|
43
|
-
ignorePatterns: ["dist/", "node_modules/", "*.js"],
|
|
44
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export interface AuditProperties {
|
|
2
|
-
resource: string;
|
|
3
|
-
propertyId: string;
|
|
4
|
-
propertyName?: string;
|
|
5
|
-
userId?: string;
|
|
6
|
-
userName?: string;
|
|
7
|
-
deviceId: string;
|
|
8
|
-
deviceName: string;
|
|
9
|
-
zoneId?: string;
|
|
10
|
-
zoneName?: string;
|
|
11
|
-
accessGroupId?: string;
|
|
12
|
-
accessGroupName?: string;
|
|
13
|
-
scheduleId?: string;
|
|
14
|
-
scheduleName?: string;
|
|
15
|
-
[key: string]: any;
|
|
16
|
-
}
|
package/dist/audit/AuditUtils.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.buildAuditProperties = buildAuditProperties;
|
|
4
|
-
const AUDIT_FIELDS = [
|
|
5
|
-
"resource",
|
|
6
|
-
"propertyId",
|
|
7
|
-
"propertyName",
|
|
8
|
-
"userId",
|
|
9
|
-
"userName",
|
|
10
|
-
"deviceId",
|
|
11
|
-
"deviceName",
|
|
12
|
-
"zoneId",
|
|
13
|
-
"zoneName",
|
|
14
|
-
"accessGroupId",
|
|
15
|
-
"accessGroupName",
|
|
16
|
-
"scheduleId",
|
|
17
|
-
"scheduleName",
|
|
18
|
-
];
|
|
19
|
-
function buildAuditProperties(input) {
|
|
20
|
-
// Normalize keys to camelCase for matching
|
|
21
|
-
const normalized = { ...input };
|
|
22
|
-
// Build the audit object with all standard fields
|
|
23
|
-
const audit = {};
|
|
24
|
-
for (const field of AUDIT_FIELDS) {
|
|
25
|
-
// Try to find a matching key in input (case-insensitive)
|
|
26
|
-
const foundKey = Object.keys(normalized).find((k) => k.toLowerCase() === field.toLowerCase());
|
|
27
|
-
audit[field] = foundKey ? normalized[foundKey] : undefined;
|
|
28
|
-
}
|
|
29
|
-
// Merge in all other event-specific data (but don't overwrite audit fields)
|
|
30
|
-
for (const key of Object.keys(normalized)) {
|
|
31
|
-
if (!audit.hasOwnProperty(key)) {
|
|
32
|
-
audit[key] = normalized[key];
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return audit;
|
|
36
|
-
}
|
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
import mongoose, { Schema, Model } from "mongoose";
|
|
2
|
-
import {
|
|
3
|
-
AlertCategory,
|
|
4
|
-
AlertSeverity,
|
|
5
|
-
EntityType,
|
|
6
|
-
AlertDocument as IAlertDocument,
|
|
7
|
-
CreateAlertData,
|
|
8
|
-
UpdateAlertData,
|
|
9
|
-
} from "./alert.types";
|
|
10
|
-
|
|
11
|
-
// Interface for instance methods
|
|
12
|
-
interface IAlertMethods {
|
|
13
|
-
markAsRead(updatedBy: string): void;
|
|
14
|
-
markAsUnread(updatedBy: string): void;
|
|
15
|
-
activate(updatedBy: string): void;
|
|
16
|
-
deactivate(updatedBy: string): void;
|
|
17
|
-
snooze(until: Date, updatedBy: string): void;
|
|
18
|
-
unsnooze(updatedBy: string): void;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Interface for static methods
|
|
22
|
-
interface IAlertModel extends Model<IAlertDocument, {}, IAlertMethods> {
|
|
23
|
-
findByProperty(
|
|
24
|
-
propertyId: string,
|
|
25
|
-
includeDeleted?: boolean
|
|
26
|
-
): Promise<IAlertDocument[]>;
|
|
27
|
-
findByEntity(
|
|
28
|
-
entityId: string,
|
|
29
|
-
entityType: EntityType,
|
|
30
|
-
includeDeleted?: boolean
|
|
31
|
-
): Promise<IAlertDocument[]>;
|
|
32
|
-
findByCategory(
|
|
33
|
-
category: AlertCategory,
|
|
34
|
-
includeDeleted?: boolean
|
|
35
|
-
): Promise<IAlertDocument[]>;
|
|
36
|
-
findBySeverity(
|
|
37
|
-
severity: AlertSeverity,
|
|
38
|
-
includeDeleted?: boolean
|
|
39
|
-
): Promise<IAlertDocument[]>;
|
|
40
|
-
findActive(includeDeleted?: boolean): Promise<IAlertDocument[]>;
|
|
41
|
-
findUnread(includeDeleted?: boolean): Promise<IAlertDocument[]>;
|
|
42
|
-
findSnoozed(includeDeleted?: boolean): Promise<IAlertDocument[]>;
|
|
43
|
-
findExpiredSnooze(includeDeleted?: boolean): Promise<IAlertDocument[]>;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Main Alert schema
|
|
47
|
-
const AlertSchema = new Schema<IAlertDocument, IAlertModel, IAlertMethods>(
|
|
48
|
-
{
|
|
49
|
-
category: {
|
|
50
|
-
type: [String],
|
|
51
|
-
enum: Object.values(AlertCategory),
|
|
52
|
-
required: true,
|
|
53
|
-
validate: {
|
|
54
|
-
validator: function (categories: string[]) {
|
|
55
|
-
return categories && categories.length > 0;
|
|
56
|
-
},
|
|
57
|
-
message: "At least one category is required",
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
propertyId: {
|
|
61
|
-
type: String,
|
|
62
|
-
required: true,
|
|
63
|
-
index: true,
|
|
64
|
-
},
|
|
65
|
-
title: {
|
|
66
|
-
type: String,
|
|
67
|
-
required: true,
|
|
68
|
-
trim: true,
|
|
69
|
-
},
|
|
70
|
-
description: {
|
|
71
|
-
type: String,
|
|
72
|
-
required: true,
|
|
73
|
-
trim: true,
|
|
74
|
-
},
|
|
75
|
-
entityId: {
|
|
76
|
-
type: String,
|
|
77
|
-
index: true,
|
|
78
|
-
},
|
|
79
|
-
entityType: {
|
|
80
|
-
type: String,
|
|
81
|
-
enum: Object.values(EntityType),
|
|
82
|
-
required: true,
|
|
83
|
-
index: true,
|
|
84
|
-
},
|
|
85
|
-
severity: {
|
|
86
|
-
type: String,
|
|
87
|
-
enum: Object.values(AlertSeverity),
|
|
88
|
-
default: AlertSeverity.MEDIUM,
|
|
89
|
-
},
|
|
90
|
-
isRead: {
|
|
91
|
-
type: Boolean,
|
|
92
|
-
default: false,
|
|
93
|
-
index: true,
|
|
94
|
-
},
|
|
95
|
-
isActive: {
|
|
96
|
-
type: Boolean,
|
|
97
|
-
default: true,
|
|
98
|
-
index: true,
|
|
99
|
-
},
|
|
100
|
-
isDeleted: {
|
|
101
|
-
type: Boolean,
|
|
102
|
-
default: false,
|
|
103
|
-
},
|
|
104
|
-
snoozeUntil: {
|
|
105
|
-
type: Date,
|
|
106
|
-
},
|
|
107
|
-
createdBy: {
|
|
108
|
-
type: String,
|
|
109
|
-
},
|
|
110
|
-
updatedBy: {
|
|
111
|
-
type: String,
|
|
112
|
-
},
|
|
113
|
-
createdAt: {
|
|
114
|
-
type: Date,
|
|
115
|
-
default: Date.now,
|
|
116
|
-
},
|
|
117
|
-
updatedAt: {
|
|
118
|
-
type: Date,
|
|
119
|
-
default: Date.now,
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
timestamps: true,
|
|
124
|
-
collection: "dt_alerts",
|
|
125
|
-
}
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
// Compound indexes to match Prisma schema
|
|
129
|
-
AlertSchema.index({ propertyId: 1, isActive: 1, isRead: 1 });
|
|
130
|
-
AlertSchema.index({ entityId: 1, entityType: 1 });
|
|
131
|
-
AlertSchema.index({ createdAt: 1 });
|
|
132
|
-
|
|
133
|
-
// Pre-save middleware to update the updatedAt field
|
|
134
|
-
AlertSchema.pre("save", function (next) {
|
|
135
|
-
this.updatedAt = new Date();
|
|
136
|
-
next();
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// Pre-update middleware to update the updatedAt field
|
|
140
|
-
AlertSchema.pre(
|
|
141
|
-
["updateOne", "findOneAndUpdate", "updateMany"],
|
|
142
|
-
function (next) {
|
|
143
|
-
this.set({ updatedAt: new Date() });
|
|
144
|
-
next();
|
|
145
|
-
}
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
// Instance methods
|
|
149
|
-
AlertSchema.methods.markAsRead = function (updatedBy: string): void {
|
|
150
|
-
this.isRead = true;
|
|
151
|
-
this.updatedBy = updatedBy;
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
AlertSchema.methods.markAsUnread = function (updatedBy: string): void {
|
|
155
|
-
this.isRead = false;
|
|
156
|
-
this.updatedBy = updatedBy;
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
AlertSchema.methods.activate = function (updatedBy: string): void {
|
|
160
|
-
this.isActive = true;
|
|
161
|
-
this.updatedBy = updatedBy;
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
AlertSchema.methods.deactivate = function (updatedBy: string): void {
|
|
165
|
-
this.isActive = false;
|
|
166
|
-
this.updatedBy = updatedBy;
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
AlertSchema.methods.snooze = function (until: Date, updatedBy: string): void {
|
|
170
|
-
this.snoozeUntil = until;
|
|
171
|
-
this.updatedBy = updatedBy;
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
AlertSchema.methods.unsnooze = function (updatedBy: string): void {
|
|
175
|
-
this.snoozeUntil = undefined;
|
|
176
|
-
this.updatedBy = updatedBy;
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
// Static methods
|
|
180
|
-
AlertSchema.statics.findByProperty = function (
|
|
181
|
-
propertyId: string,
|
|
182
|
-
includeDeleted = false
|
|
183
|
-
) {
|
|
184
|
-
const query: any = { propertyId };
|
|
185
|
-
if (!includeDeleted) {
|
|
186
|
-
query.isDeleted = false;
|
|
187
|
-
}
|
|
188
|
-
return this.find(query).sort({ createdAt: -1 });
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
AlertSchema.statics.findByEntity = function (
|
|
192
|
-
entityId: string,
|
|
193
|
-
entityType: EntityType,
|
|
194
|
-
includeDeleted = false
|
|
195
|
-
) {
|
|
196
|
-
const query: any = { entityId, entityType };
|
|
197
|
-
if (!includeDeleted) {
|
|
198
|
-
query.isDeleted = false;
|
|
199
|
-
}
|
|
200
|
-
return this.find(query).sort({ createdAt: -1 });
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
AlertSchema.statics.findByCategory = function (
|
|
204
|
-
category: AlertCategory,
|
|
205
|
-
includeDeleted = false
|
|
206
|
-
) {
|
|
207
|
-
const query: any = { category: { $in: [category] } };
|
|
208
|
-
if (!includeDeleted) {
|
|
209
|
-
query.isDeleted = false;
|
|
210
|
-
}
|
|
211
|
-
return this.find(query).sort({ createdAt: -1 });
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
AlertSchema.statics.findBySeverity = function (
|
|
215
|
-
severity: AlertSeverity,
|
|
216
|
-
includeDeleted = false
|
|
217
|
-
) {
|
|
218
|
-
const query: any = { severity };
|
|
219
|
-
if (!includeDeleted) {
|
|
220
|
-
query.isDeleted = false;
|
|
221
|
-
}
|
|
222
|
-
return this.find(query).sort({ severity: -1, createdAt: -1 });
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
AlertSchema.statics.findActive = function (includeDeleted = false) {
|
|
226
|
-
const query: any = { isActive: true };
|
|
227
|
-
if (!includeDeleted) {
|
|
228
|
-
query.isDeleted = false;
|
|
229
|
-
}
|
|
230
|
-
return this.find(query).sort({ severity: -1, createdAt: -1 });
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
AlertSchema.statics.findUnread = function (includeDeleted = false) {
|
|
234
|
-
const query: any = { isRead: false };
|
|
235
|
-
if (!includeDeleted) {
|
|
236
|
-
query.isDeleted = false;
|
|
237
|
-
}
|
|
238
|
-
return this.find(query).sort({ severity: -1, createdAt: -1 });
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
AlertSchema.statics.findSnoozed = function (includeDeleted = false) {
|
|
242
|
-
const query: any = { snoozeUntil: { $exists: true, $ne: null } };
|
|
243
|
-
if (!includeDeleted) {
|
|
244
|
-
query.isDeleted = false;
|
|
245
|
-
}
|
|
246
|
-
return this.find(query).sort({ snoozeUntil: 1 });
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
AlertSchema.statics.findExpiredSnooze = function (includeDeleted = false) {
|
|
250
|
-
const query: any = {
|
|
251
|
-
snoozeUntil: { $lt: new Date() },
|
|
252
|
-
isActive: true,
|
|
253
|
-
};
|
|
254
|
-
if (!includeDeleted) {
|
|
255
|
-
query.isDeleted = false;
|
|
256
|
-
}
|
|
257
|
-
return this.find(query).sort({ snoozeUntil: 1 });
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
// Virtual for snooze status
|
|
261
|
-
AlertSchema.virtual("isSnoozed").get(function () {
|
|
262
|
-
return this.snoozeUntil && this.snoozeUntil > new Date();
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
AlertSchema.virtual("isSnoozeExpired").get(function () {
|
|
266
|
-
return this.snoozeUntil && this.snoozeUntil <= new Date();
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
// Ensure virtuals are serialized
|
|
270
|
-
AlertSchema.set("toJSON", { virtuals: true });
|
|
271
|
-
AlertSchema.set("toObject", { virtuals: true });
|
|
272
|
-
|
|
273
|
-
// Create and export the model
|
|
274
|
-
export const AlertModel = mongoose.model<IAlertDocument, IAlertModel>(
|
|
275
|
-
"Alert",
|
|
276
|
-
AlertSchema
|
|
277
|
-
);
|
|
278
|
-
|
|
279
|
-
// Export the schema for potential reuse
|
|
280
|
-
export { AlertSchema };
|
|
281
|
-
|
|
282
|
-
// Export types for external use
|
|
283
|
-
export type {
|
|
284
|
-
IAlertDocument,
|
|
285
|
-
CreateAlertData,
|
|
286
|
-
UpdateAlertData,
|
|
287
|
-
IAlertMethods,
|
|
288
|
-
IAlertModel,
|
|
289
|
-
};
|