adminforth 1.2.1 → 1.2.2
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/dataConnectors/postgres.ts +12 -0
- package/dist/dataConnectors/postgres.js +13 -0
- package/dist/index.js +12 -1001
- package/dist/modules/configValidator.js +408 -0
- package/dist/modules/restApi.js +625 -0
- package/index.ts +16 -1122
- package/modules/configValidator.ts +452 -0
- package/modules/restApi.ts +709 -0
- package/package.json +1 -1
- package/types/AdminForthConfig.ts +15 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { AdminForthResourcePages, AllowedActionsEnum, } from "../types/AdminForthConfig.js";
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { guessLabelFromName } from './utils.js';
|
|
14
|
+
import { v4 as uuid } from 'uuid';
|
|
15
|
+
export default class ConfigValidator {
|
|
16
|
+
constructor(adminforth, config) {
|
|
17
|
+
this.adminforth = adminforth;
|
|
18
|
+
this.config = config;
|
|
19
|
+
this.adminforth = adminforth;
|
|
20
|
+
this.config = config;
|
|
21
|
+
}
|
|
22
|
+
checkCustomFileExists(filePath) {
|
|
23
|
+
if (filePath.startsWith('@@/')) {
|
|
24
|
+
const checkPath = path.join(this.config.customization.customComponentsDir, filePath.replace('@@/', ''));
|
|
25
|
+
if (!fs.existsSync(checkPath)) {
|
|
26
|
+
return [`File file ${filePath} does not exist in ${this.config.customization.customComponentsDir}`];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
validateComponent(component, errors, ignoreExistsCheck = false) {
|
|
32
|
+
if (!component) {
|
|
33
|
+
return component;
|
|
34
|
+
}
|
|
35
|
+
let obj;
|
|
36
|
+
if (typeof component === 'string') {
|
|
37
|
+
obj = { file: component, meta: {} };
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
obj = component;
|
|
41
|
+
}
|
|
42
|
+
if (!ignoreExistsCheck) {
|
|
43
|
+
errors.push(...this.checkCustomFileExists(obj.file));
|
|
44
|
+
}
|
|
45
|
+
return obj;
|
|
46
|
+
}
|
|
47
|
+
validateConfig() {
|
|
48
|
+
var _a;
|
|
49
|
+
const errors = [];
|
|
50
|
+
if (this.config.rootUser) {
|
|
51
|
+
if (!this.config.rootUser.username) {
|
|
52
|
+
throw new Error('rootUser.username is required');
|
|
53
|
+
}
|
|
54
|
+
if (!this.config.rootUser.password) {
|
|
55
|
+
throw new Error('rootUser.password is required');
|
|
56
|
+
}
|
|
57
|
+
console.log('\n ⚠️⚠️⚠️ [INSECURE ALERT] config.rootUser is set, please create a new user and remove config.rootUser from config ASAP when you are in production\n');
|
|
58
|
+
}
|
|
59
|
+
if (!this.config.customization.customComponentsDir) {
|
|
60
|
+
this.config.customization.customComponentsDir = './custom';
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
// check customComponentsDir exists
|
|
64
|
+
fs.accessSync(this.config.customization.customComponentsDir, fs.constants.R_OK);
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
this.config.customization.customComponentsDir = undefined;
|
|
68
|
+
}
|
|
69
|
+
if (this.config.auth) {
|
|
70
|
+
if (!this.config.auth.resourceId) {
|
|
71
|
+
throw new Error('No config.auth.resourceId defined');
|
|
72
|
+
}
|
|
73
|
+
if (!this.config.auth.passwordHashField) {
|
|
74
|
+
throw new Error('No config.auth.passwordHashField defined');
|
|
75
|
+
}
|
|
76
|
+
if (!this.config.auth.usernameField) {
|
|
77
|
+
throw new Error('No config.auth.usernameField defined');
|
|
78
|
+
}
|
|
79
|
+
if (this.config.auth.loginBackgroundImage) {
|
|
80
|
+
errors.push(...this.checkCustomFileExists(this.config.auth.loginBackgroundImage));
|
|
81
|
+
}
|
|
82
|
+
const userResource = this.config.resources.find((res) => res.resourceId === this.config.auth.resourceId);
|
|
83
|
+
if (!userResource) {
|
|
84
|
+
throw new Error(`Resource with id "${this.config.auth.resourceId}" not found`);
|
|
85
|
+
}
|
|
86
|
+
if (!this.config.auth.beforeLoginConfirmation) {
|
|
87
|
+
this.config.auth.beforeLoginConfirmation = [];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (!this.config.customization) {
|
|
91
|
+
this.config.customization = {};
|
|
92
|
+
}
|
|
93
|
+
if (!this.config.customization.customComponentsDir) {
|
|
94
|
+
this.config.customization.customComponentsDir = './custom';
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
// check customComponentsDir exists
|
|
98
|
+
fs.accessSync(this.config.customization.customComponentsDir, fs.constants.R_OK);
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
this.config.customization.customComponentsDir = undefined;
|
|
102
|
+
}
|
|
103
|
+
if (this.config.customization.customPages) {
|
|
104
|
+
this.config.customization.customPages.forEach((page, i) => {
|
|
105
|
+
// validate component if it's not plugin injection
|
|
106
|
+
if (this.adminforth.codeInjector.allComponentNames.hasOwnProperty(page.component)) {
|
|
107
|
+
const validatedPage = this.validateComponent(page.component, errors, true);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
this.config.customization.customPages = [];
|
|
113
|
+
}
|
|
114
|
+
if (!this.config.baseUrl) {
|
|
115
|
+
this.config.baseUrl = '';
|
|
116
|
+
}
|
|
117
|
+
if (!this.config.baseUrl.endsWith('/')) {
|
|
118
|
+
this.adminforth.baseUrlSlashed = this.config.baseUrl + '/';
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
this.adminforth.baseUrlSlashed = this.config.baseUrl;
|
|
122
|
+
}
|
|
123
|
+
if (((_a = this.config) === null || _a === void 0 ? void 0 : _a.customization.brandName) === undefined) {
|
|
124
|
+
this.config.customization.brandName = 'AdminForth';
|
|
125
|
+
}
|
|
126
|
+
if (this.config.customization.brandLogo) {
|
|
127
|
+
errors.push(...this.checkCustomFileExists(this.config.customization.brandLogo));
|
|
128
|
+
}
|
|
129
|
+
if (this.config.customization.favicon) {
|
|
130
|
+
errors.push(...this.checkCustomFileExists(this.config.customization.favicon));
|
|
131
|
+
}
|
|
132
|
+
if (!this.config.customization.datesFormat) {
|
|
133
|
+
this.config.customization.datesFormat = 'MMM D, YYYY HH:mm:ss';
|
|
134
|
+
}
|
|
135
|
+
if (this.config.resources) {
|
|
136
|
+
this.config.resources.forEach((res) => {
|
|
137
|
+
var _a, _b, _c;
|
|
138
|
+
if (!res.table) {
|
|
139
|
+
errors.push(`Resource "${res.dataSource}" is missing table`);
|
|
140
|
+
}
|
|
141
|
+
// if recordLabel is not callable, throw error
|
|
142
|
+
if (res.recordLabel && typeof res.recordLabel !== 'function') {
|
|
143
|
+
errors.push(`Resource "${res.dataSource}" recordLabel is not a function`);
|
|
144
|
+
}
|
|
145
|
+
if (!res.recordLabel) {
|
|
146
|
+
res.recordLabel = (item) => {
|
|
147
|
+
const pkVal = item[res.columns.find((col) => col.primaryKey).name];
|
|
148
|
+
return `${res.label} ${pkVal}`;
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
res.resourceId = res.resourceId || res.table;
|
|
152
|
+
res.label = res.label || res.table.charAt(0).toUpperCase() + res.table.slice(1);
|
|
153
|
+
if (!res.dataSource) {
|
|
154
|
+
errors.push(`Resource "${res.resourceId}" is missing dataSource`);
|
|
155
|
+
}
|
|
156
|
+
if (!res.columns) {
|
|
157
|
+
res.columns = [];
|
|
158
|
+
}
|
|
159
|
+
res.columns.forEach((col) => {
|
|
160
|
+
var _a, _b, _c, _d;
|
|
161
|
+
col.label = col.label || guessLabelFromName(col.name);
|
|
162
|
+
//define default sortable
|
|
163
|
+
if (!Object.keys(col).includes('sortable')) {
|
|
164
|
+
col.sortable = true;
|
|
165
|
+
}
|
|
166
|
+
if (col.showIn && !Array.isArray(col.showIn)) {
|
|
167
|
+
errors.push(`Resource "${res.resourceId}" column "${col.name}" showIn must be an array`);
|
|
168
|
+
}
|
|
169
|
+
// check col.required is string or object
|
|
170
|
+
if (col.required && !((typeof col.required === 'boolean') || (typeof col.required === 'object'))) {
|
|
171
|
+
errors.push(`Resource "${res.resourceId}" column "${col.name}" required must be a string or object`);
|
|
172
|
+
}
|
|
173
|
+
// if it is object check the keys are one of ['create', 'edit']
|
|
174
|
+
if (typeof col.required === 'object') {
|
|
175
|
+
const wrongRequiredOn = Object.keys(col.required).find((c) => !['create', 'edit'].includes(c));
|
|
176
|
+
if (wrongRequiredOn) {
|
|
177
|
+
errors.push(`Resource "${res.resourceId}" column "${col.name}" has invalid required value "${wrongRequiredOn}", allowed keys are 'create', 'edit']`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// same for editingNote
|
|
181
|
+
if (col.editingNote && !((typeof col.editingNote === 'string') || (typeof col.editingNote === 'object'))) {
|
|
182
|
+
errors.push(`Resource "${res.resourceId}" column "${col.name}" editingNote must be a string or object`);
|
|
183
|
+
}
|
|
184
|
+
if (typeof col.editingNote === 'object') {
|
|
185
|
+
const wrongEditingNoteOn = Object.keys(col.editingNote).find((c) => !['create', 'edit'].includes(c));
|
|
186
|
+
if (wrongEditingNoteOn) {
|
|
187
|
+
errors.push(`Resource "${res.resourceId}" column "${col.name}" has invalid editingNote value "${wrongEditingNoteOn}", allowed keys are 'create', 'edit']`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const wrongShowIn = col.showIn && col.showIn.find((c) => AdminForthResourcePages[c] === undefined);
|
|
191
|
+
if (wrongShowIn) {
|
|
192
|
+
errors.push(`Resource "${res.resourceId}" column "${col.name}" has invalid showIn value "${wrongShowIn}", allowed values are ${Object.keys(AdminForthResourcePages).join(', ')}`);
|
|
193
|
+
}
|
|
194
|
+
col.showIn = col.showIn || Object.values(AdminForthResourcePages);
|
|
195
|
+
if (col.foreignResource) {
|
|
196
|
+
const befHook = (_b = (_a = col.foreignResource.hooks) === null || _a === void 0 ? void 0 : _a.dropdownList) === null || _b === void 0 ? void 0 : _b.beforeDatasourceRequest;
|
|
197
|
+
if (befHook) {
|
|
198
|
+
if (!Array.isArray(befHook)) {
|
|
199
|
+
col.foreignResource.hooks.dropdownList.beforeDatasourceRequest = [befHook];
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const aftHook = (_d = (_c = col.foreignResource.hooks) === null || _c === void 0 ? void 0 : _c.dropdownList) === null || _d === void 0 ? void 0 : _d.afterDatasourceResponse;
|
|
203
|
+
if (aftHook) {
|
|
204
|
+
if (!Array.isArray(aftHook)) {
|
|
205
|
+
col.foreignResource.hooks.dropdownList.afterDatasourceResponse = [aftHook];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
if (!res.options) {
|
|
211
|
+
res.options = { bulkActions: [], allowedActions: {} };
|
|
212
|
+
}
|
|
213
|
+
if (!res.options.allowedActions) {
|
|
214
|
+
res.options.allowedActions = {
|
|
215
|
+
all: true,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
if (Object.keys(res.options.allowedActions).includes('all')) {
|
|
219
|
+
if (Object.keys(res.options.allowedActions).length > 1) {
|
|
220
|
+
errors.push(`Resource "${res.resourceId}" allowedActions cannot have "all" and other keys at same time: ${Object.keys(res.options.allowedActions).join(', ')}`);
|
|
221
|
+
}
|
|
222
|
+
for (const key of Object.keys(AllowedActionsEnum)) {
|
|
223
|
+
if (key !== 'all') {
|
|
224
|
+
res.options.allowedActions[key] = res.options.allowedActions.all;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
delete res.options.allowedActions.all;
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
// by default allow all actions
|
|
231
|
+
for (const key of Object.keys(AllowedActionsEnum)) {
|
|
232
|
+
if (!Object.keys(res.options.allowedActions).includes(key)) {
|
|
233
|
+
res.options.allowedActions[key] = true;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
//check if resource has bulkActions
|
|
238
|
+
let bulkActions = ((_a = res === null || res === void 0 ? void 0 : res.options) === null || _a === void 0 ? void 0 : _a.bulkActions) || [];
|
|
239
|
+
if (!Array.isArray(bulkActions)) {
|
|
240
|
+
errors.push(`Resource "${res.resourceId}" bulkActions must be an array`);
|
|
241
|
+
bulkActions = [];
|
|
242
|
+
}
|
|
243
|
+
if (((_c = (_b = res.options) === null || _b === void 0 ? void 0 : _b.allowedActions) === null || _c === void 0 ? void 0 : _c.delete) && !bulkActions.find((action) => action.label === 'Delete checked')) {
|
|
244
|
+
bulkActions.push({
|
|
245
|
+
label: `Delete checked`,
|
|
246
|
+
state: 'danger',
|
|
247
|
+
icon: 'flowbite:trash-bin-outline',
|
|
248
|
+
action: (_d) => __awaiter(this, [_d], void 0, function* ({ selectedIds }) {
|
|
249
|
+
const connector = this.adminforth.connectors[res.dataSource];
|
|
250
|
+
yield Promise.all(selectedIds.map((recordId) => __awaiter(this, void 0, void 0, function* () {
|
|
251
|
+
yield connector.deleteRecord({ resource: res, recordId });
|
|
252
|
+
})));
|
|
253
|
+
})
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
const newBulkActions = bulkActions.map((action) => {
|
|
257
|
+
return Object.assign(action, { id: uuid() });
|
|
258
|
+
});
|
|
259
|
+
res.options.bulkActions = newBulkActions;
|
|
260
|
+
// if pageInjection is a string, make array with one element. Also check file exists
|
|
261
|
+
const possibleInjections = ['beforeBreadcrumbs', 'afterBreadcrumbs', 'bottom'];
|
|
262
|
+
if (res.options.pageInjections) {
|
|
263
|
+
Object.entries(res.options.pageInjections).map(([key, value]) => {
|
|
264
|
+
Object.entries(value).map(([injection, target]) => {
|
|
265
|
+
if (possibleInjections.includes(injection)) {
|
|
266
|
+
if (!Array.isArray(res.options.pageInjections[key][injection])) {
|
|
267
|
+
// not array
|
|
268
|
+
res.options.pageInjections[key][injection] = [target];
|
|
269
|
+
}
|
|
270
|
+
res.options.pageInjections[key][injection].forEach((target, i) => {
|
|
271
|
+
res.options.pageInjections[key][injection][i] = this.validateComponent(target, errors);
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
errors.push(`Resource "${res.resourceId}" has invalid pageInjection key "${injection}", Supported keys are ${possibleInjections.join(', ')}`);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
// transform all hooks Functions to array of functions
|
|
281
|
+
if (!res.hooks) {
|
|
282
|
+
res.hooks = {};
|
|
283
|
+
}
|
|
284
|
+
for (const hookName of ['show', 'list']) {
|
|
285
|
+
if (!res.hooks[hookName]) {
|
|
286
|
+
res.hooks[hookName] = {};
|
|
287
|
+
}
|
|
288
|
+
if (!res.hooks[hookName].beforeDatasourceRequest) {
|
|
289
|
+
res.hooks[hookName].beforeDatasourceRequest = [];
|
|
290
|
+
}
|
|
291
|
+
if (!Array.isArray(res.hooks[hookName].beforeDatasourceRequest)) {
|
|
292
|
+
res.hooks[hookName].beforeDatasourceRequest = [res.hooks[hookName].beforeDatasourceRequest];
|
|
293
|
+
}
|
|
294
|
+
if (!res.hooks[hookName].afterDatasourceResponse) {
|
|
295
|
+
res.hooks[hookName].afterDatasourceResponse = [];
|
|
296
|
+
}
|
|
297
|
+
if (!Array.isArray(res.hooks[hookName].afterDatasourceResponse)) {
|
|
298
|
+
res.hooks[hookName].afterDatasourceResponse = [res.hooks[hookName].afterDatasourceResponse];
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
for (const hookName of ['create', 'edit', 'delete']) {
|
|
302
|
+
if (!res.hooks[hookName]) {
|
|
303
|
+
res.hooks[hookName] = {};
|
|
304
|
+
}
|
|
305
|
+
if (!res.hooks[hookName].beforeSave) {
|
|
306
|
+
res.hooks[hookName].beforeSave = [];
|
|
307
|
+
}
|
|
308
|
+
if (!Array.isArray(res.hooks[hookName].beforeSave)) {
|
|
309
|
+
res.hooks[hookName].beforeSave = [res.hooks[hookName].beforeSave];
|
|
310
|
+
}
|
|
311
|
+
if (!res.hooks[hookName].afterSave) {
|
|
312
|
+
res.hooks[hookName].afterSave = [];
|
|
313
|
+
}
|
|
314
|
+
if (!Array.isArray(res.hooks[hookName].afterSave)) {
|
|
315
|
+
res.hooks[hookName].afterSave = [res.hooks[hookName].afterSave];
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
if (!this.config.menu) {
|
|
320
|
+
errors.push('No config.menu defined');
|
|
321
|
+
}
|
|
322
|
+
// check if there is only one homepage: true in menu, recursivly
|
|
323
|
+
let homepages = 0;
|
|
324
|
+
const browseMenu = (menu) => {
|
|
325
|
+
menu.forEach((item) => {
|
|
326
|
+
if (item.component && item.resourceId) {
|
|
327
|
+
errors.push(`Menu item cannot have both component and resourceId: ${JSON.stringify(item)}`);
|
|
328
|
+
}
|
|
329
|
+
if (item.component && !item.path) {
|
|
330
|
+
errors.push(`Menu item with component must have path : ${JSON.stringify(item)}`);
|
|
331
|
+
}
|
|
332
|
+
if (item.type === 'resource' && !item.resourceId) {
|
|
333
|
+
errors.push(`Menu item with type 'resource' must have resourceId : ${JSON.stringify(item)}`);
|
|
334
|
+
}
|
|
335
|
+
if (item.resourceId && !this.config.resources.find((res) => res.resourceId === item.resourceId)) {
|
|
336
|
+
errors.push(`Menu item with type 'resourceId' has resourceId which is not in resources: ${JSON.stringify(item)}`);
|
|
337
|
+
}
|
|
338
|
+
if (item.type === 'component' && !item.component) {
|
|
339
|
+
errors.push(`Menu item with type 'component' must have component : ${JSON.stringify(item)}`);
|
|
340
|
+
}
|
|
341
|
+
// make sure component starts with @@
|
|
342
|
+
if (item.component) {
|
|
343
|
+
if (!item.component.startsWith('@@')) {
|
|
344
|
+
errors.push(`Menu item component must start with @@ : ${JSON.stringify(item)}`);
|
|
345
|
+
}
|
|
346
|
+
const path = item.component.replace('@@', this.config.customization.customComponentsDir);
|
|
347
|
+
if (!fs.existsSync(path)) {
|
|
348
|
+
errors.push(`Menu item component "${item.component.replace('@@', '')}" does not exist in "${this.config.customization.customComponentsDir}"`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (item.homepage) {
|
|
352
|
+
homepages++;
|
|
353
|
+
if (homepages > 1) {
|
|
354
|
+
errors.push('There must be only one homepage: true in menu, found second one in ' + JSON.stringify(item));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (item.children) {
|
|
358
|
+
browseMenu(item.children);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
};
|
|
362
|
+
browseMenu(this.config.menu);
|
|
363
|
+
}
|
|
364
|
+
// check for duplicate resourceIds and show which ones are duplicated
|
|
365
|
+
const resourceIds = this.config.resources.map((res) => res.resourceId);
|
|
366
|
+
const uniqueResourceIds = new Set(resourceIds);
|
|
367
|
+
if (uniqueResourceIds.size != resourceIds.length) {
|
|
368
|
+
const duplicates = resourceIds.filter((item, index) => resourceIds.indexOf(item) != index);
|
|
369
|
+
errors.push(`Duplicate fields "resourceId" or "table": ${duplicates.join(', ')}`);
|
|
370
|
+
}
|
|
371
|
+
//add ids for onSelectedAllActions for each resource
|
|
372
|
+
if (errors.length > 0) {
|
|
373
|
+
throw new Error(`Invalid AdminForth config: ${errors.join(', ')}`);
|
|
374
|
+
}
|
|
375
|
+
// check is all custom components files exists
|
|
376
|
+
for (const resource of this.config.resources) {
|
|
377
|
+
for (const column of resource.columns) {
|
|
378
|
+
if (column.components) {
|
|
379
|
+
for (const [key, comp] of Object.entries(column.components)) {
|
|
380
|
+
let ignoreExistsCheck = false;
|
|
381
|
+
if (this.adminforth.codeInjector.allComponentNames[comp.file]) {
|
|
382
|
+
// not obvious, but if we are in this if, it means that this is plugin component
|
|
383
|
+
// and there is no sense to check if it exists in users folder
|
|
384
|
+
ignoreExistsCheck = true;
|
|
385
|
+
}
|
|
386
|
+
column.components[key] = this.validateComponent(comp, errors, ignoreExistsCheck);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
postProcessAfterDiscover(resource) {
|
|
393
|
+
resource.columns.forEach((column) => {
|
|
394
|
+
// if db/user says column is required in boolean, expand
|
|
395
|
+
if (typeof column.required === 'boolean') {
|
|
396
|
+
column.required = { create: column.required, edit: column.required };
|
|
397
|
+
}
|
|
398
|
+
if (!column.required) {
|
|
399
|
+
column.required = { create: false, edit: false };
|
|
400
|
+
}
|
|
401
|
+
// same for editingNote
|
|
402
|
+
if (typeof column.editingNote === 'string') {
|
|
403
|
+
column.editingNote = { create: column.editingNote, edit: column.editingNote };
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
resource.dataSourceColumns = resource.columns.filter((col) => !col.virtual);
|
|
407
|
+
}
|
|
408
|
+
}
|