@reldens/cms 0.85.0 → 0.86.0
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/.claude/api-reference.md +172 -171
- package/.claude/architecture-guide.md +1 -1
- package/.claude/configuration-guide.md +128 -126
- package/.claude/installation-guide.md +225 -225
- package/.claude/password-management-guide.md +2 -2
- package/CLAUDE.md +1 -1
- package/README.md +1 -1
- package/bin/reldens-cms-generate-entities.js +19 -8
- package/bin/reldens-cms-update-password.js +16 -8
- package/bin/reldens-cms.js +145 -138
- package/install/index.html +132 -132
- package/lib/admin-manager/router-contents.js +854 -854
- package/lib/installer.js +673 -636
- package/lib/manager-config-loader.js +35 -33
- package/lib/manager-services-initializer.js +346 -323
- package/lib/manager.js +310 -306
- package/lib/mysql-installer.js +104 -104
- package/lib/prisma-subprocess-worker.js +9 -4
- package/package.json +4 -4
- package/templates/.env.dist +2 -0
- package/templates/index.js.dist +30 -30
|
@@ -1,854 +1,854 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
* Reldens - RouterContents
|
|
4
|
-
*
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const { PageRangeProvider, Logger, sc } = require('@reldens/utils');
|
|
8
|
-
const { FileHandler } = require('@reldens/server-utils');
|
|
9
|
-
const { AdminFiltersManager } = require('./admin-filters-manager');
|
|
10
|
-
|
|
11
|
-
class RouterContents
|
|
12
|
-
{
|
|
13
|
-
|
|
14
|
-
constructor(props)
|
|
15
|
-
{
|
|
16
|
-
this.dataServer = props.dataServer;
|
|
17
|
-
this.translations = props.translations;
|
|
18
|
-
this.rootPath = props.rootPath;
|
|
19
|
-
this.relations = props.relations;
|
|
20
|
-
this.resourcesByReference = props.resourcesByReference;
|
|
21
|
-
this.adminFilesContents = props.adminFilesContents;
|
|
22
|
-
this.autoSyncDistCallback = props.autoSyncDistCallback;
|
|
23
|
-
this.viewPath = props.viewPath;
|
|
24
|
-
this.editPath = props.editPath;
|
|
25
|
-
this.deletePath = props.deletePath;
|
|
26
|
-
this.emitEvent = props.emitEvent;
|
|
27
|
-
this.adminContentsRender = props.adminContentsRender;
|
|
28
|
-
this.adminContentsRenderRoute = props.adminContentsRenderRoute;
|
|
29
|
-
this.adminContentsEntities = props.adminContentsEntities;
|
|
30
|
-
this.adminContentsSideBar = props.adminContentsSideBar;
|
|
31
|
-
this.fetchTranslation = props.fetchTranslation;
|
|
32
|
-
this.fetchEntityIdPropertyKey = props.fetchEntityIdPropertyKey;
|
|
33
|
-
this.fetchUploadProperties = props.fetchUploadProperties;
|
|
34
|
-
this.uploaderFactory = props.uploaderFactory;
|
|
35
|
-
this.filtersManager = new AdminFiltersManager();
|
|
36
|
-
this.passwordFieldNames = props.passwordFieldNames || ['password'];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async generateListRouteContent(req, driverResource, entityPath)
|
|
40
|
-
{
|
|
41
|
-
let currentPage = Number(req?.query?.page || 1);
|
|
42
|
-
let pageSize = Number(req?.query?.pageSize || 25);
|
|
43
|
-
let shouldClearFilters = 'true' === req?.query?.clearFilters;
|
|
44
|
-
let sessionFilters = this.filtersManager.getFiltersFromSession(req, entityPath);
|
|
45
|
-
let sessionSorting = this.filtersManager.getSortingFromSession(req, entityPath);
|
|
46
|
-
let filtersFromParams = shouldClearFilters ? {} : req?.body?.filters || req?.query?.filters || {};
|
|
47
|
-
let entityFilterTerm = shouldClearFilters ? '' : sc.get(req?.body, 'entityFilterTerm', '');
|
|
48
|
-
let sortByFromParams = shouldClearFilters ? '' : sc.get(req?.body, 'sortBy', sc.get(req?.query, 'sortBy', ''));
|
|
49
|
-
let sortDirectionFromParams = shouldClearFilters ? '' : sc.get(req?.body, 'sortDirection', sc.get(req?.query, 'sortDirection', ''));
|
|
50
|
-
if(shouldClearFilters){
|
|
51
|
-
this.filtersManager.clearFiltersFromSession(req, entityPath);
|
|
52
|
-
this.filtersManager.clearSortingFromSession(req, entityPath);
|
|
53
|
-
}
|
|
54
|
-
let hasNewEntityFilterTerm = entityFilterTerm && '' !== entityFilterTerm;
|
|
55
|
-
let hasNewToggleFilters = Object.keys(filtersFromParams).length > 0;
|
|
56
|
-
if(hasNewEntityFilterTerm && hasNewToggleFilters){
|
|
57
|
-
filtersFromParams = {};
|
|
58
|
-
hasNewToggleFilters = false;
|
|
59
|
-
}
|
|
60
|
-
if(hasNewEntityFilterTerm || hasNewToggleFilters){
|
|
61
|
-
let filtersToSave = {
|
|
62
|
-
regular: hasNewToggleFilters ? filtersFromParams : {},
|
|
63
|
-
entityFilterTerm: hasNewEntityFilterTerm ? entityFilterTerm : ''
|
|
64
|
-
};
|
|
65
|
-
this.filtersManager.saveFiltersToSession(req, entityPath, filtersToSave);
|
|
66
|
-
sessionFilters = filtersToSave;
|
|
67
|
-
}
|
|
68
|
-
if(sortByFromParams && sortDirectionFromParams){
|
|
69
|
-
this.filtersManager.saveSortingToSession(req, entityPath, sortByFromParams, sortDirectionFromParams);
|
|
70
|
-
sessionSorting = {sortBy: sortByFromParams, sortDirection: sortDirectionFromParams};
|
|
71
|
-
}
|
|
72
|
-
let mergedFilters = shouldClearFilters ? {} : Object.assign({}, sessionFilters.regular || {});
|
|
73
|
-
let finalEntityFilterTerm = shouldClearFilters ? '' : sessionFilters.entityFilterTerm || '';
|
|
74
|
-
let idProperty = this.fetchEntityIdPropertyKey(driverResource);
|
|
75
|
-
let finalSortBy = sessionSorting.sortBy || idProperty;
|
|
76
|
-
let finalSortDirection = sessionSorting.sortDirection || 'ASC';
|
|
77
|
-
let filters = this.filtersManager.prepareFilters(mergedFilters, driverResource);
|
|
78
|
-
let textFilters = this.filtersManager.prepareTextFilters(finalEntityFilterTerm, driverResource);
|
|
79
|
-
let combinedFilters = this.filtersManager.combineFilters(filters, textFilters);
|
|
80
|
-
let totalEntities = await this.countTotalEntities(driverResource, combinedFilters);
|
|
81
|
-
let totalPages = totalEntities <= pageSize ? 1 : Math.ceil(totalEntities / pageSize);
|
|
82
|
-
let renderedPagination = '';
|
|
83
|
-
for(let paginationItem of PageRangeProvider.fetch(currentPage, totalPages)){
|
|
84
|
-
let paginationUrl = this.rootPath+'/'+driverResource.entityPath+'?page='+ paginationItem.value;
|
|
85
|
-
if(finalEntityFilterTerm){
|
|
86
|
-
paginationUrl += '&entityFilterTerm='+encodeURIComponent(finalEntityFilterTerm);
|
|
87
|
-
}
|
|
88
|
-
if(finalSortBy){
|
|
89
|
-
paginationUrl += '&sortBy='+encodeURIComponent(finalSortBy);
|
|
90
|
-
}
|
|
91
|
-
if(finalSortDirection){
|
|
92
|
-
paginationUrl += '&sortDirection='+encodeURIComponent(finalSortDirection);
|
|
93
|
-
}
|
|
94
|
-
for(let filterKey of Object.keys(mergedFilters)){
|
|
95
|
-
if(mergedFilters[filterKey]){
|
|
96
|
-
paginationUrl += '&filters['+filterKey+']='+encodeURIComponent(mergedFilters[filterKey]);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
renderedPagination += await this.adminContentsRender(
|
|
100
|
-
this.adminFilesContents.fields.view['link'],
|
|
101
|
-
{
|
|
102
|
-
fieldName: paginationItem.label,
|
|
103
|
-
fieldValue: paginationUrl,
|
|
104
|
-
fieldOriginalValue: paginationItem.value,
|
|
105
|
-
}
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
let listProperties = {
|
|
109
|
-
entityNewRoute: this.rootPath+'/'+driverResource.entityPath+this.editPath,
|
|
110
|
-
entityFilterTermValue: finalEntityFilterTerm || ''
|
|
111
|
-
};
|
|
112
|
-
await this.emitEvent('reldens.adminListPropertiesPopulation', {
|
|
113
|
-
req,
|
|
114
|
-
driverResource,
|
|
115
|
-
listProperties
|
|
116
|
-
});
|
|
117
|
-
return await this.adminContentsRenderRoute(
|
|
118
|
-
await this.adminContentsRender(
|
|
119
|
-
this.adminContentsEntities()[entityPath].list,
|
|
120
|
-
Object.assign({
|
|
121
|
-
list: await this.adminContentsRender(this.adminFilesContents.listContent, {
|
|
122
|
-
deletePath: this.rootPath + '/' + driverResource.entityPath + this.deletePath,
|
|
123
|
-
fieldsHeaders: driverResource.options.listProperties.map((property) => {
|
|
124
|
-
let propertyTitle = this.fetchTranslation(property, driverResource.id());
|
|
125
|
-
let alias = this.fetchTranslation(
|
|
126
|
-
driverResource.options.properties[property]?.alias || '',
|
|
127
|
-
driverResource.id()
|
|
128
|
-
);
|
|
129
|
-
let isSorted = finalSortBy === property;
|
|
130
|
-
let sortDirection = isSorted ? finalSortDirection.toLowerCase() : '';
|
|
131
|
-
return {
|
|
132
|
-
name: property,
|
|
133
|
-
value: '' !== alias ? alias + ' ('+propertyTitle+')' : propertyTitle,
|
|
134
|
-
isSorted,
|
|
135
|
-
sortDirection,
|
|
136
|
-
isSortedAsc: isSorted && 'asc' === sortDirection,
|
|
137
|
-
isSortedDesc: isSorted && 'desc' === sortDirection
|
|
138
|
-
};
|
|
139
|
-
}),
|
|
140
|
-
rows: await this.loadEntitiesForList(
|
|
141
|
-
driverResource,
|
|
142
|
-
pageSize,
|
|
143
|
-
currentPage,
|
|
144
|
-
req,
|
|
145
|
-
combinedFilters,
|
|
146
|
-
finalSortBy,
|
|
147
|
-
finalSortDirection
|
|
148
|
-
)
|
|
149
|
-
}),
|
|
150
|
-
pagination: renderedPagination,
|
|
151
|
-
extraContentForList: sc.get(listProperties, 'extraContentForList', ''),
|
|
152
|
-
entityFilterTermValue: listProperties.entityFilterTermValue
|
|
153
|
-
}, ...driverResource.options.filterProperties.map((property) => {
|
|
154
|
-
let filterValue = (mergedFilters[property] || '');
|
|
155
|
-
return {[property]: '' === filterValue ? '' : 'value="'+filterValue+'"'};
|
|
156
|
-
}))
|
|
157
|
-
),
|
|
158
|
-
this.adminContentsSideBar()
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
async generateViewRouteContent(req, driverResource, entityPath)
|
|
163
|
-
{
|
|
164
|
-
let idProperty = this.fetchEntityIdPropertyKey(driverResource);
|
|
165
|
-
let id = (sc.get(req.query, idProperty, ''));
|
|
166
|
-
if('' === id){
|
|
167
|
-
Logger.error('Missing ID on view route.', entityPath, id, idProperty);
|
|
168
|
-
return '';
|
|
169
|
-
}
|
|
170
|
-
let loadedEntity = await this.loadEntityById(driverResource, id);
|
|
171
|
-
if(!loadedEntity){
|
|
172
|
-
Logger.critical('Entity not found on view route.', entityPath, id, idProperty);
|
|
173
|
-
return '';
|
|
174
|
-
}
|
|
175
|
-
let renderedViewProperties = {
|
|
176
|
-
entityEditRoute: this.generateEntityRoute('editPath', driverResource, idProperty, loadedEntity),
|
|
177
|
-
entityNewRoute: this.rootPath+'/'+driverResource.entityPath+this.editPath,
|
|
178
|
-
id
|
|
179
|
-
};
|
|
180
|
-
let entitySerializedData = {};
|
|
181
|
-
for(let propertyKey of driverResource.options.showProperties){
|
|
182
|
-
let property = driverResource.options.properties[propertyKey];
|
|
183
|
-
let {fieldValue, fieldName} = this.generatePropertyRenderedValueWithLabel(
|
|
184
|
-
loadedEntity,
|
|
185
|
-
propertyKey,
|
|
186
|
-
property
|
|
187
|
-
);
|
|
188
|
-
entitySerializedData[fieldName] = fieldValue;
|
|
189
|
-
renderedViewProperties[propertyKey] = await this.generatePropertyRenderedValue(
|
|
190
|
-
fieldValue,
|
|
191
|
-
fieldName,
|
|
192
|
-
property,
|
|
193
|
-
'view'
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
let extraDataEvent = {entitySerializedData, entityId: driverResource.id(), entity: loadedEntity};
|
|
197
|
-
await this.emitEvent('adminEntityExtraData', extraDataEvent);
|
|
198
|
-
renderedViewProperties.entitySerializedData = JSON.stringify(extraDataEvent.entitySerializedData)
|
|
199
|
-
.replace(/"/g, '"');
|
|
200
|
-
renderedViewProperties.extraContentForViewTop = '';
|
|
201
|
-
renderedViewProperties.extraContentForViewBottom = '';
|
|
202
|
-
await this.emitEvent('reldens.adminViewPropertiesPopulation', {
|
|
203
|
-
idProperty,
|
|
204
|
-
req,
|
|
205
|
-
driverResource,
|
|
206
|
-
loadedEntity,
|
|
207
|
-
renderedViewProperties
|
|
208
|
-
});
|
|
209
|
-
return await this.adminContentsRenderRoute(
|
|
210
|
-
await this.adminContentsRender(this.adminContentsEntities()[entityPath].view, renderedViewProperties),
|
|
211
|
-
this.adminContentsSideBar()
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
async generateEditRouteContent(req, driverResource, entityPath)
|
|
216
|
-
{
|
|
217
|
-
let idProperty = this.fetchEntityIdPropertyKey(driverResource);
|
|
218
|
-
let idValue = String(sc.get(req?.query, idProperty, ''));
|
|
219
|
-
let loadedEntity = !idValue ? null : await this.loadEntityById(driverResource, idValue);
|
|
220
|
-
let renderedEditProperties = {
|
|
221
|
-
idValue,
|
|
222
|
-
idProperty,
|
|
223
|
-
idPropertyLabel: this.fetchTranslation(idProperty, driverResource.id()),
|
|
224
|
-
templateTitle: (!idValue ? 'Create' : 'Edit')+' '+this.translations.labels[driverResource.id()],
|
|
225
|
-
entityViewRoute: !idValue
|
|
226
|
-
? this.rootPath+'/'+driverResource.entityPath
|
|
227
|
-
: this.generateEntityRoute('viewPath', driverResource, idProperty, loadedEntity)
|
|
228
|
-
};
|
|
229
|
-
await this.emitEvent('reldens.adminEditPropertiesPopulation', {
|
|
230
|
-
req,
|
|
231
|
-
driverResource,
|
|
232
|
-
renderedEditProperties,
|
|
233
|
-
loadedEntity,
|
|
234
|
-
entityId: driverResource.id(),
|
|
235
|
-
entityData: loadedEntity
|
|
236
|
-
});
|
|
237
|
-
for(let propertyKey of Object.keys(driverResource.options.properties)){
|
|
238
|
-
let property = driverResource.options.properties[propertyKey];
|
|
239
|
-
let fieldDisabled = -1 === driverResource.options.editProperties.indexOf(propertyKey);
|
|
240
|
-
let fieldValue = await this.generatePropertyEditRenderedValue(
|
|
241
|
-
loadedEntity,
|
|
242
|
-
propertyKey,
|
|
243
|
-
property,
|
|
244
|
-
fieldDisabled
|
|
245
|
-
);
|
|
246
|
-
let templateData = {
|
|
247
|
-
fieldName: propertyKey,
|
|
248
|
-
fieldValue,
|
|
249
|
-
fieldDisabled: fieldDisabled ? ' disabled="disabled"' : '',
|
|
250
|
-
required: (!property.isUpload || !loadedEntity) && property.isRequired ? ' required="required"' : '',
|
|
251
|
-
multiple: property.isArray ? ' multiple="multiple"' : '',
|
|
252
|
-
inputType: this.getInputType(property, propertyKey, fieldDisabled),
|
|
253
|
-
isArray: property.isArray || '',
|
|
254
|
-
arraySeparator: property.isArray || '',
|
|
255
|
-
isRequired: property.isRequired || false
|
|
256
|
-
};
|
|
257
|
-
if(property.isUpload && fieldValue && '' !== fieldValue){
|
|
258
|
-
let filesArray = property.isArray ? fieldValue.split(property.isArray) : [fieldValue];
|
|
259
|
-
templateData.filesArray = filesArray.map((filename) => {
|
|
260
|
-
return {
|
|
261
|
-
filename,
|
|
262
|
-
isRemovable: !property.isRequired || filesArray.length > 1
|
|
263
|
-
};
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
await this.emitEvent('reldens.adminBeforeFieldRender', {
|
|
267
|
-
req,
|
|
268
|
-
driverResource,
|
|
269
|
-
propertyKey,
|
|
270
|
-
property,
|
|
271
|
-
templateData,
|
|
272
|
-
loadedEntity,
|
|
273
|
-
renderedEditProperties,
|
|
274
|
-
adminContentsRender: this.adminContentsRender.bind(this),
|
|
275
|
-
adminFilesContents: this.adminFilesContents
|
|
276
|
-
});
|
|
277
|
-
renderedEditProperties[propertyKey] = await this.adminContentsRender(
|
|
278
|
-
this.adminFilesContents.fields.edit[this.propertyType(property, 'edit')],
|
|
279
|
-
templateData
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
return await this.adminContentsRenderRoute(
|
|
283
|
-
await this.adminContentsRender(this.adminContentsEntities()[entityPath].edit, renderedEditProperties),
|
|
284
|
-
this.adminContentsSideBar()
|
|
285
|
-
);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
async processDeleteEntities(req, res, driverResource, entityPath)
|
|
289
|
-
{
|
|
290
|
-
let ids = req?.body?.ids;
|
|
291
|
-
if('string' === typeof ids){
|
|
292
|
-
ids = ids.split(',');
|
|
293
|
-
}
|
|
294
|
-
let redirectPath = this.rootPath+'/'+entityPath+'?result=';
|
|
295
|
-
if(!ids || 0 === ids.length){
|
|
296
|
-
return redirectPath + 'errorMissingId';
|
|
297
|
-
}
|
|
298
|
-
try {
|
|
299
|
-
let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
|
|
300
|
-
let idProperty = this.fetchEntityIdPropertyKey(driverResource);
|
|
301
|
-
let idsFilter = {[idProperty]: {operator: 'IN', value: ids}};
|
|
302
|
-
let loadedEntities = await entityRepository.load(idsFilter);
|
|
303
|
-
let deleteControl = {prevented: false, result: ''};
|
|
304
|
-
await this.emitEvent('reldens.adminBeforeEntityDelete', {
|
|
305
|
-
driverResource,
|
|
306
|
-
entityPath,
|
|
307
|
-
idProperty,
|
|
308
|
-
ids,
|
|
309
|
-
loadedEntities,
|
|
310
|
-
deleteControl
|
|
311
|
-
});
|
|
312
|
-
if(deleteControl.prevented){
|
|
313
|
-
return redirectPath + (deleteControl.result || 'errorDeletePrevented');
|
|
314
|
-
}
|
|
315
|
-
await this.deleteEntitiesRelatedFiles(driverResource, loadedEntities);
|
|
316
|
-
let deleteResult = await entityRepository.delete(idsFilter);
|
|
317
|
-
await this.emitEvent('reldens.adminAfterEntityDelete', {driverResource, idProperty, ids});
|
|
318
|
-
return redirectPath + (deleteResult ? 'success' : 'errorStorageFailure');
|
|
319
|
-
} catch (error) {
|
|
320
|
-
return redirectPath + 'errorDeleteFailure';
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
async processSaveEntity(req, res, driverResource, entityPath)
|
|
325
|
-
{
|
|
326
|
-
let idProperty = this.fetchEntityIdPropertyKey(driverResource);
|
|
327
|
-
let id = (req?.body[idProperty] || '');
|
|
328
|
-
let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
|
|
329
|
-
let loadedEntity = id ? await this.loadEntityById(driverResource, id) : null;
|
|
330
|
-
let uploadProperties = this.fetchUploadProperties(driverResource);
|
|
331
|
-
if(0 < Object.keys(uploadProperties).length){
|
|
332
|
-
if(this.uploaderFactory?.error?.message){
|
|
333
|
-
Logger.error('Upload factory error detected', this.uploaderFactory.error);
|
|
334
|
-
return this.rootPath+'/'+entityPath+'?result=uploadError&error='
|
|
335
|
-
+encodeURIComponent(this.uploaderFactory.error.message);
|
|
336
|
-
}
|
|
337
|
-
let uploadValidationResult = this.validateUploadedFiles(req, uploadProperties, loadedEntity);
|
|
338
|
-
if(!uploadValidationResult.success){
|
|
339
|
-
Logger.error('Upload validation failed', uploadValidationResult.error);
|
|
340
|
-
return this.rootPath+'/'+entityPath+'?result=uploadValidationError&error='
|
|
341
|
-
+encodeURIComponent(uploadValidationResult.error.message);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
let entityDataPatch = this.preparePatchData(
|
|
345
|
-
driverResource,
|
|
346
|
-
idProperty,
|
|
347
|
-
req,
|
|
348
|
-
driverResource.options.properties,
|
|
349
|
-
id,
|
|
350
|
-
loadedEntity
|
|
351
|
-
);
|
|
352
|
-
if(!entityDataPatch){
|
|
353
|
-
Logger.error('Bad patch data.', entityDataPatch);
|
|
354
|
-
return this.rootPath+'/'+entityPath+'?result=saveBadPatchData';
|
|
355
|
-
}
|
|
356
|
-
try {
|
|
357
|
-
let saveResult = await this.saveEntity(id, entityRepository, entityDataPatch);
|
|
358
|
-
if(!saveResult){
|
|
359
|
-
Logger.error('Save result error.', saveResult, entityDataPatch);
|
|
360
|
-
return this.appendResultParam(
|
|
361
|
-
this.generateEntityRoute('editPath', driverResource, idProperty, null, id),
|
|
362
|
-
'saveEntityStorageError'
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
await this.emitEvent('reldens.adminAfterEntitySave', {
|
|
366
|
-
req,
|
|
367
|
-
res,
|
|
368
|
-
driverResource,
|
|
369
|
-
entityPath,
|
|
370
|
-
entityData: saveResult
|
|
371
|
-
});
|
|
372
|
-
if(sc.isFunction(this.autoSyncDistCallback)){
|
|
373
|
-
let uploadProperties = this.fetchUploadProperties(driverResource);
|
|
374
|
-
if(0 < Object.keys(uploadProperties).length){
|
|
375
|
-
for(let uploadPropertyKey of Object.keys(uploadProperties)){
|
|
376
|
-
let property = uploadProperties[uploadPropertyKey];
|
|
377
|
-
await this.autoSyncDistCallback(
|
|
378
|
-
property.bucket,
|
|
379
|
-
saveResult[uploadPropertyKey],
|
|
380
|
-
property.distFolder
|
|
381
|
-
);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
let saveAction = sc.get(req.body, 'saveAction', 'save');
|
|
386
|
-
if('saveAndContinue' === saveAction){
|
|
387
|
-
return this.appendResultParam(
|
|
388
|
-
this.generateEntityRoute('editPath', driverResource, idProperty, saveResult),
|
|
389
|
-
'success'
|
|
390
|
-
);
|
|
391
|
-
}
|
|
392
|
-
if('saveAndGoBack' === saveAction){
|
|
393
|
-
return this.rootPath+'/'+entityPath+'?result=success';
|
|
394
|
-
}
|
|
395
|
-
return this.generateEntityRoute('viewPath', driverResource, idProperty, saveResult) +'&result=success';
|
|
396
|
-
} catch (error) {
|
|
397
|
-
Logger.error('Save entity error.', error);
|
|
398
|
-
return this.rootPath+'/'+entityPath+'?result=saveEntityError';
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
validateUploadedFiles(req, uploadProperties, loadedEntity)
|
|
403
|
-
{
|
|
404
|
-
for(let uploadPropertyKey of Object.keys(uploadProperties)){
|
|
405
|
-
let property = uploadProperties[uploadPropertyKey];
|
|
406
|
-
let uploadedFiles = req.files?.[uploadPropertyKey];
|
|
407
|
-
if(!uploadedFiles || 0 === uploadedFiles.length){
|
|
408
|
-
if(property.isRequired){
|
|
409
|
-
let isExplicitClear = '1' === sc.get(req.body, 'clear_'+uploadPropertyKey, false);
|
|
410
|
-
if(isExplicitClear){
|
|
411
|
-
return {success: false, error: {message: 'Required file upload missing: '+uploadPropertyKey}};
|
|
412
|
-
}
|
|
413
|
-
let existingValue = loadedEntity ? sc.get(loadedEntity, uploadPropertyKey, '') : '';
|
|
414
|
-
if(!existingValue || '' === existingValue){
|
|
415
|
-
return {success: false, error: {message: 'Required file upload missing: '+uploadPropertyKey}};
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
continue;
|
|
419
|
-
}
|
|
420
|
-
for(let file of uploadedFiles){
|
|
421
|
-
let filePath = file.path;
|
|
422
|
-
if(!FileHandler.exists(filePath)){
|
|
423
|
-
return {
|
|
424
|
-
success: false,
|
|
425
|
-
error: {
|
|
426
|
-
message: 'Uploaded file not found in bucket: '+file.filename,
|
|
427
|
-
filePath,
|
|
428
|
-
bucket: property.bucket
|
|
429
|
-
}
|
|
430
|
-
};
|
|
431
|
-
}
|
|
432
|
-
if(!FileHandler.isFile(filePath)){
|
|
433
|
-
return {
|
|
434
|
-
success: false,
|
|
435
|
-
error: {message: 'Uploaded file is not a valid file: '+file.filename, filePath}
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
return {success: true};
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
async countTotalEntities(driverResource, filters)
|
|
444
|
-
{
|
|
445
|
-
let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
|
|
446
|
-
if(!entityRepository){
|
|
447
|
-
return false;
|
|
448
|
-
}
|
|
449
|
-
return await entityRepository.count(filters);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
async loadEntitiesForList(driverResource, pageSize, currentPage, req, filters, sortBy, sortDirection)
|
|
453
|
-
{
|
|
454
|
-
let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
|
|
455
|
-
entityRepository.limit = pageSize;
|
|
456
|
-
if(1 < currentPage){
|
|
457
|
-
entityRepository.offset = (currentPage - 1) * pageSize;
|
|
458
|
-
}
|
|
459
|
-
entityRepository.sortBy = sortBy || false;
|
|
460
|
-
entityRepository.sortDirection = sortDirection || false;
|
|
461
|
-
let loadedEntities = await entityRepository.loadWithRelations(filters, []);
|
|
462
|
-
entityRepository.limit = 0;
|
|
463
|
-
entityRepository.offset = 0;
|
|
464
|
-
entityRepository.sortBy = false;
|
|
465
|
-
entityRepository.sortDirection = false;
|
|
466
|
-
let entityRows = [];
|
|
467
|
-
let resourceProperties = driverResource.options?.properties;
|
|
468
|
-
let idProperty = this.fetchEntityIdPropertyKey(driverResource);
|
|
469
|
-
for(let entity of loadedEntities){
|
|
470
|
-
let fields = [];
|
|
471
|
-
for(let property of driverResource.options.listProperties){
|
|
472
|
-
let {fieldValue, fieldName} = this.generatePropertyRenderedValueWithLabel(
|
|
473
|
-
entity,
|
|
474
|
-
property,
|
|
475
|
-
resourceProperties[property]
|
|
476
|
-
);
|
|
477
|
-
fields.push({
|
|
478
|
-
name: property,
|
|
479
|
-
value: await this.generatePropertyRenderedValue(
|
|
480
|
-
fieldValue,
|
|
481
|
-
fieldName,
|
|
482
|
-
resourceProperties[property]
|
|
483
|
-
),
|
|
484
|
-
viewLink: '' !== idProperty
|
|
485
|
-
? this.generateEntityRoute('viewPath', driverResource, idProperty, entity)
|
|
486
|
-
: ''
|
|
487
|
-
});
|
|
488
|
-
}
|
|
489
|
-
entityRows.push({
|
|
490
|
-
fields,
|
|
491
|
-
editLink: '' !== idProperty
|
|
492
|
-
? this.generateEntityRoute('editPath', driverResource, idProperty, entity)
|
|
493
|
-
: '',
|
|
494
|
-
deleteLink: this.rootPath + '/' + driverResource.entityPath + this.deletePath,
|
|
495
|
-
id: entity[idProperty]
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
return entityRows;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
async loadEntityById(driverResource, id)
|
|
502
|
-
{
|
|
503
|
-
let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
|
|
504
|
-
if(!entityRepository){
|
|
505
|
-
return false;
|
|
506
|
-
}
|
|
507
|
-
await this.emitEvent('reldens.adminBeforeEntityLoad', {
|
|
508
|
-
driverResource,
|
|
509
|
-
entityId: id
|
|
510
|
-
});
|
|
511
|
-
let idProperty = this.fetchEntityIdPropertyKey(driverResource);
|
|
512
|
-
if(!idProperty){
|
|
513
|
-
return false;
|
|
514
|
-
}
|
|
515
|
-
return await entityRepository.loadOneByWithRelations(idProperty, id);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
async saveEntity(id, entityRepository, entityDataPatch)
|
|
519
|
-
{
|
|
520
|
-
if('' === id){
|
|
521
|
-
return entityRepository.create(entityDataPatch);
|
|
522
|
-
}
|
|
523
|
-
return entityRepository.updateById(id, entityDataPatch);
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
async deleteEntitiesRelatedFiles(driverResource, entities)
|
|
527
|
-
{
|
|
528
|
-
for(let propertyKey of Object.keys(driverResource.options.properties)){
|
|
529
|
-
let property = driverResource.options.properties[propertyKey];
|
|
530
|
-
if(!property.isUpload){
|
|
531
|
-
continue;
|
|
532
|
-
}
|
|
533
|
-
for(let entity of entities){
|
|
534
|
-
let bucket = sc.get(property, 'bucket', '');
|
|
535
|
-
if(!property.isArray){
|
|
536
|
-
FileHandler.remove([bucket, entity[propertyKey]]);
|
|
537
|
-
continue;
|
|
538
|
-
}
|
|
539
|
-
for(let entityFile of entity[propertyKey].split(property.isArray)){
|
|
540
|
-
FileHandler.remove([bucket, entityFile]);
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
preparePatchData(driverResource, idProperty, req, resourceProperties, id, loadedEntity)
|
|
547
|
-
{
|
|
548
|
-
let entityDataPatch = {};
|
|
549
|
-
for(let i of Object.keys(resourceProperties)){
|
|
550
|
-
if(i === idProperty){
|
|
551
|
-
continue;
|
|
552
|
-
}
|
|
553
|
-
let property = resourceProperties[i];
|
|
554
|
-
let shouldProcess = driverResource.options.editProperties.includes(i);
|
|
555
|
-
if(!id && !shouldProcess && property.isRequired && 'reference' === property.type){
|
|
556
|
-
shouldProcess = true;
|
|
557
|
-
}
|
|
558
|
-
if(!shouldProcess){
|
|
559
|
-
continue;
|
|
560
|
-
}
|
|
561
|
-
let clearFieldParam = sc.get(req.body, 'clear_'+i, false);
|
|
562
|
-
let isExplicitClear = '1' === clearFieldParam;
|
|
563
|
-
let propertyUpdateValue = sc.get(req.body, i, null);
|
|
564
|
-
if('null' === propertyUpdateValue){
|
|
565
|
-
propertyUpdateValue = null;
|
|
566
|
-
}
|
|
567
|
-
let propertyType = sc.get(property, 'type', 'string');
|
|
568
|
-
if(property.isUpload){
|
|
569
|
-
propertyType = 'upload';
|
|
570
|
-
if(isExplicitClear){
|
|
571
|
-
propertyUpdateValue = null;
|
|
572
|
-
}
|
|
573
|
-
if(!isExplicitClear){
|
|
574
|
-
let currentValue = loadedEntity ? sc.get(loadedEntity, i, '') : '';
|
|
575
|
-
propertyUpdateValue = this.prepareUploadPatchData(req, i, currentValue, property);
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
if('boolean' === propertyType){
|
|
579
|
-
propertyUpdateValue = '1' === propertyUpdateValue || 'on' === propertyUpdateValue;
|
|
580
|
-
}
|
|
581
|
-
let isEmpty = '' === propertyUpdateValue;
|
|
582
|
-
let isNull = null === propertyUpdateValue;
|
|
583
|
-
if('number' === propertyType && !isNull && !isEmpty){
|
|
584
|
-
let numValue = Number(propertyUpdateValue);
|
|
585
|
-
if(isNaN(numValue)){
|
|
586
|
-
Logger.critical(
|
|
587
|
-
'Invalid number value for property.',
|
|
588
|
-
{propertyKey: i, value: propertyUpdateValue, entity: driverResource.entityKey}
|
|
589
|
-
);
|
|
590
|
-
return false;
|
|
591
|
-
}
|
|
592
|
-
propertyUpdateValue = numValue;
|
|
593
|
-
}
|
|
594
|
-
if('string' === propertyType && !isNull && !isEmpty){
|
|
595
|
-
propertyUpdateValue = String(propertyUpdateValue);
|
|
596
|
-
}
|
|
597
|
-
if(isEmpty && !property.isRequired){
|
|
598
|
-
propertyUpdateValue = null;
|
|
599
|
-
}
|
|
600
|
-
let isUploadCreate = property.isUpload && !id;
|
|
601
|
-
if(property.isRequired && (isNull || isEmpty) && (!property.isUpload || isUploadCreate)){
|
|
602
|
-
Logger.critical(
|
|
603
|
-
'Missing property on prepare patch data.',
|
|
604
|
-
{propertyKey: i, config: property, propertyUpdateValue, entity: driverResource.entityKey}
|
|
605
|
-
);
|
|
606
|
-
return false;
|
|
607
|
-
}
|
|
608
|
-
if(-1 !== this.passwordFieldNames.indexOf(i)){
|
|
609
|
-
if(!propertyUpdateValue || '' === propertyUpdateValue){
|
|
610
|
-
continue;
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
if(!property.isUpload || (property.isUpload && (!isNull || isExplicitClear))){
|
|
614
|
-
entityDataPatch[i] = propertyUpdateValue;
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
return entityDataPatch;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
prepareUploadPatchData(req, i, propertyUpdateValue, property)
|
|
622
|
-
{
|
|
623
|
-
let filesData = sc.get(req.files, i, null);
|
|
624
|
-
let removedFiles = sc.get(req.body, 'removed_'+i, '');
|
|
625
|
-
let existingFiles = propertyUpdateValue || '';
|
|
626
|
-
if(property.isArray && removedFiles && '' !== removedFiles){
|
|
627
|
-
let removedFilesArray = removedFiles.split(',');
|
|
628
|
-
let existingFilesArray = existingFiles ? existingFiles.split(property.isArray) : [];
|
|
629
|
-
existingFilesArray = existingFilesArray.filter(file => {
|
|
630
|
-
return -1 === removedFilesArray.indexOf(file);
|
|
631
|
-
});
|
|
632
|
-
existingFiles = existingFilesArray.join(property.isArray);
|
|
633
|
-
}
|
|
634
|
-
if(!filesData || 0 === filesData.length){
|
|
635
|
-
if(removedFiles && '' !== removedFiles){
|
|
636
|
-
return existingFiles;
|
|
637
|
-
}
|
|
638
|
-
return null;
|
|
639
|
-
}
|
|
640
|
-
let newFileNames = [];
|
|
641
|
-
for(let file of filesData){
|
|
642
|
-
newFileNames.push(file.filename);
|
|
643
|
-
}
|
|
644
|
-
if(property.isArray && existingFiles && '' !== existingFiles){
|
|
645
|
-
let existingFilesArray = existingFiles.split(property.isArray);
|
|
646
|
-
let allFiles = existingFilesArray.concat(newFileNames);
|
|
647
|
-
return allFiles.join(property.isArray);
|
|
648
|
-
}
|
|
649
|
-
return newFileNames.join(property.isArray);
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
async generatePropertyRenderedValue(fieldValue, fieldName, resourceProperty, templateType)
|
|
653
|
-
{
|
|
654
|
-
let fieldOriginalValue = fieldValue;
|
|
655
|
-
if('view' === templateType){
|
|
656
|
-
if(resourceProperty.isArray){
|
|
657
|
-
fieldValue = fieldValue.split(resourceProperty.isArray).map((value) => {
|
|
658
|
-
let target = resourceProperty.isUpload ? ' target="_blank"' : '';
|
|
659
|
-
let fieldValuePart = resourceProperty.isUpload && resourceProperty.bucketPath && value
|
|
660
|
-
? resourceProperty.bucketPath+value
|
|
661
|
-
: value;
|
|
662
|
-
return {fieldValuePart, fieldOriginalValuePart: value, target};
|
|
663
|
-
});
|
|
664
|
-
}
|
|
665
|
-
if(!resourceProperty.isArray && resourceProperty.isUpload && fieldValue){
|
|
666
|
-
fieldValue = resourceProperty.bucketPath+fieldValue;
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
return await this.adminContentsRender(
|
|
670
|
-
this.adminFilesContents.fields.view[this.propertyType(resourceProperty, templateType, fieldValue)],
|
|
671
|
-
{fieldName, fieldValue, fieldOriginalValue, target: ' target="_blank"'}
|
|
672
|
-
);
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
generatePropertyRenderedValueWithLabel(entity, propertyKey, resourceProperty)
|
|
676
|
-
{
|
|
677
|
-
let fieldValue = (0 === entity[propertyKey] ? '0' : entity[propertyKey] || '');
|
|
678
|
-
if((sc.isObject(fieldValue) || sc.isArray(fieldValue)) && 'json' === resourceProperty.dbType){
|
|
679
|
-
fieldValue = sc.toJsonString(fieldValue);
|
|
680
|
-
}
|
|
681
|
-
let fieldName = propertyKey;
|
|
682
|
-
if('boolean' === resourceProperty.type){
|
|
683
|
-
fieldValue = 1 === entity[propertyKey]
|
|
684
|
-
|| '1' === entity[propertyKey]
|
|
685
|
-
|| true === entity[propertyKey] ? 'Yes' : 'No';
|
|
686
|
-
}
|
|
687
|
-
if('datetime' === resourceProperty.type){
|
|
688
|
-
fieldValue = '' !== fieldValue ? sc.formatDate(new Date(fieldValue)) : '';
|
|
689
|
-
}
|
|
690
|
-
if('reference' === resourceProperty.type){
|
|
691
|
-
let relationEntity = entity[resourceProperty.alias || 'related_'+resourceProperty.reference];
|
|
692
|
-
if(relationEntity){
|
|
693
|
-
let relation = this.relations()[resourceProperty.reference];
|
|
694
|
-
if(relation){
|
|
695
|
-
let relationTitleProperty = relation[resourceProperty.alias || resourceProperty.reference];
|
|
696
|
-
if(relationTitleProperty && '' !== String(relationEntity[relationTitleProperty] || '')){
|
|
697
|
-
fieldName = relationTitleProperty;
|
|
698
|
-
fieldValue = relationEntity[relationTitleProperty]+(' ('+fieldValue+')');
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
if(resourceProperty.availableValues){
|
|
704
|
-
let optionData = resourceProperty.availableValues.filter((availableValue) => {
|
|
705
|
-
return String(availableValue.value) === String(fieldValue);
|
|
706
|
-
}).shift();
|
|
707
|
-
if(optionData){
|
|
708
|
-
fieldValue = optionData.label + ' (' + fieldValue + ')';
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
return {fieldValue, fieldName};
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
async generatePropertyEditRenderedValue(entity, propertyKey, resourceProperty)
|
|
715
|
-
{
|
|
716
|
-
if(-1 !== this.passwordFieldNames.indexOf(propertyKey)){
|
|
717
|
-
return '';
|
|
718
|
-
}
|
|
719
|
-
let entityPropertyValue = sc.get(entity, propertyKey, null);
|
|
720
|
-
let fieldValue = (0 === entityPropertyValue ? '0' : entityPropertyValue || '');
|
|
721
|
-
if('null' === fieldValue){
|
|
722
|
-
fieldValue = '';
|
|
723
|
-
}
|
|
724
|
-
if((sc.isObject(fieldValue) || sc.isArray(fieldValue)) && 'json' === resourceProperty.dbType){
|
|
725
|
-
fieldValue = sc.toJsonString(fieldValue);
|
|
726
|
-
}
|
|
727
|
-
if('boolean' === resourceProperty.type){
|
|
728
|
-
fieldValue = 1 === entityPropertyValue
|
|
729
|
-
|| '1' === entityPropertyValue
|
|
730
|
-
|| true === entityPropertyValue ? ' checked="checked"' : '';
|
|
731
|
-
}
|
|
732
|
-
if('datetime' === resourceProperty.type){
|
|
733
|
-
fieldValue = !entityPropertyValue || '' === entityPropertyValue
|
|
734
|
-
? ''
|
|
735
|
-
: sc.formatDate(new Date(entityPropertyValue), 'Y-m-d H:i:s');
|
|
736
|
-
}
|
|
737
|
-
if('reference' === resourceProperty.type){
|
|
738
|
-
let relationDriverResource = this.resourcesByReference()[resourceProperty.reference];
|
|
739
|
-
if(!relationDriverResource){
|
|
740
|
-
Logger.critical('Driver resource not found by reference.', resourceProperty);
|
|
741
|
-
return fieldValue;
|
|
742
|
-
}
|
|
743
|
-
let relation = this.relations()[resourceProperty.reference];
|
|
744
|
-
let relationKey = resourceProperty.alias || resourceProperty.reference;
|
|
745
|
-
let relationTitleProperty = relation
|
|
746
|
-
? relation[relationKey]
|
|
747
|
-
: this.fetchEntityIdPropertyKey(relationDriverResource);
|
|
748
|
-
let options = (await this.fetchRelationOptions(relationDriverResource)).map((option) => {
|
|
749
|
-
let value = option[this.fetchEntityIdPropertyKey(relationDriverResource)];
|
|
750
|
-
return {
|
|
751
|
-
label: option[relationTitleProperty]+' (ID: '+value+')',
|
|
752
|
-
value,
|
|
753
|
-
selected: entity && entity[propertyKey] === value ? ' selected="selected"' : ''
|
|
754
|
-
};
|
|
755
|
-
});
|
|
756
|
-
if(!resourceProperty.isRequired){
|
|
757
|
-
let isSelected = !entity || null === entity[propertyKey] || '' === entity[propertyKey];
|
|
758
|
-
options.unshift({
|
|
759
|
-
label: '-- Select --',
|
|
760
|
-
value: '',
|
|
761
|
-
selected: isSelected ? ' selected="selected"' : ''
|
|
762
|
-
});
|
|
763
|
-
}
|
|
764
|
-
return options;
|
|
765
|
-
}
|
|
766
|
-
return fieldValue;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
async fetchRelationOptions(relationDriverResource)
|
|
770
|
-
{
|
|
771
|
-
let relationEntityRepository = this.dataServer.getEntity(relationDriverResource.entityKey);
|
|
772
|
-
if(!relationEntityRepository){
|
|
773
|
-
return [];
|
|
774
|
-
}
|
|
775
|
-
return await relationEntityRepository.loadAll();
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
propertyType(resourceProperty, templateType, fieldValue)
|
|
779
|
-
{
|
|
780
|
-
let propertyType = sc.get(resourceProperty, 'type', 'text');
|
|
781
|
-
if('reference' === propertyType && 'edit' === templateType){
|
|
782
|
-
return 'select';
|
|
783
|
-
}
|
|
784
|
-
if(resourceProperty.isUpload){
|
|
785
|
-
if('edit' === templateType){
|
|
786
|
-
return 'file';
|
|
787
|
-
}
|
|
788
|
-
if('view' === templateType){
|
|
789
|
-
if(!fieldValue || '' === fieldValue){
|
|
790
|
-
return 'text';
|
|
791
|
-
}
|
|
792
|
-
let multiple = resourceProperty.isArray ? 's' : '';
|
|
793
|
-
let allowedTypes = sc.get(resourceProperty, 'allowedTypes', '');
|
|
794
|
-
if('text' === allowedTypes){
|
|
795
|
-
return 'link'+multiple;
|
|
796
|
-
}
|
|
797
|
-
if('' !== allowedTypes){
|
|
798
|
-
let templateName = allowedTypes + multiple;
|
|
799
|
-
if(sc.hasOwn(this.adminFilesContents.fields.view, templateName)){
|
|
800
|
-
return templateName;
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
return 'text';
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
if('textarea' === resourceProperty.type){
|
|
807
|
-
return 'textarea';
|
|
808
|
-
}
|
|
809
|
-
if(-1 !== ['reference', 'number', 'datetime'].indexOf(propertyType)){
|
|
810
|
-
propertyType = 'text';
|
|
811
|
-
}
|
|
812
|
-
return propertyType;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
getInputType(resourceProperty, propertyKey, fieldDisabled)
|
|
816
|
-
{
|
|
817
|
-
if(-1 !== this.passwordFieldNames.indexOf(propertyKey) && !fieldDisabled){
|
|
818
|
-
return 'password';
|
|
819
|
-
}
|
|
820
|
-
if('datetime' === resourceProperty.type && !fieldDisabled){
|
|
821
|
-
return 'datetime-local';
|
|
822
|
-
}
|
|
823
|
-
if('number' === resourceProperty.type){
|
|
824
|
-
return 'number';
|
|
825
|
-
}
|
|
826
|
-
return 'text';
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
generateEntityRoute(routeType, driverResource, idProperty, entity, entityId)
|
|
830
|
-
{
|
|
831
|
-
if(!idProperty || (!entity && !entityId)){
|
|
832
|
-
return this.rootPath + '/' + driverResource.entityPath;
|
|
833
|
-
}
|
|
834
|
-
let idParam = '?' + idProperty + '=';
|
|
835
|
-
if(entity){
|
|
836
|
-
idParam = idParam + entity[idProperty];
|
|
837
|
-
}
|
|
838
|
-
if(entityId){
|
|
839
|
-
idParam = idParam + entityId;
|
|
840
|
-
}
|
|
841
|
-
return this.rootPath + '/' + driverResource.entityPath + this[routeType] + idParam;
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
appendResultParam(route, result)
|
|
845
|
-
{
|
|
846
|
-
if(route.includes('?')){
|
|
847
|
-
return route+'&result='+result;
|
|
848
|
-
}
|
|
849
|
-
return route+'?result='+result;
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
module.exports.RouterContents = RouterContents;
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - RouterContents
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { PageRangeProvider, Logger, sc } = require('@reldens/utils');
|
|
8
|
+
const { FileHandler } = require('@reldens/server-utils');
|
|
9
|
+
const { AdminFiltersManager } = require('./admin-filters-manager');
|
|
10
|
+
|
|
11
|
+
class RouterContents
|
|
12
|
+
{
|
|
13
|
+
|
|
14
|
+
constructor(props)
|
|
15
|
+
{
|
|
16
|
+
this.dataServer = props.dataServer;
|
|
17
|
+
this.translations = props.translations;
|
|
18
|
+
this.rootPath = props.rootPath;
|
|
19
|
+
this.relations = props.relations;
|
|
20
|
+
this.resourcesByReference = props.resourcesByReference;
|
|
21
|
+
this.adminFilesContents = props.adminFilesContents;
|
|
22
|
+
this.autoSyncDistCallback = props.autoSyncDistCallback;
|
|
23
|
+
this.viewPath = props.viewPath;
|
|
24
|
+
this.editPath = props.editPath;
|
|
25
|
+
this.deletePath = props.deletePath;
|
|
26
|
+
this.emitEvent = props.emitEvent;
|
|
27
|
+
this.adminContentsRender = props.adminContentsRender;
|
|
28
|
+
this.adminContentsRenderRoute = props.adminContentsRenderRoute;
|
|
29
|
+
this.adminContentsEntities = props.adminContentsEntities;
|
|
30
|
+
this.adminContentsSideBar = props.adminContentsSideBar;
|
|
31
|
+
this.fetchTranslation = props.fetchTranslation;
|
|
32
|
+
this.fetchEntityIdPropertyKey = props.fetchEntityIdPropertyKey;
|
|
33
|
+
this.fetchUploadProperties = props.fetchUploadProperties;
|
|
34
|
+
this.uploaderFactory = props.uploaderFactory;
|
|
35
|
+
this.filtersManager = new AdminFiltersManager();
|
|
36
|
+
this.passwordFieldNames = props.passwordFieldNames || ['password'];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async generateListRouteContent(req, driverResource, entityPath)
|
|
40
|
+
{
|
|
41
|
+
let currentPage = Number(req?.query?.page || 1);
|
|
42
|
+
let pageSize = Number(req?.query?.pageSize || 25);
|
|
43
|
+
let shouldClearFilters = 'true' === req?.query?.clearFilters;
|
|
44
|
+
let sessionFilters = this.filtersManager.getFiltersFromSession(req, entityPath);
|
|
45
|
+
let sessionSorting = this.filtersManager.getSortingFromSession(req, entityPath);
|
|
46
|
+
let filtersFromParams = shouldClearFilters ? {} : req?.body?.filters || req?.query?.filters || {};
|
|
47
|
+
let entityFilterTerm = shouldClearFilters ? '' : sc.get(req?.body, 'entityFilterTerm', '');
|
|
48
|
+
let sortByFromParams = shouldClearFilters ? '' : sc.get(req?.body, 'sortBy', sc.get(req?.query, 'sortBy', ''));
|
|
49
|
+
let sortDirectionFromParams = shouldClearFilters ? '' : sc.get(req?.body, 'sortDirection', sc.get(req?.query, 'sortDirection', ''));
|
|
50
|
+
if(shouldClearFilters){
|
|
51
|
+
this.filtersManager.clearFiltersFromSession(req, entityPath);
|
|
52
|
+
this.filtersManager.clearSortingFromSession(req, entityPath);
|
|
53
|
+
}
|
|
54
|
+
let hasNewEntityFilterTerm = entityFilterTerm && '' !== entityFilterTerm;
|
|
55
|
+
let hasNewToggleFilters = Object.keys(filtersFromParams).length > 0;
|
|
56
|
+
if(hasNewEntityFilterTerm && hasNewToggleFilters){
|
|
57
|
+
filtersFromParams = {};
|
|
58
|
+
hasNewToggleFilters = false;
|
|
59
|
+
}
|
|
60
|
+
if(hasNewEntityFilterTerm || hasNewToggleFilters){
|
|
61
|
+
let filtersToSave = {
|
|
62
|
+
regular: hasNewToggleFilters ? filtersFromParams : {},
|
|
63
|
+
entityFilterTerm: hasNewEntityFilterTerm ? entityFilterTerm : ''
|
|
64
|
+
};
|
|
65
|
+
this.filtersManager.saveFiltersToSession(req, entityPath, filtersToSave);
|
|
66
|
+
sessionFilters = filtersToSave;
|
|
67
|
+
}
|
|
68
|
+
if(sortByFromParams && sortDirectionFromParams){
|
|
69
|
+
this.filtersManager.saveSortingToSession(req, entityPath, sortByFromParams, sortDirectionFromParams);
|
|
70
|
+
sessionSorting = {sortBy: sortByFromParams, sortDirection: sortDirectionFromParams};
|
|
71
|
+
}
|
|
72
|
+
let mergedFilters = shouldClearFilters ? {} : Object.assign({}, sessionFilters.regular || {});
|
|
73
|
+
let finalEntityFilterTerm = shouldClearFilters ? '' : sessionFilters.entityFilterTerm || '';
|
|
74
|
+
let idProperty = this.fetchEntityIdPropertyKey(driverResource);
|
|
75
|
+
let finalSortBy = sessionSorting.sortBy || idProperty;
|
|
76
|
+
let finalSortDirection = sessionSorting.sortDirection || 'ASC';
|
|
77
|
+
let filters = this.filtersManager.prepareFilters(mergedFilters, driverResource);
|
|
78
|
+
let textFilters = this.filtersManager.prepareTextFilters(finalEntityFilterTerm, driverResource);
|
|
79
|
+
let combinedFilters = this.filtersManager.combineFilters(filters, textFilters);
|
|
80
|
+
let totalEntities = await this.countTotalEntities(driverResource, combinedFilters);
|
|
81
|
+
let totalPages = totalEntities <= pageSize ? 1 : Math.ceil(totalEntities / pageSize);
|
|
82
|
+
let renderedPagination = '';
|
|
83
|
+
for(let paginationItem of PageRangeProvider.fetch(currentPage, totalPages)){
|
|
84
|
+
let paginationUrl = this.rootPath+'/'+driverResource.entityPath+'?page='+ paginationItem.value;
|
|
85
|
+
if(finalEntityFilterTerm){
|
|
86
|
+
paginationUrl += '&entityFilterTerm='+encodeURIComponent(finalEntityFilterTerm);
|
|
87
|
+
}
|
|
88
|
+
if(finalSortBy){
|
|
89
|
+
paginationUrl += '&sortBy='+encodeURIComponent(finalSortBy);
|
|
90
|
+
}
|
|
91
|
+
if(finalSortDirection){
|
|
92
|
+
paginationUrl += '&sortDirection='+encodeURIComponent(finalSortDirection);
|
|
93
|
+
}
|
|
94
|
+
for(let filterKey of Object.keys(mergedFilters)){
|
|
95
|
+
if(mergedFilters[filterKey]){
|
|
96
|
+
paginationUrl += '&filters['+filterKey+']='+encodeURIComponent(mergedFilters[filterKey]);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
renderedPagination += await this.adminContentsRender(
|
|
100
|
+
this.adminFilesContents.fields.view['link'],
|
|
101
|
+
{
|
|
102
|
+
fieldName: paginationItem.label,
|
|
103
|
+
fieldValue: paginationUrl,
|
|
104
|
+
fieldOriginalValue: paginationItem.value,
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
let listProperties = {
|
|
109
|
+
entityNewRoute: this.rootPath+'/'+driverResource.entityPath+this.editPath,
|
|
110
|
+
entityFilterTermValue: finalEntityFilterTerm || ''
|
|
111
|
+
};
|
|
112
|
+
await this.emitEvent('reldens.adminListPropertiesPopulation', {
|
|
113
|
+
req,
|
|
114
|
+
driverResource,
|
|
115
|
+
listProperties
|
|
116
|
+
});
|
|
117
|
+
return await this.adminContentsRenderRoute(
|
|
118
|
+
await this.adminContentsRender(
|
|
119
|
+
this.adminContentsEntities()[entityPath].list,
|
|
120
|
+
Object.assign({
|
|
121
|
+
list: await this.adminContentsRender(this.adminFilesContents.listContent, {
|
|
122
|
+
deletePath: this.rootPath + '/' + driverResource.entityPath + this.deletePath,
|
|
123
|
+
fieldsHeaders: driverResource.options.listProperties.map((property) => {
|
|
124
|
+
let propertyTitle = this.fetchTranslation(property, driverResource.id());
|
|
125
|
+
let alias = this.fetchTranslation(
|
|
126
|
+
driverResource.options.properties[property]?.alias || '',
|
|
127
|
+
driverResource.id()
|
|
128
|
+
);
|
|
129
|
+
let isSorted = finalSortBy === property;
|
|
130
|
+
let sortDirection = isSorted ? finalSortDirection.toLowerCase() : '';
|
|
131
|
+
return {
|
|
132
|
+
name: property,
|
|
133
|
+
value: '' !== alias ? alias + ' ('+propertyTitle+')' : propertyTitle,
|
|
134
|
+
isSorted,
|
|
135
|
+
sortDirection,
|
|
136
|
+
isSortedAsc: isSorted && 'asc' === sortDirection,
|
|
137
|
+
isSortedDesc: isSorted && 'desc' === sortDirection
|
|
138
|
+
};
|
|
139
|
+
}),
|
|
140
|
+
rows: await this.loadEntitiesForList(
|
|
141
|
+
driverResource,
|
|
142
|
+
pageSize,
|
|
143
|
+
currentPage,
|
|
144
|
+
req,
|
|
145
|
+
combinedFilters,
|
|
146
|
+
finalSortBy,
|
|
147
|
+
finalSortDirection
|
|
148
|
+
)
|
|
149
|
+
}),
|
|
150
|
+
pagination: renderedPagination,
|
|
151
|
+
extraContentForList: sc.get(listProperties, 'extraContentForList', ''),
|
|
152
|
+
entityFilterTermValue: listProperties.entityFilterTermValue
|
|
153
|
+
}, ...driverResource.options.filterProperties.map((property) => {
|
|
154
|
+
let filterValue = (mergedFilters[property] || '');
|
|
155
|
+
return {[property]: '' === filterValue ? '' : 'value="'+filterValue+'"'};
|
|
156
|
+
}))
|
|
157
|
+
),
|
|
158
|
+
this.adminContentsSideBar()
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async generateViewRouteContent(req, driverResource, entityPath)
|
|
163
|
+
{
|
|
164
|
+
let idProperty = this.fetchEntityIdPropertyKey(driverResource);
|
|
165
|
+
let id = (sc.get(req.query, idProperty, ''));
|
|
166
|
+
if('' === id){
|
|
167
|
+
Logger.error('Missing ID on view route.', entityPath, id, idProperty);
|
|
168
|
+
return '';
|
|
169
|
+
}
|
|
170
|
+
let loadedEntity = await this.loadEntityById(driverResource, id);
|
|
171
|
+
if(!loadedEntity){
|
|
172
|
+
Logger.critical('Entity not found on view route.', entityPath, id, idProperty);
|
|
173
|
+
return '';
|
|
174
|
+
}
|
|
175
|
+
let renderedViewProperties = {
|
|
176
|
+
entityEditRoute: this.generateEntityRoute('editPath', driverResource, idProperty, loadedEntity),
|
|
177
|
+
entityNewRoute: this.rootPath+'/'+driverResource.entityPath+this.editPath,
|
|
178
|
+
id
|
|
179
|
+
};
|
|
180
|
+
let entitySerializedData = {};
|
|
181
|
+
for(let propertyKey of driverResource.options.showProperties){
|
|
182
|
+
let property = driverResource.options.properties[propertyKey];
|
|
183
|
+
let {fieldValue, fieldName} = this.generatePropertyRenderedValueWithLabel(
|
|
184
|
+
loadedEntity,
|
|
185
|
+
propertyKey,
|
|
186
|
+
property
|
|
187
|
+
);
|
|
188
|
+
entitySerializedData[fieldName] = fieldValue;
|
|
189
|
+
renderedViewProperties[propertyKey] = await this.generatePropertyRenderedValue(
|
|
190
|
+
fieldValue,
|
|
191
|
+
fieldName,
|
|
192
|
+
property,
|
|
193
|
+
'view'
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
let extraDataEvent = {entitySerializedData, entityId: driverResource.id(), entity: loadedEntity};
|
|
197
|
+
await this.emitEvent('adminEntityExtraData', extraDataEvent);
|
|
198
|
+
renderedViewProperties.entitySerializedData = JSON.stringify(extraDataEvent.entitySerializedData)
|
|
199
|
+
.replace(/"/g, '"');
|
|
200
|
+
renderedViewProperties.extraContentForViewTop = '';
|
|
201
|
+
renderedViewProperties.extraContentForViewBottom = '';
|
|
202
|
+
await this.emitEvent('reldens.adminViewPropertiesPopulation', {
|
|
203
|
+
idProperty,
|
|
204
|
+
req,
|
|
205
|
+
driverResource,
|
|
206
|
+
loadedEntity,
|
|
207
|
+
renderedViewProperties
|
|
208
|
+
});
|
|
209
|
+
return await this.adminContentsRenderRoute(
|
|
210
|
+
await this.adminContentsRender(this.adminContentsEntities()[entityPath].view, renderedViewProperties),
|
|
211
|
+
this.adminContentsSideBar()
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async generateEditRouteContent(req, driverResource, entityPath)
|
|
216
|
+
{
|
|
217
|
+
let idProperty = this.fetchEntityIdPropertyKey(driverResource);
|
|
218
|
+
let idValue = String(sc.get(req?.query, idProperty, ''));
|
|
219
|
+
let loadedEntity = !idValue ? null : await this.loadEntityById(driverResource, idValue);
|
|
220
|
+
let renderedEditProperties = {
|
|
221
|
+
idValue,
|
|
222
|
+
idProperty,
|
|
223
|
+
idPropertyLabel: this.fetchTranslation(idProperty, driverResource.id()),
|
|
224
|
+
templateTitle: (!idValue ? 'Create' : 'Edit')+' '+this.translations.labels[driverResource.id()],
|
|
225
|
+
entityViewRoute: !idValue
|
|
226
|
+
? this.rootPath+'/'+driverResource.entityPath
|
|
227
|
+
: this.generateEntityRoute('viewPath', driverResource, idProperty, loadedEntity)
|
|
228
|
+
};
|
|
229
|
+
await this.emitEvent('reldens.adminEditPropertiesPopulation', {
|
|
230
|
+
req,
|
|
231
|
+
driverResource,
|
|
232
|
+
renderedEditProperties,
|
|
233
|
+
loadedEntity,
|
|
234
|
+
entityId: driverResource.id(),
|
|
235
|
+
entityData: loadedEntity
|
|
236
|
+
});
|
|
237
|
+
for(let propertyKey of Object.keys(driverResource.options.properties)){
|
|
238
|
+
let property = driverResource.options.properties[propertyKey];
|
|
239
|
+
let fieldDisabled = -1 === driverResource.options.editProperties.indexOf(propertyKey);
|
|
240
|
+
let fieldValue = await this.generatePropertyEditRenderedValue(
|
|
241
|
+
loadedEntity,
|
|
242
|
+
propertyKey,
|
|
243
|
+
property,
|
|
244
|
+
fieldDisabled
|
|
245
|
+
);
|
|
246
|
+
let templateData = {
|
|
247
|
+
fieldName: propertyKey,
|
|
248
|
+
fieldValue,
|
|
249
|
+
fieldDisabled: fieldDisabled ? ' disabled="disabled"' : '',
|
|
250
|
+
required: (!property.isUpload || !loadedEntity) && property.isRequired ? ' required="required"' : '',
|
|
251
|
+
multiple: property.isArray ? ' multiple="multiple"' : '',
|
|
252
|
+
inputType: this.getInputType(property, propertyKey, fieldDisabled),
|
|
253
|
+
isArray: property.isArray || '',
|
|
254
|
+
arraySeparator: property.isArray || '',
|
|
255
|
+
isRequired: property.isRequired || false
|
|
256
|
+
};
|
|
257
|
+
if(property.isUpload && fieldValue && '' !== fieldValue){
|
|
258
|
+
let filesArray = property.isArray ? fieldValue.split(property.isArray) : [fieldValue];
|
|
259
|
+
templateData.filesArray = filesArray.map((filename) => {
|
|
260
|
+
return {
|
|
261
|
+
filename,
|
|
262
|
+
isRemovable: !property.isRequired || filesArray.length > 1
|
|
263
|
+
};
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
await this.emitEvent('reldens.adminBeforeFieldRender', {
|
|
267
|
+
req,
|
|
268
|
+
driverResource,
|
|
269
|
+
propertyKey,
|
|
270
|
+
property,
|
|
271
|
+
templateData,
|
|
272
|
+
loadedEntity,
|
|
273
|
+
renderedEditProperties,
|
|
274
|
+
adminContentsRender: this.adminContentsRender.bind(this),
|
|
275
|
+
adminFilesContents: this.adminFilesContents
|
|
276
|
+
});
|
|
277
|
+
renderedEditProperties[propertyKey] = await this.adminContentsRender(
|
|
278
|
+
this.adminFilesContents.fields.edit[this.propertyType(property, 'edit')],
|
|
279
|
+
templateData
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
return await this.adminContentsRenderRoute(
|
|
283
|
+
await this.adminContentsRender(this.adminContentsEntities()[entityPath].edit, renderedEditProperties),
|
|
284
|
+
this.adminContentsSideBar()
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async processDeleteEntities(req, res, driverResource, entityPath)
|
|
289
|
+
{
|
|
290
|
+
let ids = req?.body?.ids;
|
|
291
|
+
if('string' === typeof ids){
|
|
292
|
+
ids = ids.split(',');
|
|
293
|
+
}
|
|
294
|
+
let redirectPath = this.rootPath+'/'+entityPath+'?result=';
|
|
295
|
+
if(!ids || 0 === ids.length){
|
|
296
|
+
return redirectPath + 'errorMissingId';
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
|
|
300
|
+
let idProperty = this.fetchEntityIdPropertyKey(driverResource);
|
|
301
|
+
let idsFilter = {[idProperty]: {operator: 'IN', value: ids}};
|
|
302
|
+
let loadedEntities = await entityRepository.load(idsFilter);
|
|
303
|
+
let deleteControl = {prevented: false, result: ''};
|
|
304
|
+
await this.emitEvent('reldens.adminBeforeEntityDelete', {
|
|
305
|
+
driverResource,
|
|
306
|
+
entityPath,
|
|
307
|
+
idProperty,
|
|
308
|
+
ids,
|
|
309
|
+
loadedEntities,
|
|
310
|
+
deleteControl
|
|
311
|
+
});
|
|
312
|
+
if(deleteControl.prevented){
|
|
313
|
+
return redirectPath + (deleteControl.result || 'errorDeletePrevented');
|
|
314
|
+
}
|
|
315
|
+
await this.deleteEntitiesRelatedFiles(driverResource, loadedEntities);
|
|
316
|
+
let deleteResult = await entityRepository.delete(idsFilter);
|
|
317
|
+
await this.emitEvent('reldens.adminAfterEntityDelete', {driverResource, idProperty, ids});
|
|
318
|
+
return redirectPath + (deleteResult ? 'success' : 'errorStorageFailure');
|
|
319
|
+
} catch (error) {
|
|
320
|
+
return redirectPath + 'errorDeleteFailure';
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async processSaveEntity(req, res, driverResource, entityPath)
|
|
325
|
+
{
|
|
326
|
+
let idProperty = this.fetchEntityIdPropertyKey(driverResource);
|
|
327
|
+
let id = (req?.body[idProperty] || '');
|
|
328
|
+
let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
|
|
329
|
+
let loadedEntity = id ? await this.loadEntityById(driverResource, id) : null;
|
|
330
|
+
let uploadProperties = this.fetchUploadProperties(driverResource);
|
|
331
|
+
if(0 < Object.keys(uploadProperties).length){
|
|
332
|
+
if(this.uploaderFactory?.error?.message){
|
|
333
|
+
Logger.error('Upload factory error detected', this.uploaderFactory.error);
|
|
334
|
+
return this.rootPath+'/'+entityPath+'?result=uploadError&error='
|
|
335
|
+
+encodeURIComponent(this.uploaderFactory.error.message);
|
|
336
|
+
}
|
|
337
|
+
let uploadValidationResult = this.validateUploadedFiles(req, uploadProperties, loadedEntity);
|
|
338
|
+
if(!uploadValidationResult.success){
|
|
339
|
+
Logger.error('Upload validation failed', uploadValidationResult.error);
|
|
340
|
+
return this.rootPath+'/'+entityPath+'?result=uploadValidationError&error='
|
|
341
|
+
+encodeURIComponent(uploadValidationResult.error.message);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
let entityDataPatch = this.preparePatchData(
|
|
345
|
+
driverResource,
|
|
346
|
+
idProperty,
|
|
347
|
+
req,
|
|
348
|
+
driverResource.options.properties,
|
|
349
|
+
id,
|
|
350
|
+
loadedEntity
|
|
351
|
+
);
|
|
352
|
+
if(!entityDataPatch){
|
|
353
|
+
Logger.error('Bad patch data.', entityDataPatch);
|
|
354
|
+
return this.rootPath+'/'+entityPath+'?result=saveBadPatchData';
|
|
355
|
+
}
|
|
356
|
+
try {
|
|
357
|
+
let saveResult = await this.saveEntity(id, entityRepository, entityDataPatch);
|
|
358
|
+
if(!saveResult){
|
|
359
|
+
Logger.error('Save result error.', saveResult, entityDataPatch);
|
|
360
|
+
return this.appendResultParam(
|
|
361
|
+
this.generateEntityRoute('editPath', driverResource, idProperty, null, id),
|
|
362
|
+
'saveEntityStorageError'
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
await this.emitEvent('reldens.adminAfterEntitySave', {
|
|
366
|
+
req,
|
|
367
|
+
res,
|
|
368
|
+
driverResource,
|
|
369
|
+
entityPath,
|
|
370
|
+
entityData: saveResult
|
|
371
|
+
});
|
|
372
|
+
if(sc.isFunction(this.autoSyncDistCallback)){
|
|
373
|
+
let uploadProperties = this.fetchUploadProperties(driverResource);
|
|
374
|
+
if(0 < Object.keys(uploadProperties).length){
|
|
375
|
+
for(let uploadPropertyKey of Object.keys(uploadProperties)){
|
|
376
|
+
let property = uploadProperties[uploadPropertyKey];
|
|
377
|
+
await this.autoSyncDistCallback(
|
|
378
|
+
property.bucket,
|
|
379
|
+
saveResult[uploadPropertyKey],
|
|
380
|
+
property.distFolder
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
let saveAction = sc.get(req.body, 'saveAction', 'save');
|
|
386
|
+
if('saveAndContinue' === saveAction){
|
|
387
|
+
return this.appendResultParam(
|
|
388
|
+
this.generateEntityRoute('editPath', driverResource, idProperty, saveResult),
|
|
389
|
+
'success'
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
if('saveAndGoBack' === saveAction){
|
|
393
|
+
return this.rootPath+'/'+entityPath+'?result=success';
|
|
394
|
+
}
|
|
395
|
+
return this.generateEntityRoute('viewPath', driverResource, idProperty, saveResult) +'&result=success';
|
|
396
|
+
} catch (error) {
|
|
397
|
+
Logger.error('Save entity error.', error);
|
|
398
|
+
return this.rootPath+'/'+entityPath+'?result=saveEntityError';
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
validateUploadedFiles(req, uploadProperties, loadedEntity)
|
|
403
|
+
{
|
|
404
|
+
for(let uploadPropertyKey of Object.keys(uploadProperties)){
|
|
405
|
+
let property = uploadProperties[uploadPropertyKey];
|
|
406
|
+
let uploadedFiles = req.files?.[uploadPropertyKey];
|
|
407
|
+
if(!uploadedFiles || 0 === uploadedFiles.length){
|
|
408
|
+
if(property.isRequired){
|
|
409
|
+
let isExplicitClear = '1' === sc.get(req.body, 'clear_'+uploadPropertyKey, false);
|
|
410
|
+
if(isExplicitClear){
|
|
411
|
+
return {success: false, error: {message: 'Required file upload missing: '+uploadPropertyKey}};
|
|
412
|
+
}
|
|
413
|
+
let existingValue = loadedEntity ? sc.get(loadedEntity, uploadPropertyKey, '') : '';
|
|
414
|
+
if(!existingValue || '' === existingValue){
|
|
415
|
+
return {success: false, error: {message: 'Required file upload missing: '+uploadPropertyKey}};
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
for(let file of uploadedFiles){
|
|
421
|
+
let filePath = file.path;
|
|
422
|
+
if(!FileHandler.exists(filePath)){
|
|
423
|
+
return {
|
|
424
|
+
success: false,
|
|
425
|
+
error: {
|
|
426
|
+
message: 'Uploaded file not found in bucket: '+file.filename,
|
|
427
|
+
filePath,
|
|
428
|
+
bucket: property.bucket
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
if(!FileHandler.isFile(filePath)){
|
|
433
|
+
return {
|
|
434
|
+
success: false,
|
|
435
|
+
error: {message: 'Uploaded file is not a valid file: '+file.filename, filePath}
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return {success: true};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async countTotalEntities(driverResource, filters)
|
|
444
|
+
{
|
|
445
|
+
let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
|
|
446
|
+
if(!entityRepository){
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
return await entityRepository.count(filters);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async loadEntitiesForList(driverResource, pageSize, currentPage, req, filters, sortBy, sortDirection)
|
|
453
|
+
{
|
|
454
|
+
let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
|
|
455
|
+
entityRepository.limit = pageSize;
|
|
456
|
+
if(1 < currentPage){
|
|
457
|
+
entityRepository.offset = (currentPage - 1) * pageSize;
|
|
458
|
+
}
|
|
459
|
+
entityRepository.sortBy = sortBy || false;
|
|
460
|
+
entityRepository.sortDirection = sortDirection || false;
|
|
461
|
+
let loadedEntities = await entityRepository.loadWithRelations(filters, []);
|
|
462
|
+
entityRepository.limit = 0;
|
|
463
|
+
entityRepository.offset = 0;
|
|
464
|
+
entityRepository.sortBy = false;
|
|
465
|
+
entityRepository.sortDirection = false;
|
|
466
|
+
let entityRows = [];
|
|
467
|
+
let resourceProperties = driverResource.options?.properties;
|
|
468
|
+
let idProperty = this.fetchEntityIdPropertyKey(driverResource);
|
|
469
|
+
for(let entity of loadedEntities){
|
|
470
|
+
let fields = [];
|
|
471
|
+
for(let property of driverResource.options.listProperties){
|
|
472
|
+
let {fieldValue, fieldName} = this.generatePropertyRenderedValueWithLabel(
|
|
473
|
+
entity,
|
|
474
|
+
property,
|
|
475
|
+
resourceProperties[property]
|
|
476
|
+
);
|
|
477
|
+
fields.push({
|
|
478
|
+
name: property,
|
|
479
|
+
value: await this.generatePropertyRenderedValue(
|
|
480
|
+
fieldValue,
|
|
481
|
+
fieldName,
|
|
482
|
+
resourceProperties[property]
|
|
483
|
+
),
|
|
484
|
+
viewLink: '' !== idProperty
|
|
485
|
+
? this.generateEntityRoute('viewPath', driverResource, idProperty, entity)
|
|
486
|
+
: ''
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
entityRows.push({
|
|
490
|
+
fields,
|
|
491
|
+
editLink: '' !== idProperty
|
|
492
|
+
? this.generateEntityRoute('editPath', driverResource, idProperty, entity)
|
|
493
|
+
: '',
|
|
494
|
+
deleteLink: this.rootPath + '/' + driverResource.entityPath + this.deletePath,
|
|
495
|
+
id: entity[idProperty]
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
return entityRows;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
async loadEntityById(driverResource, id)
|
|
502
|
+
{
|
|
503
|
+
let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
|
|
504
|
+
if(!entityRepository){
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
await this.emitEvent('reldens.adminBeforeEntityLoad', {
|
|
508
|
+
driverResource,
|
|
509
|
+
entityId: id
|
|
510
|
+
});
|
|
511
|
+
let idProperty = this.fetchEntityIdPropertyKey(driverResource);
|
|
512
|
+
if(!idProperty){
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
return await entityRepository.loadOneByWithRelations(idProperty, id);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
async saveEntity(id, entityRepository, entityDataPatch)
|
|
519
|
+
{
|
|
520
|
+
if('' === id){
|
|
521
|
+
return entityRepository.create(entityDataPatch);
|
|
522
|
+
}
|
|
523
|
+
return entityRepository.updateById(id, entityDataPatch);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
async deleteEntitiesRelatedFiles(driverResource, entities)
|
|
527
|
+
{
|
|
528
|
+
for(let propertyKey of Object.keys(driverResource.options.properties)){
|
|
529
|
+
let property = driverResource.options.properties[propertyKey];
|
|
530
|
+
if(!property.isUpload){
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
for(let entity of entities){
|
|
534
|
+
let bucket = sc.get(property, 'bucket', '');
|
|
535
|
+
if(!property.isArray){
|
|
536
|
+
FileHandler.remove([bucket, entity[propertyKey]]);
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
for(let entityFile of entity[propertyKey].split(property.isArray)){
|
|
540
|
+
FileHandler.remove([bucket, entityFile]);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
preparePatchData(driverResource, idProperty, req, resourceProperties, id, loadedEntity)
|
|
547
|
+
{
|
|
548
|
+
let entityDataPatch = {};
|
|
549
|
+
for(let i of Object.keys(resourceProperties)){
|
|
550
|
+
if(i === idProperty){
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
let property = resourceProperties[i];
|
|
554
|
+
let shouldProcess = driverResource.options.editProperties.includes(i);
|
|
555
|
+
if(!id && !shouldProcess && property.isRequired && 'reference' === property.type){
|
|
556
|
+
shouldProcess = true;
|
|
557
|
+
}
|
|
558
|
+
if(!shouldProcess){
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
let clearFieldParam = sc.get(req.body, 'clear_'+i, false);
|
|
562
|
+
let isExplicitClear = '1' === clearFieldParam;
|
|
563
|
+
let propertyUpdateValue = sc.get(req.body, i, null);
|
|
564
|
+
if('null' === propertyUpdateValue){
|
|
565
|
+
propertyUpdateValue = null;
|
|
566
|
+
}
|
|
567
|
+
let propertyType = sc.get(property, 'type', 'string');
|
|
568
|
+
if(property.isUpload){
|
|
569
|
+
propertyType = 'upload';
|
|
570
|
+
if(isExplicitClear){
|
|
571
|
+
propertyUpdateValue = null;
|
|
572
|
+
}
|
|
573
|
+
if(!isExplicitClear){
|
|
574
|
+
let currentValue = loadedEntity ? sc.get(loadedEntity, i, '') : '';
|
|
575
|
+
propertyUpdateValue = this.prepareUploadPatchData(req, i, currentValue, property);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if('boolean' === propertyType){
|
|
579
|
+
propertyUpdateValue = '1' === propertyUpdateValue || 'on' === propertyUpdateValue;
|
|
580
|
+
}
|
|
581
|
+
let isEmpty = '' === propertyUpdateValue;
|
|
582
|
+
let isNull = null === propertyUpdateValue;
|
|
583
|
+
if('number' === propertyType && !isNull && !isEmpty){
|
|
584
|
+
let numValue = Number(propertyUpdateValue);
|
|
585
|
+
if(isNaN(numValue)){
|
|
586
|
+
Logger.critical(
|
|
587
|
+
'Invalid number value for property.',
|
|
588
|
+
{propertyKey: i, value: propertyUpdateValue, entity: driverResource.entityKey}
|
|
589
|
+
);
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
propertyUpdateValue = numValue;
|
|
593
|
+
}
|
|
594
|
+
if('string' === propertyType && !isNull && !isEmpty){
|
|
595
|
+
propertyUpdateValue = String(propertyUpdateValue);
|
|
596
|
+
}
|
|
597
|
+
if(isEmpty && !property.isRequired){
|
|
598
|
+
propertyUpdateValue = null;
|
|
599
|
+
}
|
|
600
|
+
let isUploadCreate = property.isUpload && !id;
|
|
601
|
+
if(property.isRequired && (isNull || isEmpty) && (!property.isUpload || isUploadCreate)){
|
|
602
|
+
Logger.critical(
|
|
603
|
+
'Missing property on prepare patch data.',
|
|
604
|
+
{propertyKey: i, config: property, propertyUpdateValue, entity: driverResource.entityKey}
|
|
605
|
+
);
|
|
606
|
+
return false;
|
|
607
|
+
}
|
|
608
|
+
if(-1 !== this.passwordFieldNames.indexOf(i)){
|
|
609
|
+
if(!propertyUpdateValue || '' === propertyUpdateValue){
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
if(!property.isUpload || (property.isUpload && (!isNull || isExplicitClear))){
|
|
614
|
+
entityDataPatch[i] = propertyUpdateValue;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return entityDataPatch;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
prepareUploadPatchData(req, i, propertyUpdateValue, property)
|
|
622
|
+
{
|
|
623
|
+
let filesData = sc.get(req.files, i, null);
|
|
624
|
+
let removedFiles = sc.get(req.body, 'removed_'+i, '');
|
|
625
|
+
let existingFiles = propertyUpdateValue || '';
|
|
626
|
+
if(property.isArray && removedFiles && '' !== removedFiles){
|
|
627
|
+
let removedFilesArray = removedFiles.split(',');
|
|
628
|
+
let existingFilesArray = existingFiles ? existingFiles.split(property.isArray) : [];
|
|
629
|
+
existingFilesArray = existingFilesArray.filter(file => {
|
|
630
|
+
return -1 === removedFilesArray.indexOf(file);
|
|
631
|
+
});
|
|
632
|
+
existingFiles = existingFilesArray.join(property.isArray);
|
|
633
|
+
}
|
|
634
|
+
if(!filesData || 0 === filesData.length){
|
|
635
|
+
if(removedFiles && '' !== removedFiles){
|
|
636
|
+
return existingFiles;
|
|
637
|
+
}
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
let newFileNames = [];
|
|
641
|
+
for(let file of filesData){
|
|
642
|
+
newFileNames.push(file.filename);
|
|
643
|
+
}
|
|
644
|
+
if(property.isArray && existingFiles && '' !== existingFiles){
|
|
645
|
+
let existingFilesArray = existingFiles.split(property.isArray);
|
|
646
|
+
let allFiles = existingFilesArray.concat(newFileNames);
|
|
647
|
+
return allFiles.join(property.isArray);
|
|
648
|
+
}
|
|
649
|
+
return newFileNames.join(property.isArray);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
async generatePropertyRenderedValue(fieldValue, fieldName, resourceProperty, templateType)
|
|
653
|
+
{
|
|
654
|
+
let fieldOriginalValue = fieldValue;
|
|
655
|
+
if('view' === templateType){
|
|
656
|
+
if(resourceProperty.isArray){
|
|
657
|
+
fieldValue = fieldValue.split(resourceProperty.isArray).map((value) => {
|
|
658
|
+
let target = resourceProperty.isUpload ? ' target="_blank"' : '';
|
|
659
|
+
let fieldValuePart = resourceProperty.isUpload && resourceProperty.bucketPath && value
|
|
660
|
+
? resourceProperty.bucketPath+value
|
|
661
|
+
: value;
|
|
662
|
+
return {fieldValuePart, fieldOriginalValuePart: value, target};
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
if(!resourceProperty.isArray && resourceProperty.isUpload && fieldValue){
|
|
666
|
+
fieldValue = resourceProperty.bucketPath+fieldValue;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return await this.adminContentsRender(
|
|
670
|
+
this.adminFilesContents.fields.view[this.propertyType(resourceProperty, templateType, fieldValue)],
|
|
671
|
+
{fieldName, fieldValue, fieldOriginalValue, target: ' target="_blank"'}
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
generatePropertyRenderedValueWithLabel(entity, propertyKey, resourceProperty)
|
|
676
|
+
{
|
|
677
|
+
let fieldValue = (0 === entity[propertyKey] ? '0' : entity[propertyKey] || '');
|
|
678
|
+
if((sc.isObject(fieldValue) || sc.isArray(fieldValue)) && 'json' === resourceProperty.dbType){
|
|
679
|
+
fieldValue = sc.toJsonString(fieldValue);
|
|
680
|
+
}
|
|
681
|
+
let fieldName = propertyKey;
|
|
682
|
+
if('boolean' === resourceProperty.type){
|
|
683
|
+
fieldValue = 1 === entity[propertyKey]
|
|
684
|
+
|| '1' === entity[propertyKey]
|
|
685
|
+
|| true === entity[propertyKey] ? 'Yes' : 'No';
|
|
686
|
+
}
|
|
687
|
+
if('datetime' === resourceProperty.type){
|
|
688
|
+
fieldValue = '' !== fieldValue ? sc.formatDate(new Date(fieldValue)) : '';
|
|
689
|
+
}
|
|
690
|
+
if('reference' === resourceProperty.type){
|
|
691
|
+
let relationEntity = entity[resourceProperty.alias || 'related_'+resourceProperty.reference];
|
|
692
|
+
if(relationEntity){
|
|
693
|
+
let relation = this.relations()[resourceProperty.reference];
|
|
694
|
+
if(relation){
|
|
695
|
+
let relationTitleProperty = relation[resourceProperty.alias || resourceProperty.reference];
|
|
696
|
+
if(relationTitleProperty && '' !== String(relationEntity[relationTitleProperty] || '')){
|
|
697
|
+
fieldName = relationTitleProperty;
|
|
698
|
+
fieldValue = relationEntity[relationTitleProperty]+(' ('+fieldValue+')');
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if(resourceProperty.availableValues){
|
|
704
|
+
let optionData = resourceProperty.availableValues.filter((availableValue) => {
|
|
705
|
+
return String(availableValue.value) === String(fieldValue);
|
|
706
|
+
}).shift();
|
|
707
|
+
if(optionData){
|
|
708
|
+
fieldValue = optionData.label + ' (' + fieldValue + ')';
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return {fieldValue, fieldName};
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
async generatePropertyEditRenderedValue(entity, propertyKey, resourceProperty)
|
|
715
|
+
{
|
|
716
|
+
if(-1 !== this.passwordFieldNames.indexOf(propertyKey)){
|
|
717
|
+
return '';
|
|
718
|
+
}
|
|
719
|
+
let entityPropertyValue = sc.get(entity, propertyKey, null);
|
|
720
|
+
let fieldValue = (0 === entityPropertyValue ? '0' : entityPropertyValue || '');
|
|
721
|
+
if('null' === fieldValue){
|
|
722
|
+
fieldValue = '';
|
|
723
|
+
}
|
|
724
|
+
if((sc.isObject(fieldValue) || sc.isArray(fieldValue)) && 'json' === resourceProperty.dbType){
|
|
725
|
+
fieldValue = sc.toJsonString(fieldValue);
|
|
726
|
+
}
|
|
727
|
+
if('boolean' === resourceProperty.type){
|
|
728
|
+
fieldValue = 1 === entityPropertyValue
|
|
729
|
+
|| '1' === entityPropertyValue
|
|
730
|
+
|| true === entityPropertyValue ? ' checked="checked"' : '';
|
|
731
|
+
}
|
|
732
|
+
if('datetime' === resourceProperty.type){
|
|
733
|
+
fieldValue = !entityPropertyValue || '' === entityPropertyValue
|
|
734
|
+
? ''
|
|
735
|
+
: sc.formatDate(new Date(entityPropertyValue), 'Y-m-d H:i:s');
|
|
736
|
+
}
|
|
737
|
+
if('reference' === resourceProperty.type){
|
|
738
|
+
let relationDriverResource = this.resourcesByReference()[resourceProperty.reference];
|
|
739
|
+
if(!relationDriverResource){
|
|
740
|
+
Logger.critical('Driver resource not found by reference.', resourceProperty);
|
|
741
|
+
return fieldValue;
|
|
742
|
+
}
|
|
743
|
+
let relation = this.relations()[resourceProperty.reference];
|
|
744
|
+
let relationKey = resourceProperty.alias || resourceProperty.reference;
|
|
745
|
+
let relationTitleProperty = relation
|
|
746
|
+
? relation[relationKey]
|
|
747
|
+
: this.fetchEntityIdPropertyKey(relationDriverResource);
|
|
748
|
+
let options = (await this.fetchRelationOptions(relationDriverResource)).map((option) => {
|
|
749
|
+
let value = option[this.fetchEntityIdPropertyKey(relationDriverResource)];
|
|
750
|
+
return {
|
|
751
|
+
label: option[relationTitleProperty]+' (ID: '+value+')',
|
|
752
|
+
value,
|
|
753
|
+
selected: entity && entity[propertyKey] === value ? ' selected="selected"' : ''
|
|
754
|
+
};
|
|
755
|
+
});
|
|
756
|
+
if(!resourceProperty.isRequired){
|
|
757
|
+
let isSelected = !entity || null === entity[propertyKey] || '' === entity[propertyKey];
|
|
758
|
+
options.unshift({
|
|
759
|
+
label: '-- Select --',
|
|
760
|
+
value: '',
|
|
761
|
+
selected: isSelected ? ' selected="selected"' : ''
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
return options;
|
|
765
|
+
}
|
|
766
|
+
return fieldValue;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
async fetchRelationOptions(relationDriverResource)
|
|
770
|
+
{
|
|
771
|
+
let relationEntityRepository = this.dataServer.getEntity(relationDriverResource.entityKey);
|
|
772
|
+
if(!relationEntityRepository){
|
|
773
|
+
return [];
|
|
774
|
+
}
|
|
775
|
+
return await relationEntityRepository.loadAll();
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
propertyType(resourceProperty, templateType, fieldValue)
|
|
779
|
+
{
|
|
780
|
+
let propertyType = sc.get(resourceProperty, 'type', 'text');
|
|
781
|
+
if('reference' === propertyType && 'edit' === templateType){
|
|
782
|
+
return 'select';
|
|
783
|
+
}
|
|
784
|
+
if(resourceProperty.isUpload){
|
|
785
|
+
if('edit' === templateType){
|
|
786
|
+
return 'file';
|
|
787
|
+
}
|
|
788
|
+
if('view' === templateType){
|
|
789
|
+
if(!fieldValue || '' === fieldValue){
|
|
790
|
+
return 'text';
|
|
791
|
+
}
|
|
792
|
+
let multiple = resourceProperty.isArray ? 's' : '';
|
|
793
|
+
let allowedTypes = sc.get(resourceProperty, 'allowedTypes', '');
|
|
794
|
+
if('text' === allowedTypes){
|
|
795
|
+
return 'link'+multiple;
|
|
796
|
+
}
|
|
797
|
+
if('' !== allowedTypes){
|
|
798
|
+
let templateName = allowedTypes + multiple;
|
|
799
|
+
if(sc.hasOwn(this.adminFilesContents.fields.view, templateName)){
|
|
800
|
+
return templateName;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return 'text';
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
if('textarea' === resourceProperty.type){
|
|
807
|
+
return 'textarea';
|
|
808
|
+
}
|
|
809
|
+
if(-1 !== ['reference', 'number', 'datetime'].indexOf(propertyType)){
|
|
810
|
+
propertyType = 'text';
|
|
811
|
+
}
|
|
812
|
+
return propertyType;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
getInputType(resourceProperty, propertyKey, fieldDisabled)
|
|
816
|
+
{
|
|
817
|
+
if(-1 !== this.passwordFieldNames.indexOf(propertyKey) && !fieldDisabled){
|
|
818
|
+
return 'password';
|
|
819
|
+
}
|
|
820
|
+
if('datetime' === resourceProperty.type && !fieldDisabled){
|
|
821
|
+
return 'datetime-local';
|
|
822
|
+
}
|
|
823
|
+
if('number' === resourceProperty.type){
|
|
824
|
+
return 'number';
|
|
825
|
+
}
|
|
826
|
+
return 'text';
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
generateEntityRoute(routeType, driverResource, idProperty, entity, entityId)
|
|
830
|
+
{
|
|
831
|
+
if(!idProperty || (!entity && !entityId)){
|
|
832
|
+
return this.rootPath + '/' + driverResource.entityPath;
|
|
833
|
+
}
|
|
834
|
+
let idParam = '?' + idProperty + '=';
|
|
835
|
+
if(entity){
|
|
836
|
+
idParam = idParam + entity[idProperty];
|
|
837
|
+
}
|
|
838
|
+
if(entityId){
|
|
839
|
+
idParam = idParam + entityId;
|
|
840
|
+
}
|
|
841
|
+
return this.rootPath + '/' + driverResource.entityPath + this[routeType] + idParam;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
appendResultParam(route, result)
|
|
845
|
+
{
|
|
846
|
+
if(route.includes('?')){
|
|
847
|
+
return route+'&result='+result;
|
|
848
|
+
}
|
|
849
|
+
return route+'?result='+result;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
module.exports.RouterContents = RouterContents;
|