adminizer 5.0.0-build.11 → 5.0.0-build.13
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-DQE53mIb.js → add-B_hn__Ba.js} +1 -1
- package/assets/{add-group-BsizwU4R.js → add-group-0SdYgiXZ.js} +1 -1
- package/assets/{add-user-BkSrCzw4.js → add-user-Bh1iqSem.js} +1 -1
- package/assets/ai-assistant/agent.es.js +5036 -4931
- package/assets/app.js +32 -32
- package/assets/{catalog-DPbsKvEY.js → catalog-DjK8r0Mn.js} +1 -1
- package/assets/controls/handsontable.es.js +11637 -11632
- package/assets/controls/jsoneditor.es.js +6308 -6297
- package/assets/controls/toast-ui.es.js +18 -17
- package/assets/{dashboard-DDbX6YWf.js → dashboard-CbJIK7ka.js} +1 -1
- package/assets/{history-DzHm8RJs.js → history-CZAJgVXP.js} +1 -1
- package/assets/{list-CMymFRqO.js → list-C6w_xDK2.js} +1 -1
- package/assets/manifest.json +24 -24
- package/assets/{module-DkFrKFpO.js → module-DvPjKyF_.js} +1 -1
- package/assets/{notification-ChERJE5c.js → notification-N_b-N0Zu.js} +1 -1
- package/assets/{user-filters-list-BytQFcmQ.js → user-filters-list-xbJoY_5_.js} +1 -1
- package/assets/{welcome-Daw8Hukl.js → welcome-BUkdkKa5.js} +1 -1
- package/assets/{with-app-layout-0iXcLqi5.js → with-app-layout-WhRyO5WK.js} +1 -1
- package/controllers/ai/AiAgentController.js +4 -1
- package/controllers/view.js +1 -1
- package/helpers/configHelper.js +12 -17
- package/helpers/controllerHelper.d.ts +1 -0
- package/helpers/controllerHelper.js +25 -15
- package/helpers/fieldsHelper.js +3 -3
- package/helpers/inertiaAddHelper.js +1 -1
- package/helpers/inertiaMenuHelper.d.ts +0 -1
- package/helpers/inertiaMenuHelper.js +3 -26
- package/helpers/modelResourceHelper.d.ts +9 -0
- package/helpers/modelResourceHelper.js +43 -0
- package/helpers/navigationAccessHelper.d.ts +13 -0
- package/helpers/navigationAccessHelper.js +40 -0
- package/index.d.ts +6 -0
- package/index.js +6 -0
- package/interfaces/adminpanelConfig.d.ts +6 -1
- package/interfaces/types.d.ts +40 -3
- package/lib/Adminizer.d.ts +6 -0
- package/lib/Adminizer.js +9 -0
- package/lib/DataAccessor.d.ts +1 -0
- package/lib/DataAccessor.js +45 -47
- package/lib/admin-links/AdminLinkHandler.d.ts +72 -0
- package/lib/admin-links/AdminLinkHandler.js +175 -0
- package/lib/ai-assistant/AbstractAiModelService.d.ts +44 -3
- package/lib/ai-assistant/AbstractAiModelService.js +61 -2
- package/lib/ai-assistant/AiAssistantAgentSkillHandler.d.ts +58 -0
- package/lib/ai-assistant/AiAssistantAgentSkillHandler.js +87 -0
- package/lib/ai-assistant/AiAssistantUiMethodHandler.d.ts +60 -0
- package/lib/ai-assistant/AiAssistantUiMethodHandler.js +138 -0
- package/lib/ai-assistant/builtinAgentSkills.d.ts +9 -0
- package/lib/ai-assistant/builtinAgentSkills.js +256 -0
- package/lib/ai-assistant/jsonSafe.d.ts +13 -0
- package/lib/ai-assistant/jsonSafe.js +81 -0
- package/lib/app-manager/AdminizerApp.d.ts +24 -0
- package/lib/app-manager/AppManager.js +39 -42
- package/lib/filters/FilterService.js +4 -3
- package/lib/history-actions/AbstractHistoryAdapter.js +11 -9
- package/lib/model/AbstractModel.d.ts +2 -0
- package/lib/model/ModelHandler.d.ts +47 -2
- package/lib/model/ModelHandler.js +107 -8
- package/package.json +1 -1
- package/system/bindAccessRights.js +2 -3
- package/system/buildInternalModelAccess.js +5 -2
- package/system/validateSystemModels.js +29 -9
|
@@ -25,8 +25,9 @@ export class FilterService {
|
|
|
25
25
|
if (!models) {
|
|
26
26
|
return null;
|
|
27
27
|
}
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const foundKey = Object.prototype.hasOwnProperty.call(models, modelName)
|
|
29
|
+
? modelName
|
|
30
|
+
: Object.keys(models).find(key => key.toLowerCase() === modelName.toLowerCase());
|
|
30
31
|
if (!foundKey) {
|
|
31
32
|
return null;
|
|
32
33
|
}
|
|
@@ -35,7 +36,7 @@ export class FilterService {
|
|
|
35
36
|
return null;
|
|
36
37
|
}
|
|
37
38
|
// Get AbstractModel
|
|
38
|
-
const model = this.adminizer.modelHandler.
|
|
39
|
+
const model = this.adminizer.modelHandler.getResource(foundKey);
|
|
39
40
|
if (!model) {
|
|
40
41
|
return null;
|
|
41
42
|
}
|
|
@@ -86,9 +86,9 @@ export class AbstractHistoryAdapter {
|
|
|
86
86
|
* @returns Array of model names (in lowercase) the user can access.
|
|
87
87
|
*/
|
|
88
88
|
getModels(user) {
|
|
89
|
-
const models = this.adminizer.modelHandler.
|
|
90
|
-
.filter(model => !this.excludedModels.has(model.modelname))
|
|
91
|
-
.map(
|
|
89
|
+
const models = Array.from(this.adminizer.modelHandler.model.entries())
|
|
90
|
+
.filter(([, model]) => !this.excludedModels.has(model.modelname))
|
|
91
|
+
.map(([resourceName]) => resourceName);
|
|
92
92
|
const accessModels = [];
|
|
93
93
|
for (const model of models) {
|
|
94
94
|
const access = this.adminizer.accessRightsHelper.enoughPermissions([
|
|
@@ -304,14 +304,15 @@ export class AbstractHistoryAdapter {
|
|
|
304
304
|
* @protected
|
|
305
305
|
*/
|
|
306
306
|
findModelResource(history) {
|
|
307
|
-
const modelResourceName = history.modelName
|
|
307
|
+
const modelResourceName = this.adminizer.modelHandler.getResourceRecord(history.modelName)?.name
|
|
308
|
+
?? history.modelName;
|
|
308
309
|
const modelResourceUri = `${this.adminizer.config.routePrefix}/model/${modelResourceName}`;
|
|
309
310
|
const models = this.adminizer.config.models;
|
|
310
311
|
const foundKey = Object.keys(models).find(key => key.toLowerCase() === modelResourceName.toLowerCase());
|
|
311
312
|
const modelResource = {
|
|
312
313
|
name: modelResourceName,
|
|
313
314
|
uri: modelResourceUri,
|
|
314
|
-
model: this.adminizer.modelHandler.
|
|
315
|
+
model: this.adminizer.modelHandler.getResource(modelResourceName),
|
|
315
316
|
config: models[foundKey]
|
|
316
317
|
};
|
|
317
318
|
return modelResource;
|
|
@@ -341,8 +342,9 @@ export class AbstractHistoryAdapter {
|
|
|
341
342
|
Adminizer.log.error('No model found for field: ', fields[key]);
|
|
342
343
|
return;
|
|
343
344
|
}
|
|
344
|
-
|
|
345
|
-
|
|
345
|
+
const resourceName = this.adminizer.modelHandler.resolveAssociationResource(modelName, fields[key].model.resourceName);
|
|
346
|
+
let Model = resourceName ? this.adminizer.modelHandler.getResource(resourceName) : undefined;
|
|
347
|
+
if (!Model || !resourceName) {
|
|
346
348
|
return;
|
|
347
349
|
}
|
|
348
350
|
let list;
|
|
@@ -351,8 +353,8 @@ export class AbstractHistoryAdapter {
|
|
|
351
353
|
Adminizer.log.warn("Warning: executing malicious job trying to add a huge amount of records in field config," +
|
|
352
354
|
" please rewrite this part of code in the nearest future");
|
|
353
355
|
let modelResource = {
|
|
354
|
-
name:
|
|
355
|
-
model: Model, uri: `${this.adminizer.config.routePrefix}/model/${
|
|
356
|
+
name: resourceName, config: this.adminizer.config.models[resourceName],
|
|
357
|
+
model: Model, uri: `${this.adminizer.config.routePrefix}/model/${resourceName}`
|
|
356
358
|
};
|
|
357
359
|
let dataAccessor = new DataAccessor(this.adminizer, user, modelResource, "view");
|
|
358
360
|
list = await Model.find({}, dataAccessor);
|
|
@@ -9,10 +9,26 @@ export interface AppModelAccessRecord {
|
|
|
9
9
|
export interface ModelRecord {
|
|
10
10
|
id: string;
|
|
11
11
|
name: string;
|
|
12
|
+
hostModelName: string;
|
|
13
|
+
primaryHostModel: boolean;
|
|
12
14
|
model: AbstractModel<any>;
|
|
13
15
|
ownerApp?: string;
|
|
14
16
|
enabled: boolean;
|
|
15
17
|
}
|
|
18
|
+
export interface ModelRegistrationOptions {
|
|
19
|
+
/** Physical ORM model name. It is intentionally independent from the Adminizer resource name. */
|
|
20
|
+
hostModelName?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Selects this resource as the association target when multiple resources use the same host model.
|
|
23
|
+
* Exactly one resource must be primary for every shared host model.
|
|
24
|
+
*/
|
|
25
|
+
primary?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* @deprecated Register and use the canonical Adminizer resource name instead.
|
|
28
|
+
* Kept only for compatibility with existing integrations. Host model names must not be used as aliases.
|
|
29
|
+
*/
|
|
30
|
+
aliases?: string[];
|
|
31
|
+
}
|
|
16
32
|
export declare class AppModelAccess {
|
|
17
33
|
private readonly getModel;
|
|
18
34
|
private readonly appName;
|
|
@@ -33,9 +49,12 @@ export declare class AppModelAccess {
|
|
|
33
49
|
export declare class ModelHandler {
|
|
34
50
|
private models;
|
|
35
51
|
private modelAliases;
|
|
52
|
+
private hostModelResources;
|
|
36
53
|
private internalAccess?;
|
|
37
54
|
private appAccessRecords;
|
|
38
|
-
add<T>(
|
|
55
|
+
add<T>(resourceName: string, modelInstance: AbstractModel<T>, options?: ModelRegistrationOptions): void;
|
|
56
|
+
/** @deprecated Pass `{ aliases }` in ModelRegistrationOptions, or preferably use the canonical resource name. */
|
|
57
|
+
add<T>(resourceName: string, modelInstance: AbstractModel<T>, aliases?: string[]): void;
|
|
39
58
|
register<T>(appName: string, modelName: string, modelInstance: AbstractModel<T>): string;
|
|
40
59
|
unregister(id: string): void;
|
|
41
60
|
disable(id: string): void;
|
|
@@ -45,7 +64,10 @@ export declare class ModelHandler {
|
|
|
45
64
|
unregisterAppAccess(id: string): void;
|
|
46
65
|
getAppAccessRecords(appName: string): AppModelAccessRecord[];
|
|
47
66
|
createAppAccess(appName: string): AppModelAccess;
|
|
48
|
-
/**
|
|
67
|
+
/**
|
|
68
|
+
* Improved model getter with case-insensitive lookup and configured alias support.
|
|
69
|
+
* @deprecated New code should use getResource() and getByHostModel() explicitly.
|
|
70
|
+
*/
|
|
49
71
|
get model(): {
|
|
50
72
|
get: (modelName: string) => AbstractModel<any>;
|
|
51
73
|
has: (modelName: string) => boolean;
|
|
@@ -53,6 +75,26 @@ export declare class ModelHandler {
|
|
|
53
75
|
keys: () => IterableIterator<string>;
|
|
54
76
|
values: () => IterableIterator<AbstractModel<any>>;
|
|
55
77
|
};
|
|
78
|
+
/**
|
|
79
|
+
* Returns a model by its Adminizer resource name or an explicit deprecated compatibility alias.
|
|
80
|
+
* New code must use canonical resource names.
|
|
81
|
+
*/
|
|
82
|
+
getResource(resourceName: string): AbstractModel<any> | undefined;
|
|
83
|
+
getResourceRecord(resourceName: string): ModelRecord | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* Resolves a physical ORM model name to its Adminizer resource.
|
|
86
|
+
* This lookup is deliberately separate from getResource(): a host model may have the
|
|
87
|
+
* same name as a system resource while being exposed under a project resource alias.
|
|
88
|
+
*/
|
|
89
|
+
resolveResourceByHostModel(hostModelName: string): string | undefined;
|
|
90
|
+
getByHostModel(hostModelName: string): AbstractModel<any> | undefined;
|
|
91
|
+
/**
|
|
92
|
+
* Resolves an ORM association target. Adapter-provided host model names take precedence;
|
|
93
|
+
* system-model relations may pass their explicit canonical resource name instead.
|
|
94
|
+
*/
|
|
95
|
+
resolveAssociationResource(targetName: string, resourceName?: string): string | undefined;
|
|
96
|
+
/** Validates that every host ORM model used by multiple resources has one primary resource. */
|
|
97
|
+
validateHostModelMappings(): void;
|
|
56
98
|
resolveModelName(modelName: string): string;
|
|
57
99
|
configureInternalAccess(accessMap?: InternalModelAccessMap): void;
|
|
58
100
|
internal(scopeName: string): InternalModelScope;
|
|
@@ -63,6 +105,9 @@ export declare class ModelHandler {
|
|
|
63
105
|
get all(): AbstractModel<any>[];
|
|
64
106
|
private getAllowedAppModels;
|
|
65
107
|
private registerAliases;
|
|
108
|
+
private registerHostModel;
|
|
109
|
+
private unregisterHostModel;
|
|
110
|
+
private getSharedHostModelError;
|
|
66
111
|
private resolveName;
|
|
67
112
|
private enabledModelEntries;
|
|
68
113
|
private enabledModelKeys;
|
|
@@ -40,18 +40,27 @@ export class AppModelAccess {
|
|
|
40
40
|
export class ModelHandler {
|
|
41
41
|
models = new Map();
|
|
42
42
|
modelAliases = new Map();
|
|
43
|
+
hostModelResources = new Map();
|
|
43
44
|
internalAccess;
|
|
44
45
|
appAccessRecords = new Map();
|
|
45
|
-
add(
|
|
46
|
-
const
|
|
47
|
-
this.models.
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
add(resourceName, modelInstance, options = {}) {
|
|
47
|
+
const resourceId = normalizeName(resourceName);
|
|
48
|
+
if (this.models.has(resourceId)) {
|
|
49
|
+
throw new Error(`Model resource "${resourceName}" is already registered`);
|
|
50
|
+
}
|
|
51
|
+
const normalizedOptions = Array.isArray(options) ? { aliases: options } : options;
|
|
52
|
+
const hostModelName = normalizedOptions.hostModelName ?? resourceName;
|
|
53
|
+
this.registerHostModel(resourceName, hostModelName, true);
|
|
54
|
+
this.models.set(resourceId, {
|
|
55
|
+
id: resourceId,
|
|
56
|
+
name: resourceName,
|
|
57
|
+
hostModelName,
|
|
58
|
+
primaryHostModel: normalizedOptions.primary === true,
|
|
50
59
|
model: modelInstance,
|
|
51
60
|
enabled: true,
|
|
52
61
|
});
|
|
53
|
-
this.registerAliases(
|
|
54
|
-
Adminizer.log.debug(`Model
|
|
62
|
+
this.registerAliases(resourceName, normalizedOptions.aliases ?? []);
|
|
63
|
+
Adminizer.log.debug(`Model resource [${resourceName}] was registered for host model [${hostModelName}]`);
|
|
55
64
|
}
|
|
56
65
|
register(appName, modelName, modelInstance) {
|
|
57
66
|
const normalizedModelName = normalizeName(modelName);
|
|
@@ -61,10 +70,13 @@ export class ModelHandler {
|
|
|
61
70
|
this.models.set(normalizedModelName, {
|
|
62
71
|
id: normalizedModelName,
|
|
63
72
|
name: modelName,
|
|
73
|
+
hostModelName: modelName,
|
|
74
|
+
primaryHostModel: true,
|
|
64
75
|
model: modelInstance,
|
|
65
76
|
ownerApp: appName,
|
|
66
77
|
enabled: true,
|
|
67
78
|
});
|
|
79
|
+
this.registerHostModel(modelName, modelName, false);
|
|
68
80
|
Adminizer.log.debug(`Model with name [${normalizedModelName}] was registered by app [${appName}]`);
|
|
69
81
|
return normalizedModelName;
|
|
70
82
|
}
|
|
@@ -74,6 +86,7 @@ export class ModelHandler {
|
|
|
74
86
|
return;
|
|
75
87
|
}
|
|
76
88
|
this.models.delete(normalizeName(id));
|
|
89
|
+
this.unregisterHostModel(record.hostModelName, record.name);
|
|
77
90
|
}
|
|
78
91
|
disable(id) {
|
|
79
92
|
const record = this.models.get(normalizeName(id));
|
|
@@ -118,7 +131,10 @@ export class ModelHandler {
|
|
|
118
131
|
return new AppModelAccess((modelName) => this.model.get(modelName), appName, () => this.getAllowedAppModels(appName));
|
|
119
132
|
}
|
|
120
133
|
// TODO: 'hot reload' need add method for delete model & unbind
|
|
121
|
-
/**
|
|
134
|
+
/**
|
|
135
|
+
* Improved model getter with case-insensitive lookup and configured alias support.
|
|
136
|
+
* @deprecated New code should use getResource() and getByHostModel() explicitly.
|
|
137
|
+
*/
|
|
122
138
|
get model() {
|
|
123
139
|
return {
|
|
124
140
|
get: (modelName) => this.models.get(this.resolveName(modelName))?.enabled
|
|
@@ -130,6 +146,65 @@ export class ModelHandler {
|
|
|
130
146
|
values: () => this.enabledModelValues(),
|
|
131
147
|
};
|
|
132
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Returns a model by its Adminizer resource name or an explicit deprecated compatibility alias.
|
|
151
|
+
* New code must use canonical resource names.
|
|
152
|
+
*/
|
|
153
|
+
getResource(resourceName) {
|
|
154
|
+
return this.model.get(resourceName);
|
|
155
|
+
}
|
|
156
|
+
getResourceRecord(resourceName) {
|
|
157
|
+
const record = this.models.get(this.resolveName(resourceName));
|
|
158
|
+
return record?.enabled ? record : undefined;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Resolves a physical ORM model name to its Adminizer resource.
|
|
162
|
+
* This lookup is deliberately separate from getResource(): a host model may have the
|
|
163
|
+
* same name as a system resource while being exposed under a project resource alias.
|
|
164
|
+
*/
|
|
165
|
+
resolveResourceByHostModel(hostModelName) {
|
|
166
|
+
const resourceIds = this.hostModelResources.get(normalizeName(hostModelName));
|
|
167
|
+
const records = Array.from(resourceIds ?? [])
|
|
168
|
+
.map((resourceId) => this.models.get(resourceId))
|
|
169
|
+
.filter((record) => Boolean(record?.enabled));
|
|
170
|
+
if (records.length === 1) {
|
|
171
|
+
return records[0].name;
|
|
172
|
+
}
|
|
173
|
+
const primaryRecords = records.filter((record) => record.primaryHostModel);
|
|
174
|
+
if (primaryRecords.length === 1) {
|
|
175
|
+
return primaryRecords[0].name;
|
|
176
|
+
}
|
|
177
|
+
if (records.length > 1) {
|
|
178
|
+
throw new Error(this.getSharedHostModelError(hostModelName, records));
|
|
179
|
+
}
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
getByHostModel(hostModelName) {
|
|
183
|
+
const resourceName = this.resolveResourceByHostModel(hostModelName);
|
|
184
|
+
return resourceName ? this.getResource(resourceName) : undefined;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Resolves an ORM association target. Adapter-provided host model names take precedence;
|
|
188
|
+
* system-model relations may pass their explicit canonical resource name instead.
|
|
189
|
+
*/
|
|
190
|
+
resolveAssociationResource(targetName, resourceName) {
|
|
191
|
+
if (resourceName) {
|
|
192
|
+
return this.getResourceRecord(resourceName)?.name;
|
|
193
|
+
}
|
|
194
|
+
return this.resolveResourceByHostModel(targetName)
|
|
195
|
+
?? this.getResourceRecord(targetName)?.name;
|
|
196
|
+
}
|
|
197
|
+
/** Validates that every host ORM model used by multiple resources has one primary resource. */
|
|
198
|
+
validateHostModelMappings() {
|
|
199
|
+
for (const [normalizedHostModelName, resourceIds] of this.hostModelResources) {
|
|
200
|
+
const records = Array.from(resourceIds)
|
|
201
|
+
.map((resourceId) => this.models.get(resourceId))
|
|
202
|
+
.filter((record) => Boolean(record?.enabled));
|
|
203
|
+
if (records.length > 1 && records.filter((record) => record.primaryHostModel).length !== 1) {
|
|
204
|
+
throw new Error(this.getSharedHostModelError(records[0]?.hostModelName ?? normalizedHostModelName, records));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
133
208
|
resolveModelName(modelName) {
|
|
134
209
|
const resolvedName = this.resolveName(modelName);
|
|
135
210
|
return this.models.get(resolvedName)?.name ?? modelName;
|
|
@@ -184,6 +259,30 @@ export class ModelHandler {
|
|
|
184
259
|
this.modelAliases.set(normalizedAlias, normalizedModelName);
|
|
185
260
|
}
|
|
186
261
|
}
|
|
262
|
+
registerHostModel(resourceName, hostModelName, allowMultipleResources) {
|
|
263
|
+
const normalizedHostModelName = normalizeName(hostModelName);
|
|
264
|
+
const resourceId = normalizeName(resourceName);
|
|
265
|
+
const resourceIds = this.hostModelResources.get(normalizedHostModelName) ?? new Set();
|
|
266
|
+
if (!allowMultipleResources && resourceIds.size && !resourceIds.has(resourceId)) {
|
|
267
|
+
const existingResourceId = resourceIds.values().next().value;
|
|
268
|
+
const existingResource = this.models.get(existingResourceId)?.name ?? existingResourceId;
|
|
269
|
+
throw new Error(`Host model "${hostModelName}" is already registered for Adminizer resource "${existingResource}"`);
|
|
270
|
+
}
|
|
271
|
+
resourceIds.add(resourceId);
|
|
272
|
+
this.hostModelResources.set(normalizedHostModelName, resourceIds);
|
|
273
|
+
}
|
|
274
|
+
unregisterHostModel(hostModelName, resourceName) {
|
|
275
|
+
const normalizedHostModelName = normalizeName(hostModelName);
|
|
276
|
+
const resourceIds = this.hostModelResources.get(normalizedHostModelName);
|
|
277
|
+
resourceIds?.delete(normalizeName(resourceName));
|
|
278
|
+
if (!resourceIds?.size) {
|
|
279
|
+
this.hostModelResources.delete(normalizedHostModelName);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
getSharedHostModelError(hostModelName, records) {
|
|
283
|
+
return (`Host model "${hostModelName}" is mapped to multiple Adminizer resources: ` +
|
|
284
|
+
`${records.map((record) => record.name).join(", ")}. Configure exactly one with primary: true.`);
|
|
285
|
+
}
|
|
187
286
|
resolveName(modelName) {
|
|
188
287
|
const normalizedModelName = normalizeName(modelName);
|
|
189
288
|
return this.modelAliases.get(normalizedModelName) ?? normalizedModelName;
|
package/package.json
CHANGED
|
@@ -2,10 +2,9 @@ import { GROUP_FILTER_VISIBILITY_TOKEN } from "../policies/permissionResolvers.j
|
|
|
2
2
|
export default async function bindAccessRights(adminizer) {
|
|
3
3
|
if (adminizer.config.models) {
|
|
4
4
|
let models = adminizer.config.models;
|
|
5
|
-
for (
|
|
6
|
-
const model = models[key];
|
|
5
|
+
for (const [resourceName, model] of Object.entries(models)) {
|
|
7
6
|
if (typeof model !== "boolean") {
|
|
8
|
-
adminizer.accessRightsHelper.registerModelTokens(
|
|
7
|
+
adminizer.accessRightsHelper.registerModelTokens(resourceName);
|
|
9
8
|
}
|
|
10
9
|
}
|
|
11
10
|
}
|
|
@@ -30,10 +30,13 @@ function getDataAccessorInternalModels(config, modelHandler) {
|
|
|
30
30
|
if (!userAccessRelation || typeof userAccessRelation !== "object" || Array.isArray(userAccessRelation)) {
|
|
31
31
|
continue;
|
|
32
32
|
}
|
|
33
|
-
const model = modelHandler.
|
|
33
|
+
const model = modelHandler.getResource(modelName);
|
|
34
34
|
const intermediateModelName = model?.attributes?.[userAccessRelation.field]?.model;
|
|
35
35
|
if (intermediateModelName) {
|
|
36
|
-
|
|
36
|
+
const resourceName = modelHandler.resolveAssociationResource(intermediateModelName, model?.attributes?.[userAccessRelation.field]?.resourceName);
|
|
37
|
+
if (resourceName) {
|
|
38
|
+
models.push(resourceName);
|
|
39
|
+
}
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
return Array.from(new Set(models));
|
|
@@ -10,13 +10,11 @@ export function validateSystemModels(adminizer) {
|
|
|
10
10
|
}
|
|
11
11
|
const systemAdapter = getAdapter(adminizer, defaultOrmAdapter);
|
|
12
12
|
const bindings = buildSystemModelBindings(systemAdapter);
|
|
13
|
-
const
|
|
13
|
+
const systemResourceNames = new Set(SYSTEM_MODEL_CONTRACTS.map((contract) => contract.name));
|
|
14
14
|
for (const contract of SYSTEM_MODEL_CONTRACTS) {
|
|
15
15
|
const binding = bindings.get(contract.name.toLowerCase()) ?? {
|
|
16
16
|
model: contract.name,
|
|
17
17
|
};
|
|
18
|
-
systemModels.add(contract.name.toLowerCase());
|
|
19
|
-
systemModels.add(binding.model.toLowerCase());
|
|
20
18
|
const registeredModel = systemAdapter.getModel(binding.model);
|
|
21
19
|
if (!registeredModel) {
|
|
22
20
|
throw new Error(`System model "${contract.name}" was not provided by adapter "${systemAdapter.ormType}" as host model "${binding.model}". ` +
|
|
@@ -27,7 +25,11 @@ export function validateSystemModels(adminizer) {
|
|
|
27
25
|
resolveRelationTarget: (target) => resolveSystemRelationTarget(target, bindings),
|
|
28
26
|
});
|
|
29
27
|
normalizeSystemModelRelations(model, bindings);
|
|
30
|
-
adminizer.modelHandler.add(contract.name, model,
|
|
28
|
+
adminizer.modelHandler.add(contract.name, model, {
|
|
29
|
+
hostModelName: binding.model,
|
|
30
|
+
// @deprecated Compatibility lookup for modules that still request UserAP/GroupAP.
|
|
31
|
+
aliases: [binding.model],
|
|
32
|
+
});
|
|
31
33
|
}
|
|
32
34
|
const modelsFromConfig = Object.entries(adminizer.config.models ?? {});
|
|
33
35
|
Adminizer.log.debug(`Bind models > Models from config: ${modelsFromConfig.map(([name]) => name)}`);
|
|
@@ -35,7 +37,10 @@ export function validateSystemModels(adminizer) {
|
|
|
35
37
|
const modelName = modelConfig && typeof modelConfig !== "boolean"
|
|
36
38
|
? modelConfig.model
|
|
37
39
|
: configName;
|
|
38
|
-
|
|
40
|
+
// System resources are already registered above. Do not compare host model names
|
|
41
|
+
// here: a project resource may intentionally map its own Adminizer name to a
|
|
42
|
+
// host model named "User" or "Group".
|
|
43
|
+
if (systemResourceNames.has(configName)) {
|
|
39
44
|
continue;
|
|
40
45
|
}
|
|
41
46
|
const adapterName = modelConfig && typeof modelConfig !== "boolean"
|
|
@@ -46,8 +51,12 @@ export function validateSystemModels(adminizer) {
|
|
|
46
51
|
if (!registeredModel) {
|
|
47
52
|
throw new Error(`Bind models > Model not found: ${modelName}`);
|
|
48
53
|
}
|
|
49
|
-
adminizer.modelHandler.add(
|
|
54
|
+
adminizer.modelHandler.add(configName, new ormAdapter.Model(modelName, registeredModel), {
|
|
55
|
+
hostModelName: modelName,
|
|
56
|
+
primary: modelConfig && typeof modelConfig !== "boolean" && modelConfig.primary === true,
|
|
57
|
+
});
|
|
50
58
|
}
|
|
59
|
+
adminizer.modelHandler.validateHostModelMappings();
|
|
51
60
|
Adminizer.log.info("Models loaded");
|
|
52
61
|
}
|
|
53
62
|
function getAdapter(adminizer, adapterName) {
|
|
@@ -79,6 +88,9 @@ function normalizeSystemModelBinding(binding) {
|
|
|
79
88
|
return binding;
|
|
80
89
|
}
|
|
81
90
|
function resolveSystemRelationTarget(target, bindings) {
|
|
91
|
+
return getSystemRelationResource(target, bindings) ?? target;
|
|
92
|
+
}
|
|
93
|
+
function getSystemRelationResource(target, bindings) {
|
|
82
94
|
const normalizedTarget = target.toLowerCase();
|
|
83
95
|
for (const contract of SYSTEM_MODEL_CONTRACTS) {
|
|
84
96
|
const binding = bindings.get(contract.name.toLowerCase());
|
|
@@ -87,15 +99,23 @@ function resolveSystemRelationTarget(target, bindings) {
|
|
|
87
99
|
return contract.name;
|
|
88
100
|
}
|
|
89
101
|
}
|
|
90
|
-
return
|
|
102
|
+
return undefined;
|
|
91
103
|
}
|
|
92
104
|
function normalizeSystemModelRelations(model, bindings) {
|
|
93
105
|
for (const attribute of Object.values(model.attributes)) {
|
|
94
106
|
if (attribute.model) {
|
|
95
|
-
|
|
107
|
+
const resourceName = getSystemRelationResource(attribute.model, bindings);
|
|
108
|
+
if (resourceName) {
|
|
109
|
+
attribute.resourceName = resourceName;
|
|
110
|
+
attribute.model = resourceName;
|
|
111
|
+
}
|
|
96
112
|
}
|
|
97
113
|
if (attribute.collection) {
|
|
98
|
-
|
|
114
|
+
const resourceName = getSystemRelationResource(attribute.collection, bindings);
|
|
115
|
+
if (resourceName) {
|
|
116
|
+
attribute.resourceName = resourceName;
|
|
117
|
+
attribute.collection = resourceName;
|
|
118
|
+
}
|
|
99
119
|
}
|
|
100
120
|
}
|
|
101
121
|
}
|