adminizer 4.2.0 → 4.3.0-build.101
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-1E1UsMVZ.js +1 -0
- package/assets/add-form-CGa9QbwE.js +1 -0
- package/assets/add-group-7yAK6uFY.js +1 -0
- package/assets/add-group-form-BNmpvW8K.js +1 -0
- package/assets/add-user-NoYkTfZi.js +1 -0
- package/assets/add-user-form-DrclnTOd.js +1 -0
- package/assets/{app-CVnUv8s_.css → app-D8oqdt8J.css} +1 -1
- package/assets/app-layout-i2zN9Rn-.js +1 -0
- package/assets/app-shell-CHEYhVco.js +1 -0
- package/assets/app-sidebar-DeF8WNmW.js +1 -0
- package/assets/app-sidebar-header-DdM1EOrb.js +1 -0
- package/assets/app.js +1699 -1657
- package/assets/breadcrumbs-C439DWrC.js +1 -0
- package/assets/catalog-B9hdop59.js +6 -0
- package/assets/dashboard-Bi5KeZu2.js +3 -0
- package/assets/date-picker-DbJ8nHp5.js +1 -0
- package/assets/del-modal-_b3CSNHb.js +1 -0
- package/assets/dynamic-controls-BOYmRX_z.js +1 -0
- package/assets/field-renderer-4obZ_jUo.js +407 -0
- package/assets/{field-renderer-CsBa_tMB.css → field-renderer-CRNLShJD.css} +1 -1
- package/assets/form-Bte5kzjB.js +1 -0
- package/assets/form-oUlsuu7h.js +1 -0
- package/assets/geo-json-CadjdhwW.js +4 -0
- package/assets/handsontable-PVakPtJV.js +281 -0
- package/assets/home-CsUXA-O6.js +2 -0
- package/assets/icon-DM49pZW3.js +1 -0
- package/assets/init-user-DNrB8Xt5.js +1 -0
- package/assets/input-error--IOnWghB.js +1 -0
- package/assets/jsoneditor-D95dIAP2.js +3878 -0
- package/assets/list-CdfSnSN1.js +1 -0
- package/assets/list-table-Byzv346I.js +22 -0
- package/assets/login-6Xb8bcPq.js +1 -0
- package/assets/manifest.json +331 -221
- package/assets/material-icon-BedgD2NE.js +1 -0
- package/assets/media-manager-UQT6RhI8.js +14 -0
- package/assets/module-B4-7WkY5.js +1 -0
- package/assets/monaco-ICtx6xoc.js +11 -0
- package/assets/nav-footer-BwkQkwow.js +1 -0
- package/assets/nav-main-9Eb_A0rf.js +1 -0
- package/assets/nav-user-CRAfyE-x.js +1 -0
- package/assets/notification-ma2x7vka.js +27 -0
- package/assets/pagination-render-xsEktzA8.js +1 -0
- package/assets/register-BIyjCy8M.js +1 -0
- package/assets/tabs-BK_5wdPA.js +1 -0
- package/assets/theme-switcher-CKiJbMkh.js +1 -0
- package/assets/toast-editor-BZM5RHph.js +845 -0
- package/assets/user-info-CcYdmjMZ.js +1 -0
- package/assets/user-menu-content-DbUk8ZeY.js +1 -0
- package/assets/vanilla-picker-l5rcX3cq.js +8 -0
- package/assets/vendor-wzZzN_Jv.js +17 -0
- package/assets/welcome-cWh1qnl_.js +1 -0
- package/controllers/add.js +1 -1
- package/controllers/catalog/FrontentCatalogAdapter.d.ts +4 -4
- package/controllers/ckeditorUpload.d.ts +1 -1
- package/controllers/ckeditorUpload.js +50 -26
- package/controllers/edit.js +2 -2
- package/controllers/form.js +29 -13
- package/controllers/list.js +2 -2
- package/controllers/login.js +2 -2
- package/controllers/media-manager/mediaManagerAdapter.js +13 -2
- package/controllers/media-manager/mediaManagerApi.js +4 -3
- package/controllers/navigation/edit.js +1 -1
- package/controllers/notifications/NotificationController.d.ts +11 -0
- package/controllers/notifications/NotificationController.js +219 -0
- package/controllers/remove.js +2 -1
- package/controllers/view.js +1 -1
- package/files/favicon.png +0 -0
- package/helpers/configHelper.d.ts +1 -1
- package/helpers/controllerHelper.js +1 -1
- package/helpers/diffHelpers.d.ts +2 -0
- package/helpers/diffHelpers.js +69 -0
- package/helpers/fieldsHelper.d.ts +1 -1
- package/helpers/fieldsHelper.js +1 -1
- package/helpers/formHelper.js +3 -1
- package/helpers/inertiaAddHelper.d.ts +1 -1
- package/helpers/inertiaAutHelper.js +19 -0
- package/helpers/inertiaFromHelper.js +12 -0
- package/helpers/translationHelper.d.ts +1 -1
- package/helpers/translationHelper.js +1 -1
- package/index.d.ts +16 -14
- package/index.js +16 -14
- package/interfaces/adminpanelConfig.d.ts +18 -0
- package/interfaces/global.d.ts +3 -3
- package/interfaces/types.d.ts +44 -2
- package/lib/Adminizer.d.ts +16 -8
- package/lib/Adminizer.js +95 -12
- package/lib/{v4/DataAccessor.d.ts → DataAccessor.d.ts} +5 -5
- package/lib/{v4/DataAccessor.js → DataAccessor.js} +3 -3
- package/lib/{v4/I18n.js → I18n.js} +2 -2
- package/lib/{v4/PolicyManager.d.ts → PolicyManager.d.ts} +1 -1
- package/lib/{v4/PolicyManager.js → PolicyManager.js} +2 -2
- package/lib/{v4/catalog → catalog}/AbstractCatalog.d.ts +1 -1
- package/lib/{v4/catalog → catalog}/Navigation.d.ts +2 -2
- package/lib/{v4/catalog → catalog}/Navigation.js +1 -1
- package/lib/{v4/controls → controls}/AbstractControls.d.ts +1 -1
- package/lib/{v4/controls → controls}/codeEditor/MonacoEditor.d.ts +1 -1
- package/lib/{v4/controls → controls}/geojsoneditor/GeoEditor.d.ts +1 -1
- package/lib/{v4/controls → controls}/jsoneditor/JsonEditor.d.ts +1 -1
- package/lib/{v4/controls → controls}/markdown/ToastUiEditor.d.ts +1 -1
- package/lib/{v4/controls → controls}/table/Handsontable.d.ts +1 -1
- package/lib/{v4/controls → controls}/wysiwyg/CKeditor.d.ts +1 -1
- package/lib/{v4/controls → controls}/wysiwyg/CKeditor.js +1 -1
- package/lib/{v4/datatable → datatable}/NodeTable.d.ts +1 -1
- package/lib/media-manager/AbstractMediaManager.d.ts +3 -3
- package/lib/media-manager/AbstractMediaManager.js +2 -2
- package/lib/media-manager/DefaultMediaManager.d.ts +1 -1
- package/lib/media-manager/DefaultMediaManager.js +6 -8
- package/lib/media-manager/Items.d.ts +2 -1
- package/lib/media-manager/Items.js +7 -0
- package/lib/media-manager/helpers/MediaManagerHelper.d.ts +10 -0
- package/lib/media-manager/helpers/MediaManagerHelper.js +27 -10
- package/lib/{v4/model → model}/AbstractModel.d.ts +12 -0
- package/lib/model/AbstractModel.js +178 -0
- package/lib/{v4/model → model}/ModelHandler.js +1 -1
- package/lib/{v4/model → model}/adapter/sequelize.d.ts +3 -4
- package/lib/{v4/model → model}/adapter/sequelize.js +41 -14
- package/lib/notifications/AbstractNotificationService.d.ts +163 -0
- package/lib/notifications/AbstractNotificationService.js +353 -0
- package/lib/notifications/GeneralNotificationService.d.ts +8 -0
- package/lib/notifications/GeneralNotificationService.js +61 -0
- package/lib/notifications/NotificationHandler.d.ts +27 -0
- package/lib/notifications/NotificationHandler.js +41 -0
- package/lib/notifications/SystemNotificationService.d.ts +19 -0
- package/lib/notifications/SystemNotificationService.js +173 -0
- package/lib/{v4/widgets → widgets}/abstractCustom.d.ts +1 -1
- package/lib/{v4/widgets → widgets}/abstractLink.d.ts +1 -1
- package/lib/{v4/widgets → widgets}/abstractWidgetBase.d.ts +1 -1
- package/lib/{v4/widgets → widgets}/widgetHandler.d.ts +2 -2
- package/migrations/index.d.ts +3 -0
- package/migrations/index.js +4 -0
- package/migrations/umzug/0001.d.ts +7 -0
- package/migrations/umzug/0001.js +121 -0
- package/migrations/umzugExports.d.ts +2 -0
- package/migrations/umzugExports.js +9 -0
- package/models/MediaManagerAP.d.ts +9 -3
- package/models/MediaManagerAP.js +11 -5
- package/models/MediaManagerAssociationsAP.d.ts +9 -3
- package/models/MediaManagerAssociationsAP.js +11 -5
- package/models/MediaManagerMetaAP.d.ts +9 -3
- package/models/MediaManagerMetaAP.js +11 -5
- package/models/NotificationAP.d.ts +44 -0
- package/models/NotificationAP.js +30 -0
- package/models/UserAP.d.ts +1 -1
- package/models/UserNotificationAP.d.ts +26 -0
- package/models/UserNotificationAP.js +18 -0
- package/package.json +17 -8
- package/system/Router.js +21 -7
- package/system/bindAssets.js +1 -0
- package/system/bindControls.js +6 -6
- package/system/bindInertia.js +9 -3
- package/system/bindMediaManager.js +2 -1
- package/system/bindNavigation.js +3 -4
- package/system/bindNotifications.d.ts +2 -0
- package/system/bindNotifications.js +14 -0
- package/system/bindReqFunctions.js +8 -25
- package/system/defaults.js +6 -0
- package/translations/ar.json +5 -4
- package/translations/cn.json +5 -4
- package/translations/de.json +72 -71
- package/translations/en.json +5 -4
- package/translations/es.json +5 -4
- package/translations/fr.json +5 -4
- package/translations/hi.json +5 -4
- package/translations/jp.json +5 -4
- package/translations/ko.json +5 -4
- package/translations/pt.json +5 -4
- package/translations/ru.json +2 -1
- package/translations/ua.json +5 -4
- package/translations/uz.json +73 -72
- package/translations/vi.json +5 -4
- package/ui/components/VanillaJSONEditor.d.ts +2 -0
- package/ui/components/VanillaJSONEditor.js +101 -0
- package/ui/components/handsontable.d.ts +10 -0
- package/ui/components/handsontable.js +51 -0
- package/ui/components/monaco-editor.d.ts +11 -0
- package/ui/components/monaco-editor.js +57 -0
- package/ui/components/ui/table.d.ts +1 -1
- package/ui/components/ui/table.js +2 -2
- package/ui/hooks/form-state.d.ts +4 -0
- package/ui/hooks/form-state.js +45 -0
- package/ui/hooks/use-appearance.d.ts +7 -0
- package/ui/hooks/use-appearance.js +76 -0
- package/ui/hooks/use-window-size.d.ts +5 -0
- package/ui/hooks/use-window-size.js +21 -0
- package/assets/VanillaJSONEditor-x8tDbio1.js +0 -3833
- package/assets/add-DOaZgoTz.js +0 -1
- package/assets/add-form-BjackXJe.js +0 -1
- package/assets/add-group-Br44s2D6.js +0 -1
- package/assets/add-group-form-YI6KS37x.js +0 -1
- package/assets/add-user-DWcH0s9-.js +0 -1
- package/assets/add-user-form-bv5BNX5A.js +0 -1
- package/assets/app-layout-DecN527i.js +0 -1
- package/assets/app-shell-DHzMWA1L.js +0 -1
- package/assets/app-sidebar-4JFP8UdU.js +0 -1
- package/assets/app-sidebar-header-CIKLfLsF.js +0 -1
- package/assets/breadcrumbs-Bo2z-LNC.js +0 -1
- package/assets/catalog-DVyrGTNR.js +0 -6
- package/assets/dashboard-Dy3pk9t7.js +0 -3
- package/assets/date-picker-B2z-aeuv.js +0 -1
- package/assets/debounce-D6eaSoDg.js +0 -1
- package/assets/del-modal-l3LxJWCN.js +0 -1
- package/assets/dynamic-controls-CSVVCONh.js +0 -1
- package/assets/field-renderer-BHo944Cn.js +0 -407
- package/assets/form-BXfnz6h5.js +0 -1
- package/assets/form-hP8sjbND.js +0 -1
- package/assets/form-state-702tb_BV.js +0 -1
- package/assets/geo-json-CVaNUZbb.js +0 -4
- package/assets/handsontable-CZQ5Ietj.js +0 -347
- package/assets/home-BD4MWmm9.js +0 -2
- package/assets/icon-C_X2cQ6W.js +0 -1
- package/assets/index-C148XJoK.js +0 -1
- package/assets/index-Vcq4gwWv.js +0 -1
- package/assets/init-user-DgCD4ZN1.js +0 -1
- package/assets/input-error-Bab-HPbn.js +0 -1
- package/assets/list-C-27EH39.js +0 -1
- package/assets/list-table-_OiQmr2A.js +0 -22
- package/assets/login-jzvVXgAV.js +0 -1
- package/assets/material-icon-DsiW0iQG.js +0 -1
- package/assets/media-manager-CSB_XRcE.js +0 -14
- package/assets/module-Ceq6vuwa.js +0 -1
- package/assets/monaco-editor-XX8Hz76f.js +0 -11
- package/assets/multi-select-BbKSrMw2.js +0 -1
- package/assets/nav-footer-B8gMV6Wp.js +0 -1
- package/assets/nav-main-BUp9qA8R.js +0 -1
- package/assets/nav-user-DeF3SDRj.js +0 -1
- package/assets/pagination-render-B9U_hVbB.js +0 -1
- package/assets/register-C6acWsLe.js +0 -1
- package/assets/theme-switcher-D8T9NgWG.js +0 -1
- package/assets/toast-editor-C0550yms.js +0 -845
- package/assets/use-appearance-CXP5cHaQ.js +0 -1
- package/assets/use-window-size-DLSEoR6e.js +0 -1
- package/assets/user-info-CAgkqARx.js +0 -1
- package/assets/user-menu-content-BxL0QMC9.js +0 -1
- package/assets/vanilla-picker-B6E6ObS_.js +0 -8
- package/assets/welcome-Dk7GoS5R.js +0 -1
- package/lib/v4/model/AbstractModel.js +0 -67
- package/lib/v4/model/adapter/typeorm.d.ts +0 -23
- package/lib/v4/model/adapter/typeorm.js +0 -68
- package/migrations/20230303171134-adminpanel-init.js +0 -122
- package/migrations/20231223215835-add-widgets.js +0 -32
- package/migrations/20240804191001-added-navigationap.js +0 -53
- package/migrations/20241121184236-v4-init.js +0 -134
- /package/lib/{v4/I18n.d.ts → I18n.d.ts} +0 -0
- /package/lib/{v4/POWCaptcha.d.ts → POWCaptcha.d.ts} +0 -0
- /package/lib/{v4/POWCaptcha.js → POWCaptcha.js} +0 -0
- /package/lib/{v4/catalog → catalog}/AbstractCatalog.js +0 -0
- /package/lib/{v4/catalog → catalog}/CatalogHandler.d.ts +0 -0
- /package/lib/{v4/catalog → catalog}/CatalogHandler.js +0 -0
- /package/lib/{v4/controls → controls}/AbstractControls.js +0 -0
- /package/lib/{v4/controls → controls}/ControlsHandler.d.ts +0 -0
- /package/lib/{v4/controls → controls}/ControlsHandler.js +0 -0
- /package/lib/{v4/controls → controls}/codeEditor/MonacoEditor.js +0 -0
- /package/lib/{v4/controls → controls}/geojsoneditor/GeoEditor.js +0 -0
- /package/lib/{v4/controls → controls}/jsoneditor/JsonEditor.js +0 -0
- /package/lib/{v4/controls → controls}/markdown/ToastUiEditor.js +0 -0
- /package/lib/{v4/controls → controls}/table/Handsontable.js +0 -0
- /package/lib/{v4/datatable → datatable}/NodeTable.js +0 -0
- /package/lib/{v4/helper → helper}/jwt.d.ts +0 -0
- /package/lib/{v4/helper → helper}/jwt.js +0 -0
- /package/lib/{v4/inertia → inertia}/flash.d.ts +0 -0
- /package/lib/{v4/inertia → inertia}/flash.js +0 -0
- /package/lib/{v4/inertia → inertia}/inertiaAdapter.d.ts +0 -0
- /package/lib/{v4/inertia → inertia}/inertiaAdapter.js +0 -0
- /package/lib/{v4/model → model}/ModelHandler.d.ts +0 -0
- /package/lib/{v4/model → model}/adapter/waterline.d.ts +0 -0
- /package/lib/{v4/model → model}/adapter/waterline.js +0 -0
- /package/lib/{v4/widgets → widgets}/abstractAction.d.ts +0 -0
- /package/lib/{v4/widgets → widgets}/abstractAction.js +0 -0
- /package/lib/{v4/widgets → widgets}/abstractCustom.js +0 -0
- /package/lib/{v4/widgets → widgets}/abstractInfo.d.ts +0 -0
- /package/lib/{v4/widgets → widgets}/abstractInfo.js +0 -0
- /package/lib/{v4/widgets → widgets}/abstractLink.js +0 -0
- /package/lib/{v4/widgets → widgets}/abstractSwitch.d.ts +0 -0
- /package/lib/{v4/widgets → widgets}/abstractSwitch.js +0 -0
- /package/lib/{v4/widgets → widgets}/abstractWidgetBase.js +0 -0
- /package/lib/{v4/widgets → widgets}/widgetHandler.js +0 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
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
|
+
*/
|
|
48
|
+
export class AbstractNotificationService extends EventEmitter {
|
|
49
|
+
clients = new Map();
|
|
50
|
+
adminizer;
|
|
51
|
+
constructor(adminizer) {
|
|
52
|
+
super();
|
|
53
|
+
this.adminizer = adminizer;
|
|
54
|
+
this._bindAccessRight();
|
|
55
|
+
}
|
|
56
|
+
_bindAccessRight() {
|
|
57
|
+
setTimeout(() => {
|
|
58
|
+
this.adminizer.accessRightsHelper.registerToken({
|
|
59
|
+
id: `notification-${this.notificationClass}`,
|
|
60
|
+
name: this.notificationClass,
|
|
61
|
+
description: `Access to notification ${this.notificationClass}`,
|
|
62
|
+
department: 'notification',
|
|
63
|
+
});
|
|
64
|
+
}, 100);
|
|
65
|
+
}
|
|
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
|
+
*/
|
|
74
|
+
addClient(clientId, sendFn, user) {
|
|
75
|
+
const userId = user.id;
|
|
76
|
+
// Если у пользователя еще нет Map клиентов - создаем
|
|
77
|
+
if (!this.clients.has(userId)) {
|
|
78
|
+
this.clients.set(userId, new Map());
|
|
79
|
+
}
|
|
80
|
+
// Получаем Map клиентов пользователя и добавляем нового клиента
|
|
81
|
+
const userClients = this.clients.get(userId);
|
|
82
|
+
userClients.set(clientId, sendFn);
|
|
83
|
+
Adminizer.log.info(`[${this.notificationClass}] Client ${clientId} connected for user ${userId}. Total users: ${this.clients.size}, user clients: ${userClients.size}`);
|
|
84
|
+
}
|
|
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
|
+
*/
|
|
90
|
+
removeClient(clientId) {
|
|
91
|
+
// Ищем клиента во всех пользовательских Map
|
|
92
|
+
for (const [userId, userClients] of this.clients.entries()) {
|
|
93
|
+
if (userClients.has(clientId)) {
|
|
94
|
+
userClients.delete(clientId);
|
|
95
|
+
// Если у пользователя больше нет клиентов - удаляем его Map
|
|
96
|
+
if (userClients.size === 0) {
|
|
97
|
+
this.clients.delete(userId);
|
|
98
|
+
}
|
|
99
|
+
Adminizer.log.info(`[${this.notificationClass}] Client ${clientId} disconnected from user ${userId}. Total users: ${this.clients.size}`);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
Adminizer.log.warn(`[${this.notificationClass}] Client ${clientId} not found for removal`);
|
|
104
|
+
}
|
|
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
|
+
*/
|
|
111
|
+
getUserClients(userId) {
|
|
112
|
+
return this.clients.get(userId) || new Map();
|
|
113
|
+
}
|
|
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
|
+
*/
|
|
118
|
+
getClientCount() {
|
|
119
|
+
return this.clients.size;
|
|
120
|
+
}
|
|
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
|
+
*/
|
|
126
|
+
getAllClients() {
|
|
127
|
+
const allClients = new Map();
|
|
128
|
+
for (const userClients of this.clients.values()) {
|
|
129
|
+
for (const [clientId, sendFn] of userClients.entries()) {
|
|
130
|
+
allClients.set(clientId, sendFn);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return allClients;
|
|
134
|
+
}
|
|
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
|
+
*/
|
|
141
|
+
broadcast(event) {
|
|
142
|
+
for (const userClients of this.clients.values()) {
|
|
143
|
+
userClients.forEach((sendFn, clientId) => {
|
|
144
|
+
try {
|
|
145
|
+
sendFn(event);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
Adminizer.log.error(`[${this.notificationClass}] Error sending to client ${clientId}:`, error);
|
|
149
|
+
this.removeClient(clientId);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
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
|
+
*/
|
|
159
|
+
sendHeartbeat(clientId) {
|
|
160
|
+
for (const userClients of this.clients.values()) {
|
|
161
|
+
const sendFn = userClients.get(clientId);
|
|
162
|
+
if (sendFn) {
|
|
163
|
+
const heartbeatEvent = {
|
|
164
|
+
type: 'heartbeat',
|
|
165
|
+
data: 'ping'
|
|
166
|
+
};
|
|
167
|
+
sendFn(heartbeatEvent);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
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
|
+
*/
|
|
179
|
+
async createUserNotification(notificationId, userId) {
|
|
180
|
+
if (this.adminizer.modelHandler.model.has('usernotificationap') && userId) {
|
|
181
|
+
try {
|
|
182
|
+
await this.adminizer.modelHandler.model.get('usernotificationap')["_create"]({
|
|
183
|
+
userId: userId,
|
|
184
|
+
notificationId: notificationId,
|
|
185
|
+
read: false
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
Adminizer.log.error(`Error creating user notification record:`, error);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
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
|
+
*/
|
|
200
|
+
async getUserNotification(notificationId, userId) {
|
|
201
|
+
if (this.adminizer.modelHandler.model.has('usernotificationap')) {
|
|
202
|
+
try {
|
|
203
|
+
return await this.adminizer.modelHandler.model.get('usernotificationap')["_findOne"]({
|
|
204
|
+
where: { notificationId: notificationId, userId: userId }
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
Adminizer.log.error(`Error getting user notification:`, error);
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
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
|
+
*/
|
|
222
|
+
async getNotifications(userId, limit = 20, skip = 0, unreadOnly = false) {
|
|
223
|
+
if (!this.adminizer.modelHandler.model.has('notificationap')) {
|
|
224
|
+
return [];
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
let query = { notificationClass: this.notificationClass };
|
|
228
|
+
// Если запрашиваются уведомления для конкретного пользователя получаем ID уведомлений пользователя
|
|
229
|
+
const userNotifications = await this.adminizer.modelHandler.model.get('usernotificationap')["_find"]({
|
|
230
|
+
where: {
|
|
231
|
+
userId: userId
|
|
232
|
+
}
|
|
233
|
+
}, { populate: [['notificationId', {}]] });
|
|
234
|
+
const notificationIds = userNotifications.map((un) => un.notificationId.id);
|
|
235
|
+
if (unreadOnly) {
|
|
236
|
+
// Только непрочитанные
|
|
237
|
+
query.id = userNotifications
|
|
238
|
+
.filter((un) => !un.read)
|
|
239
|
+
.map((un) => un.notificationId.id);
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
query.id = notificationIds;
|
|
243
|
+
}
|
|
244
|
+
if (query.id.length === 0)
|
|
245
|
+
return [];
|
|
246
|
+
const notificationsDB = await this.adminizer.modelHandler.model.get('notificationap')["_find"]({
|
|
247
|
+
where: query,
|
|
248
|
+
sort: 'createdAt DESC',
|
|
249
|
+
limit: limit,
|
|
250
|
+
skip: skip
|
|
251
|
+
});
|
|
252
|
+
return await this.prepareNotification(notificationsDB, userId);
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
Adminizer.log.error('Error fetching notifications:', error);
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
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
|
+
*/
|
|
265
|
+
async prepareNotification(notificationsDB, userId) {
|
|
266
|
+
let notifications = [];
|
|
267
|
+
for (const notification of notificationsDB) {
|
|
268
|
+
let readStatus = false;
|
|
269
|
+
// Получаем статус прочтения из UserNotificationAP
|
|
270
|
+
const userNotification = await this.getUserNotification(notification.id, userId);
|
|
271
|
+
readStatus = userNotification ? userNotification.read : false;
|
|
272
|
+
notifications.push({
|
|
273
|
+
...notification,
|
|
274
|
+
read: readStatus,
|
|
275
|
+
icon: {
|
|
276
|
+
icon: this.icon,
|
|
277
|
+
iconColor: this.iconColor
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
return notifications;
|
|
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
|
+
*/
|
|
289
|
+
async search(s, userId) {
|
|
290
|
+
try {
|
|
291
|
+
const userNotifications = await this.adminizer.modelHandler.model.get('usernotificationap')["_find"]({
|
|
292
|
+
where: {
|
|
293
|
+
userId: userId
|
|
294
|
+
}
|
|
295
|
+
}, { populate: [['notificationId', {}]] });
|
|
296
|
+
const notificationIds = userNotifications.map((un) => un.notificationId.id);
|
|
297
|
+
if (notificationIds.length === 0)
|
|
298
|
+
return [];
|
|
299
|
+
const notificationsDB = await this.adminizer.modelHandler.model.get('notificationap')["_find"]({
|
|
300
|
+
where: {
|
|
301
|
+
message: { contains: s },
|
|
302
|
+
id: notificationIds,
|
|
303
|
+
notificationClass: this.notificationClass
|
|
304
|
+
},
|
|
305
|
+
sort: "createdAt DESC"
|
|
306
|
+
});
|
|
307
|
+
return await this.prepareNotification(notificationsDB, userId);
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
Adminizer.log.error('Error searching notifications:', error);
|
|
311
|
+
return [];
|
|
312
|
+
}
|
|
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
|
+
*/
|
|
320
|
+
async markAsRead(userId, id) {
|
|
321
|
+
try {
|
|
322
|
+
const userNotification = await this.getUserNotification(id, userId);
|
|
323
|
+
if (userNotification) {
|
|
324
|
+
await this.adminizer.modelHandler.model.get('usernotificationap')["_update"]({
|
|
325
|
+
id: userNotification.id
|
|
326
|
+
}, { read: true });
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
Adminizer.log.error('Error marking notification as read:', error);
|
|
331
|
+
}
|
|
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
|
+
*/
|
|
338
|
+
async markAllAsRead(userId) {
|
|
339
|
+
try {
|
|
340
|
+
const userNotifications = await this.adminizer.modelHandler.model.get('usernotificationap')["_find"]({
|
|
341
|
+
where: { userId: userId }
|
|
342
|
+
});
|
|
343
|
+
for (const userNotification of userNotifications) {
|
|
344
|
+
await this.adminizer.modelHandler.model.get('usernotificationap')["_update"]({
|
|
345
|
+
where: { id: userNotification.id },
|
|
346
|
+
}, { read: true });
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
Adminizer.log.error('Error marking all notifications as read:', error);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AbstractNotificationService } from './AbstractNotificationService';
|
|
2
|
+
import { INotification } from '../../interfaces/types';
|
|
3
|
+
export declare class GeneralNotificationService extends AbstractNotificationService {
|
|
4
|
+
readonly notificationClass = "general";
|
|
5
|
+
readonly icon: string;
|
|
6
|
+
readonly iconColor: string;
|
|
7
|
+
dispatchNotification(notification: Omit<INotification, 'id' | 'createdAt' | 'notificationClass' | 'icon'>): Promise<boolean>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { AbstractNotificationService } from './AbstractNotificationService';
|
|
2
|
+
import { Adminizer } from '../Adminizer';
|
|
3
|
+
export class GeneralNotificationService extends AbstractNotificationService {
|
|
4
|
+
notificationClass = 'general';
|
|
5
|
+
icon = 'info';
|
|
6
|
+
iconColor = '#5987de';
|
|
7
|
+
async dispatchNotification(notification) {
|
|
8
|
+
const fullNotification = {
|
|
9
|
+
...notification,
|
|
10
|
+
notificationClass: this.notificationClass
|
|
11
|
+
};
|
|
12
|
+
let notificationDB;
|
|
13
|
+
// Сохраняем в базу
|
|
14
|
+
if (this.adminizer.modelHandler.model.has('notificationap')) {
|
|
15
|
+
try {
|
|
16
|
+
notificationDB = await this.adminizer.modelHandler.model.get('notificationap')["_create"](fullNotification);
|
|
17
|
+
if (notification.userId) {
|
|
18
|
+
// For specific user
|
|
19
|
+
await this.createUserNotification(notificationDB.id, notification.userId);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
// For all users
|
|
23
|
+
const users = await this.adminizer.modelHandler.model.get('userap')["_find"]({});
|
|
24
|
+
for (const user of users) {
|
|
25
|
+
try {
|
|
26
|
+
if (this.adminizer.accessRightsHelper.hasPermission(`notification-${this.notificationClass}`, user)) {
|
|
27
|
+
await this.createUserNotification(notificationDB.id, user.id);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
Adminizer.log.error(`[${this.notificationClass}] User ${user.id} doesn't have permission to receive this notification`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
Adminizer.log.error('Error creating UserNotificationAP:', error);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const event = {
|
|
39
|
+
type: 'notification',
|
|
40
|
+
data: {
|
|
41
|
+
...notificationDB,
|
|
42
|
+
read: false,
|
|
43
|
+
icon: {
|
|
44
|
+
icon: this.icon,
|
|
45
|
+
iconColor: this.iconColor
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
notificationClass: this.notificationClass,
|
|
49
|
+
userId: notification.userId ?? null
|
|
50
|
+
};
|
|
51
|
+
this.broadcast(event);
|
|
52
|
+
Adminizer.log.info(`[General] Notification dispatched: ${fullNotification.title}`);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
Adminizer.log.error('Error saving notification to database:', error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import { AbstractNotificationService } from "./AbstractNotificationService";
|
|
3
|
+
import { INotification } from '../../interfaces/types';
|
|
4
|
+
export declare class NotificationHandler extends EventEmitter {
|
|
5
|
+
private services;
|
|
6
|
+
constructor();
|
|
7
|
+
/**
|
|
8
|
+
* Register a new notification service
|
|
9
|
+
* @param service
|
|
10
|
+
*/
|
|
11
|
+
registerService(service: AbstractNotificationService): void;
|
|
12
|
+
/**
|
|
13
|
+
* Get a notification service by class
|
|
14
|
+
* @param notificationClass
|
|
15
|
+
*/
|
|
16
|
+
getService(notificationClass: string): AbstractNotificationService | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Get all registered services
|
|
19
|
+
*/
|
|
20
|
+
getAllServices(): AbstractNotificationService[];
|
|
21
|
+
/**
|
|
22
|
+
* Dispatch a notification to a service
|
|
23
|
+
* @param notificationClass
|
|
24
|
+
* @param notification
|
|
25
|
+
*/
|
|
26
|
+
dispatchNotification(notificationClass: string, notification: Omit<INotification, 'id' | 'createdAt' | 'notificationClass' | 'icon'>): Promise<boolean>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import { Adminizer } from '../Adminizer';
|
|
3
|
+
export class NotificationHandler extends EventEmitter {
|
|
4
|
+
services = new Map();
|
|
5
|
+
constructor() {
|
|
6
|
+
super();
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Register a new notification service
|
|
10
|
+
* @param service
|
|
11
|
+
*/
|
|
12
|
+
registerService(service) {
|
|
13
|
+
this.services.set(service.notificationClass, service);
|
|
14
|
+
Adminizer.log.info(`Notification service registered: ${service.notificationClass}`);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Get a notification service by class
|
|
18
|
+
* @param notificationClass
|
|
19
|
+
*/
|
|
20
|
+
getService(notificationClass) {
|
|
21
|
+
return this.services.get(notificationClass);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get all registered services
|
|
25
|
+
*/
|
|
26
|
+
getAllServices() {
|
|
27
|
+
return Array.from(this.services.values());
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Dispatch a notification to a service
|
|
31
|
+
* @param notificationClass
|
|
32
|
+
* @param notification
|
|
33
|
+
*/
|
|
34
|
+
async dispatchNotification(notificationClass, notification) {
|
|
35
|
+
const service = this.getService(notificationClass);
|
|
36
|
+
if (!service) {
|
|
37
|
+
throw new Error(`Notification service not found: ${notificationClass}`);
|
|
38
|
+
}
|
|
39
|
+
return service.dispatchNotification(notification);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { AbstractNotificationService } from './AbstractNotificationService';
|
|
2
|
+
import { INotification } from '../../interfaces/types';
|
|
3
|
+
export declare class SystemNotificationService extends AbstractNotificationService {
|
|
4
|
+
readonly notificationClass = "system";
|
|
5
|
+
readonly icon: string;
|
|
6
|
+
readonly iconColor: string;
|
|
7
|
+
private crudChannels;
|
|
8
|
+
dispatchNotification(notification: Omit<INotification, 'id' | 'createdAt' | 'notificationClass' | 'icon'>): Promise<boolean>;
|
|
9
|
+
private broadcastToChannel;
|
|
10
|
+
addClientToChannel(clientId: string, channel: string, userId: number): void;
|
|
11
|
+
removeClientFromChannel(clientId: string, channel: string, userId: number): void;
|
|
12
|
+
removeClientFromAllChannels(clientId: string, userId: number): void;
|
|
13
|
+
removeClient(clientId: string): void;
|
|
14
|
+
logSystemEvent(title: string, message: string, channel?: string, metadata?: Record<string | number, any>): Promise<boolean>;
|
|
15
|
+
logCreatedEvent(title: string, message: string, metadata?: Record<string | number, any>): Promise<boolean>;
|
|
16
|
+
logUpdatedEvent(title: string, message: string, metadata?: Record<string | number, any>): Promise<boolean>;
|
|
17
|
+
logDeletedEvent(title: string, message: string, metadata?: Record<string | number, any>): Promise<boolean>;
|
|
18
|
+
getUserChannels(userId: number): Map<string, Set<string>>;
|
|
19
|
+
}
|