@webtypen/webframez-core 0.3.59 → 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/README.md CHANGED
@@ -478,8 +478,8 @@ builder.registerType({
478
478
  mixed_target: {
479
479
  type: "option",
480
480
  optionsMapping: [
481
- { from: "default_values", value: "key", label: "name", valuePrefix: "@defaults." },
482
- { from: "features_targets", value: "key", label: "name" },
481
+ { from: "default_values", value: "key", label: "name", valuePrefix: "@defaults.", labelPrefix: "Standard: " },
482
+ { from: "features_targets", value: "key", label: "name", labelPrefix: "Ziel: " },
483
483
  { from: "@parent.attributes", value: "key", label: "name" }
484
484
  ]
485
485
  },
@@ -499,9 +499,11 @@ builder.registerType({
499
499
 
500
500
  `option` fields can use static `options: [{ value, label }]` or a client-side
501
501
  `optionsMapping`. The mapping can be a single object or an array of objects with
502
- `{ from: string; value: string; label: string; valuePrefix?: string }`. Multiple
503
- mapping entries are aggregated in order, and `@parent...` source paths resolve
504
- against the nearest outer array entry in the current form data.
502
+ `{ from: string; value: string; label: string; valuePrefix?: string; labelPrefix?: string }`. Multiple
503
+ mapping entries are aggregated in order. `@parent.<field>` source paths resolve
504
+ schema-aware to the closest parent schema context defining `<field>`, for example
505
+ `features_groups[].licenses[].adds[].key` with `@parent.attributes` resolves to
506
+ `features_groups[].attributes`.
505
507
 
506
508
  Array fields can set `toggable: true` to let the frontend collapse entries. Use
507
509
  `previewLabel` with the same template syntax to render a label in the entry title
@@ -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.started_at = result.__execute_again;
565
- updateData.started_at = result.__execute_again;
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);
@@ -23,6 +23,7 @@ export type DataBuilderOptionMapping = {
23
23
  value: string;
24
24
  label: string;
25
25
  valuePrefix?: string;
26
+ labelPrefix?: string;
26
27
  };
27
28
  export type DataBuilderOptionsMapping = DataBuilderOptionMapping | DataBuilderOptionMapping[];
28
29
  export type DataBuilderType = {
@@ -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,9 @@
1
+ import { Notification } from "./Notification";
2
+ export declare class NotificationOutputChannel {
3
+ key: string;
4
+ activated: boolean;
5
+ handle(notification: Notification): Promise<{
6
+ status: boolean;
7
+ payload?: any;
8
+ }>;
9
+ }
@@ -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";
@@ -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
  }
@@ -3,6 +3,7 @@ declare class QueueJobsRegistyFacade {
3
3
  private registeredByKey;
4
4
  private registeredByReference;
5
5
  getJobs(): any[];
6
+ getJobOrFail(name: string): any;
6
7
  private getJobKey;
7
8
  private addJob;
8
9
  registerJob(data: any): this;
@@ -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;
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webtypen/webframez-core",
3
- "version": "0.3.59",
3
+ "version": "0.3.61",
4
4
  "description": "The core package of the typescript based webframez (web-framework) by webtypen.de",
5
5
  "homepage": "https://webtypen.de/",
6
6
  "license": "MIT",