adminizer 4.3.0-build.85 → 4.3.0-build.87
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/assets/{add-CZdDpNV2.js → add-C2tF8-gD.js} +1 -1
- package/assets/{add-group-urhA47Qf.js → add-group-Gl84bOeV.js} +1 -1
- package/assets/{add-user-CzWUu7wG.js → add-user-DqGl8Dxi.js} +1 -1
- package/assets/{app-layout-CbyH1MDK.js → app-layout-DinOHZzD.js} +1 -1
- package/assets/app-sidebar-header-CMmguJzw.js +1 -0
- package/assets/app.js +2 -2
- package/assets/{catalog-CCSagWKW.js → catalog-CzQI0e2V.js} +1 -1
- package/assets/{dashboard-DUNXib6b.js → dashboard-DNjESo7D.js} +1 -1
- package/assets/{form-C8pRhEOA.js → form-wHGNS6BZ.js} +1 -1
- package/assets/{home-D--JrFFj.js → home-DuF0qLi8.js} +2 -2
- package/assets/{list-CQivvagG.js → list-3mxaSvck.js} +1 -1
- package/assets/manifest.json +37 -37
- package/assets/{module-DqrIORMW.js → module-Db7-sqNw.js} +1 -1
- package/assets/notification-DYA1ZN8s.js +27 -0
- package/assets/{welcome-CZlWN4gM.js → welcome-CNbz1L8F.js} +1 -1
- package/controllers/notifications/NotificationController.js +23 -6
- package/helpers/formHelper.js +3 -1
- package/lib/notifications/AbstractNotificationService.d.ts +134 -0
- package/lib/notifications/AbstractNotificationService.js +129 -9
- package/lib/notifications/GeneralNotificationService.js +3 -0
- package/package.json +1 -1
- package/system/Router.js +1 -0
- package/assets/app-sidebar-header-8KWnVpkN.js +0 -1
- package/assets/notification-db0RD9I6.js +0 -27
|
@@ -3,6 +3,51 @@ import { Adminizer } from '../Adminizer';
|
|
|
3
3
|
import { INotification, INotificationEvent } from '../../interfaces/types';
|
|
4
4
|
import { NotificationAPModel } from "../../models/NotificationAP";
|
|
5
5
|
import { UserAP } from "../../models/UserAP";
|
|
6
|
+
/**
|
|
7
|
+
* ░█████╗░██████╗░░██████╗████████╗██████╗░░█████╗░░█████╗░████████╗
|
|
8
|
+
* ██╔══██╗██╔══██╗██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝
|
|
9
|
+
* ███████║██████╦╝╚█████╗░░░░██║░░░██████╔╝███████║██║░░╚═╝░░░██║░░░
|
|
10
|
+
* ██╔══██║██╔══██╗░╚═══██╗░░░██║░░░██╔══██╗██╔══██║██║░░██╗░░░██║░░░
|
|
11
|
+
* ██║░░██║██████╦╝██████╔╝░░░██║░░░██║░░██║██║░░██║╚█████╔╝░░░██║░░░
|
|
12
|
+
* ╚═╝░░╚═╝╚═════╝░╚═════╝░░░░╚═╝░░░╚═╝░░╚═╝╚═╝░░╚═╝░╚════╝░░░░╚═╝░░░
|
|
13
|
+
*
|
|
14
|
+
* ███╗░░██╗░█████╗░████████╗██╗███████╗██╗░█████╗░░█████╗░████████╗██╗░█████╗░███╗░░██╗
|
|
15
|
+
* ████╗░██║██╔══██╗╚══██╔══╝██║██╔════╝██║██╔══██╗██╔══██╗╚══██╔══╝██║██╔══██╗████╗░██║
|
|
16
|
+
* ██╔██╗██║██║░░██║░░░██║░░░██║█████╗░░██║██║░░╚═╝███████║░░░██║░░░██║██║░░██║██╔██╗██║
|
|
17
|
+
* ██║╚████║██║░░██║░░░██║░░░██║██╔══╝░░██║██║░░██╗██╔══██║░░░██║░░░██║██║░░██║██║╚████║
|
|
18
|
+
* ██║░╚███║╚█████╔╝░░░██║░░░██║██║░░░░░██║╚█████╔╝██║░░██║░░░██║░░░██║╚█████╔╝██║░╚███║
|
|
19
|
+
* ╚═╝░░╚══╝░╚════╝░░░░╚═╝░░░╚═╝╚═╝░░░░░╚═╝░╚════╝░╚═╝░░╚═╝░░░╚═╝░░░╚═╝░╚════╝░╚═╝░░╚══╝
|
|
20
|
+
*
|
|
21
|
+
* Class: AbstractNotificationService
|
|
22
|
+
*
|
|
23
|
+
* Description:
|
|
24
|
+
* The `AbstractNotificationService` is a base class that provides the fundamental structure and functionality
|
|
25
|
+
* for implementing notification services in the application. It manages client connections, dispatches notifications,
|
|
26
|
+
* tracks user notification status, and handles database interactions for storing and retrieving notification data.
|
|
27
|
+
* This abstract class is intended to be extended by concrete notification service implementations.
|
|
28
|
+
*
|
|
29
|
+
* Key Features:
|
|
30
|
+
* - Manages a registry of clients associated with users using a nested Map structure.
|
|
31
|
+
* - Provides methods to add, remove, and retrieve clients.
|
|
32
|
+
* - Dispatches notifications to connected clients via an abstract method to be implemented by subclasses.
|
|
33
|
+
* - Sends heartbeat (ping) messages to keep client connections alive.
|
|
34
|
+
* - Creates and retrieves user notification records in the database.
|
|
35
|
+
* - Fetches paginated and filtered notification lists for a specific user.
|
|
36
|
+
* - Supports searching notifications by message content.
|
|
37
|
+
* - Marks notifications as read or all notifications as read for a user.
|
|
38
|
+
* - Emits events using Node.js EventEmitter for extensibility and integration.
|
|
39
|
+
*
|
|
40
|
+
* Usage Example:
|
|
41
|
+
* Subclasses should override the `dispatchNotification` method to implement custom notification logic.
|
|
42
|
+
*
|
|
43
|
+
* @abstract
|
|
44
|
+
* @extends EventEmitter
|
|
45
|
+
* @property {Map<number, Map<string, Function>>} clients - A map of user IDs to their client maps.
|
|
46
|
+
* @property {Adminizer} adminizer - Reference to the Adminizer instance for accessing helpers and models.
|
|
47
|
+
* @property {string} notificationClass - Abstract property representing the class name of the notification.
|
|
48
|
+
* @property {string} icon - Abstract property for the notification icon.
|
|
49
|
+
* @property {string} iconColor - Abstract property for the notification icon color.
|
|
50
|
+
*/
|
|
6
51
|
export declare abstract class AbstractNotificationService extends EventEmitter {
|
|
7
52
|
protected clients: Map<number, Map<string, (event: INotificationEvent) => void>>;
|
|
8
53
|
protected adminizer: Adminizer;
|
|
@@ -11,19 +56,108 @@ export declare abstract class AbstractNotificationService extends EventEmitter {
|
|
|
11
56
|
abstract readonly iconColor: string;
|
|
12
57
|
constructor(adminizer: Adminizer);
|
|
13
58
|
private _bindAccessRight;
|
|
59
|
+
/**
|
|
60
|
+
* Registers a client for a user and associates it with a send function.
|
|
61
|
+
* If the user does not yet have a Map of clients, one is created.
|
|
62
|
+
* The client is then added to the user's Map with the provided send function.
|
|
63
|
+
* @param {string} clientId - The unique identifier of the client to be registered.
|
|
64
|
+
* @param {(event: INotificationEvent) => void} sendFn - The function used to send notifications to the client.
|
|
65
|
+
* @param {UserAP} user - The user associated with the client.
|
|
66
|
+
*/
|
|
14
67
|
addClient(clientId: string, sendFn: (event: INotificationEvent) => void, user: UserAP): void;
|
|
68
|
+
/**
|
|
69
|
+
* Removes a client from the internal tracking structure.
|
|
70
|
+
* If the user associated with the client has no more clients after removal, the user's Map is also removed.
|
|
71
|
+
* @param {string} clientId - The unique identifier of the client to be removed.
|
|
72
|
+
*/
|
|
15
73
|
removeClient(clientId: string): void;
|
|
74
|
+
/**
|
|
75
|
+
* Retrieves the Map of clients associated with a specific user.
|
|
76
|
+
* If the user has no clients, an empty Map is returned.
|
|
77
|
+
* @param {number} userId - The unique identifier of the user whose clients should be retrieved.
|
|
78
|
+
* @returns {Map<string, (event: INotificationEvent) => void>} A Map where the keys are client IDs and the values are send functions.
|
|
79
|
+
*/
|
|
16
80
|
getUserClients(userId: number): Map<string, (event: INotificationEvent) => void>;
|
|
81
|
+
/**
|
|
82
|
+
* Returns the total number of users who have at least one connected client.
|
|
83
|
+
* @returns {number} The count of users with active clients.
|
|
84
|
+
*/
|
|
17
85
|
getClientCount(): number;
|
|
86
|
+
/**
|
|
87
|
+
* Aggregates all clients from all users into a single Map for backward compatibility.
|
|
88
|
+
* Each client is uniquely identified by its ID and mapped to its corresponding send function.
|
|
89
|
+
* @returns {Map<string, (event: INotificationEvent) => void>} A combined Map of all client IDs and their send functions.
|
|
90
|
+
*/
|
|
18
91
|
getAllClients(): Map<string, (event: INotificationEvent) => void>;
|
|
92
|
+
/**
|
|
93
|
+
* Abstract method that must be implemented by subclasses to dispatch a notification.
|
|
94
|
+
* @param {Omit<INotification, 'id' | 'createdAt' | 'notificationClass' | 'icon'>} notification - The notification data to be dispatched (excluding id, createdAt, notificationClass, and icon).
|
|
95
|
+
* @returns {Promise<boolean>} A promise that resolves to a boolean indicating whether the notification was successfully dispatched.
|
|
96
|
+
*/
|
|
19
97
|
abstract dispatchNotification(notification: Omit<INotification, 'id' | 'createdAt' | 'notificationClass' | 'icon'>): Promise<boolean>;
|
|
98
|
+
/**
|
|
99
|
+
* Sends a notification event to all connected clients.
|
|
100
|
+
* Iterates through each user's client Map and invokes the send function for each client.
|
|
101
|
+
* If sending fails, logs an error and removes the client.
|
|
102
|
+
* @param {INotificationEvent} event - The notification event to broadcast.
|
|
103
|
+
*/
|
|
20
104
|
protected broadcast(event: INotificationEvent): void;
|
|
105
|
+
/**
|
|
106
|
+
* Sends a heartbeat (ping) message to a specific client.
|
|
107
|
+
* Searches for the client across all user client Maps and sends a heartbeat event if found.
|
|
108
|
+
* @param {string} clientId - The unique identifier of the client to receive the heartbeat.
|
|
109
|
+
*/
|
|
21
110
|
sendHeartbeat(clientId: string): void;
|
|
111
|
+
/**
|
|
112
|
+
* Creates a new user notification record in the 'usernotificationap' model.
|
|
113
|
+
* This method is used to associate a notification with a specific user and mark it as unread.
|
|
114
|
+
* @param {string} notificationId - The unique identifier of the notification.
|
|
115
|
+
* @param {number} [userId] - The ID of the user to associate the notification with. Optional.
|
|
116
|
+
* @returns {Promise<void>} A promise that resolves when the record is created or an error is logged.
|
|
117
|
+
*/
|
|
22
118
|
protected createUserNotification(notificationId: string, userId?: number): Promise<void>;
|
|
119
|
+
/**
|
|
120
|
+
* Retrieves a user notification record from the 'usernotificationap' model.
|
|
121
|
+
* The record is fetched based on the provided notification ID and user ID.
|
|
122
|
+
* @param {string} notificationId - The unique identifier of the notification.
|
|
123
|
+
* @param {number} userId - The ID of the user associated with the notification.
|
|
124
|
+
* @returns {Promise<any>} A promise that resolves with the found record or null if not found or an error occurs.
|
|
125
|
+
*/
|
|
23
126
|
protected getUserNotification(notificationId: string, userId: number): Promise<any>;
|
|
127
|
+
/**
|
|
128
|
+
* Retrieves a paginated list of notifications for a specific user, optionally filtered by read status.
|
|
129
|
+
* @param {number} userId - The ID of the user whose notifications should be fetched.
|
|
130
|
+
* @param {number} [limit=20] - The maximum number of notifications to return.
|
|
131
|
+
* @param {number} [skip=0] - The number of notifications to skip (used for pagination).
|
|
132
|
+
* @param {boolean} [unreadOnly=false] - Whether to return only unread notifications.
|
|
133
|
+
* @returns {Promise<INotification[]>} A promise that resolves with an array of notification objects.
|
|
134
|
+
*/
|
|
24
135
|
getNotifications(userId: number, limit?: number, skip?: number, unreadOnly?: boolean): Promise<INotification[]>;
|
|
136
|
+
/**
|
|
137
|
+
* Prepares notification data by enriching it with user-specific read status and icon information.
|
|
138
|
+
* @param {NotificationAPModel[]} notificationsDB - An array of raw notification records from the database.
|
|
139
|
+
* @param {number} userId - The ID of the user for whom the read status is determined.
|
|
140
|
+
* @returns {Promise<INotification[]>} A promise that resolves with an array of enriched notification objects.
|
|
141
|
+
*/
|
|
25
142
|
protected prepareNotification(notificationsDB: NotificationAPModel[], userId: number): Promise<INotification[]>;
|
|
143
|
+
/**
|
|
144
|
+
* Searches for notifications for a specific user based on a message keyword.
|
|
145
|
+
* @param {string} s - The search string used to filter notifications by message content.
|
|
146
|
+
* @param {number} userId - The ID of the user whose notifications should be searched.
|
|
147
|
+
* @returns {Promise<INotification[]>} A promise that resolves with an array of matching notification objects.
|
|
148
|
+
*/
|
|
26
149
|
search(s: string, userId: number): Promise<INotification[]>;
|
|
150
|
+
/**
|
|
151
|
+
* Marks a specific notification as read for a given user.
|
|
152
|
+
* @param {number} userId - The ID of the user for whom the notification should be marked as read.
|
|
153
|
+
* @param {string} id - The ID of the notification to mark as read.
|
|
154
|
+
* @returns {Promise<void>} A promise that resolves when the operation is complete.
|
|
155
|
+
*/
|
|
27
156
|
markAsRead(userId: number, id: string): Promise<void>;
|
|
157
|
+
/**
|
|
158
|
+
* Marks all notifications as read for a given user.
|
|
159
|
+
* @param {number} userId - The ID of the user for whom all notifications should be marked as read.
|
|
160
|
+
* @returns {Promise<void>} A promise that resolves when the operation is complete.
|
|
161
|
+
*/
|
|
28
162
|
markAllAsRead(userId: number): Promise<void>;
|
|
29
163
|
}
|
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
2
|
import { Adminizer } from '../Adminizer';
|
|
3
|
+
/**
|
|
4
|
+
* ░█████╗░██████╗░░██████╗████████╗██████╗░░█████╗░░█████╗░████████╗
|
|
5
|
+
* ██╔══██╗██╔══██╗██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝
|
|
6
|
+
* ███████║██████╦╝╚█████╗░░░░██║░░░██████╔╝███████║██║░░╚═╝░░░██║░░░
|
|
7
|
+
* ██╔══██║██╔══██╗░╚═══██╗░░░██║░░░██╔══██╗██╔══██║██║░░██╗░░░██║░░░
|
|
8
|
+
* ██║░░██║██████╦╝██████╔╝░░░██║░░░██║░░██║██║░░██║╚█████╔╝░░░██║░░░
|
|
9
|
+
* ╚═╝░░╚═╝╚═════╝░╚═════╝░░░░╚═╝░░░╚═╝░░╚═╝╚═╝░░╚═╝░╚════╝░░░░╚═╝░░░
|
|
10
|
+
*
|
|
11
|
+
* ███╗░░██╗░█████╗░████████╗██╗███████╗██╗░█████╗░░█████╗░████████╗██╗░█████╗░███╗░░██╗
|
|
12
|
+
* ████╗░██║██╔══██╗╚══██╔══╝██║██╔════╝██║██╔══██╗██╔══██╗╚══██╔══╝██║██╔══██╗████╗░██║
|
|
13
|
+
* ██╔██╗██║██║░░██║░░░██║░░░██║█████╗░░██║██║░░╚═╝███████║░░░██║░░░██║██║░░██║██╔██╗██║
|
|
14
|
+
* ██║╚████║██║░░██║░░░██║░░░██║██╔══╝░░██║██║░░██╗██╔══██║░░░██║░░░██║██║░░██║██║╚████║
|
|
15
|
+
* ██║░╚███║╚█████╔╝░░░██║░░░██║██║░░░░░██║╚█████╔╝██║░░██║░░░██║░░░██║╚█████╔╝██║░╚███║
|
|
16
|
+
* ╚═╝░░╚══╝░╚════╝░░░░╚═╝░░░╚═╝╚═╝░░░░░╚═╝░╚════╝░╚═╝░░╚═╝░░░╚═╝░░░╚═╝░╚════╝░╚═╝░░╚══╝
|
|
17
|
+
*
|
|
18
|
+
* Class: AbstractNotificationService
|
|
19
|
+
*
|
|
20
|
+
* Description:
|
|
21
|
+
* The `AbstractNotificationService` is a base class that provides the fundamental structure and functionality
|
|
22
|
+
* for implementing notification services in the application. It manages client connections, dispatches notifications,
|
|
23
|
+
* tracks user notification status, and handles database interactions for storing and retrieving notification data.
|
|
24
|
+
* This abstract class is intended to be extended by concrete notification service implementations.
|
|
25
|
+
*
|
|
26
|
+
* Key Features:
|
|
27
|
+
* - Manages a registry of clients associated with users using a nested Map structure.
|
|
28
|
+
* - Provides methods to add, remove, and retrieve clients.
|
|
29
|
+
* - Dispatches notifications to connected clients via an abstract method to be implemented by subclasses.
|
|
30
|
+
* - Sends heartbeat (ping) messages to keep client connections alive.
|
|
31
|
+
* - Creates and retrieves user notification records in the database.
|
|
32
|
+
* - Fetches paginated and filtered notification lists for a specific user.
|
|
33
|
+
* - Supports searching notifications by message content.
|
|
34
|
+
* - Marks notifications as read or all notifications as read for a user.
|
|
35
|
+
* - Emits events using Node.js EventEmitter for extensibility and integration.
|
|
36
|
+
*
|
|
37
|
+
* Usage Example:
|
|
38
|
+
* Subclasses should override the `dispatchNotification` method to implement custom notification logic.
|
|
39
|
+
*
|
|
40
|
+
* @abstract
|
|
41
|
+
* @extends EventEmitter
|
|
42
|
+
* @property {Map<number, Map<string, Function>>} clients - A map of user IDs to their client maps.
|
|
43
|
+
* @property {Adminizer} adminizer - Reference to the Adminizer instance for accessing helpers and models.
|
|
44
|
+
* @property {string} notificationClass - Abstract property representing the class name of the notification.
|
|
45
|
+
* @property {string} icon - Abstract property for the notification icon.
|
|
46
|
+
* @property {string} iconColor - Abstract property for the notification icon color.
|
|
47
|
+
*/
|
|
3
48
|
export class AbstractNotificationService extends EventEmitter {
|
|
4
49
|
clients = new Map();
|
|
5
50
|
adminizer;
|
|
@@ -18,7 +63,14 @@ export class AbstractNotificationService extends EventEmitter {
|
|
|
18
63
|
});
|
|
19
64
|
}, 100);
|
|
20
65
|
}
|
|
21
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Registers a client for a user and associates it with a send function.
|
|
68
|
+
* If the user does not yet have a Map of clients, one is created.
|
|
69
|
+
* The client is then added to the user's Map with the provided send function.
|
|
70
|
+
* @param {string} clientId - The unique identifier of the client to be registered.
|
|
71
|
+
* @param {(event: INotificationEvent) => void} sendFn - The function used to send notifications to the client.
|
|
72
|
+
* @param {UserAP} user - The user associated with the client.
|
|
73
|
+
*/
|
|
22
74
|
addClient(clientId, sendFn, user) {
|
|
23
75
|
const userId = user.id;
|
|
24
76
|
// Если у пользователя еще нет Map клиентов - создаем
|
|
@@ -30,7 +82,11 @@ export class AbstractNotificationService extends EventEmitter {
|
|
|
30
82
|
userClients.set(clientId, sendFn);
|
|
31
83
|
Adminizer.log.info(`[${this.notificationClass}] Client ${clientId} connected for user ${userId}. Total users: ${this.clients.size}, user clients: ${userClients.size}`);
|
|
32
84
|
}
|
|
33
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Removes a client from the internal tracking structure.
|
|
87
|
+
* If the user associated with the client has no more clients after removal, the user's Map is also removed.
|
|
88
|
+
* @param {string} clientId - The unique identifier of the client to be removed.
|
|
89
|
+
*/
|
|
34
90
|
removeClient(clientId) {
|
|
35
91
|
// Ищем клиента во всех пользовательских Map
|
|
36
92
|
for (const [userId, userClients] of this.clients.entries()) {
|
|
@@ -46,15 +102,27 @@ export class AbstractNotificationService extends EventEmitter {
|
|
|
46
102
|
}
|
|
47
103
|
Adminizer.log.warn(`[${this.notificationClass}] Client ${clientId} not found for removal`);
|
|
48
104
|
}
|
|
49
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Retrieves the Map of clients associated with a specific user.
|
|
107
|
+
* If the user has no clients, an empty Map is returned.
|
|
108
|
+
* @param {number} userId - The unique identifier of the user whose clients should be retrieved.
|
|
109
|
+
* @returns {Map<string, (event: INotificationEvent) => void>} A Map where the keys are client IDs and the values are send functions.
|
|
110
|
+
*/
|
|
50
111
|
getUserClients(userId) {
|
|
51
112
|
return this.clients.get(userId) || new Map();
|
|
52
113
|
}
|
|
53
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Returns the total number of users who have at least one connected client.
|
|
116
|
+
* @returns {number} The count of users with active clients.
|
|
117
|
+
*/
|
|
54
118
|
getClientCount() {
|
|
55
119
|
return this.clients.size;
|
|
56
120
|
}
|
|
57
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Aggregates all clients from all users into a single Map for backward compatibility.
|
|
123
|
+
* Each client is uniquely identified by its ID and mapped to its corresponding send function.
|
|
124
|
+
* @returns {Map<string, (event: INotificationEvent) => void>} A combined Map of all client IDs and their send functions.
|
|
125
|
+
*/
|
|
58
126
|
getAllClients() {
|
|
59
127
|
const allClients = new Map();
|
|
60
128
|
for (const userClients of this.clients.values()) {
|
|
@@ -64,7 +132,12 @@ export class AbstractNotificationService extends EventEmitter {
|
|
|
64
132
|
}
|
|
65
133
|
return allClients;
|
|
66
134
|
}
|
|
67
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Sends a notification event to all connected clients.
|
|
137
|
+
* Iterates through each user's client Map and invokes the send function for each client.
|
|
138
|
+
* If sending fails, logs an error and removes the client.
|
|
139
|
+
* @param {INotificationEvent} event - The notification event to broadcast.
|
|
140
|
+
*/
|
|
68
141
|
broadcast(event) {
|
|
69
142
|
for (const userClients of this.clients.values()) {
|
|
70
143
|
userClients.forEach((sendFn, clientId) => {
|
|
@@ -78,7 +151,11 @@ export class AbstractNotificationService extends EventEmitter {
|
|
|
78
151
|
});
|
|
79
152
|
}
|
|
80
153
|
}
|
|
81
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Sends a heartbeat (ping) message to a specific client.
|
|
156
|
+
* Searches for the client across all user client Maps and sends a heartbeat event if found.
|
|
157
|
+
* @param {string} clientId - The unique identifier of the client to receive the heartbeat.
|
|
158
|
+
*/
|
|
82
159
|
sendHeartbeat(clientId) {
|
|
83
160
|
for (const userClients of this.clients.values()) {
|
|
84
161
|
const sendFn = userClients.get(clientId);
|
|
@@ -92,7 +169,13 @@ export class AbstractNotificationService extends EventEmitter {
|
|
|
92
169
|
}
|
|
93
170
|
}
|
|
94
171
|
}
|
|
95
|
-
|
|
172
|
+
/**
|
|
173
|
+
* Creates a new user notification record in the 'usernotificationap' model.
|
|
174
|
+
* This method is used to associate a notification with a specific user and mark it as unread.
|
|
175
|
+
* @param {string} notificationId - The unique identifier of the notification.
|
|
176
|
+
* @param {number} [userId] - The ID of the user to associate the notification with. Optional.
|
|
177
|
+
* @returns {Promise<void>} A promise that resolves when the record is created or an error is logged.
|
|
178
|
+
*/
|
|
96
179
|
async createUserNotification(notificationId, userId) {
|
|
97
180
|
if (this.adminizer.modelHandler.model.has('usernotificationap') && userId) {
|
|
98
181
|
try {
|
|
@@ -107,7 +190,13 @@ export class AbstractNotificationService extends EventEmitter {
|
|
|
107
190
|
}
|
|
108
191
|
}
|
|
109
192
|
}
|
|
110
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Retrieves a user notification record from the 'usernotificationap' model.
|
|
195
|
+
* The record is fetched based on the provided notification ID and user ID.
|
|
196
|
+
* @param {string} notificationId - The unique identifier of the notification.
|
|
197
|
+
* @param {number} userId - The ID of the user associated with the notification.
|
|
198
|
+
* @returns {Promise<any>} A promise that resolves with the found record or null if not found or an error occurs.
|
|
199
|
+
*/
|
|
111
200
|
async getUserNotification(notificationId, userId) {
|
|
112
201
|
if (this.adminizer.modelHandler.model.has('usernotificationap')) {
|
|
113
202
|
try {
|
|
@@ -122,6 +211,14 @@ export class AbstractNotificationService extends EventEmitter {
|
|
|
122
211
|
}
|
|
123
212
|
return null;
|
|
124
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Retrieves a paginated list of notifications for a specific user, optionally filtered by read status.
|
|
216
|
+
* @param {number} userId - The ID of the user whose notifications should be fetched.
|
|
217
|
+
* @param {number} [limit=20] - The maximum number of notifications to return.
|
|
218
|
+
* @param {number} [skip=0] - The number of notifications to skip (used for pagination).
|
|
219
|
+
* @param {boolean} [unreadOnly=false] - Whether to return only unread notifications.
|
|
220
|
+
* @returns {Promise<INotification[]>} A promise that resolves with an array of notification objects.
|
|
221
|
+
*/
|
|
125
222
|
async getNotifications(userId, limit = 20, skip = 0, unreadOnly = false) {
|
|
126
223
|
if (!this.adminizer.modelHandler.model.has('notificationap')) {
|
|
127
224
|
return [];
|
|
@@ -159,6 +256,12 @@ export class AbstractNotificationService extends EventEmitter {
|
|
|
159
256
|
return [];
|
|
160
257
|
}
|
|
161
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Prepares notification data by enriching it with user-specific read status and icon information.
|
|
261
|
+
* @param {NotificationAPModel[]} notificationsDB - An array of raw notification records from the database.
|
|
262
|
+
* @param {number} userId - The ID of the user for whom the read status is determined.
|
|
263
|
+
* @returns {Promise<INotification[]>} A promise that resolves with an array of enriched notification objects.
|
|
264
|
+
*/
|
|
162
265
|
async prepareNotification(notificationsDB, userId) {
|
|
163
266
|
let notifications = [];
|
|
164
267
|
for (const notification of notificationsDB) {
|
|
@@ -177,6 +280,12 @@ export class AbstractNotificationService extends EventEmitter {
|
|
|
177
280
|
}
|
|
178
281
|
return notifications;
|
|
179
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* Searches for notifications for a specific user based on a message keyword.
|
|
285
|
+
* @param {string} s - The search string used to filter notifications by message content.
|
|
286
|
+
* @param {number} userId - The ID of the user whose notifications should be searched.
|
|
287
|
+
* @returns {Promise<INotification[]>} A promise that resolves with an array of matching notification objects.
|
|
288
|
+
*/
|
|
180
289
|
async search(s, userId) {
|
|
181
290
|
try {
|
|
182
291
|
const userNotifications = await this.adminizer.modelHandler.model.get('usernotificationap')["_find"]({
|
|
@@ -202,6 +311,12 @@ export class AbstractNotificationService extends EventEmitter {
|
|
|
202
311
|
return [];
|
|
203
312
|
}
|
|
204
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* Marks a specific notification as read for a given user.
|
|
316
|
+
* @param {number} userId - The ID of the user for whom the notification should be marked as read.
|
|
317
|
+
* @param {string} id - The ID of the notification to mark as read.
|
|
318
|
+
* @returns {Promise<void>} A promise that resolves when the operation is complete.
|
|
319
|
+
*/
|
|
205
320
|
async markAsRead(userId, id) {
|
|
206
321
|
try {
|
|
207
322
|
const userNotification = await this.getUserNotification(id, userId);
|
|
@@ -215,6 +330,11 @@ export class AbstractNotificationService extends EventEmitter {
|
|
|
215
330
|
Adminizer.log.error('Error marking notification as read:', error);
|
|
216
331
|
}
|
|
217
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* Marks all notifications as read for a given user.
|
|
335
|
+
* @param {number} userId - The ID of the user for whom all notifications should be marked as read.
|
|
336
|
+
* @returns {Promise<void>} A promise that resolves when the operation is complete.
|
|
337
|
+
*/
|
|
218
338
|
async markAllAsRead(userId) {
|
|
219
339
|
try {
|
|
220
340
|
const userNotifications = await this.adminizer.modelHandler.model.get('usernotificationap')["_find"]({
|
|
@@ -26,6 +26,9 @@ export class GeneralNotificationService extends AbstractNotificationService {
|
|
|
26
26
|
if (this.adminizer.accessRightsHelper.hasPermission(`notification-${this.notificationClass}`, user)) {
|
|
27
27
|
await this.createUserNotification(notificationDB.id, user.id);
|
|
28
28
|
}
|
|
29
|
+
else {
|
|
30
|
+
Adminizer.log.error(`[${this.notificationClass}] User ${user.id} doesn't have permission to receive this notification`);
|
|
31
|
+
}
|
|
29
32
|
}
|
|
30
33
|
catch (error) {
|
|
31
34
|
Adminizer.log.error('Error creating UserNotificationAP:', error);
|
package/package.json
CHANGED
package/system/Router.js
CHANGED
|
@@ -86,6 +86,7 @@ export default class Router {
|
|
|
86
86
|
*/
|
|
87
87
|
if (adminizer.config.notifications.enabled) {
|
|
88
88
|
adminizer.app.get(`${adminizer.config.routePrefix}/notifications`, adminizer.policyManager.bindPolicies(policies, NotificationController.viewAll));
|
|
89
|
+
adminizer.app.post(`${adminizer.config.routePrefix}/notifications`, adminizer.policyManager.bindPolicies(policies, NotificationController.viewAll));
|
|
89
90
|
adminizer.app.get(`${adminizer.config.routePrefix}/api/notifications/stream`, adminizer.policyManager.bindPolicies(policies, NotificationController.getNotificationsStream));
|
|
90
91
|
adminizer.app.get(`${adminizer.config.routePrefix}/api/notifications/:notificationClass`, adminizer.policyManager.bindPolicies(policies, NotificationController.getNotificationsByClass));
|
|
91
92
|
adminizer.app.get(`${adminizer.config.routePrefix}/api/notifications`, adminizer.policyManager.bindPolicies(policies, NotificationController.getUserNotifications));
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{u as E,j as e,g as m,aG as R,p as B,B as M,aH as L,s as O,v as z,aI as _,J as H,K as J,Z as F,M as G,r as K,aJ as U,U as V,L as $,aK as Y}from"./app.js";import{Breadcrumbs as Z}from"./breadcrumbs-C439DWrC.js";import{NavUser as q}from"./nav-user-CRAfyE-x.js";import{ThemeSwitcher as Q}from"./theme-switcher-CKiJbMkh.js";import{r as l}from"./monaco-ICtx6xoc.js";import W from"./material-icon-BedgD2NE.js";const P=l.createContext(void 0),ne=({children:n})=>{const[p,f]=l.useState([]),[y,d]=l.useState([]),[v,u]=l.useState([]),[b,r]=l.useState(!1),[c,g]=l.useState([]),S=E(),w=[...y,...v],i=async()=>{try{const t=await m.get(`${window.routePrefix}/api/notifications`,{params:{unreadOnly:!0,limit:4}});f(t.data)}catch(t){console.error("Error fetching bell notifications:",t)}},j=async()=>{await i()},x=async()=>{try{const t=await m.put(`${window.routePrefix}/api/notifications/get-classes`);g(t.data)}catch(t){console.error("Error fetching tabs:",t)}},N=async(t,s)=>{try{if(!t){await h(s);return}const a=await m.post(`${window.routePrefix}/api/notifications/search`,{s:t,notificationClass:s});u(a.data)}catch(a){console.error("Error searching notifications:",a)}},h=async t=>{r(!0);try{const s=`${window.routePrefix}/api/notifications/${t}`,a=await m.get(s,{params:{limit:20,skip:0,unreadOnly:!1}});return d([]),u(a.data),a.data}catch(s){return console.error("Error fetching all notifications:",s),[]}finally{r(!1)}},C=async(t,s)=>{try{const a=`${window.routePrefix}/api/notifications/${t}`,o=await m.get(a,{params:{limit:20,skip:s,unreadOnly:!1}});return u(D=>[...D,...o.data]),o.data}catch(a){return console.error("Error paginating notifications:",a),[]}};l.useEffect(()=>{if(!S.props.notifications)return;i();const t=new EventSource(`${window.routePrefix}/api/notifications/stream`);return t.addEventListener("connected",s=>{const a=JSON.parse(s.data);console.log("Connected event:",a),x()}),t.addEventListener("notification",s=>{const a=JSON.parse(s.data);d(o=>[a,...o]),f(o=>[a,...o].slice(0,4))}),t.onerror=()=>{console.error("SSE connection error")},()=>t.close()},[]);const k=async(t,s)=>{try{await m.put(`${window.routePrefix}/api/notifications/${t}/${s}/read`,{}),d(a=>a.map(o=>o.id===s?{...o,read:!0}:o)),u(a=>a.map(o=>o.id===s?{...o,read:!0}:o)),f([])}catch(a){console.error("Error marking as read:",a)}},A=async()=>{try{await m.put(`${window.routePrefix}/api/notifications/read-all`),d(t=>t.map(s=>({...s,read:!0}))),u(t=>t.map(s=>({...s,read:!0}))),await i()}catch(t){console.error("Error marking all as read:",t)}},I=p.filter(t=>!t.read).length;return e.jsx(P.Provider,{value:{bellNotifications:p,allNotifications:w,unreadCount:I,getTabs:x,tabs:c,search:N,markAsRead:k,markAllAsRead:A,fetchAllNotifications:h,paginateNotifications:C,loading:b,refreshBellNotifications:j},children:n})},T=()=>{const n=l.useContext(P);if(n===void 0)throw new Error("useNotifications must be used within a NotificationProvider");return n};function X(){const{bellNotifications:n,markAsRead:p,unreadCount:f,refreshBellNotifications:y}=T(),[d,v]=l.useState(!1),u=async(r,c)=>{v(!0);try{await p(r,c),await y()}catch(g){console.error("Error marking as read:",g)}finally{v(!1)}},b=r=>{const c=new Date,g=new Date(r),S=c.getTime()-g.getTime(),w=Math.floor(S/1e3),i=new Intl.RelativeTimeFormat("en",{numeric:"auto"});if(w<60)return i.format(-w,"second");const j=Math.floor(w/60);if(j<60)return i.format(-j,"minute");const x=Math.floor(j/60);if(x<24)return i.format(-x,"hour");const N=Math.floor(x/24);if(N<30)return i.format(-N,"day");const h=Math.floor(N/30);if(h<12)return i.format(-h,"month");const C=Math.floor(h/12);return i.format(-C,"year")};return e.jsxs(R,{children:[e.jsx(B,{asChild:!0,className:"cursor-pointer data-[state=open]:bg-sidebar-accent",children:e.jsxs(M,{variant:"ghost",className:"relative",children:[e.jsx(L,{}),f>0&&e.jsx("div",{className:"absolute top-1.5 right-1.5 size-1.5 rounded-full bg-destructive"})]})}),e.jsxs(O,{className:"z-[1002] p-2",align:"end",onCloseAutoFocus:r=>r.preventDefault(),children:[e.jsx(z,{className:`${d?"opacity-50 pointer-events-none":""}`,children:n.length===0?e.jsx("div",{className:"p-4 text-center text-muted-foreground",children:"No notifications"}):n.map(r=>e.jsxs("div",{children:[e.jsx(_,{asChild:!0,className:"cursor-pointer",onSelect:c=>{c.preventDefault(),K.visit(`${window.routePrefix}/notifications?type=${r.notificationClass}`)},children:e.jsxs("div",{className:"grid grid-cols-[32px_250px] grid-rows-[auto_1fr] items-start gap-x-4 gap-y-2",children:[r.icon?e.jsx(W,{name:r.icon.icon,style:{color:r.icon.iconColor},className:"!text-[32px]"}):e.jsx("div",{}),e.jsxs("div",{className:"flex flex-col",children:[e.jsx("span",{className:"font-medium",children:r.title}),e.jsx("span",{className:"truncate",children:r.message})]}),e.jsxs("div",{className:"col-start-2 flex justify-between flex-nowrap items-center",children:[e.jsxs("div",{className:"flex flex-nowrap gap-2 items-center",children:[r.notificationClass==="general"?e.jsx("div",{className:"font-medium",children:"Info"}):e.jsx("div",{className:"font-medium",children:"Activity"}),e.jsx("span",{children:"●"}),e.jsx("div",{children:b(r.createdAt)})]}),e.jsxs(H,{children:[e.jsx(J,{asChild:!0,children:e.jsx(M,{variant:"ghost",size:"icon",className:"size-4",onClick:c=>{c.stopPropagation(),u(r.notificationClass,r.id)},children:e.jsx(F,{className:"text-primary"})})}),e.jsx(G,{side:"left",className:"z-[1003]",children:e.jsx("p",{children:"Make read"})})]})]})]})}),e.jsx(U,{})]},r.id))}),e.jsx(M,{variant:"secondary",asChild:!0,children:e.jsxs(V,{href:`${window.routePrefix}/notifications?type=general`,className:`w-full ${d?"opacity-50 pointer-events-none":""}`,children:["View All",d&&e.jsx($,{className:"h-4 w-4 animate-spin"})]})})]})]})}function ee({breadcrumbs:n=[]}){const{tabs:p}=T(),f=E();return e.jsx("header",{className:"border-sidebar-border/50 flex h-16 shrink-0 items-center gap-2 border-b px-6 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12 md:px-4",children:e.jsxs("div",{className:"flex justify-between w-full",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(Y,{className:"-ml-1"}),e.jsx(Z,{breadcrumbs:n})]}),e.jsxs("div",{className:"flex gap-4 items-center",children:[e.jsx(Q,{}),f.props.notifications&&(p.length>0?e.jsx(X,{}):e.jsx("div",{className:"w-[40px] flex-none flex justify-center",children:e.jsx($,{className:"size-4 animate-spin"})})),e.jsx(q,{})]})]})})}const ce=Object.freeze(Object.defineProperty({__proto__:null,AppSidebarHeader:ee},Symbol.toStringTag,{value:"Module"}));export{ee as A,ne as N,ce as a,T as u};
|