@webtypen/webframez-core 0.3.60 → 0.3.61
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/Commands/QueueWorkerCommand.js +36 -3
- package/dist/ConsoleApplication.js +2 -0
- package/dist/Notifications/BaseNotificationsController.d.ts +18 -0
- package/dist/Notifications/BaseNotificationsController.js +177 -0
- package/dist/Notifications/Notification.d.ts +15 -0
- package/dist/Notifications/Notification.js +11 -0
- package/dist/Notifications/NotificationService.d.ts +86 -0
- package/dist/Notifications/NotificationService.js +371 -0
- package/dist/Notifications/NotificationsOutputChannel.d.ts +9 -0
- package/dist/Notifications/NotificationsOutputChannel.js +24 -0
- package/dist/Notifications/NotificationsOutputChannelsJob.d.ts +15 -0
- package/dist/Notifications/NotificationsOutputChannelsJob.js +245 -0
- package/dist/Queue/QueueJob.d.ts +5 -0
- package/dist/Queue/QueueJobsRegisty.d.ts +1 -0
- package/dist/Queue/QueueJobsRegisty.js +7 -0
- package/dist/WebApplication.js +2 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/package.json +1 -1
|
@@ -26,6 +26,7 @@ const ErrorHandler_1 = require("../ErrorHandling/ErrorHandler");
|
|
|
26
26
|
const BackupManager_1 = require("../Backup/BackupManager");
|
|
27
27
|
const WebframezHooks_1 = require("../Hooks/WebframezHooks");
|
|
28
28
|
const AutomationSchedule_1 = require("../Queue/AutomationSchedule");
|
|
29
|
+
const NotificationService_1 = require("../Notifications/NotificationService");
|
|
29
30
|
class QueueWorkerCommand extends ConsoleCommand_1.ConsoleCommand {
|
|
30
31
|
constructor() {
|
|
31
32
|
super(...arguments);
|
|
@@ -534,6 +535,7 @@ class QueueWorkerCommand extends ConsoleCommand_1.ConsoleCommand {
|
|
|
534
535
|
job_executions_count: job.executions.length,
|
|
535
536
|
};
|
|
536
537
|
let jobInstance = null;
|
|
538
|
+
let jobNotification = null;
|
|
537
539
|
const jobType = this.jobTypes && this.jobTypes[job.jobclass] ? this.jobTypes[job.jobclass] : null;
|
|
538
540
|
const jobOperationId = WebframezHooks_1.WebframezHooks.createOperationId("job");
|
|
539
541
|
yield WebframezHooks_1.WebframezHooks.emit("queue.job.start", {
|
|
@@ -546,6 +548,14 @@ class QueueWorkerCommand extends ConsoleCommand_1.ConsoleCommand {
|
|
|
546
548
|
},
|
|
547
549
|
});
|
|
548
550
|
try {
|
|
551
|
+
if (job.notification_queue_job) {
|
|
552
|
+
if (!job._notification) {
|
|
553
|
+
throw new Error(`Notification queue-job '${job._id.toString()}' has no _notification reference.`);
|
|
554
|
+
}
|
|
555
|
+
jobNotification = yield NotificationService_1.NotificationService.getNotification(job._notification);
|
|
556
|
+
job.notification = jobNotification;
|
|
557
|
+
yield NotificationService_1.NotificationService.setChangingStatus(jobNotification, "running");
|
|
558
|
+
}
|
|
549
559
|
if (!jobType) {
|
|
550
560
|
throw new Error(`QueueError: Invalid job-type ${job.jobclass}`);
|
|
551
561
|
}
|
|
@@ -558,11 +568,13 @@ class QueueWorkerCommand extends ConsoleCommand_1.ConsoleCommand {
|
|
|
558
568
|
job.executions[0].ended_at = endedAt;
|
|
559
569
|
job.executions[0].log = jobInstance.getLog();
|
|
560
570
|
job.executions[0].duration_ms = (0, moment_timezone_1.default)().diff((0, moment_timezone_1.default)(startedAt), "milliseconds");
|
|
561
|
-
const updateData = {};
|
|
562
571
|
if (result && typeof result === "object" && result.__execute_again) {
|
|
563
572
|
job.status = "pending";
|
|
564
|
-
job.
|
|
565
|
-
|
|
573
|
+
job.ended_at = null;
|
|
574
|
+
job.not_before = result.__execute_again;
|
|
575
|
+
}
|
|
576
|
+
if (jobNotification) {
|
|
577
|
+
yield NotificationService_1.NotificationService.setChangingStatus(jobNotification, job.status === "pending" ? "pending" : "success");
|
|
566
578
|
}
|
|
567
579
|
yield connection.client
|
|
568
580
|
.db(null)
|
|
@@ -571,6 +583,7 @@ class QueueWorkerCommand extends ConsoleCommand_1.ConsoleCommand {
|
|
|
571
583
|
$set: {
|
|
572
584
|
status: job.status,
|
|
573
585
|
ended_at: job.ended_at,
|
|
586
|
+
not_before: job.status === "pending" ? job.not_before : null,
|
|
574
587
|
executions: [...job.executions],
|
|
575
588
|
},
|
|
576
589
|
});
|
|
@@ -615,6 +628,26 @@ class QueueWorkerCommand extends ConsoleCommand_1.ConsoleCommand {
|
|
|
615
628
|
});
|
|
616
629
|
const errorMessage = e instanceof Error ? e.stack || e.message : String(e);
|
|
617
630
|
const endedAt = new Date();
|
|
631
|
+
if (jobNotification) {
|
|
632
|
+
try {
|
|
633
|
+
yield NotificationService_1.NotificationService.setChangingStatus(jobNotification, "error", errorMessage);
|
|
634
|
+
}
|
|
635
|
+
catch (notificationError) {
|
|
636
|
+
yield ErrorHandler_1.ErrorHandler.report(notificationError, {
|
|
637
|
+
scope: "job",
|
|
638
|
+
source: "queue.worker.notification.status",
|
|
639
|
+
job: {
|
|
640
|
+
id: job && job._id ? job._id.toString() : undefined,
|
|
641
|
+
number: job && job.number ? job.number : undefined,
|
|
642
|
+
jobclass: job && job.jobclass ? job.jobclass : undefined,
|
|
643
|
+
worker: this.workerKey,
|
|
644
|
+
},
|
|
645
|
+
metadata: {
|
|
646
|
+
notification: job._notification ? job._notification.toString() : null,
|
|
647
|
+
},
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
}
|
|
618
651
|
job.status = "failed";
|
|
619
652
|
job.ended_at = endedAt;
|
|
620
653
|
job.executions[0].status = "failed";
|
|
@@ -33,6 +33,7 @@ const BackupRestorePointsCommand_1 = require("./Commands/BackupRestorePointsComm
|
|
|
33
33
|
const BackupRunJob_1 = require("./Backup/Jobs/BackupRunJob");
|
|
34
34
|
const SystemCommands_1 = require("./Commands/SystemCommands");
|
|
35
35
|
const ModulesLoader_1 = require("./Modules/ModulesLoader");
|
|
36
|
+
const NotificationService_1 = require("./Notifications/NotificationService");
|
|
36
37
|
class ConsoleApplication {
|
|
37
38
|
constructor() {
|
|
38
39
|
this.modulesLoader = null;
|
|
@@ -62,6 +63,7 @@ class ConsoleApplication {
|
|
|
62
63
|
Config_1.Config.register(key, options.config[key]);
|
|
63
64
|
}
|
|
64
65
|
}
|
|
66
|
+
NotificationService_1.NotificationService.init();
|
|
65
67
|
if (options && options.errorHandler) {
|
|
66
68
|
if (Array.isArray(options.errorHandler)) {
|
|
67
69
|
ErrorHandler_1.ErrorHandler.setHandlers(options.errorHandler);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Controller } from "../Controller/Controller";
|
|
2
|
+
import { Request } from "../Router/Request";
|
|
3
|
+
import { Response } from "../Router/Response";
|
|
4
|
+
import { Notification } from "./Notification";
|
|
5
|
+
export declare class BaseNotificationsController extends Controller {
|
|
6
|
+
endpoint(req: Request, res: Response): Promise<Response>;
|
|
7
|
+
protected getUserId(req: Request): Promise<any>;
|
|
8
|
+
protected getNotificationReference(req: Request): any;
|
|
9
|
+
protected serializeNotification(notification: Notification): object;
|
|
10
|
+
private parsePaginationValue;
|
|
11
|
+
private unauthorized;
|
|
12
|
+
private notificationNotFound;
|
|
13
|
+
list(req: Request, res: Response): Promise<Response>;
|
|
14
|
+
details(req: Request, res: Response): Promise<Response>;
|
|
15
|
+
markOneAsRead(req: Request, res: Response): Promise<Response>;
|
|
16
|
+
markAllAsRead(req: Request, res: Response): Promise<Response>;
|
|
17
|
+
markAllAsViewed(req: Request, res: Response): Promise<Response>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.BaseNotificationsController = void 0;
|
|
13
|
+
const Controller_1 = require("../Controller/Controller");
|
|
14
|
+
const NotificationService_1 = require("./NotificationService");
|
|
15
|
+
class BaseNotificationsController extends Controller_1.Controller {
|
|
16
|
+
endpoint(req, res) {
|
|
17
|
+
var _a;
|
|
18
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
19
|
+
const requestType = (_a = req.body) === null || _a === void 0 ? void 0 : _a.__type;
|
|
20
|
+
if (requestType === "list") {
|
|
21
|
+
return yield this.list(req, res);
|
|
22
|
+
}
|
|
23
|
+
if (requestType === "details") {
|
|
24
|
+
return yield this.details(req, res);
|
|
25
|
+
}
|
|
26
|
+
if (requestType === "mark_one_as_read") {
|
|
27
|
+
return yield this.markOneAsRead(req, res);
|
|
28
|
+
}
|
|
29
|
+
if (requestType === "mark_all_as_read") {
|
|
30
|
+
return yield this.markAllAsRead(req, res);
|
|
31
|
+
}
|
|
32
|
+
if (requestType === "mark_all_as_viewed") {
|
|
33
|
+
return yield this.markAllAsViewed(req, res);
|
|
34
|
+
}
|
|
35
|
+
return res.status(400).send({
|
|
36
|
+
status: "error",
|
|
37
|
+
message: "Invalid notification request.",
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
getUserId(req) {
|
|
42
|
+
var _a, _b, _c, _d, _e;
|
|
43
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
44
|
+
const configuredResolver = (_a = NotificationService_1.NotificationService.notificationsConfig) === null || _a === void 0 ? void 0 : _a.getUserIdByRequest;
|
|
45
|
+
if (typeof configuredResolver === "function") {
|
|
46
|
+
return yield configuredResolver(req);
|
|
47
|
+
}
|
|
48
|
+
return (_e = (_c = (_b = req.user) === null || _b === void 0 ? void 0 : _b._id) !== null && _c !== void 0 ? _c : (_d = req.user) === null || _d === void 0 ? void 0 : _d.id) !== null && _e !== void 0 ? _e : null;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
getNotificationReference(req) {
|
|
52
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
53
|
+
return (_h = (_f = (_d = (_b = (_a = req.body) === null || _a === void 0 ? void 0 : _a._notification) !== null && _b !== void 0 ? _b : (_c = req.body) === null || _c === void 0 ? void 0 : _c.notification) !== null && _d !== void 0 ? _d : (_e = req.body) === null || _e === void 0 ? void 0 : _e._id) !== null && _f !== void 0 ? _f : (_g = req.body) === null || _g === void 0 ? void 0 : _g.id) !== null && _h !== void 0 ? _h : null;
|
|
54
|
+
}
|
|
55
|
+
serializeNotification(notification) {
|
|
56
|
+
return notification.toArray();
|
|
57
|
+
}
|
|
58
|
+
parsePaginationValue(value, fallback, maximum) {
|
|
59
|
+
const parsed = Number(value);
|
|
60
|
+
if (!Number.isFinite(parsed) || parsed < 0)
|
|
61
|
+
return fallback;
|
|
62
|
+
const integer = Math.floor(parsed);
|
|
63
|
+
return maximum !== undefined ? Math.min(integer, maximum) : integer;
|
|
64
|
+
}
|
|
65
|
+
unauthorized(res) {
|
|
66
|
+
return res.status(401).send({
|
|
67
|
+
status: "error",
|
|
68
|
+
message: "Authenticated user required.",
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
notificationNotFound(res) {
|
|
72
|
+
return res.status(404).send({
|
|
73
|
+
status: "error",
|
|
74
|
+
message: "Notification not found.",
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
list(req, res) {
|
|
78
|
+
var _a, _b;
|
|
79
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
80
|
+
const userId = yield this.getUserId(req);
|
|
81
|
+
if (!userId)
|
|
82
|
+
return this.unauthorized(res);
|
|
83
|
+
const offset = this.parsePaginationValue((_a = req.body) === null || _a === void 0 ? void 0 : _a.offset, 0);
|
|
84
|
+
const limit = this.parsePaginationValue((_b = req.body) === null || _b === void 0 ? void 0 : _b.limit, 10, 100) || 10;
|
|
85
|
+
const [loadedNotifications, unreadCount, unviewedCount] = yield Promise.all([
|
|
86
|
+
NotificationService_1.NotificationService.loadNotifications({
|
|
87
|
+
_user: userId,
|
|
88
|
+
offset,
|
|
89
|
+
limit: limit + 1,
|
|
90
|
+
request: req,
|
|
91
|
+
}),
|
|
92
|
+
NotificationService_1.NotificationService.getUserUnreadCount(userId, req),
|
|
93
|
+
NotificationService_1.NotificationService.getUserUnviewedCount(userId, req),
|
|
94
|
+
]);
|
|
95
|
+
const hasMore = loadedNotifications.length > limit;
|
|
96
|
+
const entries = loadedNotifications.slice(0, limit).map((notification) => this.serializeNotification(notification));
|
|
97
|
+
return res.send({
|
|
98
|
+
status: "success",
|
|
99
|
+
data: {
|
|
100
|
+
entries,
|
|
101
|
+
unread_count: unreadCount,
|
|
102
|
+
unviewed_count: unviewedCount,
|
|
103
|
+
has_more: hasMore,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
details(req, res) {
|
|
109
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
110
|
+
const userId = yield this.getUserId(req);
|
|
111
|
+
if (!userId)
|
|
112
|
+
return this.unauthorized(res);
|
|
113
|
+
const notificationRef = this.getNotificationReference(req);
|
|
114
|
+
if (!notificationRef)
|
|
115
|
+
return this.notificationNotFound(res);
|
|
116
|
+
const notification = yield NotificationService_1.NotificationService.getNotificationForUser(notificationRef, userId, req);
|
|
117
|
+
if (!notification)
|
|
118
|
+
return this.notificationNotFound(res);
|
|
119
|
+
return res.send({
|
|
120
|
+
status: "success",
|
|
121
|
+
data: {
|
|
122
|
+
entry: this.serializeNotification(notification),
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
markOneAsRead(req, res) {
|
|
128
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
129
|
+
const userId = yield this.getUserId(req);
|
|
130
|
+
if (!userId)
|
|
131
|
+
return this.unauthorized(res);
|
|
132
|
+
const notificationRef = this.getNotificationReference(req);
|
|
133
|
+
if (!notificationRef)
|
|
134
|
+
return this.notificationNotFound(res);
|
|
135
|
+
const notification = yield NotificationService_1.NotificationService.getNotificationForUser(notificationRef, userId, req);
|
|
136
|
+
if (!notification)
|
|
137
|
+
return this.notificationNotFound(res);
|
|
138
|
+
const updatedNotification = yield NotificationService_1.NotificationService.markAsRead(notification);
|
|
139
|
+
return res.send({
|
|
140
|
+
status: "success",
|
|
141
|
+
data: {
|
|
142
|
+
entry: this.serializeNotification(updatedNotification),
|
|
143
|
+
message: "Notification marked as read.",
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
markAllAsRead(req, res) {
|
|
149
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
150
|
+
const userId = yield this.getUserId(req);
|
|
151
|
+
if (!userId)
|
|
152
|
+
return this.unauthorized(res);
|
|
153
|
+
yield NotificationService_1.NotificationService.markAllAsRead(userId, req);
|
|
154
|
+
return res.send({
|
|
155
|
+
status: "success",
|
|
156
|
+
data: {
|
|
157
|
+
message: "All notifications marked as read.",
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
markAllAsViewed(req, res) {
|
|
163
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
164
|
+
const userId = yield this.getUserId(req);
|
|
165
|
+
if (!userId)
|
|
166
|
+
return this.unauthorized(res);
|
|
167
|
+
yield NotificationService_1.NotificationService.markAllAsViewed(userId, req);
|
|
168
|
+
return res.send({
|
|
169
|
+
status: "success",
|
|
170
|
+
data: {
|
|
171
|
+
message: "All notifications marked as viewed.",
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
exports.BaseNotificationsController = BaseNotificationsController;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ObjectId } from "mongodb";
|
|
2
|
+
import { Model } from "../Database/Model";
|
|
3
|
+
import type { NotificationChangingStatus, NotificationModes, NotificationReadStatus, NotificationViewStatus } from "./NotificationService";
|
|
4
|
+
export declare class Notification extends Model {
|
|
5
|
+
key?: string;
|
|
6
|
+
mode?: NotificationModes;
|
|
7
|
+
changing_status?: NotificationChangingStatus;
|
|
8
|
+
changing_error?: string | null;
|
|
9
|
+
_queue_job?: ObjectId;
|
|
10
|
+
read_status?: NotificationReadStatus;
|
|
11
|
+
read_at?: Date | null;
|
|
12
|
+
view_status?: NotificationViewStatus;
|
|
13
|
+
viewed_at?: Date | null;
|
|
14
|
+
__table: string;
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Notification = void 0;
|
|
4
|
+
const Model_1 = require("../Database/Model");
|
|
5
|
+
class Notification extends Model_1.Model {
|
|
6
|
+
constructor() {
|
|
7
|
+
super(...arguments);
|
|
8
|
+
this.__table = "notifications";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
exports.Notification = Notification;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { ObjectId } from "mongodb";
|
|
2
|
+
import { Request } from "../Router/Request";
|
|
3
|
+
import { Notification } from "./Notification";
|
|
4
|
+
export type NotificationModes = "fixed" | "changing";
|
|
5
|
+
export type NotificationReadStatus = "unread" | "read";
|
|
6
|
+
export type NotificationViewStatus = "unviewed" | "viewed";
|
|
7
|
+
export type NotificationChangingStatus = "pending" | "running" | "success" | "error";
|
|
8
|
+
export type NotificationOptions = {
|
|
9
|
+
showAt?: Date;
|
|
10
|
+
baseData?: {
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
};
|
|
13
|
+
_user: ObjectId | string;
|
|
14
|
+
};
|
|
15
|
+
export type NotificationPayload = {
|
|
16
|
+
[key: string]: any;
|
|
17
|
+
};
|
|
18
|
+
export type NotificationQueueJobAutomation = {
|
|
19
|
+
job: string;
|
|
20
|
+
check?: (notification: Notification) => boolean | Promise<boolean>;
|
|
21
|
+
};
|
|
22
|
+
export type NotificationDefinition = {
|
|
23
|
+
key: string;
|
|
24
|
+
mode?: NotificationModes;
|
|
25
|
+
baseData?: {
|
|
26
|
+
[key: string]: any;
|
|
27
|
+
};
|
|
28
|
+
payload?: {
|
|
29
|
+
[key: string]: any;
|
|
30
|
+
};
|
|
31
|
+
title?: string | null;
|
|
32
|
+
getTitle?: (notification: Notification) => Promise<String | null>;
|
|
33
|
+
description?: string | null;
|
|
34
|
+
getDescription?: (notification: Notification) => Promise<String | null>;
|
|
35
|
+
link?: string | null;
|
|
36
|
+
getLink?: (notification: Notification) => Promise<String | null>;
|
|
37
|
+
groupKey?: string | null;
|
|
38
|
+
getGroupKey?: (payload?: NotificationPayload, options?: NotificationOptions) => Promise<String | null>;
|
|
39
|
+
beforeSave?: (notification: Notification) => Promise<void>;
|
|
40
|
+
afterSave?: (notification: Notification) => Promise<void>;
|
|
41
|
+
queueJobAutomation?: NotificationQueueJobAutomation;
|
|
42
|
+
};
|
|
43
|
+
declare class NotificationServiceFacade {
|
|
44
|
+
registy: {
|
|
45
|
+
[key: string]: NotificationDefinition;
|
|
46
|
+
};
|
|
47
|
+
notificationsConfig: any;
|
|
48
|
+
init(): void;
|
|
49
|
+
getTypeOrFail(typeKey: string): NotificationDefinition;
|
|
50
|
+
private getErrorMessage;
|
|
51
|
+
private normalizeUserId;
|
|
52
|
+
private getLoadMatch;
|
|
53
|
+
private enrichNotification;
|
|
54
|
+
create(typeKey: string, payload: NotificationPayload, options?: NotificationOptions): Promise<Notification>;
|
|
55
|
+
getNotification(notificationRef: Notification | ObjectId | string | null): Promise<Notification>;
|
|
56
|
+
markAsRead(notification: Notification | ObjectId | string | null): Promise<Notification>;
|
|
57
|
+
markAsViewed(notification: Notification | ObjectId | string | null): Promise<Notification>;
|
|
58
|
+
markAllAsRead(_user: ObjectId | string, request?: Request): Promise<void>;
|
|
59
|
+
markAllAsViewed(_user: ObjectId | string, request?: Request): Promise<void>;
|
|
60
|
+
getNotificationForUser(notificationRef: Notification | ObjectId | string | null, _user: ObjectId | string, request?: Request): Promise<Notification | null>;
|
|
61
|
+
private countByStatus;
|
|
62
|
+
getUserUnreadCount(_user: ObjectId | string, request?: Request): Promise<number>;
|
|
63
|
+
getUserUnviewedCount(_user: ObjectId | string, request?: Request): Promise<number>;
|
|
64
|
+
countUnreadNotifications(options: {
|
|
65
|
+
_user: ObjectId | string;
|
|
66
|
+
request?: Request;
|
|
67
|
+
}): Promise<number>;
|
|
68
|
+
updateNotification(notification: Notification | ObjectId | string | null, updateData: {
|
|
69
|
+
payload?: NotificationPayload;
|
|
70
|
+
_user?: ObjectId | string;
|
|
71
|
+
show_at?: Date | null;
|
|
72
|
+
read_status?: NotificationReadStatus;
|
|
73
|
+
read_at?: Date | null;
|
|
74
|
+
view_status?: NotificationViewStatus;
|
|
75
|
+
viewed_at?: Date | null;
|
|
76
|
+
}): Promise<Notification>;
|
|
77
|
+
setChangingStatus(notification: Notification | ObjectId | string | null, status: NotificationChangingStatus, error?: string | null): Promise<Notification>;
|
|
78
|
+
loadNotifications(options?: {
|
|
79
|
+
_user: ObjectId | string;
|
|
80
|
+
offset?: number;
|
|
81
|
+
limit?: number;
|
|
82
|
+
request?: Request;
|
|
83
|
+
}): Promise<Notification[]>;
|
|
84
|
+
}
|
|
85
|
+
export declare const NotificationService: NotificationServiceFacade;
|
|
86
|
+
export {};
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.NotificationService = void 0;
|
|
13
|
+
const mongodb_1 = require("mongodb");
|
|
14
|
+
const Notification_1 = require("./Notification");
|
|
15
|
+
const Config_1 = require("../Config");
|
|
16
|
+
const DBConnection_1 = require("../Database/DBConnection");
|
|
17
|
+
const QueueJobsRegisty_1 = require("../Queue/QueueJobsRegisty");
|
|
18
|
+
class NotificationServiceFacade {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.registy = {};
|
|
21
|
+
this.notificationsConfig = {};
|
|
22
|
+
}
|
|
23
|
+
init() {
|
|
24
|
+
this.registy = {};
|
|
25
|
+
this.notificationsConfig = Config_1.Config.get("notifications") || {};
|
|
26
|
+
if (Array.isArray(this.notificationsConfig.types)) {
|
|
27
|
+
for (let t of this.notificationsConfig.types) {
|
|
28
|
+
this.registy[t.key] = t;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
getTypeOrFail(typeKey) {
|
|
33
|
+
if (this.registy[typeKey]) {
|
|
34
|
+
return this.registy[typeKey];
|
|
35
|
+
}
|
|
36
|
+
throw new Error("Invalid notification-type '" + typeKey + "' ...");
|
|
37
|
+
}
|
|
38
|
+
getErrorMessage(error) {
|
|
39
|
+
if ((error === null || error === void 0 ? void 0 : error.message) && error.message.toString().trim() !== "") {
|
|
40
|
+
return error.message.toString();
|
|
41
|
+
}
|
|
42
|
+
if (error && error.toString().trim() !== "") {
|
|
43
|
+
return error.toString();
|
|
44
|
+
}
|
|
45
|
+
return "Unknown notification queue-job error";
|
|
46
|
+
}
|
|
47
|
+
normalizeUserId(_user) {
|
|
48
|
+
return typeof _user === "string" && _user.length === 24 && mongodb_1.ObjectId.isValid(_user) ? new mongodb_1.ObjectId(_user) : _user;
|
|
49
|
+
}
|
|
50
|
+
getLoadMatch(options) {
|
|
51
|
+
var _a;
|
|
52
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
53
|
+
const additionalMatch = ((_a = this.notificationsConfig) === null || _a === void 0 ? void 0 : _a.onLoadNotificationsMatch)
|
|
54
|
+
? yield this.notificationsConfig.onLoadNotificationsMatch(options)
|
|
55
|
+
: {};
|
|
56
|
+
return Object.assign(Object.assign({}, (additionalMatch || {})), { _user: this.normalizeUserId(options._user), key: { $in: Object.keys(this.registy) }, show_at: { $lte: new Date() } });
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
enrichNotification(notification) {
|
|
60
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
61
|
+
const notType = (notification === null || notification === void 0 ? void 0 : notification.key) ? this.registy[notification.key] : null;
|
|
62
|
+
if (!notType)
|
|
63
|
+
return null;
|
|
64
|
+
notification.title = notType.getTitle
|
|
65
|
+
? yield notType.getTitle(notification)
|
|
66
|
+
: notType.title !== undefined
|
|
67
|
+
? notType.title
|
|
68
|
+
: null;
|
|
69
|
+
notification.description = notType.getDescription
|
|
70
|
+
? yield notType.getDescription(notification)
|
|
71
|
+
: notType.description !== undefined
|
|
72
|
+
? notType.description
|
|
73
|
+
: null;
|
|
74
|
+
notification.link = notType.getLink
|
|
75
|
+
? yield notType.getLink(notification)
|
|
76
|
+
: notType.link !== undefined
|
|
77
|
+
? notType.link
|
|
78
|
+
: null;
|
|
79
|
+
return notification;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
create(typeKey, payload, options) {
|
|
83
|
+
var _a, _b;
|
|
84
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
85
|
+
const notType = this.getTypeOrFail(typeKey);
|
|
86
|
+
const notificationMode = notType.mode || "fixed";
|
|
87
|
+
if (notType.queueJobAutomation && notificationMode !== "changing") {
|
|
88
|
+
throw new Error(`Notification-type '${notType.key}' can only use queueJobAutomation in changing mode.`);
|
|
89
|
+
}
|
|
90
|
+
const notification = new Notification_1.Notification();
|
|
91
|
+
notification.key = notType.key;
|
|
92
|
+
notification.mode = notificationMode;
|
|
93
|
+
notification.group_key = notType.getGroupKey ? yield notType.getGroupKey(payload, options) : null;
|
|
94
|
+
if (options === null || options === void 0 ? void 0 : options.baseData) {
|
|
95
|
+
for (let key in options.baseData) {
|
|
96
|
+
notification[key] = options.baseData[key];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (notification.payload) {
|
|
100
|
+
notification.payload = Object.assign(Object.assign({}, notification.payload), payload);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
notification.payload = payload;
|
|
104
|
+
}
|
|
105
|
+
notification.key = notType.key;
|
|
106
|
+
notification.mode = notificationMode;
|
|
107
|
+
notification.created_at = new Date();
|
|
108
|
+
notification.read_status = "unread";
|
|
109
|
+
notification.read_at = null;
|
|
110
|
+
notification.view_status = "unviewed";
|
|
111
|
+
notification.viewed_at = null;
|
|
112
|
+
notification.show_at = (options === null || options === void 0 ? void 0 : options.showAt) || new Date();
|
|
113
|
+
if (notification.mode === "changing") {
|
|
114
|
+
notification.changing_status = "pending";
|
|
115
|
+
notification.changing_error = null;
|
|
116
|
+
}
|
|
117
|
+
if (options === null || options === void 0 ? void 0 : options._user) {
|
|
118
|
+
if (typeof options._user === "string" && options._user.length === 24) {
|
|
119
|
+
notification._user = new mongodb_1.ObjectId(options._user);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
notification._user = options._user;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if ((_a = this.notificationsConfig) === null || _a === void 0 ? void 0 : _a.beforeNotificationSave) {
|
|
126
|
+
yield this.notificationsConfig.beforeNotificationSave(notification);
|
|
127
|
+
}
|
|
128
|
+
if (notType.beforeSave) {
|
|
129
|
+
yield notType.beforeSave(notification);
|
|
130
|
+
}
|
|
131
|
+
yield notification.save();
|
|
132
|
+
if (notType.queueJobAutomation) {
|
|
133
|
+
try {
|
|
134
|
+
const shouldCreateJob = notType.queueJobAutomation.check
|
|
135
|
+
? yield notType.queueJobAutomation.check(notification)
|
|
136
|
+
: true;
|
|
137
|
+
if (!shouldCreateJob) {
|
|
138
|
+
yield this.setChangingStatus(notification, "success");
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
const queueJobType = QueueJobsRegisty_1.QueueJobsRegisty.getJobOrFail(notType.queueJobAutomation.job);
|
|
142
|
+
const queueJob = yield queueJobType.create({
|
|
143
|
+
status: "initializing",
|
|
144
|
+
notification_queue_job: true,
|
|
145
|
+
_notification: notification._id,
|
|
146
|
+
});
|
|
147
|
+
notification._queue_job = queueJob._id;
|
|
148
|
+
yield notification.save();
|
|
149
|
+
queueJob.status = "pending";
|
|
150
|
+
yield queueJob.save();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
yield this.setChangingStatus(notification, "error", this.getErrorMessage(error));
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if ((_b = this.notificationsConfig) === null || _b === void 0 ? void 0 : _b.afterNotificationSave) {
|
|
159
|
+
yield this.notificationsConfig.afterNotificationSave(notification);
|
|
160
|
+
}
|
|
161
|
+
if (notType.afterSave) {
|
|
162
|
+
yield notType.afterSave(notification);
|
|
163
|
+
}
|
|
164
|
+
return notification;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
getNotification(notificationRef) {
|
|
168
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
169
|
+
if (notificationRef instanceof Notification_1.Notification) {
|
|
170
|
+
return notificationRef;
|
|
171
|
+
}
|
|
172
|
+
if (typeof notificationRef === "string" || notificationRef instanceof mongodb_1.ObjectId) {
|
|
173
|
+
const loadNotification = yield Notification_1.Notification.where("_id", "=", yield Notification_1.Notification.objectId(notificationRef)).first();
|
|
174
|
+
if (loadNotification) {
|
|
175
|
+
return loadNotification;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
throw new Error("Invalid notification type provided: " + (notificationRef === null || notificationRef === void 0 ? void 0 : notificationRef.toString()));
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
markAsRead(notification) {
|
|
182
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
183
|
+
notification = yield this.getNotification(notification);
|
|
184
|
+
if (notification.read_status === "read" && notification.view_status === "viewed") {
|
|
185
|
+
return notification;
|
|
186
|
+
}
|
|
187
|
+
const now = new Date();
|
|
188
|
+
notification.read_at = notification.read_at || now;
|
|
189
|
+
notification.read_status = "read";
|
|
190
|
+
notification.viewed_at = notification.viewed_at || now;
|
|
191
|
+
notification.view_status = "viewed";
|
|
192
|
+
yield notification.save();
|
|
193
|
+
return notification;
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
markAsViewed(notification) {
|
|
197
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
198
|
+
notification = yield this.getNotification(notification);
|
|
199
|
+
if (notification.view_status === "viewed") {
|
|
200
|
+
return notification;
|
|
201
|
+
}
|
|
202
|
+
notification.viewed_at = new Date();
|
|
203
|
+
notification.view_status = "viewed";
|
|
204
|
+
yield notification.save();
|
|
205
|
+
return notification;
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
markAllAsRead(_user, request) {
|
|
209
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
210
|
+
const connection = yield DBConnection_1.DBConnection.getConnection();
|
|
211
|
+
const now = new Date();
|
|
212
|
+
yield connection.client
|
|
213
|
+
.db(null)
|
|
214
|
+
.collection(new Notification_1.Notification().__table)
|
|
215
|
+
.updateMany(Object.assign(Object.assign({}, (yield this.getLoadMatch({ _user, request }))), { $or: [{ read_status: "unread" }, { view_status: "unviewed" }] }), [
|
|
216
|
+
{
|
|
217
|
+
$set: {
|
|
218
|
+
read_at: { $ifNull: ["$read_at", now] },
|
|
219
|
+
read_status: "read",
|
|
220
|
+
viewed_at: { $ifNull: ["$viewed_at", now] },
|
|
221
|
+
view_status: "viewed",
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
]);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
markAllAsViewed(_user, request) {
|
|
228
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
229
|
+
const connection = yield DBConnection_1.DBConnection.getConnection();
|
|
230
|
+
yield connection.client
|
|
231
|
+
.db(null)
|
|
232
|
+
.collection(new Notification_1.Notification().__table)
|
|
233
|
+
.updateMany(Object.assign(Object.assign({}, (yield this.getLoadMatch({ _user, request }))), { view_status: "unviewed" }), {
|
|
234
|
+
$set: {
|
|
235
|
+
viewed_at: new Date(),
|
|
236
|
+
view_status: "viewed",
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
getNotificationForUser(notificationRef, _user, request) {
|
|
242
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
243
|
+
const notificationId = notificationRef instanceof Notification_1.Notification ? notificationRef._id : notificationRef;
|
|
244
|
+
if (!notificationId)
|
|
245
|
+
return null;
|
|
246
|
+
let objectId;
|
|
247
|
+
if (notificationId instanceof mongodb_1.ObjectId) {
|
|
248
|
+
objectId = notificationId;
|
|
249
|
+
}
|
|
250
|
+
else if (typeof notificationId === "string" && mongodb_1.ObjectId.isValid(notificationId)) {
|
|
251
|
+
objectId = new mongodb_1.ObjectId(notificationId);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
const notifications = yield Notification_1.Notification.aggregate([
|
|
257
|
+
{
|
|
258
|
+
$match: Object.assign(Object.assign({}, (yield this.getLoadMatch({ _user, request }))), { _id: objectId }),
|
|
259
|
+
},
|
|
260
|
+
{ $limit: 1 },
|
|
261
|
+
]);
|
|
262
|
+
if (!notifications || notifications.length === 0)
|
|
263
|
+
return null;
|
|
264
|
+
const notification = DBConnection_1.DBConnection.mapDataToModel(Notification_1.Notification, notifications[0]);
|
|
265
|
+
return yield this.enrichNotification(notification);
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
countByStatus(options, match) {
|
|
269
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
270
|
+
const result = yield Notification_1.Notification.aggregate([
|
|
271
|
+
{
|
|
272
|
+
$match: Object.assign(Object.assign({}, (yield this.getLoadMatch(options))), match),
|
|
273
|
+
},
|
|
274
|
+
{ $count: "count" },
|
|
275
|
+
]);
|
|
276
|
+
return result && result[0] && typeof result[0].count === "number" ? result[0].count : 0;
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
getUserUnreadCount(_user, request) {
|
|
280
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
281
|
+
return yield this.countByStatus({ _user, request }, { read_status: "unread" });
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
getUserUnviewedCount(_user, request) {
|
|
285
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
286
|
+
return yield this.countByStatus({ _user, request }, { view_status: "unviewed" });
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
countUnreadNotifications(options) {
|
|
290
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
291
|
+
return yield this.getUserUnreadCount(options._user, options.request);
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
updateNotification(notification, updateData) {
|
|
295
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
296
|
+
notification = yield this.getNotification(notification);
|
|
297
|
+
if (updateData.payload) {
|
|
298
|
+
notification.payload = Object.assign(Object.assign({}, notification.payload), updateData.payload);
|
|
299
|
+
}
|
|
300
|
+
if (updateData._user !== undefined) {
|
|
301
|
+
if (typeof updateData._user === "string" && updateData._user.length === 24) {
|
|
302
|
+
notification._user = new mongodb_1.ObjectId(updateData._user);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
notification._user = updateData._user;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (updateData.show_at !== undefined) {
|
|
309
|
+
notification.show_at = updateData.show_at;
|
|
310
|
+
}
|
|
311
|
+
if (updateData.read_status !== undefined) {
|
|
312
|
+
notification.read_status = updateData.read_status;
|
|
313
|
+
}
|
|
314
|
+
if (updateData.read_at !== undefined) {
|
|
315
|
+
notification.read_at = updateData.read_at;
|
|
316
|
+
}
|
|
317
|
+
if (updateData.view_status !== undefined) {
|
|
318
|
+
notification.view_status = updateData.view_status;
|
|
319
|
+
}
|
|
320
|
+
if (updateData.viewed_at !== undefined) {
|
|
321
|
+
notification.viewed_at = updateData.viewed_at;
|
|
322
|
+
}
|
|
323
|
+
yield notification.save();
|
|
324
|
+
return notification;
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
setChangingStatus(notification, status, error) {
|
|
328
|
+
var _a;
|
|
329
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
330
|
+
notification = yield this.getNotification(notification);
|
|
331
|
+
if (notification.mode !== "changing") {
|
|
332
|
+
throw new Error(`Notification '${(_a = notification._id) === null || _a === void 0 ? void 0 : _a.toString()}' is not in changing mode.`);
|
|
333
|
+
}
|
|
334
|
+
notification.changing_status = status;
|
|
335
|
+
notification.changing_error = status === "error" ? error || "Unknown notification error" : null;
|
|
336
|
+
yield notification.save();
|
|
337
|
+
return notification;
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
loadNotifications(options) {
|
|
341
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
342
|
+
if (!(options === null || options === void 0 ? void 0 : options._user))
|
|
343
|
+
return [];
|
|
344
|
+
const notifications = yield Notification_1.Notification.aggregate([
|
|
345
|
+
{
|
|
346
|
+
$match: yield this.getLoadMatch(options),
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
$sort: { show_at: -1 },
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
$skip: (options === null || options === void 0 ? void 0 : options.offset) || 0,
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
$limit: (options === null || options === void 0 ? void 0 : options.limit) || 10,
|
|
356
|
+
},
|
|
357
|
+
]);
|
|
358
|
+
if (!notifications || notifications.length === 0)
|
|
359
|
+
return [];
|
|
360
|
+
const out = [];
|
|
361
|
+
for (const notificationData of notifications) {
|
|
362
|
+
const el = DBConnection_1.DBConnection.mapDataToModel(Notification_1.Notification, notificationData);
|
|
363
|
+
const enrichedNotification = yield this.enrichNotification(el);
|
|
364
|
+
if (enrichedNotification)
|
|
365
|
+
out.push(enrichedNotification);
|
|
366
|
+
}
|
|
367
|
+
return out;
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
exports.NotificationService = new NotificationServiceFacade();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.NotificationOutputChannel = void 0;
|
|
13
|
+
class NotificationOutputChannel {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.key = "example-output-channel";
|
|
16
|
+
this.activated = false;
|
|
17
|
+
}
|
|
18
|
+
handle(notification) {
|
|
19
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
20
|
+
return { status: true };
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.NotificationOutputChannel = NotificationOutputChannel;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BaseQueueJob } from "../Queue/BaseQueueJob";
|
|
2
|
+
export declare class NotificationsOutputChannelsJob extends BaseQueueJob {
|
|
3
|
+
static title: string;
|
|
4
|
+
attempts: number;
|
|
5
|
+
perAttempt: number;
|
|
6
|
+
private positiveNumber;
|
|
7
|
+
private getErrorMessage;
|
|
8
|
+
private getActiveChannels;
|
|
9
|
+
private getChannelResults;
|
|
10
|
+
private hasSuccessfulDelivery;
|
|
11
|
+
private claimNotification;
|
|
12
|
+
private saveChannelResult;
|
|
13
|
+
private deliverNotification;
|
|
14
|
+
handle(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.NotificationsOutputChannelsJob = void 0;
|
|
13
|
+
const mongodb_1 = require("mongodb");
|
|
14
|
+
const Config_1 = require("../Config");
|
|
15
|
+
const DBConnection_1 = require("../Database/DBConnection");
|
|
16
|
+
const BaseQueueJob_1 = require("../Queue/BaseQueueJob");
|
|
17
|
+
const Notification_1 = require("./Notification");
|
|
18
|
+
const NotificationService_1 = require("./NotificationService");
|
|
19
|
+
class NotificationsOutputChannelsJob extends BaseQueueJob_1.BaseQueueJob {
|
|
20
|
+
constructor() {
|
|
21
|
+
super(...arguments);
|
|
22
|
+
this.attempts = 10;
|
|
23
|
+
this.perAttempt = 50;
|
|
24
|
+
}
|
|
25
|
+
positiveNumber(value, fallback) {
|
|
26
|
+
const parsed = Number(value);
|
|
27
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
28
|
+
}
|
|
29
|
+
getErrorMessage(error) {
|
|
30
|
+
if ((error === null || error === void 0 ? void 0 : error.message) && error.message.toString().trim() !== "") {
|
|
31
|
+
return error.message.toString();
|
|
32
|
+
}
|
|
33
|
+
if (error && error.toString().trim() !== "") {
|
|
34
|
+
return error.toString();
|
|
35
|
+
}
|
|
36
|
+
return "Failed without exception";
|
|
37
|
+
}
|
|
38
|
+
getActiveChannels(notificationsConfig) {
|
|
39
|
+
const channels = [];
|
|
40
|
+
const registeredKeys = new Set();
|
|
41
|
+
for (const channel of notificationsConfig.output_channels) {
|
|
42
|
+
if (!channel || !channel.key || !channel.is_active || !channel.driver)
|
|
43
|
+
continue;
|
|
44
|
+
if (registeredKeys.has(channel.key)) {
|
|
45
|
+
throw new Error(`Duplicate notification output-channel key '${channel.key}'.`);
|
|
46
|
+
}
|
|
47
|
+
const driver = new channel.driver();
|
|
48
|
+
if (!driver || !driver.activated)
|
|
49
|
+
continue;
|
|
50
|
+
registeredKeys.add(channel.key);
|
|
51
|
+
channels.push({ key: channel.key, driver });
|
|
52
|
+
}
|
|
53
|
+
return channels;
|
|
54
|
+
}
|
|
55
|
+
getChannelResults(notification, channelKey) {
|
|
56
|
+
if (!Array.isArray(notification.output_channels_result))
|
|
57
|
+
return [];
|
|
58
|
+
return notification.output_channels_result.filter((result) => (result === null || result === void 0 ? void 0 : result.channel) === channelKey);
|
|
59
|
+
}
|
|
60
|
+
hasSuccessfulDelivery(notification, channelKey) {
|
|
61
|
+
return this.getChannelResults(notification, channelKey).some((result) => result.status === "success");
|
|
62
|
+
}
|
|
63
|
+
claimNotification(collection, claimTimeoutMinutes) {
|
|
64
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
65
|
+
const now = new Date();
|
|
66
|
+
const staleClaimBefore = new Date(now.getTime() - claimTimeoutMinutes * 60 * 1000);
|
|
67
|
+
const claimToken = new mongodb_1.ObjectId().toHexString();
|
|
68
|
+
const claimResult = yield collection.findOneAndUpdate({
|
|
69
|
+
key: { $ne: null },
|
|
70
|
+
show_at: { $lte: now },
|
|
71
|
+
output_channels_finished_at: null,
|
|
72
|
+
$and: [
|
|
73
|
+
{
|
|
74
|
+
$or: [
|
|
75
|
+
{ output_channels_next_attempt_at: null },
|
|
76
|
+
{ output_channels_next_attempt_at: { $lte: now } },
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
$or: [
|
|
81
|
+
{ output_channels_claimed_at: null },
|
|
82
|
+
{ output_channels_claimed_at: { $lte: staleClaimBefore } },
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
$or: [
|
|
87
|
+
{ mode: null },
|
|
88
|
+
{ mode: "fixed" },
|
|
89
|
+
{ mode: "changing", changing_status: { $in: ["success", "error"] } },
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
}, {
|
|
94
|
+
$set: {
|
|
95
|
+
output_channels_status: "processing",
|
|
96
|
+
output_channels_claimed_at: now,
|
|
97
|
+
output_channels_claim_token: claimToken,
|
|
98
|
+
output_channels_handled_at: now,
|
|
99
|
+
},
|
|
100
|
+
$inc: { output_channels_attempts: 1 },
|
|
101
|
+
}, {
|
|
102
|
+
sort: { show_at: 1, created_at: 1 },
|
|
103
|
+
returnDocument: "after",
|
|
104
|
+
});
|
|
105
|
+
const notificationData = claimResult && claimResult._id
|
|
106
|
+
? claimResult
|
|
107
|
+
: (claimResult === null || claimResult === void 0 ? void 0 : claimResult.value) && claimResult.value._id
|
|
108
|
+
? claimResult.value
|
|
109
|
+
: null;
|
|
110
|
+
if (!notificationData)
|
|
111
|
+
return null;
|
|
112
|
+
return DBConnection_1.DBConnection.mapDataToModel(Notification_1.Notification, notificationData);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
saveChannelResult(collection, notification, result) {
|
|
116
|
+
var _a;
|
|
117
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
118
|
+
const updateResult = yield collection.updateOne({
|
|
119
|
+
_id: notification._id,
|
|
120
|
+
output_channels_claim_token: notification.output_channels_claim_token,
|
|
121
|
+
}, {
|
|
122
|
+
$push: { output_channels_result: result },
|
|
123
|
+
});
|
|
124
|
+
if ((updateResult === null || updateResult === void 0 ? void 0 : updateResult.matchedCount) === 0) {
|
|
125
|
+
throw new Error(`Lost delivery claim for notification '${(_a = notification._id) === null || _a === void 0 ? void 0 : _a.toString()}'.`);
|
|
126
|
+
}
|
|
127
|
+
if (!Array.isArray(notification.output_channels_result)) {
|
|
128
|
+
notification.output_channels_result = [];
|
|
129
|
+
}
|
|
130
|
+
notification.output_channels_result.push(result);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
deliverNotification(collection, notification, channels, maxChannelAttempts, retryDelayMinutes) {
|
|
134
|
+
var _a;
|
|
135
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
136
|
+
const notType = notification.key ? NotificationService_1.NotificationService.registy[notification.key] : null;
|
|
137
|
+
if (!notType) {
|
|
138
|
+
yield collection.updateOne({
|
|
139
|
+
_id: notification._id,
|
|
140
|
+
output_channels_claim_token: notification.output_channels_claim_token,
|
|
141
|
+
}, {
|
|
142
|
+
$set: {
|
|
143
|
+
output_channels_status: "failed",
|
|
144
|
+
output_channels_error: "Invalid notification-type: " + notification.key,
|
|
145
|
+
output_channels_finished_at: new Date(),
|
|
146
|
+
output_channels_claimed_at: null,
|
|
147
|
+
output_channels_claim_token: null,
|
|
148
|
+
output_channels_next_attempt_at: null,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (!Array.isArray(notification.output_channels_result)) {
|
|
154
|
+
notification.output_channels_result = [];
|
|
155
|
+
}
|
|
156
|
+
for (const channel of channels) {
|
|
157
|
+
if (this.hasSuccessfulDelivery(notification, channel.key))
|
|
158
|
+
continue;
|
|
159
|
+
const previousAttempts = this.getChannelResults(notification, channel.key).length;
|
|
160
|
+
if (previousAttempts >= maxChannelAttempts)
|
|
161
|
+
continue;
|
|
162
|
+
let result;
|
|
163
|
+
try {
|
|
164
|
+
const status = yield channel.driver.handle(notification);
|
|
165
|
+
if (!(status === null || status === void 0 ? void 0 : status.status)) {
|
|
166
|
+
result = {
|
|
167
|
+
channel: channel.key,
|
|
168
|
+
date: new Date(),
|
|
169
|
+
attempt: previousAttempts + 1,
|
|
170
|
+
status: "error",
|
|
171
|
+
error_message: "Output-channel returned status false.",
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
const payload = status.payload;
|
|
176
|
+
result = Object.assign(Object.assign({}, (payload && typeof payload === "object" && !Array.isArray(payload)
|
|
177
|
+
? payload
|
|
178
|
+
: payload !== undefined
|
|
179
|
+
? { payload }
|
|
180
|
+
: {})), { channel: channel.key, date: new Date(), attempt: previousAttempts + 1, status: "success" });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
result = {
|
|
185
|
+
channel: channel.key,
|
|
186
|
+
date: new Date(),
|
|
187
|
+
attempt: previousAttempts + 1,
|
|
188
|
+
status: "error",
|
|
189
|
+
error_message: this.getErrorMessage(error),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
yield this.saveChannelResult(collection, notification, result);
|
|
193
|
+
}
|
|
194
|
+
const failedChannels = channels.filter((channel) => !this.hasSuccessfulDelivery(notification, channel.key));
|
|
195
|
+
const retryableChannels = failedChannels.filter((channel) => this.getChannelResults(notification, channel.key).length < maxChannelAttempts);
|
|
196
|
+
const now = new Date();
|
|
197
|
+
const isSuccessful = failedChannels.length === 0;
|
|
198
|
+
const isTerminalFailure = !isSuccessful && retryableChannels.length === 0;
|
|
199
|
+
const outputStatus = isSuccessful ? "sent" : isTerminalFailure ? "failed" : "retry_pending";
|
|
200
|
+
const errorMessage = failedChannels.length > 0 ? `Failed output-channels: ${failedChannels.map((c) => c.key).join(", ")}` : null;
|
|
201
|
+
const updateResult = yield collection.updateOne({
|
|
202
|
+
_id: notification._id,
|
|
203
|
+
output_channels_claim_token: notification.output_channels_claim_token,
|
|
204
|
+
}, {
|
|
205
|
+
$set: {
|
|
206
|
+
output_channels_status: outputStatus,
|
|
207
|
+
output_channels_error: errorMessage,
|
|
208
|
+
output_channels_finished_at: isSuccessful || isTerminalFailure ? now : null,
|
|
209
|
+
output_channels_next_attempt_at: isSuccessful || isTerminalFailure ? null : new Date(now.getTime() + retryDelayMinutes * 60 * 1000),
|
|
210
|
+
output_channels_claimed_at: null,
|
|
211
|
+
output_channels_claim_token: null,
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
if ((updateResult === null || updateResult === void 0 ? void 0 : updateResult.matchedCount) === 0) {
|
|
215
|
+
throw new Error(`Lost delivery claim for notification '${(_a = notification._id) === null || _a === void 0 ? void 0 : _a.toString()}'.`);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
handle() {
|
|
220
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
221
|
+
const notificationsConfig = Config_1.Config.get("notifications");
|
|
222
|
+
if (!(notificationsConfig === null || notificationsConfig === void 0 ? void 0 : notificationsConfig.output_channels) || !Array.isArray(notificationsConfig.output_channels)) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const channels = this.getActiveChannels(notificationsConfig);
|
|
226
|
+
if (channels.length === 0)
|
|
227
|
+
return;
|
|
228
|
+
const deliveryConfig = notificationsConfig.output_channels_delivery || {};
|
|
229
|
+
const maxChannelAttempts = this.positiveNumber(deliveryConfig.max_attempts, 3);
|
|
230
|
+
const retryDelayMinutes = this.positiveNumber(deliveryConfig.retry_delay_minutes, 5);
|
|
231
|
+
const claimTimeoutMinutes = this.positiveNumber(deliveryConfig.claim_timeout_minutes, 30);
|
|
232
|
+
const connection = yield DBConnection_1.DBConnection.getConnection();
|
|
233
|
+
const collection = connection.client.db(null).collection(new Notification_1.Notification().__table);
|
|
234
|
+
const maxNotifications = this.attempts * this.perAttempt;
|
|
235
|
+
for (let handledNotifications = 0; handledNotifications < maxNotifications; handledNotifications++) {
|
|
236
|
+
const notification = yield this.claimNotification(collection, claimTimeoutMinutes);
|
|
237
|
+
if (!notification)
|
|
238
|
+
break;
|
|
239
|
+
yield this.deliverNotification(collection, notification, channels, maxChannelAttempts, retryDelayMinutes);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
exports.NotificationsOutputChannelsJob = NotificationsOutputChannelsJob;
|
|
245
|
+
NotificationsOutputChannelsJob.title = "NotificationsOutputChannelsJob";
|
package/dist/Queue/QueueJob.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
import type { ObjectId } from "mongodb";
|
|
1
2
|
import { Model } from "../Database/Model";
|
|
3
|
+
import type { Notification } from "../Notifications/Notification";
|
|
2
4
|
export declare class QueueJob extends Model {
|
|
5
|
+
notification_queue_job?: boolean;
|
|
6
|
+
_notification?: ObjectId;
|
|
7
|
+
notification?: Notification;
|
|
3
8
|
__table: string;
|
|
4
9
|
}
|
|
@@ -10,6 +10,13 @@ class QueueJobsRegistyFacade {
|
|
|
10
10
|
getJobs() {
|
|
11
11
|
return this.jobs;
|
|
12
12
|
}
|
|
13
|
+
getJobOrFail(name) {
|
|
14
|
+
const job = this.jobs.find((entry) => typeof entry === "function" && entry.name === name && typeof entry.create === "function");
|
|
15
|
+
if (!job) {
|
|
16
|
+
throw new Error(`Queue job '${name}' is not registered.`);
|
|
17
|
+
}
|
|
18
|
+
return job;
|
|
19
|
+
}
|
|
13
20
|
getJobKey(job) {
|
|
14
21
|
if (!job) {
|
|
15
22
|
return null;
|
package/dist/WebApplication.js
CHANGED
|
@@ -24,6 +24,7 @@ const ErrorHandler_1 = require("./ErrorHandling/ErrorHandler");
|
|
|
24
24
|
const WebframezHooks_1 = require("./Hooks/WebframezHooks");
|
|
25
25
|
const HttpKernelHandlerRegistry_1 = require("./Http/HttpKernelHandlerRegistry");
|
|
26
26
|
const ApiScopeRegistry_1 = require("./Api/ApiScopeRegistry");
|
|
27
|
+
const NotificationService_1 = require("./Notifications/NotificationService");
|
|
27
28
|
// import { SigNozTelemetry } from "./Telemetry/SigNozTelemetry";
|
|
28
29
|
class WebApplication {
|
|
29
30
|
constructor() {
|
|
@@ -46,6 +47,7 @@ class WebApplication {
|
|
|
46
47
|
Config_1.Config.register(key, options.config[key]);
|
|
47
48
|
}
|
|
48
49
|
}
|
|
50
|
+
NotificationService_1.NotificationService.init();
|
|
49
51
|
if (options && options.errorHandler) {
|
|
50
52
|
if (Array.isArray(options.errorHandler)) {
|
|
51
53
|
ErrorHandler_1.ErrorHandler.setHandlers(options.errorHandler);
|
package/dist/index.d.ts
CHANGED
|
@@ -54,4 +54,9 @@ export * from "./Ai/AiTypes";
|
|
|
54
54
|
export * from "./Mcp/McpTypes";
|
|
55
55
|
export * from "./Http/HttpKernelHandlerRegistry";
|
|
56
56
|
export * from "./Auth";
|
|
57
|
+
export * from "./Notifications/Notification";
|
|
58
|
+
export * from "./Notifications/BaseNotificationsController";
|
|
59
|
+
export * from "./Notifications/NotificationService";
|
|
60
|
+
export * from "./Notifications/NotificationsOutputChannel";
|
|
61
|
+
export * from "./Notifications/NotificationsOutputChannelsJob";
|
|
57
62
|
export * from "./info";
|
package/dist/index.js
CHANGED
|
@@ -70,4 +70,9 @@ __exportStar(require("./Ai/AiTypes"), exports);
|
|
|
70
70
|
__exportStar(require("./Mcp/McpTypes"), exports);
|
|
71
71
|
__exportStar(require("./Http/HttpKernelHandlerRegistry"), exports);
|
|
72
72
|
__exportStar(require("./Auth"), exports);
|
|
73
|
+
__exportStar(require("./Notifications/Notification"), exports);
|
|
74
|
+
__exportStar(require("./Notifications/BaseNotificationsController"), exports);
|
|
75
|
+
__exportStar(require("./Notifications/NotificationService"), exports);
|
|
76
|
+
__exportStar(require("./Notifications/NotificationsOutputChannel"), exports);
|
|
77
|
+
__exportStar(require("./Notifications/NotificationsOutputChannelsJob"), exports);
|
|
73
78
|
__exportStar(require("./info"), exports);
|
package/package.json
CHANGED