@reldens/cms 0.16.0 → 0.19.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/README.md +92 -9
- package/admin/reldens-admin-client.css +55 -0
- package/admin/reldens-admin-client.js +24 -0
- package/admin/templates/cache-clean-button.html +4 -0
- package/admin/templates/clear-all-cache-button.html +18 -0
- package/admin/templates/fields/view/textarea.html +1 -1
- package/bin/reldens-cms-generate-entities.js +85 -18
- package/bin/reldens-cms.js +6 -6
- package/lib/admin-manager/contents-builder.js +257 -0
- package/lib/admin-manager/router-contents.js +618 -0
- package/lib/admin-manager/router.js +208 -0
- package/lib/admin-manager-validator.js +2 -1
- package/lib/admin-manager.js +116 -990
- package/lib/admin-translations.js +9 -1
- package/lib/cache/add-cache-button-subscriber.js +149 -0
- package/lib/cache/cache-manager.js +168 -0
- package/lib/cache/cache-routes-handler.js +99 -0
- package/lib/cms-pages-route-manager.js +45 -21
- package/lib/frontend.js +288 -71
- package/lib/installer.js +5 -2
- package/lib/json-fields-parser.js +74 -0
- package/lib/manager.js +49 -4
- package/lib/pagination-handler.js +243 -0
- package/lib/search-renderer.js +116 -0
- package/lib/search.js +344 -0
- package/lib/template-engine/collections-single-transformer.js +53 -0
- package/lib/template-engine/collections-transformer-base.js +84 -0
- package/lib/template-engine/collections-transformer.js +353 -0
- package/lib/template-engine/entities-transformer.js +65 -0
- package/lib/template-engine/partials-transformer.js +171 -0
- package/lib/template-engine.js +53 -387
- package/lib/templates-list.js +2 -0
- package/migrations/default-homepage.sql +6 -6
- package/migrations/install.sql +21 -20
- package/package.json +4 -4
- package/templates/page.html +19 -2
- package/templates/partials/entriesListView.html +14 -0
- package/templates/partials/pagedCollection.html +33 -0
package/lib/admin-manager.js
CHANGED
|
@@ -4,8 +4,13 @@
|
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
const { UploaderFactory
|
|
8
|
-
const {
|
|
7
|
+
const { UploaderFactory } = require('@reldens/server-utils');
|
|
8
|
+
const { ValidatorInterface, Logger, sc } = require('@reldens/utils');
|
|
9
|
+
const { ContentsBuilder } = require('./admin-manager/contents-builder');
|
|
10
|
+
const { Router } = require('./admin-manager/router');
|
|
11
|
+
const { RouterContents } = require('./admin-manager/router-contents');
|
|
12
|
+
const { CacheRoutesHandler } = require('./cache/cache-routes-handler');
|
|
13
|
+
const { AddCacheButtonSubscriber } = require('./cache/add-cache-button-subscriber');
|
|
9
14
|
|
|
10
15
|
class AdminManager
|
|
11
16
|
{
|
|
@@ -35,6 +40,7 @@ class AdminManager
|
|
|
35
40
|
this.autoSyncDistCallback = sc.get(configData, 'autoSyncDistCallback', false);
|
|
36
41
|
this.branding = sc.get(configData, 'branding', {});
|
|
37
42
|
this.entities = sc.get(configData, 'entities', {});
|
|
43
|
+
this.cacheManager = sc.get(configData, 'cacheManager', false);
|
|
38
44
|
this.logoutPath = '/logout';
|
|
39
45
|
this.loginPath = '/login';
|
|
40
46
|
this.viewPath = '/view';
|
|
@@ -48,8 +54,92 @@ class AdminManager
|
|
|
48
54
|
allowedExtensions: this.allowedExtensions,
|
|
49
55
|
applySecureFileNames: sc.get(configData, 'applySecureFileNames', false)
|
|
50
56
|
}));
|
|
51
|
-
this.adminContents = {};
|
|
52
57
|
this.blackList = {};
|
|
58
|
+
this.emitEvent = (eventName, eventData = {}) => this.events.emit(eventName, {adminManager: this, ...eventData});
|
|
59
|
+
this.contentsBuilder = new ContentsBuilder({
|
|
60
|
+
renderCallback: this.renderCallback,
|
|
61
|
+
adminFilesContents: this.adminFilesContents,
|
|
62
|
+
stylesFilePath: this.stylesFilePath,
|
|
63
|
+
scriptsFilePath: this.scriptsFilePath,
|
|
64
|
+
rootPath: this.rootPath,
|
|
65
|
+
branding: this.branding,
|
|
66
|
+
translations: this.translations,
|
|
67
|
+
resources: () => this.resources,
|
|
68
|
+
buildAdminCssOnActivation: this.buildAdminCssOnActivation,
|
|
69
|
+
buildAdminScriptsOnActivation: this.buildAdminScriptsOnActivation,
|
|
70
|
+
updateAdminAssetsDistOnActivation: this.updateAdminAssetsDistOnActivation,
|
|
71
|
+
emitEvent: this.emitEvent,
|
|
72
|
+
editPath: this.editPath,
|
|
73
|
+
savePath: this.savePath,
|
|
74
|
+
deletePath: this.deletePath,
|
|
75
|
+
fetchUploadProperties: this.fetchUploadProperties.bind(this),
|
|
76
|
+
fetchTranslation: this.fetchTranslation.bind(this),
|
|
77
|
+
fetchEntityIdPropertyKey: this.fetchEntityIdPropertyKey.bind(this)
|
|
78
|
+
});
|
|
79
|
+
this.router = new Router({
|
|
80
|
+
app: this.app,
|
|
81
|
+
applicationFramework: this.applicationFramework,
|
|
82
|
+
bodyParser: this.bodyParser,
|
|
83
|
+
session: this.session,
|
|
84
|
+
secret: this.secret,
|
|
85
|
+
rootPath: this.rootPath,
|
|
86
|
+
adminRoleId: this.adminRoleId,
|
|
87
|
+
authenticationCallback: this.authenticationCallback,
|
|
88
|
+
uploaderFactory: this.uploaderFactory,
|
|
89
|
+
buckets: this.buckets,
|
|
90
|
+
blackList: this.blackList,
|
|
91
|
+
loginPath: this.loginPath,
|
|
92
|
+
logoutPath: this.logoutPath,
|
|
93
|
+
viewPath: this.viewPath,
|
|
94
|
+
editPath: this.editPath,
|
|
95
|
+
savePath: this.savePath,
|
|
96
|
+
deletePath: this.deletePath,
|
|
97
|
+
resources: () => this.resources,
|
|
98
|
+
emitEvent: this.emitEvent,
|
|
99
|
+
fetchUploadProperties: this.fetchUploadProperties.bind(this),
|
|
100
|
+
adminContents: () => this.contentsBuilder.adminContents,
|
|
101
|
+
generateListRouteContent: (...args) => this.routerContents.generateListRouteContent(...args),
|
|
102
|
+
generateViewRouteContent: (...args) => this.routerContents.generateViewRouteContent(...args),
|
|
103
|
+
generateEditRouteContent: (...args) => this.routerContents.generateEditRouteContent(...args),
|
|
104
|
+
processDeleteEntities: (...args) => this.routerContents.processDeleteEntities(...args),
|
|
105
|
+
processSaveEntity: (...args) => this.routerContents.processSaveEntity(...args)
|
|
106
|
+
});
|
|
107
|
+
this.routerContents = new RouterContents({
|
|
108
|
+
dataServer: this.dataServer,
|
|
109
|
+
translations: this.translations,
|
|
110
|
+
rootPath: this.rootPath,
|
|
111
|
+
relations: () => this.relations,
|
|
112
|
+
resourcesByReference: () => this.resourcesByReference,
|
|
113
|
+
adminFilesContents: this.adminFilesContents,
|
|
114
|
+
autoSyncDistCallback: this.autoSyncDistCallback,
|
|
115
|
+
viewPath: this.viewPath,
|
|
116
|
+
editPath: this.editPath,
|
|
117
|
+
deletePath: this.deletePath,
|
|
118
|
+
emitEvent: this.emitEvent,
|
|
119
|
+
adminContentsRender: (...args) => this.contentsBuilder.render(...args),
|
|
120
|
+
adminContentsRenderRoute: (...args) => this.contentsBuilder.renderRoute(...args),
|
|
121
|
+
adminContentsEntities: () => this.contentsBuilder.adminContents.entities,
|
|
122
|
+
adminContentsSideBar: () => this.contentsBuilder.adminContents.sideBar,
|
|
123
|
+
fetchTranslation: this.fetchTranslation.bind(this),
|
|
124
|
+
fetchEntityIdPropertyKey: this.fetchEntityIdPropertyKey.bind(this),
|
|
125
|
+
fetchUploadProperties: this.fetchUploadProperties.bind(this)
|
|
126
|
+
});
|
|
127
|
+
this.cacheRoutesHandler = new CacheRoutesHandler({
|
|
128
|
+
router: this.router,
|
|
129
|
+
rootPath: this.rootPath,
|
|
130
|
+
dataServer: this.dataServer,
|
|
131
|
+
cacheManager: this.cacheManager
|
|
132
|
+
});
|
|
133
|
+
this.addCacheButtonSubscriber = new AddCacheButtonSubscriber({
|
|
134
|
+
events: this.events,
|
|
135
|
+
cacheManager: this.cacheManager,
|
|
136
|
+
renderCallback: this.renderCallback,
|
|
137
|
+
cacheCleanButton: this.adminFilesContents.cacheCleanButton,
|
|
138
|
+
clearAllCacheButton: this.adminFilesContents.clearAllCacheButton,
|
|
139
|
+
translations: this.translations,
|
|
140
|
+
cacheCleanRoute: this.cacheRoutesHandler.cacheCleanRoute,
|
|
141
|
+
clearAllCacheRoute: this.cacheRoutesHandler.clearAllCacheRoute
|
|
142
|
+
});
|
|
53
143
|
}
|
|
54
144
|
|
|
55
145
|
async setupAdmin()
|
|
@@ -60,260 +150,17 @@ class AdminManager
|
|
|
60
150
|
this.resourcesByReference = {};
|
|
61
151
|
this.resources = this.prepareResources(this.entities);
|
|
62
152
|
this.relations = this.prepareRelations(this.entities);
|
|
63
|
-
await this.buildAdminContents();
|
|
64
|
-
await this.buildAdminScripts();
|
|
65
|
-
await this.buildAdminCss();
|
|
66
|
-
await this.updateAdminAssets();
|
|
67
|
-
this.setupAdminRouter();
|
|
153
|
+
await this.contentsBuilder.buildAdminContents();
|
|
154
|
+
await this.contentsBuilder.buildAdminScripts();
|
|
155
|
+
await this.contentsBuilder.buildAdminCss();
|
|
156
|
+
await this.contentsBuilder.updateAdminAssets();
|
|
68
157
|
await this.events.emit('reldens.setupAdminRouter', {adminManager: this});
|
|
69
|
-
this.setupAdminRoutes();
|
|
158
|
+
this.router.setupAdminRoutes();
|
|
70
159
|
await this.events.emit('reldens.setupAdminRoutes', {adminManager: this});
|
|
71
|
-
await this.setupEntitiesRoutes();
|
|
160
|
+
await this.router.setupEntitiesRoutes();
|
|
72
161
|
await this.events.emit('reldens.setupAdminManagers', {adminManager: this});
|
|
73
162
|
}
|
|
74
163
|
|
|
75
|
-
async buildAdminContents()
|
|
76
|
-
{
|
|
77
|
-
this.adminContents.layout = await this.buildLayout();
|
|
78
|
-
this.adminContents.sideBar = await this.buildSideBar();
|
|
79
|
-
this.adminContents.login = await this.buildLogin();
|
|
80
|
-
this.adminContents.dashboard = await this.buildDashboard();
|
|
81
|
-
this.adminContents.entities = await this.buildEntitiesContents();
|
|
82
|
-
await this.events.emit('reldens.buildAdminContentsAfter', {adminManager: this});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async buildLayout()
|
|
86
|
-
{
|
|
87
|
-
return await this.render(
|
|
88
|
-
this.adminFilesContents.layout,
|
|
89
|
-
{
|
|
90
|
-
sideBar: '{{&sideBar}}',
|
|
91
|
-
pageContent: '{{&pageContent}}',
|
|
92
|
-
stylesFilePath: this.stylesFilePath,
|
|
93
|
-
scriptsFilePath: this.scriptsFilePath,
|
|
94
|
-
rootPath: this.rootPath,
|
|
95
|
-
brandingCompanyName: this.branding.companyName,
|
|
96
|
-
copyRight: this.branding.copyRight
|
|
97
|
-
}
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async buildSideBar()
|
|
102
|
-
{
|
|
103
|
-
let navigationContents = {};
|
|
104
|
-
let eventBuildSideBarBefore = {navigationContents, adminManager: this};
|
|
105
|
-
await this.events.emit('reldens.eventBuildSideBarBefore', eventBuildSideBarBefore);
|
|
106
|
-
navigationContents = eventBuildSideBarBefore.navigationContents;
|
|
107
|
-
for(let driverResource of this.resources){
|
|
108
|
-
let navigation = driverResource.options?.navigation;
|
|
109
|
-
let name = this.translations.labels[driverResource.id()] || this.translations.labels[driverResource.entityKey];
|
|
110
|
-
let path = this.rootPath+'/'+(driverResource.id().replace(/_/g, '-'));
|
|
111
|
-
if(navigation?.name){
|
|
112
|
-
if(!navigationContents[navigation.name]){
|
|
113
|
-
navigationContents[navigation.name] = {};
|
|
114
|
-
}
|
|
115
|
-
navigationContents[navigation.name][driverResource.id()] = await this.render(
|
|
116
|
-
this.adminFilesContents.sideBarItem,
|
|
117
|
-
{name, path}
|
|
118
|
-
);
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
navigationContents[driverResource.id()] = await this.render(
|
|
122
|
-
this.adminFilesContents.sideBarItem,
|
|
123
|
-
{name, path}
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
let eventAdminSideBarBeforeSubItems = {navigationContents, adminManager: this};
|
|
127
|
-
await this.events.emit('reldens.adminSideBarBeforeSubItems', eventAdminSideBarBeforeSubItems);
|
|
128
|
-
let navigationView = '';
|
|
129
|
-
for(let id of Object.keys(navigationContents)){
|
|
130
|
-
if(sc.isObject(navigationContents[id])){
|
|
131
|
-
let subItems = '';
|
|
132
|
-
for(let subId of Object.keys(navigationContents[id])){
|
|
133
|
-
subItems += navigationContents[id][subId];
|
|
134
|
-
}
|
|
135
|
-
navigationView += await this.render(
|
|
136
|
-
this.adminFilesContents.sideBarHeader,
|
|
137
|
-
{name: id, subItems}
|
|
138
|
-
);
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
navigationView += navigationContents[id];
|
|
142
|
-
}
|
|
143
|
-
let eventAdminSideBarBeforeRender = {navigationContents, navigationView, adminManager: this};
|
|
144
|
-
await this.events.emit('reldens.adminSideBarBeforeRender', eventAdminSideBarBeforeRender);
|
|
145
|
-
return await this.render(
|
|
146
|
-
this.adminFilesContents.sideBar,
|
|
147
|
-
{
|
|
148
|
-
rootPath: this.rootPath,
|
|
149
|
-
navigationView: eventAdminSideBarBeforeRender.navigationView
|
|
150
|
-
}
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
async buildLogin()
|
|
155
|
-
{
|
|
156
|
-
return await this.renderRoute(this.adminFilesContents.login, '');
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async buildDashboard()
|
|
160
|
-
{
|
|
161
|
-
return await this.renderRoute(this.adminFilesContents.dashboard, this.adminContents.sideBar);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
async buildEntitiesContents()
|
|
165
|
-
{
|
|
166
|
-
let entitiesContents = {};
|
|
167
|
-
for(let driverResource of this.resources){
|
|
168
|
-
let templateTitle = this.translations.labels[driverResource.id()];
|
|
169
|
-
let entityName = (driverResource.id().replace(/_/g, '-'));
|
|
170
|
-
let entityListRoute = this.rootPath+'/'+entityName;
|
|
171
|
-
let entityEditRoute = entityListRoute+this.editPath;
|
|
172
|
-
let entitySaveRoute = entityListRoute+this.savePath;
|
|
173
|
-
let entityDeleteRoute = entityListRoute+this.deletePath;
|
|
174
|
-
let uploadProperties = this.fetchUploadProperties(driverResource);
|
|
175
|
-
let multipartFormData = 0 < Object.keys(uploadProperties).length ? ' enctype="multipart/form-data"' : '';
|
|
176
|
-
let idProperty = this.fetchEntityIdPropertyKey(driverResource);
|
|
177
|
-
let editProperties = Object.keys(driverResource.options.properties);
|
|
178
|
-
editProperties.splice(editProperties.indexOf(idProperty), 1);
|
|
179
|
-
let filters = driverResource.options.filterProperties.map((property) => {
|
|
180
|
-
return {
|
|
181
|
-
propertyKey: property,
|
|
182
|
-
name: this.fetchTranslation(property),
|
|
183
|
-
value: '{{&'+property+'}}'
|
|
184
|
-
};
|
|
185
|
-
});
|
|
186
|
-
let fields = driverResource.options.showProperties.map((property) => {
|
|
187
|
-
return {
|
|
188
|
-
name: this.fetchTranslation(property),
|
|
189
|
-
value: '{{&'+property+'}}'
|
|
190
|
-
};
|
|
191
|
-
});
|
|
192
|
-
let editFields = editProperties.map((property) => {
|
|
193
|
-
return {
|
|
194
|
-
name: this.fetchTranslation(property),
|
|
195
|
-
value: '{{&'+property+'}}'
|
|
196
|
-
};
|
|
197
|
-
});
|
|
198
|
-
let sectionsContents = this.adminFilesContents?.sections;
|
|
199
|
-
let extraContentForList = sc.get(sectionsContents?.list, driverResource.entityPath, '');
|
|
200
|
-
let extraContentForView = await this.render(
|
|
201
|
-
sc.get(sectionsContents?.view, driverResource.entityPath, ''),
|
|
202
|
-
{
|
|
203
|
-
id: '{{&id}}',
|
|
204
|
-
entitySerializedData: '{{&entitySerializedData}}'
|
|
205
|
-
}
|
|
206
|
-
);
|
|
207
|
-
let extraFormContentForView = sc.get(sectionsContents?.viewForm, driverResource.entityPath, '');
|
|
208
|
-
let extraContentForEdit = sc.get(sectionsContents?.edit, driverResource.entityPath, '');
|
|
209
|
-
let extraFormContentForEdit = sc.get(sectionsContents?.editForm, driverResource.entityPath, '');
|
|
210
|
-
entitiesContents[entityName] = {
|
|
211
|
-
list: await this.render(
|
|
212
|
-
this.adminFilesContents.list,
|
|
213
|
-
{
|
|
214
|
-
entityName,
|
|
215
|
-
templateTitle,
|
|
216
|
-
entityListRoute,
|
|
217
|
-
entityEditRoute,
|
|
218
|
-
filters,
|
|
219
|
-
list: '{{&list}}',
|
|
220
|
-
pagination: '{{&pagination}}',
|
|
221
|
-
extraContent: extraContentForList,
|
|
222
|
-
}
|
|
223
|
-
),
|
|
224
|
-
view: await this.render(
|
|
225
|
-
this.adminFilesContents.view,
|
|
226
|
-
{
|
|
227
|
-
entityName,
|
|
228
|
-
templateTitle,
|
|
229
|
-
entityDeleteRoute,
|
|
230
|
-
entityListRoute,
|
|
231
|
-
fields,
|
|
232
|
-
id: '{{&id}}',
|
|
233
|
-
entityEditRoute: '{{&entityEditRoute}}',
|
|
234
|
-
entityNewRoute: '{{&entityNewRoute}}',
|
|
235
|
-
extraContent: extraContentForView,
|
|
236
|
-
extraFormContent: extraFormContentForView
|
|
237
|
-
}
|
|
238
|
-
),
|
|
239
|
-
edit: await this.render(
|
|
240
|
-
this.adminFilesContents.edit,
|
|
241
|
-
{
|
|
242
|
-
entityName,
|
|
243
|
-
entitySaveRoute,
|
|
244
|
-
multipartFormData,
|
|
245
|
-
editFields,
|
|
246
|
-
idValue: '{{&idValue}}',
|
|
247
|
-
idProperty: '{{&idProperty}}',
|
|
248
|
-
templateTitle: '{{&templateTitle}}',
|
|
249
|
-
entityViewRoute: '{{&entityViewRoute}}',
|
|
250
|
-
extraContent: extraContentForEdit,
|
|
251
|
-
extraFormContent: extraFormContentForEdit
|
|
252
|
-
}
|
|
253
|
-
)
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
return entitiesContents;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
fetchUploadProperties(driverResource)
|
|
260
|
-
{
|
|
261
|
-
if(!driverResource.options.uploadProperties){
|
|
262
|
-
driverResource.options.uploadProperties = {};
|
|
263
|
-
for(let propertyKey of Object.keys(driverResource.options.properties)){
|
|
264
|
-
let property = driverResource.options.properties[propertyKey];
|
|
265
|
-
if(property.isUpload){
|
|
266
|
-
driverResource.options.uploadProperties[propertyKey] = property;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
return driverResource.options.uploadProperties;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
async render(content, params)
|
|
274
|
-
{
|
|
275
|
-
return await this.renderCallback(content, params);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
async renderRoute(pageContent, sideBar)
|
|
279
|
-
{
|
|
280
|
-
return await this.render(
|
|
281
|
-
this.adminContents.layout,
|
|
282
|
-
{
|
|
283
|
-
stylesFilePath: this.stylesFilePath,
|
|
284
|
-
scriptsFilePath: this.scriptsFilePath,
|
|
285
|
-
brandingCompanyName: this.branding.companyName,
|
|
286
|
-
copyRight: this.branding.copyRight,
|
|
287
|
-
pageContent,
|
|
288
|
-
sideBar
|
|
289
|
-
}
|
|
290
|
-
);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
async buildAdminScripts()
|
|
294
|
-
{
|
|
295
|
-
if(!sc.isFunction(this.buildAdminScriptsOnActivation)){
|
|
296
|
-
return false;
|
|
297
|
-
}
|
|
298
|
-
return this.buildAdminScriptsOnActivation();
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async updateAdminAssets()
|
|
302
|
-
{
|
|
303
|
-
if(!sc.isFunction(this.updateAdminAssetsDistOnActivation)){
|
|
304
|
-
return false;
|
|
305
|
-
}
|
|
306
|
-
return this.updateAdminAssetsDistOnActivation();
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
async buildAdminCss()
|
|
310
|
-
{
|
|
311
|
-
if(!sc.isFunction(this.buildAdminCssOnActivation)){
|
|
312
|
-
return false;
|
|
313
|
-
}
|
|
314
|
-
return this.buildAdminCssOnActivation();
|
|
315
|
-
}
|
|
316
|
-
|
|
317
164
|
prepareResources(rawResources)
|
|
318
165
|
{
|
|
319
166
|
let rawResourcesKeys = Object.keys(rawResources);
|
|
@@ -324,7 +171,6 @@ class AdminManager
|
|
|
324
171
|
for(let i of rawResourcesKeys){
|
|
325
172
|
let rawResource = rawResources[i];
|
|
326
173
|
let tableName = rawResource.rawEntity.tableName();
|
|
327
|
-
// @TODO - BETA - Refactor to add the ID property and composed labels (id + label), in the resource.
|
|
328
174
|
let driverResource = {
|
|
329
175
|
id: () => {
|
|
330
176
|
return tableName;
|
|
@@ -355,7 +201,6 @@ class AdminManager
|
|
|
355
201
|
|
|
356
202
|
prepareRelations()
|
|
357
203
|
{
|
|
358
|
-
// @TODO - BETA - Refactor, include in resources generation at once.
|
|
359
204
|
let registeredRelations = {};
|
|
360
205
|
for(let resource of this.resources){
|
|
361
206
|
for(let propertyKey of Object.keys(resource.options.properties)){
|
|
@@ -380,680 +225,18 @@ class AdminManager
|
|
|
380
225
|
return registeredRelations;
|
|
381
226
|
}
|
|
382
227
|
|
|
383
|
-
|
|
384
|
-
{
|
|
385
|
-
this.adminRouter = this.applicationFramework.Router();
|
|
386
|
-
// apply session middleware only to /admin routes:
|
|
387
|
-
if(this.session){
|
|
388
|
-
if(!this.secret){
|
|
389
|
-
Logger.warning('Admin Manager "secret" key was not provided.');
|
|
390
|
-
}
|
|
391
|
-
this.adminRouter.use(this.session({secret: this.secret, resave: false, saveUninitialized: true}));
|
|
392
|
-
}
|
|
393
|
-
this.adminRouter.use(this.bodyParser.json());
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
setupAdminRoutes()
|
|
397
|
-
{
|
|
398
|
-
this.adminRouter.get(this.loginPath, async (req, res) => {
|
|
399
|
-
return res.send(this.adminContents.login);
|
|
400
|
-
});
|
|
401
|
-
// route for handling login:
|
|
402
|
-
this.adminRouter.post(this.loginPath, async (req, res) => {
|
|
403
|
-
let { email, password } = req.body;
|
|
404
|
-
let loginResult = await this.authenticationCallback(email, password, this.adminRoleId);
|
|
405
|
-
if(loginResult){
|
|
406
|
-
req.session.user = loginResult;
|
|
407
|
-
return res.redirect(this.rootPath);
|
|
408
|
-
}
|
|
409
|
-
return res.redirect(this.rootPath+this.loginPath+'?login-error=true');
|
|
410
|
-
});
|
|
411
|
-
// route for the admin panel dashboard:
|
|
412
|
-
this.adminRouter.get('/', this.isAuthenticated.bind(this), async (req, res) => {
|
|
413
|
-
return res.send(this.adminContents.dashboard);
|
|
414
|
-
});
|
|
415
|
-
// route for logout:
|
|
416
|
-
this.adminRouter.get(this.logoutPath, (req, res) => {
|
|
417
|
-
req.session.destroy();
|
|
418
|
-
res.redirect(this.rootPath+this.loginPath);
|
|
419
|
-
});
|
|
420
|
-
this.app.use(this.rootPath, this.adminRouter);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
async setupEntitiesRoutes()
|
|
424
|
-
{
|
|
425
|
-
if(!this.resources || 0 === this.resources.length){
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
|
-
for(let driverResource of this.resources){
|
|
429
|
-
let entityPath = driverResource.entityPath;
|
|
430
|
-
let entityRoute = '/'+entityPath;
|
|
431
|
-
this.adminRouter.get(entityRoute, this.isAuthenticated.bind(this), async (req, res) => {
|
|
432
|
-
let routeContents = await this.generateListRouteContent(req, driverResource, entityPath);
|
|
433
|
-
return res.send(routeContents);
|
|
434
|
-
});
|
|
435
|
-
this.adminRouter.post(entityRoute, this.isAuthenticated.bind(this), async (req, res) => {
|
|
436
|
-
let routeContents = await this.generateListRouteContent(req, driverResource, entityPath);
|
|
437
|
-
return res.send(routeContents);
|
|
438
|
-
});
|
|
439
|
-
this.adminRouter.get(entityRoute+this.viewPath, this.isAuthenticated.bind(this), async (req, res) => {
|
|
440
|
-
let routeContents = await this.generateViewRouteContent(req, driverResource, entityPath);
|
|
441
|
-
if('' === routeContents){
|
|
442
|
-
return res.redirect(this.rootPath+'/'+entityPath+'?result=errorView');
|
|
443
|
-
}
|
|
444
|
-
return res.send(routeContents);
|
|
445
|
-
});
|
|
446
|
-
this.adminRouter.get(entityRoute+this.editPath, this.isAuthenticated.bind(this), async (req, res) => {
|
|
447
|
-
let adminBeforeEntityEditEvent = {
|
|
448
|
-
adminManager: this,
|
|
449
|
-
req,
|
|
450
|
-
res,
|
|
451
|
-
driverResource,
|
|
452
|
-
entityPath
|
|
453
|
-
};
|
|
454
|
-
await this.events.emit('reldens.adminBeforeEntityEdit', adminBeforeEntityEditEvent);
|
|
455
|
-
let routeContents = await this.generateEditRouteContent(req, driverResource, entityPath);
|
|
456
|
-
if('' === routeContents){
|
|
457
|
-
return res.redirect(this.rootPath+'/'+entityPath+'?result=errorEdit');
|
|
458
|
-
}
|
|
459
|
-
return res.send(routeContents);
|
|
460
|
-
});
|
|
461
|
-
this.setupSavePath(entityRoute, driverResource, entityPath);
|
|
462
|
-
this.adminRouter.post(entityRoute+this.deletePath, this.isAuthenticated.bind(this), async (req, res) => {
|
|
463
|
-
let redirectResult = await this.processDeleteEntities(req, res, driverResource, entityPath);
|
|
464
|
-
return res.redirect(redirectResult);
|
|
465
|
-
});
|
|
466
|
-
await this.events.emit('reldens.setupEntitiesRoutes', {
|
|
467
|
-
adminManager: this,
|
|
468
|
-
entityPath,
|
|
469
|
-
entityRoute,
|
|
470
|
-
driverResource
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
setupSavePath(entityRoute, driverResource, entityPath)
|
|
476
|
-
{
|
|
477
|
-
let uploadProperties = this.fetchUploadProperties(driverResource);
|
|
478
|
-
let uploadPropertiesKeys = Object.keys(uploadProperties || {});
|
|
479
|
-
if(0 === uploadPropertiesKeys.length){
|
|
480
|
-
this.adminRouter.post(
|
|
481
|
-
entityRoute+this.savePath,
|
|
482
|
-
this.isAuthenticated.bind(this),
|
|
483
|
-
async (req, res) => {
|
|
484
|
-
await this.events.emit('reldens.adminBeforeEntitySave', {
|
|
485
|
-
adminManager: this,
|
|
486
|
-
req,
|
|
487
|
-
res,
|
|
488
|
-
driverResource,
|
|
489
|
-
entityPath
|
|
490
|
-
});
|
|
491
|
-
let redirectResult = await this.processSaveEntity(req, res, driverResource, entityPath);
|
|
492
|
-
return res.redirect(redirectResult);
|
|
493
|
-
}
|
|
494
|
-
);
|
|
495
|
-
return;
|
|
496
|
-
}
|
|
497
|
-
let fields = [];
|
|
498
|
-
let allowedFileTypes = {};
|
|
499
|
-
for(let uploadPropertyKey of uploadPropertiesKeys){
|
|
500
|
-
let property = uploadProperties[uploadPropertyKey];
|
|
501
|
-
allowedFileTypes[uploadPropertyKey] = property.allowedTypes || false;
|
|
502
|
-
let field = {name: uploadPropertyKey};
|
|
503
|
-
if(!property.isArray){
|
|
504
|
-
field.maxCount = 1;
|
|
505
|
-
}
|
|
506
|
-
fields.push(field);
|
|
507
|
-
this.buckets[uploadPropertyKey] = property.bucket;
|
|
508
|
-
}
|
|
509
|
-
this.adminRouter.post(
|
|
510
|
-
entityRoute + this.savePath,
|
|
511
|
-
this.isAuthenticated.bind(this),
|
|
512
|
-
this.uploaderFactory.createUploader(fields, this.buckets, allowedFileTypes),
|
|
513
|
-
async (req, res) => {
|
|
514
|
-
await this.events.emit('reldens.adminBeforeEntitySave', {
|
|
515
|
-
adminManager: this,
|
|
516
|
-
req,
|
|
517
|
-
res,
|
|
518
|
-
driverResource,
|
|
519
|
-
entityPath
|
|
520
|
-
});
|
|
521
|
-
let redirectResult = await this.processSaveEntity(req, res, driverResource, entityPath);
|
|
522
|
-
return res.redirect(redirectResult);
|
|
523
|
-
}
|
|
524
|
-
);
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
async processDeleteEntities(req, res, driverResource, entityPath)
|
|
528
|
-
{
|
|
529
|
-
let ids = req?.body?.ids;
|
|
530
|
-
if('string' === typeof ids){
|
|
531
|
-
ids = ids.split(',');
|
|
532
|
-
}
|
|
533
|
-
let redirectPath = this.rootPath+'/'+entityPath+'?result=';
|
|
534
|
-
let resultString = 'errorMissingId';
|
|
535
|
-
if(!ids || 0 === ids.length){
|
|
536
|
-
return redirectPath + resultString;
|
|
537
|
-
}
|
|
538
|
-
try {
|
|
539
|
-
let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
|
|
540
|
-
let idProperty = this.fetchEntityIdPropertyKey(driverResource);
|
|
541
|
-
let idsFilter = {[idProperty]: {operator: 'IN', value: ids}};
|
|
542
|
-
let loadedEntities = await entityRepository.load(idsFilter);
|
|
543
|
-
await this.deleteEntitiesRelatedFiles(driverResource, loadedEntities);
|
|
544
|
-
let deleteResult = await entityRepository.delete(idsFilter);
|
|
545
|
-
resultString = deleteResult ? 'success' : 'errorStorageFailure';
|
|
546
|
-
} catch (error) {
|
|
547
|
-
resultString = 'errorDeleteFailure';
|
|
548
|
-
}
|
|
549
|
-
return redirectPath + resultString;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
async deleteEntitiesRelatedFiles(driverResource, entities)
|
|
553
|
-
{
|
|
554
|
-
let resourcePropertiesKeys = Object.keys(driverResource.options.properties);
|
|
555
|
-
for(let propertyKey of resourcePropertiesKeys){
|
|
556
|
-
let property = driverResource.options.properties[propertyKey];
|
|
557
|
-
if(!property.isUpload){
|
|
558
|
-
continue;
|
|
559
|
-
}
|
|
560
|
-
for(let entity of entities){
|
|
561
|
-
if(!property.isArray){
|
|
562
|
-
FileHandler.remove([(property.bucket || ''), entity[propertyKey]]);
|
|
563
|
-
continue;
|
|
564
|
-
}
|
|
565
|
-
let entityFiles = entity[propertyKey].split(property.isArray);
|
|
566
|
-
for(let entityFile of entityFiles){
|
|
567
|
-
FileHandler.remove([(property.bucket || ''), entityFile]);
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
async processSaveEntity(req, res, driverResource, entityPath)
|
|
574
|
-
{
|
|
575
|
-
let idProperty = this.fetchEntityIdPropertyKey(driverResource);
|
|
576
|
-
let id = (req?.body[idProperty] || '').toString();
|
|
577
|
-
let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
|
|
578
|
-
let resourceProperties = driverResource.options.properties;
|
|
579
|
-
let entityDataPatch = this.preparePatchData(driverResource, idProperty, req, resourceProperties, id);
|
|
580
|
-
if(!entityDataPatch){
|
|
581
|
-
Logger.error('Bad patch data.', entityDataPatch);
|
|
582
|
-
return this.rootPath+'/'+entityPath+'?result=saveBadPatchData';
|
|
583
|
-
}
|
|
584
|
-
let editRoute = this.generateEntityRoute('editPath', driverResource, idProperty, null, id);
|
|
585
|
-
try {
|
|
586
|
-
let saveResult = await this.saveEntity(id, entityRepository, entityDataPatch);
|
|
587
|
-
if(!saveResult){
|
|
588
|
-
Logger.error('Save result error.', saveResult, entityDataPatch);
|
|
589
|
-
return editRoute+'?result=saveEntityStorageError';
|
|
590
|
-
}
|
|
591
|
-
await this.events.emit('reldens.adminAfterEntitySave', {
|
|
592
|
-
adminManager: this,
|
|
593
|
-
req,
|
|
594
|
-
res,
|
|
595
|
-
driverResource,
|
|
596
|
-
entityPath,
|
|
597
|
-
entityData: saveResult
|
|
598
|
-
});
|
|
599
|
-
if(sc.isFunction(this.autoSyncDistCallback)){
|
|
600
|
-
let uploadProperties = this.fetchUploadProperties(driverResource);
|
|
601
|
-
if(0 < Object.keys(uploadProperties).length){
|
|
602
|
-
for(let uploadPropertyKey of Object.keys(uploadProperties)){
|
|
603
|
-
let property = uploadProperties[uploadPropertyKey];
|
|
604
|
-
await this.autoSyncDistCallback(
|
|
605
|
-
property.bucket,
|
|
606
|
-
saveResult[uploadPropertyKey],
|
|
607
|
-
property.distFolder
|
|
608
|
-
);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
let saveAction = sc.get(req.body, 'saveAction', 'save');
|
|
613
|
-
if('saveAndContinue' === saveAction){
|
|
614
|
-
return this.generateEntityRoute('editPath', driverResource, idProperty, saveResult) +'&result=success';
|
|
615
|
-
}
|
|
616
|
-
return this.generateEntityRoute('viewPath', driverResource, idProperty, saveResult) +'&result=success';
|
|
617
|
-
} catch (error) {
|
|
618
|
-
Logger.error('Save entity error.', error);
|
|
619
|
-
return this.rootPath+'/'+entityPath+'?result=saveEntityError';
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
async saveEntity(id, entityRepository, entityDataPatch)
|
|
624
|
-
{
|
|
625
|
-
if('' === id){
|
|
626
|
-
return entityRepository.create(entityDataPatch);
|
|
627
|
-
}
|
|
628
|
-
return entityRepository.updateById(id, entityDataPatch);
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
preparePatchData(driverResource, idProperty, req, resourceProperties, id)
|
|
632
|
-
{
|
|
633
|
-
let entityDataPatch = {};
|
|
634
|
-
for(let i of driverResource.options.editProperties){
|
|
635
|
-
if(i === idProperty){
|
|
636
|
-
continue;
|
|
637
|
-
}
|
|
638
|
-
let propertyUpdateValue = sc.get(req.body, i, null);
|
|
639
|
-
let property = resourceProperties[i];
|
|
640
|
-
let isNullValue = null === propertyUpdateValue;
|
|
641
|
-
let propertyType = property.type || 'string';
|
|
642
|
-
if(property.isUpload){
|
|
643
|
-
propertyType = 'upload';
|
|
644
|
-
propertyUpdateValue = this.prepareUploadPatchData(req, i, propertyUpdateValue, property);
|
|
645
|
-
}
|
|
646
|
-
if('number' === propertyType && !isNullValue){
|
|
647
|
-
propertyUpdateValue = Number(propertyUpdateValue);
|
|
648
|
-
}
|
|
649
|
-
if('string' === propertyType && !isNullValue){
|
|
650
|
-
propertyUpdateValue = String(propertyUpdateValue);
|
|
651
|
-
}
|
|
652
|
-
if('boolean' === propertyType){
|
|
653
|
-
propertyUpdateValue = Boolean(propertyUpdateValue);
|
|
654
|
-
}
|
|
655
|
-
let isUploadCreate = property.isUpload && !id;
|
|
656
|
-
if(property.isRequired && null === propertyUpdateValue && (!property.isUpload || isUploadCreate)){
|
|
657
|
-
// missing required fields would break the update:
|
|
658
|
-
Logger.critical('Bad patch data on update.', propertyUpdateValue, property);
|
|
659
|
-
return false;
|
|
660
|
-
}
|
|
661
|
-
if(!property.isUpload || (property.isUpload && null !== propertyUpdateValue)){
|
|
662
|
-
entityDataPatch[i] = propertyUpdateValue;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
return entityDataPatch;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
prepareUploadPatchData(req, i, propertyUpdateValue, property)
|
|
669
|
-
{
|
|
670
|
-
let filesData = sc.get(req.files, i, null);
|
|
671
|
-
if(null === filesData){
|
|
672
|
-
return null;
|
|
673
|
-
}
|
|
674
|
-
let fileNames = [];
|
|
675
|
-
for(let file of filesData){
|
|
676
|
-
fileNames.push(file.filename);
|
|
677
|
-
}
|
|
678
|
-
return fileNames.join(property.isArray);
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
async generateEditRouteContent(req, driverResource, entityPath)
|
|
682
|
-
{
|
|
683
|
-
let idProperty = this.fetchEntityIdPropertyKey(driverResource);
|
|
684
|
-
let idValue = String(sc.get(req?.query, idProperty, ''));
|
|
685
|
-
let templateTitle = (!idValue ? 'Create' : 'Edit')+' '+this.translations.labels[driverResource.id()];
|
|
686
|
-
let loadedEntity = !idValue ? null : await this.loadEntityById(driverResource, idValue);
|
|
687
|
-
let entityViewRoute = !idValue
|
|
688
|
-
? this.rootPath+'/'+driverResource.entityPath
|
|
689
|
-
: this.generateEntityRoute('viewPath', driverResource, idProperty, loadedEntity);
|
|
690
|
-
let renderedEditProperties = {
|
|
691
|
-
idValue,
|
|
692
|
-
idProperty,
|
|
693
|
-
idPropertyLabel: this.fetchTranslation(idProperty),
|
|
694
|
-
templateTitle,
|
|
695
|
-
entityViewRoute
|
|
696
|
-
};
|
|
697
|
-
let editPropertiesEvent = {
|
|
698
|
-
adminManager: this,
|
|
699
|
-
req,
|
|
700
|
-
driverResource,
|
|
701
|
-
renderedEditProperties,
|
|
702
|
-
loadedEntity
|
|
703
|
-
};
|
|
704
|
-
await this.events.emit('reldens.adminEditPropertiesPopulation', editPropertiesEvent);
|
|
705
|
-
let propertiesKeys = Object.keys(driverResource.options.properties);
|
|
706
|
-
for(let propertyKey of propertiesKeys){
|
|
707
|
-
let property = driverResource.options.properties[propertyKey];
|
|
708
|
-
let fieldDisabled = -1 === driverResource.options.editProperties.indexOf(propertyKey);
|
|
709
|
-
renderedEditProperties[propertyKey] = await this.render(
|
|
710
|
-
this.adminFilesContents.fields.edit[this.propertyType(property, 'edit')],
|
|
711
|
-
{
|
|
712
|
-
fieldName: propertyKey,
|
|
713
|
-
fieldValue: await this.generatePropertyEditRenderedValue(
|
|
714
|
-
loadedEntity,
|
|
715
|
-
propertyKey,
|
|
716
|
-
property,
|
|
717
|
-
fieldDisabled
|
|
718
|
-
),
|
|
719
|
-
fieldDisabled: fieldDisabled ? ' disabled="disabled"' : '',
|
|
720
|
-
required: (!property.isUpload || !loadedEntity) && property.isRequired ? ' required="required"' : '',
|
|
721
|
-
multiple: property.isArray ? ' multiple="multiple"' : '',
|
|
722
|
-
inputType: this.getInputType(property, fieldDisabled)
|
|
723
|
-
}
|
|
724
|
-
);
|
|
725
|
-
}
|
|
726
|
-
return await this.renderRoute(
|
|
727
|
-
await this.render(this.adminContents.entities[entityPath].edit, renderedEditProperties),
|
|
728
|
-
this.adminContents.sideBar
|
|
729
|
-
);
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
getInputType(resourceProperty, fieldDisabled)
|
|
733
|
-
{
|
|
734
|
-
if('datetime' === resourceProperty.type && !fieldDisabled){
|
|
735
|
-
return 'datetime-local';
|
|
736
|
-
}
|
|
737
|
-
if('number' === resourceProperty.type){
|
|
738
|
-
return 'number';
|
|
739
|
-
}
|
|
740
|
-
return 'text';
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
async loadEntityById(driverResource, id)
|
|
744
|
-
{
|
|
745
|
-
let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
|
|
746
|
-
if(!entityRepository){
|
|
747
|
-
return false;
|
|
748
|
-
}
|
|
749
|
-
await this.events.emit('reldens.adminBeforeEntityLoad', {
|
|
750
|
-
adminManager: this,
|
|
751
|
-
driverResource,
|
|
752
|
-
entityId: id
|
|
753
|
-
});
|
|
754
|
-
return await entityRepository.loadByIdWithRelations(id);
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
async generateViewRouteContent(req, driverResource, entityPath)
|
|
758
|
-
{
|
|
759
|
-
let idProperty = this.fetchEntityIdPropertyKey(driverResource);
|
|
760
|
-
let id = (sc.get(req.query, idProperty, '')).toString();
|
|
761
|
-
if('' === id){
|
|
762
|
-
Logger.error('Missing ID on view route.', entityPath, id, idProperty);
|
|
763
|
-
return '';
|
|
764
|
-
}
|
|
765
|
-
let loadedEntity = await this.loadEntityById(driverResource, id);
|
|
766
|
-
let renderedViewProperties = {
|
|
767
|
-
entityEditRoute: this.generateEntityRoute('editPath', driverResource, idProperty, loadedEntity),
|
|
768
|
-
entityNewRoute: this.generateEntityRoute('editPath', driverResource, idProperty),
|
|
769
|
-
id
|
|
770
|
-
};
|
|
771
|
-
let entitySerializedData = {};
|
|
772
|
-
for(let propertyKey of driverResource.options.showProperties){
|
|
773
|
-
let property = driverResource.options.properties[propertyKey];
|
|
774
|
-
let {fieldValue, fieldName} = this.generatePropertyRenderedValueWithLabel(
|
|
775
|
-
loadedEntity,
|
|
776
|
-
propertyKey,
|
|
777
|
-
property
|
|
778
|
-
);
|
|
779
|
-
entitySerializedData[fieldName] = fieldValue;
|
|
780
|
-
renderedViewProperties[propertyKey] = await this.render(
|
|
781
|
-
this.adminFilesContents.fields.view[this.propertyType(property)],
|
|
782
|
-
{
|
|
783
|
-
fieldName: propertyKey,
|
|
784
|
-
fieldValue: await this.generatePropertyRenderedValue(
|
|
785
|
-
fieldValue,
|
|
786
|
-
fieldName,
|
|
787
|
-
property,
|
|
788
|
-
'view'
|
|
789
|
-
),
|
|
790
|
-
fieldOriginalValue: fieldValue,
|
|
791
|
-
target: ' target="_blank"'
|
|
792
|
-
}
|
|
793
|
-
);
|
|
794
|
-
}
|
|
795
|
-
let extraDataEvent = {entitySerializedData, entityId: driverResource.id(), entity: loadedEntity};
|
|
796
|
-
await this.events.emit('adminEntityExtraData', extraDataEvent);
|
|
797
|
-
entitySerializedData = extraDataEvent.entitySerializedData;
|
|
798
|
-
renderedViewProperties.entitySerializedData = JSON.stringify(entitySerializedData).replace(/"/g, '"');
|
|
799
|
-
let viewPropertiesEvent = {
|
|
800
|
-
adminManager: this,
|
|
801
|
-
idProperty,
|
|
802
|
-
req,
|
|
803
|
-
driverResource,
|
|
804
|
-
loadedEntity,
|
|
805
|
-
renderedViewProperties
|
|
806
|
-
};
|
|
807
|
-
await this.events.emit('reldens.adminViewPropertiesPopulation', viewPropertiesEvent);
|
|
808
|
-
return await this.renderRoute(
|
|
809
|
-
await this.render(this.adminContents.entities[entityPath].view, renderedViewProperties),
|
|
810
|
-
this.adminContents.sideBar
|
|
811
|
-
);
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
propertyType(resourceProperty, templateType)
|
|
815
|
-
{
|
|
816
|
-
let propertyType = sc.get(resourceProperty, 'type', 'text');
|
|
817
|
-
if('reference' === propertyType && 'edit' === templateType){
|
|
818
|
-
return 'select';
|
|
819
|
-
}
|
|
820
|
-
if(resourceProperty.isUpload){
|
|
821
|
-
if('edit' === templateType){
|
|
822
|
-
return 'file';
|
|
823
|
-
}
|
|
824
|
-
if('view' === templateType){
|
|
825
|
-
let multiple = resourceProperty.isArray ? 's' : '';
|
|
826
|
-
if('image' === resourceProperty.allowedTypes){
|
|
827
|
-
return resourceProperty.allowedTypes + multiple;
|
|
828
|
-
}
|
|
829
|
-
if('text' === resourceProperty.allowedTypes){
|
|
830
|
-
return 'link'+multiple
|
|
831
|
-
}
|
|
832
|
-
return 'text';
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
if('textarea' === propertyType){
|
|
836
|
-
return 'textarea';
|
|
837
|
-
}
|
|
838
|
-
if(-1 !== ['reference', 'number', 'datetime'].indexOf(propertyType)){
|
|
839
|
-
propertyType = 'text';
|
|
840
|
-
}
|
|
841
|
-
return propertyType;
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
async generateListRouteContent(req, driverResource, entityPath)
|
|
845
|
-
{
|
|
846
|
-
let page = Number(req?.query?.page || 1);
|
|
847
|
-
let pageSize = Number(req?.query?.pageSize || 25);
|
|
848
|
-
let filtersFromParams = req?.body?.filters || {};
|
|
849
|
-
let filters = this.prepareFilters(filtersFromParams, driverResource);
|
|
850
|
-
let mappedFiltersValues = driverResource.options.filterProperties.map((property) => {
|
|
851
|
-
let filterValue = (filtersFromParams[property] || '').toString();
|
|
852
|
-
return {[property]: '' === filterValue ? '' : 'value="'+filterValue+'"'};
|
|
853
|
-
});
|
|
854
|
-
let entitiesRows = await this.loadEntitiesForList(driverResource, pageSize, page, req, filters);
|
|
855
|
-
let listRawContent = this.adminContents.entities[entityPath].list.toString();
|
|
856
|
-
let totalEntities = await this.countTotalEntities(driverResource, filters);
|
|
857
|
-
let totalPages = totalEntities <= pageSize ? 1 : Math.ceil(totalEntities / pageSize);
|
|
858
|
-
let pages = PageRangeProvider.fetch(page, totalPages);
|
|
859
|
-
let renderedPagination = '';
|
|
860
|
-
for(let page of pages){
|
|
861
|
-
renderedPagination += await this.render(
|
|
862
|
-
this.adminFilesContents.fields.view['link'],
|
|
863
|
-
{
|
|
864
|
-
fieldName: page.label,
|
|
865
|
-
fieldValue: this.rootPath+'/'+driverResource.entityPath+'?page='+ page.value,
|
|
866
|
-
fieldOriginalValue: page.value,
|
|
867
|
-
}
|
|
868
|
-
);
|
|
869
|
-
}
|
|
870
|
-
let listVars = {
|
|
871
|
-
deletePath: this.rootPath + '/' + driverResource.entityPath + this.deletePath,
|
|
872
|
-
fieldsHeaders: driverResource.options.listProperties.map((property) => {
|
|
873
|
-
let propertyTitle = this.fetchTranslation(property, driverResource.id());
|
|
874
|
-
let alias = this.fetchTranslation(
|
|
875
|
-
driverResource.options.properties[property]?.alias || '',
|
|
876
|
-
driverResource.id()
|
|
877
|
-
);
|
|
878
|
-
let title = '' !== alias ? alias + ' ('+propertyTitle+')' : propertyTitle;
|
|
879
|
-
return {name: property, value: title};
|
|
880
|
-
}),
|
|
881
|
-
rows: entitiesRows
|
|
882
|
-
};
|
|
883
|
-
let list = await this.render(this.adminFilesContents.listContent, listVars);
|
|
884
|
-
let entitiesListView = await this.render(
|
|
885
|
-
listRawContent,
|
|
886
|
-
Object.assign({list, pagination: renderedPagination}, ...mappedFiltersValues)
|
|
887
|
-
);
|
|
888
|
-
return await this.renderRoute(entitiesListView, this.adminContents.sideBar);
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
fetchTranslation(snippet, group)
|
|
892
|
-
{
|
|
893
|
-
if('' === snippet){
|
|
894
|
-
return snippet;
|
|
895
|
-
}
|
|
896
|
-
let translationGroup = sc.get(this.translations, group);
|
|
897
|
-
if(translationGroup){
|
|
898
|
-
let translationByGroup = sc.get(translationGroup, snippet, '');
|
|
899
|
-
if('' !== translationByGroup){
|
|
900
|
-
return translationByGroup;
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
return sc.get(this.translations, snippet, snippet);
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
async countTotalEntities(driverResource, filters)
|
|
907
|
-
{
|
|
908
|
-
/** @type {BaseDriver|ObjectionJsDriver} entityRepository **/
|
|
909
|
-
let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
|
|
910
|
-
if(!entityRepository){
|
|
911
|
-
return false;
|
|
912
|
-
}
|
|
913
|
-
return await entityRepository.count(filters);
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
async loadEntitiesForList(driverResource, pageSize, page, req, filters)
|
|
917
|
-
{
|
|
918
|
-
let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
|
|
919
|
-
entityRepository.limit = pageSize;
|
|
920
|
-
if(1 < page){
|
|
921
|
-
entityRepository.offset = (page - 1) * pageSize;
|
|
922
|
-
}
|
|
923
|
-
entityRepository.sortBy = req?.body?.sortBy || false;
|
|
924
|
-
entityRepository.sortDirection = req?.body?.sortDirection || false;
|
|
925
|
-
let loadedEntities = await entityRepository.loadWithRelations(filters, []);
|
|
926
|
-
entityRepository.limit = 0;
|
|
927
|
-
entityRepository.offset = 0;
|
|
928
|
-
entityRepository.sortBy = false;
|
|
929
|
-
entityRepository.sortDirection = false;
|
|
930
|
-
let entityRows = [];
|
|
931
|
-
let deleteLink = this.rootPath + '/' + driverResource.entityPath + this.deletePath;
|
|
932
|
-
for(let entity of loadedEntities){
|
|
933
|
-
let entityRow = {fields: []};
|
|
934
|
-
let resourceProperties = driverResource.options?.properties;
|
|
935
|
-
let idProperty = this.fetchEntityIdPropertyKey(driverResource);
|
|
936
|
-
let viewLink = '';
|
|
937
|
-
let editLink = '';
|
|
938
|
-
if('' !== idProperty){
|
|
939
|
-
viewLink = this.generateEntityRoute('viewPath', driverResource, idProperty, entity);
|
|
940
|
-
editLink = this.generateEntityRoute('editPath', driverResource, idProperty, entity);
|
|
941
|
-
}
|
|
942
|
-
for(let property of driverResource.options.listProperties){
|
|
943
|
-
let {fieldValue, fieldName} = this.generatePropertyRenderedValueWithLabel(
|
|
944
|
-
entity,
|
|
945
|
-
property,
|
|
946
|
-
resourceProperties[property]
|
|
947
|
-
);
|
|
948
|
-
let value = await this.generatePropertyRenderedValue(
|
|
949
|
-
fieldValue,
|
|
950
|
-
fieldName,
|
|
951
|
-
resourceProperties[property]
|
|
952
|
-
);
|
|
953
|
-
entityRow.fields.push({
|
|
954
|
-
name: property,
|
|
955
|
-
value,
|
|
956
|
-
viewLink
|
|
957
|
-
});
|
|
958
|
-
}
|
|
959
|
-
entityRow.editLink = editLink;
|
|
960
|
-
entityRow.deleteLink = deleteLink;
|
|
961
|
-
entityRow.id = entity[idProperty];
|
|
962
|
-
entityRows.push(entityRow);
|
|
963
|
-
}
|
|
964
|
-
return entityRows;
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
async generatePropertyRenderedValue(fieldValue, fieldName, resourceProperty, templateType)
|
|
968
|
-
{
|
|
969
|
-
let fieldOriginalValue = fieldValue;
|
|
970
|
-
if('view' === templateType){
|
|
971
|
-
if(resourceProperty.isArray){
|
|
972
|
-
fieldValue = fieldValue.split(resourceProperty.isArray).map((value) => {
|
|
973
|
-
let target = resourceProperty.isUpload ? ' target="_blank"' : '';
|
|
974
|
-
let fieldValuePart = resourceProperty.isUpload && resourceProperty.bucketPath
|
|
975
|
-
? resourceProperty.bucketPath+value
|
|
976
|
-
: value;
|
|
977
|
-
return {fieldValuePart, fieldOriginalValuePart: value, target};
|
|
978
|
-
});
|
|
979
|
-
}
|
|
980
|
-
if(!resourceProperty.isArray && resourceProperty.isUpload){
|
|
981
|
-
fieldValue = resourceProperty.bucketPath+fieldValue;
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
return await this.render(
|
|
985
|
-
this.adminFilesContents.fields.view[this.propertyType(resourceProperty, templateType)],
|
|
986
|
-
{fieldName, fieldValue, fieldOriginalValue, target: ' target="_blank"'}
|
|
987
|
-
);
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
generatePropertyRenderedValueWithLabel(entity, propertyKey, resourceProperty)
|
|
228
|
+
fetchUploadProperties(driverResource)
|
|
991
229
|
{
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
fieldValue = '' !== fieldValue ? sc.formatDate(new Date(fieldValue)) : '';
|
|
999
|
-
}
|
|
1000
|
-
if('reference' === resourceProperty.type){
|
|
1001
|
-
let relationKey = resourceProperty.alias || resourceProperty.reference;
|
|
1002
|
-
let relationEntity = entity[relationKey];
|
|
1003
|
-
if(relationEntity){
|
|
1004
|
-
let relation = this.relations[resourceProperty.reference];
|
|
1005
|
-
if(relation){
|
|
1006
|
-
let relationTitleProperty = relation[relationKey];
|
|
1007
|
-
if(relationTitleProperty && '' !== String(relationEntity[relationTitleProperty] || '')){
|
|
1008
|
-
fieldName = relationTitleProperty;
|
|
1009
|
-
fieldValue = relationEntity[relationTitleProperty]+(' ('+fieldValue+')');
|
|
1010
|
-
}
|
|
230
|
+
if(!driverResource.options.uploadProperties){
|
|
231
|
+
driverResource.options.uploadProperties = {};
|
|
232
|
+
for(let propertyKey of Object.keys(driverResource.options.properties)){
|
|
233
|
+
let property = driverResource.options.properties[propertyKey];
|
|
234
|
+
if(property.isUpload){
|
|
235
|
+
driverResource.options.uploadProperties[propertyKey] = property;
|
|
1011
236
|
}
|
|
1012
237
|
}
|
|
1013
238
|
}
|
|
1014
|
-
|
|
1015
|
-
let optionData = resourceProperty.availableValues.filter((availableValue) => {
|
|
1016
|
-
return String(availableValue.value) === String(fieldValue);
|
|
1017
|
-
}).shift();
|
|
1018
|
-
if(optionData){
|
|
1019
|
-
fieldValue = optionData.label + ' (' + fieldValue + ')';
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
return {fieldValue, fieldName};
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
async generatePropertyEditRenderedValue(entity, propertyKey, resourceProperty, isFieldDisabled)
|
|
1026
|
-
{
|
|
1027
|
-
let entityPropertyValue = sc.get(entity, propertyKey, null);
|
|
1028
|
-
let fieldValue = (0 === entityPropertyValue ? '0' : entityPropertyValue || '').toString();
|
|
1029
|
-
if('boolean' === resourceProperty.type){
|
|
1030
|
-
fieldValue = '1' === fieldValue || 'true' === fieldValue ? ' checked="checked"' : '';
|
|
1031
|
-
}
|
|
1032
|
-
if('datetime' === resourceProperty.type){
|
|
1033
|
-
fieldValue = !entityPropertyValue || '' === entityPropertyValue
|
|
1034
|
-
? ''
|
|
1035
|
-
: sc.formatDate(new Date(entityPropertyValue), 'Y-m-d H:i:s');
|
|
1036
|
-
}
|
|
1037
|
-
if('reference' === resourceProperty.type){
|
|
1038
|
-
let relationDriverResource = this.resourcesByReference[resourceProperty.reference];
|
|
1039
|
-
let relation = this.relations[resourceProperty.reference];
|
|
1040
|
-
let relationKey = resourceProperty.alias || resourceProperty.reference;
|
|
1041
|
-
let idProperty = this.fetchEntityIdPropertyKey(relationDriverResource);
|
|
1042
|
-
let relationTitleProperty = relation ? relation[relationKey] : idProperty;
|
|
1043
|
-
let relationOptions = await this.fetchRelationOptions(relationDriverResource);
|
|
1044
|
-
return relationOptions.map((option) => {
|
|
1045
|
-
let value = option[idProperty];
|
|
1046
|
-
let selected = entity && entity[propertyKey] === value ? ' selected="selected"' : '';
|
|
1047
|
-
return {label: option[relationTitleProperty]+' (ID: '+value+')', value, selected}
|
|
1048
|
-
});
|
|
1049
|
-
}
|
|
1050
|
-
return fieldValue;
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
async fetchRelationOptions(relationDriverResource)
|
|
1054
|
-
{
|
|
1055
|
-
let relationEntityRepository = this.dataServer.getEntity(relationDriverResource.entityKey);
|
|
1056
|
-
return await relationEntityRepository.loadAll();
|
|
239
|
+
return driverResource.options.uploadProperties;
|
|
1057
240
|
}
|
|
1058
241
|
|
|
1059
242
|
fetchEntityIdPropertyKey(driverResource)
|
|
@@ -1076,76 +259,19 @@ class AdminManager
|
|
|
1076
259
|
return idProperty;
|
|
1077
260
|
}
|
|
1078
261
|
|
|
1079
|
-
|
|
1080
|
-
{
|
|
1081
|
-
if(!idProperty || (!entity && !entityId)){
|
|
1082
|
-
return this.rootPath + '/' + driverResource.entityPath;
|
|
1083
|
-
}
|
|
1084
|
-
let idParam = '?' + idProperty + '=';
|
|
1085
|
-
if(entity){
|
|
1086
|
-
idParam = idParam + entity[idProperty];
|
|
1087
|
-
}
|
|
1088
|
-
if(entityId){
|
|
1089
|
-
idParam = idParam + entityId;
|
|
1090
|
-
}
|
|
1091
|
-
return this.rootPath + '/' + driverResource.entityPath + this[routeType] + idParam;
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
isAuthenticated(req, res, next)
|
|
1095
|
-
{
|
|
1096
|
-
let allowContinue = {result: true, callback: null};
|
|
1097
|
-
let event = {adminManager: this, req, res, next, allowContinue};
|
|
1098
|
-
this.events.emit('reldens.adminIsAuthenticated', event);
|
|
1099
|
-
let returnPath = this.rootPath+this.loginPath;
|
|
1100
|
-
if(false === allowContinue.result){
|
|
1101
|
-
return res.redirect(returnPath);
|
|
1102
|
-
}
|
|
1103
|
-
if(null !== allowContinue.callback){
|
|
1104
|
-
return allowContinue.callback(event);
|
|
1105
|
-
}
|
|
1106
|
-
let user = req.session?.user;
|
|
1107
|
-
if(!user){
|
|
1108
|
-
return res.redirect(returnPath);
|
|
1109
|
-
}
|
|
1110
|
-
let userBlackList = this.blackList[user.role_id] || [];
|
|
1111
|
-
if(-1 !== userBlackList.indexOf(req.path)){
|
|
1112
|
-
let referrer = String(req.headers?.referer || '');
|
|
1113
|
-
return res.redirect('' !== referrer ? referrer : returnPath);
|
|
1114
|
-
}
|
|
1115
|
-
return next();
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
prepareFilters(filtersList, driverResource)
|
|
262
|
+
fetchTranslation(snippet, group)
|
|
1119
263
|
{
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
return {};
|
|
264
|
+
if('' === snippet){
|
|
265
|
+
return snippet;
|
|
1123
266
|
}
|
|
1124
|
-
let
|
|
1125
|
-
|
|
1126
|
-
let
|
|
1127
|
-
if(''
|
|
1128
|
-
|
|
1129
|
-
}
|
|
1130
|
-
let rawConfigFilterProperties = driverResource.options.properties[i];
|
|
1131
|
-
if(!rawConfigFilterProperties){
|
|
1132
|
-
Logger.critical('Could not found property by key.', i);
|
|
1133
|
-
continue;
|
|
1134
|
-
}
|
|
1135
|
-
if(rawConfigFilterProperties.isUpload){
|
|
1136
|
-
continue;
|
|
1137
|
-
}
|
|
1138
|
-
if('reference' === rawConfigFilterProperties.type){
|
|
1139
|
-
filters[i] = filter;
|
|
1140
|
-
continue;
|
|
1141
|
-
}
|
|
1142
|
-
if('boolean' === rawConfigFilterProperties.type){
|
|
1143
|
-
filters[i] = ('true' === filter);
|
|
1144
|
-
continue;
|
|
267
|
+
let translationGroup = sc.get(this.translations, group);
|
|
268
|
+
if(translationGroup){
|
|
269
|
+
let translationByGroup = sc.get(translationGroup, snippet, '');
|
|
270
|
+
if('' !== translationByGroup){
|
|
271
|
+
return translationByGroup;
|
|
1145
272
|
}
|
|
1146
|
-
filters[i] = {operator: 'like', value: '%'+filter+'%'};
|
|
1147
273
|
}
|
|
1148
|
-
return
|
|
274
|
+
return sc.get(this.translations, snippet, snippet);
|
|
1149
275
|
}
|
|
1150
276
|
|
|
1151
277
|
}
|