adminizer 4.3.0-build.80 → 4.3.0-build.81
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-ChaPnvFv.js +1 -0
- package/assets/{add-form-C04ZI5LT.js → add-form-DNqlORQQ.js} +1 -1
- package/assets/add-group-D6YsQVrC.js +1 -0
- package/assets/{add-group-form-CyRwxTs3.js → add-group-form-CDi1y4af.js} +1 -1
- package/assets/{add-user-form-yp020h4r.js → add-user-form-BzXHahSY.js} +1 -1
- package/assets/add-user-psdNyGx8.js +1 -0
- package/assets/{app-CpGtjhxF.css → app-PLVuwM-H.css} +1 -1
- package/assets/{app-layout-BmT5KQwc.js → app-layout-AWzEe1aH.js} +1 -1
- package/assets/{app-sidebar-4lm9f_CG.js → app-sidebar-Bd4nWVXq.js} +1 -1
- package/assets/app-sidebar-header-jBFp1qZb.js +1 -0
- package/assets/app.js +3 -3
- package/assets/{catalog-CG_EB2kz.js → catalog-XiWBZptC.js} +1 -1
- package/assets/{dashboard-CsmJfdLb.js → dashboard-DcRJErQB.js} +1 -1
- package/assets/date-picker-A6CYw4SC.js +1 -0
- package/assets/del-modal-0klf0PNC.js +1 -0
- package/assets/{field-renderer-Bp1lfm0v.js → field-renderer-CYB5W2He.js} +3 -3
- package/assets/form-B-he6_tT.js +1 -0
- package/assets/{form-CSTeMDf5.js → form-BGRpsR-M.js} +1 -1
- package/assets/{home-D00XjLjD.js → home-B7aZTHTv.js} +2 -2
- package/assets/icon-k6999vVE.js +1 -0
- package/assets/{init-user-L-oxFTOv.js → init-user-Btm16ElL.js} +1 -1
- package/assets/input-error-Dbp_lp61.js +1 -0
- package/assets/list-_tZumymo.js +1 -0
- package/assets/{list-table-vIvydJV2.js → list-table-OzVUH7i4.js} +4 -4
- package/assets/{login-DLMJ6l_r.js → login-DeqxLVx3.js} +1 -1
- package/assets/manifest.json +74 -74
- package/assets/material-icon-B9BY_7p9.js +1 -0
- package/assets/{media-manager-ASGJ4PcS.js → media-manager-DQ5CA_na.js} +1 -1
- package/assets/{module-mxngd2cA.js → module-Drcr0QjC.js} +1 -1
- package/assets/{nav-footer-DbGCtmmN.js → nav-footer-DWHxIgb7.js} +1 -1
- package/assets/{nav-main-BcWhl71g.js → nav-main-CRT9ezos.js} +1 -1
- package/assets/notification-C7K-zK4k.js +27 -0
- package/assets/{register-C_W_hOmq.js → register-CMhPasDj.js} +1 -1
- package/assets/tabs-GDFuClHU.js +1 -0
- package/assets/{theme-switcher-D1G6OSMc.js → theme-switcher-C35zBCil.js} +1 -1
- package/assets/{welcome-FHRCdke0.js → welcome-CbhDDQlg.js} +1 -1
- package/controllers/add.js +1 -1
- package/controllers/catalog/FrontentCatalogAdapter.d.ts +4 -4
- package/controllers/edit.js +1 -15
- package/controllers/list.js +2 -2
- package/controllers/login.js +2 -2
- package/controllers/navigation/edit.js +1 -1
- package/controllers/notifications/NotificationController.d.ts +5 -5
- package/controllers/notifications/NotificationController.js +77 -78
- package/controllers/remove.js +1 -11
- package/controllers/view.js +1 -1
- package/helpers/configHelper.d.ts +1 -1
- package/helpers/fieldsHelper.d.ts +1 -1
- package/helpers/fieldsHelper.js +1 -1
- package/helpers/inertiaAddHelper.d.ts +1 -1
- package/helpers/translationHelper.d.ts +1 -1
- package/helpers/translationHelper.js +1 -1
- package/index.d.ts +14 -13
- package/index.js +14 -13
- package/interfaces/adminpanelConfig.d.ts +1 -1
- package/interfaces/global.d.ts +3 -3
- package/interfaces/types.d.ts +10 -1
- package/lib/Adminizer.d.ts +7 -7
- package/lib/Adminizer.js +7 -7
- package/lib/{v4/DataAccessor.d.ts → DataAccessor.d.ts} +4 -4
- 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/datatable → datatable}/NodeTable.d.ts +1 -1
- 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 +0 -1
- package/lib/{v4/model → model}/adapter/sequelize.js +6 -3
- package/lib/{v4/notifications → notifications}/AbstractNotificationService.d.ts +14 -7
- package/lib/notifications/AbstractNotificationService.js +233 -0
- package/lib/{v4/notifications → notifications}/GeneralNotificationService.d.ts +1 -1
- package/lib/{v4/notifications → notifications}/GeneralNotificationService.js +4 -2
- package/lib/notifications/NotificationHandler.d.ts +27 -0
- package/lib/notifications/NotificationHandler.js +41 -0
- package/lib/{v4/notifications → notifications}/SystemNotificationService.d.ts +5 -4
- 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/index.ts +5 -0
- package/migrations/umzug/0001.d.ts +7 -0
- package/migrations/umzug/0001.js +119 -0
- package/migrations/umzug/0001.ts +132 -0
- package/migrations/umzug.d.ts +2 -0
- package/migrations/umzug.js +8 -0
- package/migrations/umzug.ts +9 -0
- package/models/NotificationAP.d.ts +8 -0
- package/models/NotificationAP.js +8 -0
- package/models/UserAP.d.ts +1 -1
- package/package.json +5 -4
- package/system/Router.js +4 -8
- package/system/bindControls.js +6 -6
- package/system/bindInertia.js +4 -3
- package/system/bindNavigation.js +1 -1
- package/system/bindNotifications.js +3 -3
- package/system/bindReqFunctions.js +8 -25
- package/system/defaults.js +1 -1
- package/ui/components/ui/table.d.ts +1 -1
- package/ui/components/ui/table.js +2 -2
- package/assets/add-DSapOiv0.js +0 -1
- package/assets/add-group-DWR7SBTO.js +0 -1
- package/assets/add-user-acATGxUL.js +0 -1
- package/assets/app-sidebar-header-CxXmzszq.js +0 -1
- package/assets/date-picker-By3kj-Rx.js +0 -1
- package/assets/del-modal-DlcbcdR5.js +0 -1
- package/assets/form-CxHRGp7p.js +0 -1
- package/assets/icon-ButfCBrt.js +0 -1
- package/assets/input-error-CHsDLcK0.js +0 -1
- package/assets/list-4TOTsjqE.js +0 -1
- package/assets/material-icon-sXk4BK-N.js +0 -1
- package/assets/notification-BEfA5sT6.js +0 -1
- package/assets/tabs-CgZW_LLn.js +0 -1
- package/lib/v4/model/AbstractModel.js +0 -79
- package/lib/v4/notifications/AbstractNotificationService.js +0 -150
- package/lib/v4/notifications/NotificationHandler.d.ts +0 -69
- package/lib/v4/notifications/NotificationHandler.js +0 -127
- package/lib/v4/notifications/SystemNotificationService.js +0 -124
- /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/controls → controls}/wysiwyg/CKeditor.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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AbstractControls, ControlType, Config, Path } from "../AbstractControls";
|
|
2
|
-
import { Adminizer } from "
|
|
2
|
+
import { Adminizer } from "../../Adminizer";
|
|
3
3
|
export declare class MonacoEditor extends AbstractControls {
|
|
4
4
|
readonly config: Config;
|
|
5
5
|
readonly name: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AbstractControls, ControlType, Config, Path } from "../AbstractControls";
|
|
2
|
-
import { Adminizer } from "
|
|
2
|
+
import { Adminizer } from "../../Adminizer";
|
|
3
3
|
export declare class GeoEditor extends AbstractControls {
|
|
4
4
|
readonly config: Config;
|
|
5
5
|
readonly name: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AbstractControls, ControlType, Path, Config } from "../AbstractControls";
|
|
2
2
|
import { EditorOptions } from "@toast-ui/editor/types/editor";
|
|
3
|
-
import { Adminizer } from "
|
|
3
|
+
import { Adminizer } from "../../Adminizer";
|
|
4
4
|
export declare class JsonEditor extends AbstractControls {
|
|
5
5
|
readonly config: Partial<EditorOptions>;
|
|
6
6
|
readonly name: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AbstractControls, ControlType, Path, Config } from "../AbstractControls";
|
|
2
2
|
import { EditorOptions } from "@toast-ui/editor/types/editor";
|
|
3
|
-
import { Adminizer } from "
|
|
3
|
+
import { Adminizer } from "../../Adminizer";
|
|
4
4
|
export declare class ToastUiEditor extends AbstractControls {
|
|
5
5
|
readonly config: Partial<EditorOptions>;
|
|
6
6
|
readonly name: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AbstractControls, ControlType, Config, Path } from "../AbstractControls";
|
|
2
|
-
import { Adminizer } from "
|
|
2
|
+
import { Adminizer } from "../../Adminizer";
|
|
3
3
|
export declare class Handsontable extends AbstractControls {
|
|
4
4
|
readonly config: Config;
|
|
5
5
|
readonly name: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AbstractControls, ControlType, Config, Path } from "../AbstractControls";
|
|
2
|
-
import { Adminizer } from "
|
|
2
|
+
import { Adminizer } from "../../Adminizer";
|
|
3
3
|
export declare class CKeditor extends AbstractControls {
|
|
4
4
|
readonly name: string;
|
|
5
5
|
readonly type: ControlType;
|
|
@@ -46,6 +46,18 @@ export declare abstract class AbstractModel<T> {
|
|
|
46
46
|
protected abstract _destroyOne(criteria: Partial<T>): Promise<T | null>;
|
|
47
47
|
protected abstract _destroy(criteria: Partial<T>): Promise<T[]>;
|
|
48
48
|
protected abstract _count(criteria: Partial<T> | undefined): Promise<number>;
|
|
49
|
+
/**
|
|
50
|
+
* Generate diff between old and new records
|
|
51
|
+
*/
|
|
52
|
+
private generateDiff;
|
|
53
|
+
/**
|
|
54
|
+
* Generate delete diff (record vs empty object)
|
|
55
|
+
*/
|
|
56
|
+
private generateDeleteDiff;
|
|
57
|
+
/**
|
|
58
|
+
* Log system event with diff
|
|
59
|
+
*/
|
|
60
|
+
private logSystemEvent;
|
|
49
61
|
create(data: T, dataAccessor: DataAccessor): Promise<Partial<T>>;
|
|
50
62
|
findOne(criteria: Partial<T>, dataAccessor: DataAccessor): Promise<Partial<T> | null>;
|
|
51
63
|
find(criteria: Partial<T>, dataAccessor: DataAccessor): Promise<Partial<T>[]>;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { formatChanges, sanitizeForDiff } from "../../helpers/diffHelpers";
|
|
2
|
+
import { diff } from "deep-object-diff";
|
|
3
|
+
export class AbstractModel {
|
|
4
|
+
modelname;
|
|
5
|
+
attributes;
|
|
6
|
+
primaryKey;
|
|
7
|
+
identity;
|
|
8
|
+
constructor(modelname, attributes, primaryKey, identity) {
|
|
9
|
+
this.modelname = modelname;
|
|
10
|
+
this.attributes = attributes;
|
|
11
|
+
this.primaryKey = primaryKey;
|
|
12
|
+
this.identity = identity;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Generate diff between old and new records
|
|
16
|
+
*/
|
|
17
|
+
generateDiff(oldRecord, newRecord) {
|
|
18
|
+
const cleanOldRecord = sanitizeForDiff(oldRecord);
|
|
19
|
+
const cleanNewRecord = sanitizeForDiff(newRecord);
|
|
20
|
+
const changesDiff = diff(cleanOldRecord, cleanNewRecord);
|
|
21
|
+
const formattedChanges = formatChanges(changesDiff, cleanOldRecord, cleanNewRecord);
|
|
22
|
+
return {
|
|
23
|
+
changesDiff,
|
|
24
|
+
formattedChanges,
|
|
25
|
+
cleanOldRecord,
|
|
26
|
+
cleanNewRecord
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Generate delete diff (record vs empty object)
|
|
31
|
+
*/
|
|
32
|
+
generateDeleteDiff(record) {
|
|
33
|
+
const cleanRecord = sanitizeForDiff(record);
|
|
34
|
+
const changesDiff = diff(cleanRecord, {});
|
|
35
|
+
const formattedChanges = formatChanges(changesDiff, cleanRecord, {});
|
|
36
|
+
return {
|
|
37
|
+
changesDiff,
|
|
38
|
+
formattedChanges,
|
|
39
|
+
cleanRecord
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Log system event with diff
|
|
44
|
+
*/
|
|
45
|
+
async logSystemEvent(dataAccessor, eventType, message, oldRecord, newRecord) {
|
|
46
|
+
let formattedChanges = [];
|
|
47
|
+
let summary = '';
|
|
48
|
+
switch (eventType) {
|
|
49
|
+
case 'Created':
|
|
50
|
+
const cleanNewRecord = sanitizeForDiff(newRecord);
|
|
51
|
+
formattedChanges = formatChanges({}, {}, cleanNewRecord, 'add');
|
|
52
|
+
summary = `Created ${formattedChanges.length} fields`;
|
|
53
|
+
break;
|
|
54
|
+
case 'Updated':
|
|
55
|
+
const { formattedChanges: updateChanges } = this.generateDiff(oldRecord, newRecord);
|
|
56
|
+
formattedChanges = updateChanges;
|
|
57
|
+
summary = `Changes ${formattedChanges.length} fields`;
|
|
58
|
+
break;
|
|
59
|
+
case 'Deleted':
|
|
60
|
+
const { formattedChanges: deleteChanges } = this.generateDeleteDiff(oldRecord);
|
|
61
|
+
formattedChanges = deleteChanges;
|
|
62
|
+
summary = `Deleted ${formattedChanges.length} fields`;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
// log system event notification
|
|
66
|
+
await dataAccessor.adminizer.logSystemEvent(eventType, message, {
|
|
67
|
+
changes: formattedChanges,
|
|
68
|
+
summary
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
async create(data, dataAccessor) {
|
|
72
|
+
let _data = await dataAccessor.setUserRelationAccess(dataAccessor.process(data));
|
|
73
|
+
let record = await this._create(_data);
|
|
74
|
+
// Log creation event
|
|
75
|
+
await this.logSystemEvent(dataAccessor, 'Created',
|
|
76
|
+
// @ts-ignore
|
|
77
|
+
`user ${dataAccessor.user.login} create ${dataAccessor.entity.name} ${record[this.primaryKey]}`, {}, _data);
|
|
78
|
+
return dataAccessor.process(record);
|
|
79
|
+
}
|
|
80
|
+
async findOne(criteria, dataAccessor) {
|
|
81
|
+
criteria = await dataAccessor.sanitizeUserRelationAccess(criteria);
|
|
82
|
+
let record = await this._findOne(criteria);
|
|
83
|
+
return record ? dataAccessor.process(record) : null;
|
|
84
|
+
}
|
|
85
|
+
async find(criteria, dataAccessor) {
|
|
86
|
+
criteria = await dataAccessor.sanitizeUserRelationAccess(criteria);
|
|
87
|
+
let records = await this._find(criteria);
|
|
88
|
+
return records.map(record => dataAccessor.process(record));
|
|
89
|
+
}
|
|
90
|
+
async updateOne(criteria, data, dataAccessor) {
|
|
91
|
+
let _data = dataAccessor.process(data);
|
|
92
|
+
// Get the old record first for diff
|
|
93
|
+
criteria = await dataAccessor.sanitizeUserRelationAccess(criteria);
|
|
94
|
+
const oldRecord = await this._findOne(criteria);
|
|
95
|
+
if (!oldRecord) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
let record = await this._updateOne(criteria, _data);
|
|
99
|
+
if (!record) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
// Log update event
|
|
103
|
+
await this.logSystemEvent(dataAccessor, 'Updated',
|
|
104
|
+
// @ts-ignore
|
|
105
|
+
`user ${dataAccessor.user.login} update ${dataAccessor.entity.name} ${record[this.primaryKey]}`, oldRecord, record);
|
|
106
|
+
return dataAccessor.process(record);
|
|
107
|
+
}
|
|
108
|
+
async update(criteria, data, dataAccessor) {
|
|
109
|
+
let _data = dataAccessor.process(data);
|
|
110
|
+
criteria = await dataAccessor.sanitizeUserRelationAccess(criteria);
|
|
111
|
+
// Get old records automatically
|
|
112
|
+
const oldRecords = await this._find(criteria);
|
|
113
|
+
let records = await this._update(criteria, _data);
|
|
114
|
+
// Log update events for each record
|
|
115
|
+
for (let i = 0; i < records.length; i++) {
|
|
116
|
+
const record = records[i];
|
|
117
|
+
const oldRecord = oldRecords[i];
|
|
118
|
+
if (oldRecord) {
|
|
119
|
+
await this.logSystemEvent(dataAccessor, 'Updated',
|
|
120
|
+
// @ts-ignore
|
|
121
|
+
`user ${dataAccessor.user.login} update ${dataAccessor.entity.name} ${record[this.primaryKey]}`, oldRecord, record);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return records.map(record => dataAccessor.process(record));
|
|
125
|
+
}
|
|
126
|
+
async destroyOne(criteria, dataAccessor) {
|
|
127
|
+
// Get the record first for diff
|
|
128
|
+
criteria = await dataAccessor.sanitizeUserRelationAccess(criteria);
|
|
129
|
+
const oldRecord = await this._findOne(criteria);
|
|
130
|
+
if (!oldRecord) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
let record = await this._destroyOne(criteria);
|
|
134
|
+
if (!record) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
// Log delete event
|
|
138
|
+
await this.logSystemEvent(dataAccessor, 'Deleted',
|
|
139
|
+
//@ts-ignore
|
|
140
|
+
`user ${dataAccessor.user.login} delete ${dataAccessor.entity.name} ${record[this.primaryKey]}`, oldRecord, {});
|
|
141
|
+
return dataAccessor.process(record);
|
|
142
|
+
}
|
|
143
|
+
async destroy(criteria, dataAccessor) {
|
|
144
|
+
// Get records first for diff
|
|
145
|
+
criteria = await dataAccessor.sanitizeUserRelationAccess(criteria);
|
|
146
|
+
const oldRecords = await this._find(criteria);
|
|
147
|
+
let records = await this._destroy(criteria);
|
|
148
|
+
// Log delete events for each record
|
|
149
|
+
for (let i = 0; i < records.length; i++) {
|
|
150
|
+
const record = records[i];
|
|
151
|
+
const oldRecord = oldRecords[i];
|
|
152
|
+
if (oldRecord) {
|
|
153
|
+
await this.logSystemEvent(dataAccessor, 'Deleted',
|
|
154
|
+
//@ts-ignore
|
|
155
|
+
`user ${dataAccessor.user.login} delete ${dataAccessor.entity.name} ${record[this.primaryKey]}`, oldRecord, {});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return records.map(record => dataAccessor.process(record));
|
|
159
|
+
}
|
|
160
|
+
async count(criteria, dataAccessor) {
|
|
161
|
+
criteria = await dataAccessor.sanitizeUserRelationAccess(criteria);
|
|
162
|
+
return this._count(criteria);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
export class AbstractAdapter {
|
|
166
|
+
orm;
|
|
167
|
+
ormType;
|
|
168
|
+
constructor(type, orm) {
|
|
169
|
+
this.ormType = type;
|
|
170
|
+
this.orm = orm;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* This method must ensure system models registration and throw an error in case of errors. We force all adapters to realize it
|
|
174
|
+
*/
|
|
175
|
+
static registerSystemModels(orm) {
|
|
176
|
+
throw new Error("Function 'registerSystemModels' must be implemented by subclass");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -26,7 +26,6 @@ export declare class SequelizeModel<T> extends AbstractModel<T> {
|
|
|
26
26
|
export declare class SequelizeAdapter extends AbstractAdapter {
|
|
27
27
|
private sequelize;
|
|
28
28
|
Model: typeof SequelizeModel;
|
|
29
|
-
alter: boolean;
|
|
30
29
|
constructor(sequelize: Sequelize);
|
|
31
30
|
get models(): Record<string, any>;
|
|
32
31
|
getModel(modelName: string): any;
|
|
@@ -15,7 +15,11 @@ function generateAssociationsFromSchema(models, schemas) {
|
|
|
15
15
|
const targetModel = models[field.collection];
|
|
16
16
|
const targetSchema = schemas[field.collection];
|
|
17
17
|
const inverseField = targetSchema?.[field.via];
|
|
18
|
-
|
|
18
|
+
// Normalize join table to lowercase for consistency across dialects
|
|
19
|
+
const throughTableName = [modelName, field.collection]
|
|
20
|
+
.sort()
|
|
21
|
+
.join("")
|
|
22
|
+
.toLowerCase();
|
|
19
23
|
// 💡 M:N связь
|
|
20
24
|
if (inverseField && inverseField.collection === modelName) {
|
|
21
25
|
model.belongsToMany(targetModel, {
|
|
@@ -533,7 +537,6 @@ export class SequelizeModel extends AbstractModel {
|
|
|
533
537
|
export class SequelizeAdapter extends AbstractAdapter {
|
|
534
538
|
sequelize;
|
|
535
539
|
Model = SequelizeModel;
|
|
536
|
-
alter = false;
|
|
537
540
|
constructor(sequelize) {
|
|
538
541
|
super("sequelize", sequelize);
|
|
539
542
|
this.sequelize = sequelize;
|
|
@@ -575,7 +578,7 @@ export class SequelizeAdapter extends AbstractAdapter {
|
|
|
575
578
|
generateSequelizeModel(sequelize, modelName, definition);
|
|
576
579
|
}
|
|
577
580
|
generateAssociationsFromSchema(sequelize.models, schemas);
|
|
578
|
-
if (alter
|
|
581
|
+
if (alter) {
|
|
579
582
|
await sequelize.sync();
|
|
580
583
|
}
|
|
581
584
|
}
|
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
|
-
import { Adminizer } from '
|
|
3
|
-
import { INotification, INotificationEvent } from '
|
|
2
|
+
import { Adminizer } from '../Adminizer';
|
|
3
|
+
import { INotification, INotificationEvent } from '../../interfaces/types';
|
|
4
|
+
import { NotificationAPModel } from "../../models/NotificationAP";
|
|
5
|
+
import { UserAP } from "../../models/UserAP";
|
|
4
6
|
export declare abstract class AbstractNotificationService extends EventEmitter {
|
|
5
|
-
protected clients: Map<string, (event: INotificationEvent) => void
|
|
7
|
+
protected clients: Map<number, Map<string, (event: INotificationEvent) => void>>;
|
|
6
8
|
protected adminizer: Adminizer;
|
|
7
9
|
abstract readonly notificationClass: string;
|
|
8
10
|
abstract readonly icon: string;
|
|
9
11
|
abstract readonly iconColor: string;
|
|
10
12
|
constructor(adminizer: Adminizer);
|
|
11
|
-
|
|
13
|
+
private _bindAccessRight;
|
|
14
|
+
addClient(clientId: string, sendFn: (event: INotificationEvent) => void, user: UserAP): void;
|
|
12
15
|
removeClient(clientId: string): void;
|
|
16
|
+
getUserClients(userId: number): Map<string, (event: INotificationEvent) => void>;
|
|
17
|
+
getClientCount(): number;
|
|
18
|
+
getAllClients(): Map<string, (event: INotificationEvent) => void>;
|
|
13
19
|
abstract dispatchNotification(notification: Omit<INotification, 'id' | 'createdAt' | 'notificationClass' | 'icon'>): Promise<boolean>;
|
|
14
20
|
protected broadcast(event: INotificationEvent): void;
|
|
15
21
|
sendHeartbeat(clientId: string): void;
|
|
16
|
-
protected generateId(): string;
|
|
17
|
-
getClientCount(): number;
|
|
18
22
|
protected createUserNotification(notificationId: string, userId?: number): Promise<void>;
|
|
19
23
|
protected getUserNotification(notificationId: string, userId: number): Promise<any>;
|
|
20
|
-
getNotifications(userId: number, limit?: number, unreadOnly?: boolean): Promise<INotification[]>;
|
|
24
|
+
getNotifications(userId: number, limit?: number, skip?: number, unreadOnly?: boolean): Promise<INotification[]>;
|
|
25
|
+
protected prepareNotification(notificationsDB: NotificationAPModel[], userId: number): Promise<INotification[]>;
|
|
26
|
+
search(s: string, userId: number): Promise<INotification[]>;
|
|
21
27
|
markAsRead(userId: number, id: string): Promise<void>;
|
|
28
|
+
markAllAsRead(userId: number): Promise<void>;
|
|
22
29
|
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import { Adminizer } from '../Adminizer';
|
|
3
|
+
export class AbstractNotificationService extends EventEmitter {
|
|
4
|
+
clients = new Map();
|
|
5
|
+
adminizer;
|
|
6
|
+
constructor(adminizer) {
|
|
7
|
+
super();
|
|
8
|
+
this.adminizer = adminizer;
|
|
9
|
+
this._bindAccessRight();
|
|
10
|
+
}
|
|
11
|
+
_bindAccessRight() {
|
|
12
|
+
setTimeout(() => {
|
|
13
|
+
this.adminizer.accessRightsHelper.registerToken({
|
|
14
|
+
id: `notification-${this.notificationClass}`,
|
|
15
|
+
name: this.notificationClass,
|
|
16
|
+
description: `Access to notification ${this.notificationClass}`,
|
|
17
|
+
department: 'notification',
|
|
18
|
+
});
|
|
19
|
+
}, 100);
|
|
20
|
+
}
|
|
21
|
+
// Добавление клиента
|
|
22
|
+
addClient(clientId, sendFn, user) {
|
|
23
|
+
const userId = user.id;
|
|
24
|
+
// Если у пользователя еще нет Map клиентов - создаем
|
|
25
|
+
if (!this.clients.has(userId)) {
|
|
26
|
+
this.clients.set(userId, new Map());
|
|
27
|
+
}
|
|
28
|
+
// Получаем Map клиентов пользователя и добавляем нового клиента
|
|
29
|
+
const userClients = this.clients.get(userId);
|
|
30
|
+
userClients.set(clientId, sendFn);
|
|
31
|
+
Adminizer.log.info(`[${this.notificationClass}] Client ${clientId} connected for user ${userId}. Total users: ${this.clients.size}, user clients: ${userClients.size}`);
|
|
32
|
+
}
|
|
33
|
+
// Удаление клиента
|
|
34
|
+
removeClient(clientId) {
|
|
35
|
+
// Ищем клиента во всех пользовательских Map
|
|
36
|
+
for (const [userId, userClients] of this.clients.entries()) {
|
|
37
|
+
if (userClients.has(clientId)) {
|
|
38
|
+
userClients.delete(clientId);
|
|
39
|
+
// Если у пользователя больше нет клиентов - удаляем его Map
|
|
40
|
+
if (userClients.size === 0) {
|
|
41
|
+
this.clients.delete(userId);
|
|
42
|
+
}
|
|
43
|
+
Adminizer.log.info(`[${this.notificationClass}] Client ${clientId} disconnected from user ${userId}. Total users: ${this.clients.size}`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
Adminizer.log.warn(`[${this.notificationClass}] Client ${clientId} not found for removal`);
|
|
48
|
+
}
|
|
49
|
+
// Получение клиентов конкретного пользователя
|
|
50
|
+
getUserClients(userId) {
|
|
51
|
+
return this.clients.get(userId) || new Map();
|
|
52
|
+
}
|
|
53
|
+
// Получение количества подключенных клиентов
|
|
54
|
+
getClientCount() {
|
|
55
|
+
return this.clients.size;
|
|
56
|
+
}
|
|
57
|
+
// Получение всех клиентов (для обратной совместимости)
|
|
58
|
+
getAllClients() {
|
|
59
|
+
const allClients = new Map();
|
|
60
|
+
for (const userClients of this.clients.values()) {
|
|
61
|
+
for (const [clientId, sendFn] of userClients.entries()) {
|
|
62
|
+
allClients.set(clientId, sendFn);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return allClients;
|
|
66
|
+
}
|
|
67
|
+
// Рассылка события всем клиентам
|
|
68
|
+
broadcast(event) {
|
|
69
|
+
for (const userClients of this.clients.values()) {
|
|
70
|
+
userClients.forEach((sendFn, clientId) => {
|
|
71
|
+
try {
|
|
72
|
+
sendFn(event);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
Adminizer.log.error(`[${this.notificationClass}] Error sending to client ${clientId}:`, error);
|
|
76
|
+
this.removeClient(clientId);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Отправка heartbeat
|
|
82
|
+
sendHeartbeat(clientId) {
|
|
83
|
+
for (const userClients of this.clients.values()) {
|
|
84
|
+
const sendFn = userClients.get(clientId);
|
|
85
|
+
if (sendFn) {
|
|
86
|
+
const heartbeatEvent = {
|
|
87
|
+
type: 'heartbeat',
|
|
88
|
+
data: 'ping'
|
|
89
|
+
};
|
|
90
|
+
sendFn(heartbeatEvent);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Метод для создания записи в UserNotificationAP
|
|
96
|
+
async createUserNotification(notificationId, userId) {
|
|
97
|
+
if (this.adminizer.modelHandler.model.has('usernotificationap') && userId) {
|
|
98
|
+
try {
|
|
99
|
+
await this.adminizer.modelHandler.model.get('usernotificationap')["_create"]({
|
|
100
|
+
userId: userId,
|
|
101
|
+
notificationId: notificationId,
|
|
102
|
+
read: false
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
Adminizer.log.error(`Error creating user notification record:`, error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Метод для получения UserNotificationAP по ID уведомления и пользователя
|
|
111
|
+
async getUserNotification(notificationId, userId) {
|
|
112
|
+
if (this.adminizer.modelHandler.model.has('usernotificationap')) {
|
|
113
|
+
try {
|
|
114
|
+
return await this.adminizer.modelHandler.model.get('usernotificationap')["_findOne"]({
|
|
115
|
+
where: { notificationId: notificationId, userId: userId }
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
Adminizer.log.error(`Error getting user notification:`, error);
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
async getNotifications(userId, limit = 20, skip = 0, unreadOnly = false) {
|
|
126
|
+
if (!this.adminizer.modelHandler.model.has('notificationap')) {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
let query = { notificationClass: this.notificationClass };
|
|
131
|
+
// Если запрашиваются уведомления для конкретного пользователя получаем ID уведомлений пользователя
|
|
132
|
+
const userNotifications = await this.adminizer.modelHandler.model.get('usernotificationap')["_find"]({
|
|
133
|
+
where: {
|
|
134
|
+
userId: userId
|
|
135
|
+
}
|
|
136
|
+
}, { populate: [['notificationId', {}]] });
|
|
137
|
+
const notificationIds = userNotifications.map((un) => un.notificationId.id);
|
|
138
|
+
if (unreadOnly) {
|
|
139
|
+
// Только непрочитанные
|
|
140
|
+
query.id = userNotifications
|
|
141
|
+
.filter((un) => !un.read)
|
|
142
|
+
.map((un) => un.notificationId.id);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
query.id = notificationIds;
|
|
146
|
+
}
|
|
147
|
+
if (query.id.length === 0)
|
|
148
|
+
return [];
|
|
149
|
+
const notificationsDB = await this.adminizer.modelHandler.model.get('notificationap')["_find"]({
|
|
150
|
+
where: query,
|
|
151
|
+
sort: 'createdAt DESC',
|
|
152
|
+
limit: limit,
|
|
153
|
+
skip: skip
|
|
154
|
+
});
|
|
155
|
+
return await this.prepareNotification(notificationsDB, userId);
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
Adminizer.log.error('Error fetching notifications:', error);
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async prepareNotification(notificationsDB, userId) {
|
|
163
|
+
let notifications = [];
|
|
164
|
+
for (const notification of notificationsDB) {
|
|
165
|
+
let readStatus = false;
|
|
166
|
+
// Получаем статус прочтения из UserNotificationAP
|
|
167
|
+
const userNotification = await this.getUserNotification(notification.id, userId);
|
|
168
|
+
readStatus = userNotification ? userNotification.read : false;
|
|
169
|
+
notifications.push({
|
|
170
|
+
...notification,
|
|
171
|
+
read: readStatus,
|
|
172
|
+
icon: {
|
|
173
|
+
icon: this.icon,
|
|
174
|
+
iconColor: this.iconColor
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return notifications;
|
|
179
|
+
}
|
|
180
|
+
async search(s, userId) {
|
|
181
|
+
try {
|
|
182
|
+
const userNotifications = await this.adminizer.modelHandler.model.get('usernotificationap')["_find"]({
|
|
183
|
+
where: {
|
|
184
|
+
userId: userId
|
|
185
|
+
}
|
|
186
|
+
}, { populate: [['notificationId', {}]] });
|
|
187
|
+
const notificationIds = userNotifications.map((un) => un.notificationId.id);
|
|
188
|
+
if (notificationIds.length === 0)
|
|
189
|
+
return [];
|
|
190
|
+
const notificationsDB = await this.adminizer.modelHandler.model.get('notificationap')["_find"]({
|
|
191
|
+
where: {
|
|
192
|
+
message: { contains: s },
|
|
193
|
+
id: notificationIds,
|
|
194
|
+
notificationClass: this.notificationClass
|
|
195
|
+
},
|
|
196
|
+
sort: "createdAt DESC"
|
|
197
|
+
});
|
|
198
|
+
return await this.prepareNotification(notificationsDB, userId);
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
Adminizer.log.error('Error searching notifications:', error);
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
async markAsRead(userId, id) {
|
|
206
|
+
try {
|
|
207
|
+
const userNotification = await this.getUserNotification(id, userId);
|
|
208
|
+
if (userNotification) {
|
|
209
|
+
await this.adminizer.modelHandler.model.get('usernotificationap')["_update"]({
|
|
210
|
+
id: userNotification.id
|
|
211
|
+
}, { read: true });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
Adminizer.log.error('Error marking notification as read:', error);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async markAllAsRead(userId) {
|
|
219
|
+
try {
|
|
220
|
+
const userNotifications = await this.adminizer.modelHandler.model.get('usernotificationap')["_find"]({
|
|
221
|
+
where: { userId: userId }
|
|
222
|
+
});
|
|
223
|
+
for (const userNotification of userNotifications) {
|
|
224
|
+
await this.adminizer.modelHandler.model.get('usernotificationap')["_update"]({
|
|
225
|
+
where: { id: userNotification.id },
|
|
226
|
+
}, { read: true });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
Adminizer.log.error('Error marking all notifications as read:', error);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AbstractNotificationService } from './AbstractNotificationService';
|
|
2
|
-
import { INotification } from '
|
|
2
|
+
import { INotification } from '../../interfaces/types';
|
|
3
3
|
export declare class GeneralNotificationService extends AbstractNotificationService {
|
|
4
4
|
readonly notificationClass = "general";
|
|
5
5
|
readonly icon: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AbstractNotificationService } from './AbstractNotificationService';
|
|
2
|
-
import { Adminizer } from '
|
|
2
|
+
import { Adminizer } from '../Adminizer';
|
|
3
3
|
export class GeneralNotificationService extends AbstractNotificationService {
|
|
4
4
|
notificationClass = 'general';
|
|
5
5
|
icon = 'info';
|
|
@@ -23,7 +23,9 @@ export class GeneralNotificationService extends AbstractNotificationService {
|
|
|
23
23
|
const users = await this.adminizer.modelHandler.model.get('userap')["_find"]({});
|
|
24
24
|
for (const user of users) {
|
|
25
25
|
try {
|
|
26
|
-
|
|
26
|
+
if (this.adminizer.accessRightsHelper.hasPermission(`notification-${this.notificationClass}`, user)) {
|
|
27
|
+
await this.createUserNotification(notificationDB.id, user.id);
|
|
28
|
+
}
|
|
27
29
|
}
|
|
28
30
|
catch (error) {
|
|
29
31
|
Adminizer.log.error('Error creating UserNotificationAP:', error);
|
|
@@ -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
|
+
}
|