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