@reldens/cms 0.8.0 → 0.10.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 CHANGED
@@ -1,112 +1,354 @@
1
1
  [![Reldens - GitHub - Release](https://www.dwdeveloper.com/media/reldens/reldens-mmorpg-platform.png)](https://github.com/damian-pastorini/reldens)
2
2
 
3
- # Reldens - CMS
3
+ # Reldens CMS
4
4
 
5
- ## About
6
-
7
- Reldens CMS is a straightforward content management system designed for easy setup and extensibility.
8
- It provides both an administration panel for content management and a frontend for content delivery.
5
+ A powerful, flexible Content Management System built with Node.js, featuring an admin panel, multi-domain frontend support, enhanced templating with reusable content blocks, and automated installation.
9
6
 
10
7
  ## Features
11
8
 
12
- - Simple installation process
13
- - Administration panel for content management
14
- - Support for multiple database drivers (Prisma, Objection.js)
15
- - Template-based content rendering
16
- - SEO-friendly routes and meta-data
17
- - Modular architecture
9
+ ### - Quick Setup
10
+ - **Web-based installer** with a guided setup process
11
+ - **Automatic database schema creation** and seeding
12
+ - **Environment configuration generation**
13
+ - **Directory structure initialization**
14
+
15
+ ### - Frontend Engine
16
+ - **Multi-domain support** with domain-specific templates and partials
17
+ - **Dynamic routing** from database-driven routes
18
+ - **Entity-based URLs** (e.g., `/articles/123`)
19
+ - **Template fallback system** (domain → default → base)
20
+ - **Layout system** with body content layouts and page wrappers
21
+ - **Reusable content blocks** with `{{ entity() }}` template functions
22
+ - **Entity access control** for public/private content
23
+ - **Static asset serving** with Express integration as default
24
+ - **Template engine** with Mustache integration as default
25
+ - **Custom 404 handling**
26
+
27
+ ### - Admin Panel
28
+ - **Full CRUD operations** for all entities including content blocks
29
+ - **File upload handling** with multiple storage buckets
30
+ - **Role-based authentication** and access control
31
+ - **Advanced filtering and search** across entity properties
32
+ - **Bulk operations** (delete multiple records)
33
+ - **Relationship management** with foreign key support
34
+ - **Template-driven UI** with customizable admin themes
35
+
36
+ ### -️ Database & Entities
37
+ - **Multiple database drivers** (Prisma by default, others via DriversMap)
38
+ - **Automatic entity generation** from a database schema
39
+ - **Relationship mapping** and foreign key handling
40
+ - **Custom entity configuration** with validation rules
41
+ - **Translation support** for entity labels and properties
42
+ - **Content blocks management** via cms_blocks table
43
+ - **Entity access control** via cms_entity_access table
44
+
45
+ ### - Configuration & Architecture
46
+ - **Environment-based configuration** (.env file)
47
+ - **Modular service architecture** (Frontend, AdminManager, DataServer)
48
+ - **Event-driven system** with hooks for customization
49
+ - **Extensible authentication** (database users or custom callbacks)
50
+ - **File security** with path validation and dangerous key filtering
18
51
 
19
52
  ## Installation
20
53
 
21
- ### Option 1: NPX Installation (Recommended)
22
-
23
- The easiest way to install Reldens CMS is using NPX:
24
-
54
+ ### Method 1: Automated Web Installer
25
55
  ```bash
26
- npx @reldens/cms [directory]
56
+ npx reldens-cms
27
57
  ```
58
+ Navigate to `http://localhost:8080` and follow the installation wizard.
28
59
 
29
- This will start the installer in your browser, allowing you to configure your CMS installation.
30
-
31
- ### Option 2: Manual Installation
60
+ ### Method 2: Manual Setup
61
+ ```javascript
62
+ const { Manager } = require('@reldens/cms');
32
63
 
33
- 1. Install the package:
64
+ const cms = new Manager({
65
+ projectRoot: process.cwd(),
66
+ entityAccess: {
67
+ cmsPages: { public: true, operations: ['read'] },
68
+ products: { public: true, operations: ['read'] },
69
+ users: { public: false }
70
+ }
71
+ });
34
72
 
35
- ```bash
36
- npm install @reldens/cms
73
+ cms.start();
37
74
  ```
38
75
 
39
- 2. Create a basic implementation:
76
+ ## Configuration
77
+
78
+ ### Environment Variables
79
+ ```env
80
+ RELDENS_APP_HOST=http://localhost
81
+ RELDENS_APP_PORT=8080
82
+ RELDENS_ADMIN_ROUTE_PATH=/admin
83
+ RELDENS_ADMIN_SECRET=your-secret-key
84
+
85
+ RELDENS_DB_CLIENT=mysql
86
+ RELDENS_DB_HOST=localhost
87
+ RELDENS_DB_PORT=3306
88
+ RELDENS_DB_NAME=cms_db
89
+ RELDENS_DB_USER=username
90
+ RELDENS_DB_PASSWORD=password
91
+ RELDENS_STORAGE_DRIVER=prisma
92
+
93
+ RELDENS_DEFAULT_DOMAIN=example.com
94
+ RELDENS_DOMAIN_MAPPING={"dev.example.com":"development"}
95
+ RELDENS_SITE_KEY_MAPPING={"example.com":"main"}
96
+ ```
40
97
 
98
+ ### Custom Entity Configuration
41
99
  ```javascript
42
- const { Manager } = require('@reldens/cms');
100
+ const entityConfig = {
101
+ articles: {
102
+ listProperties: ['title', 'status', 'created_at'],
103
+ showProperties: ['title', 'content', 'author', 'status'],
104
+ editProperties: ['title', 'content', 'author_id', 'status'],
105
+ filterProperties: ['status', 'author_id'],
106
+ titleProperty: 'title',
107
+ parentItemLabel: 'Content',
108
+ properties: {
109
+ title: { type: 'string', isRequired: true },
110
+ content: { type: 'text' },
111
+ author_id: { type: 'reference', reference: 'users' },
112
+ featured_image: {
113
+ type: 'string',
114
+ isUpload: true,
115
+ allowedTypes: 'image',
116
+ bucket: 'uploads'
117
+ }
118
+ }
119
+ }
120
+ };
43
121
 
44
- const manager = new Manager({
45
- projectRoot: './your-project-root',
46
- authenticationMethod: 'db-users' // or 'callback'
122
+ const cms = new Manager({
123
+ entitiesConfig: entityConfig
47
124
  });
48
-
49
- manager.start().catch(console.error);
50
125
  ```
51
126
 
52
- ## Authentication Methods
127
+ ## Enhanced Templating System
53
128
 
54
- Reldens CMS supports two authentication methods:
129
+ ### Template Functions
130
+ Templates now support dynamic content blocks and entity rendering:
131
+ ```html
132
+ <!-- Render content blocks -->
133
+ {{ entity('cms_blocks', 'header-main') }}
134
+ {{ entity('cms_blocks', 'sidebar-left') }}
55
135
 
56
- 1. **db-users**: Uses the built-in users table for authentication
57
- 2. **callback**: Use a custom authentication function
136
+ <!-- Render other entities -->
137
+ {{ entity('products', '123') }}
138
+ {{ entity('cms_pages', '1') }}
139
+ ```
58
140
 
59
- Example with custom authentication:
141
+ ### Layout System
142
+ The CMS uses a two-tier layout system:
143
+
144
+ **page.html** - Full HTML wrapper:
145
+ ```html
146
+ <!DOCTYPE html>
147
+ <html lang="{{locale}}">
148
+ <head>
149
+ <title>{{title}}</title>
150
+ <meta name="description" content="{{description}}"/>
151
+ <link href="/css/styles.css" rel="stylesheet"/>
152
+ </head>
153
+ <body class="{{siteHandle}}">
154
+ {{{content}}}
155
+ <script src="/js/scripts.js"></script>
156
+ </body>
157
+ </html>
158
+ ```
60
159
 
160
+ **layouts/default.html** - Body content only:
161
+ ```html
162
+ {{ entity('cms_blocks', 'header-main') }}
163
+
164
+ <main id="main" class="main-container">
165
+ <div class="container">
166
+ <div class="row">
167
+ <div class="col-md-3">
168
+ {{ entity('cms_blocks', 'sidebar-left') }}
169
+ </div>
170
+ <div class="col-md-9">
171
+ {{{content}}}
172
+ </div>
173
+ </div>
174
+ </div>
175
+ </main>
176
+
177
+ {{ entity('cms_blocks', 'footer-main') }}
178
+ ```
179
+
180
+ Pages can use different layouts by setting the `layout` field in `cms_pages`:
181
+ - `default` - Header, sidebar, main content, footer
182
+ - `full-width` - Full width without sidebars
183
+ - `minimal` - Basic layout with minimal styling
184
+
185
+ ### Content Blocks
186
+ Create reusable content blocks in the `cms_blocks` table via admin panel:
187
+ ```sql
188
+ INSERT INTO cms_blocks (name, title, content) VALUES
189
+ ('contact-info', 'Contact Information', '<p>Email: info@example.com</p>'),
190
+ ('product-sidebar', 'Product Categories',
191
+ '<div class="categories"><h3>Categories</h3><ul><li><a href="/products/electronics">Electronics</a></li></ul></div>');
192
+ ```
193
+
194
+ ### Entity Access Control
195
+ Control which entities are publicly accessible:
61
196
  ```javascript
62
- const manager = new Manager({
63
- authenticationMethod: 'callback',
64
- authenticationCallback: async (email, password, roleId) => {
65
- // Your custom authentication logic
66
- // Must return a user object or false
197
+ const cms = new Manager({
198
+ entityAccess: {
199
+ products: { public: true, operations: ['read'] },
200
+ cmsPages: { public: true, operations: ['read'] },
201
+ users: { public: false }
67
202
  }
68
203
  });
69
204
  ```
70
205
 
71
- ## Extending with Custom Entities
206
+ ## Multi-Domain Setup
207
+
208
+ ### Directory Structure
209
+ ```
210
+ templates/
211
+ ├── layouts/
212
+ │ ├── default.html # Body content layouts
213
+ │ ├── full-width.html
214
+ │ └── minimal.html
215
+ ├── domains/
216
+ │ ├── example.com/
217
+ │ │ ├── layouts/ # Domain-specific layouts
218
+ │ │ ├── partials/
219
+ │ │ │ ├── header.html
220
+ │ │ │ └── footer.html
221
+ │ │ ├── page.html # Domain-specific page wrapper
222
+ │ │ └── index.html
223
+ │ └── dev.example.com/
224
+ │ └── page.html
225
+ ├── partials/
226
+ │ ├── header.html (default)
227
+ │ └── footer.html (default)
228
+ ├── page.html (base HTML wrapper)
229
+ └── 404.html
230
+ ```
231
+
232
+ ## Advanced Usage
72
233
 
73
- You can extend the CMS with your own entities:
234
+ ### Custom Authentication
235
+ ```javascript
236
+ const customAuth = async (email, password, roleId) => {
237
+ const user = await yourAuthService.authenticate(email, password);
238
+ return user && user.role_id === roleId ? user : false;
239
+ };
240
+
241
+ const cms = new Manager({
242
+ authenticationMethod: 'custom',
243
+ authenticationCallback: customAuth
244
+ });
245
+ ```
74
246
 
247
+ ### File Upload Configuration
75
248
  ```javascript
76
- const manager = new Manager({
77
- entities: {
78
- products: {
79
- config: {
80
- listProperties: ['id', 'name', 'price', 'status'],
81
- showProperties: ['id', 'name', 'price', 'description', 'status', 'created_at'],
82
- filterProperties: ['name', 'status'],
83
- editProperties: ['name', 'price', 'description', 'status'],
84
- properties: {
85
- id: { isId: true },
86
- name: { isRequired: true },
87
- price: { type: 'number', isRequired: true },
88
- description: { type: 'textarea' },
89
- status: { type: 'boolean' },
90
- created_at: { type: 'datetime' }
91
- },
92
- titleProperty: 'name',
93
- navigationPosition: 100
94
- }
95
- }
249
+ const uploadConfig = {
250
+ mimeTypes: {
251
+ image: ['image/jpeg', 'image/png', 'image/webp'],
252
+ document: ['application/pdf', 'text/plain']
253
+ },
254
+ allowedExtensions: {
255
+ image: ['.jpg', '.jpeg', '.png', '.webp'],
256
+ document: ['.pdf', '.txt']
96
257
  }
258
+ };
259
+
260
+ const cms = new Manager(uploadConfig);
261
+ ```
262
+
263
+ ### Event Hooks
264
+ ```javascript
265
+ cms.events.on('reldens.setupAdminRoutes', ({adminManager}) => {
266
+ // Add custom admin routes
267
+ adminManager.adminRouter.get('/custom', (req, res) => {
268
+ res.send('Custom admin page');
269
+ });
270
+ });
271
+
272
+ cms.events.on('adminEntityExtraData', ({entitySerializedData, entity}) => {
273
+ // Add extra data to admin views
274
+ entitySerializedData.customField = 'Custom Value';
97
275
  });
98
276
  ```
99
277
 
100
- ## Templates
278
+ ## Default database Schema
279
+
280
+ ### Core Tables
281
+ - `routes` - URL routing and SEO metadata
282
+ - `cms_pages` - Page content with layout assignments
283
+ - `cms_blocks` - Reusable content blocks
284
+ - `cms_entity_access` - Entity access control rules
285
+ - `entities_meta` - Generic metadata storage
286
+ - `cms_pages_meta` - Page-specific metadata
287
+
288
+ ### Installation Options
289
+ The installer provides checkboxes for:
290
+ - CMS core tables
291
+ - User authentication system
292
+ - Default admin user
293
+ - Default homepage
294
+ - Default content blocks
295
+ - Entity access control rules
296
+
297
+ ## API Reference
298
+
299
+ ### Manager Class
300
+ - `start()` - Initialize and start the CMS
301
+ - `isInstalled()` - Check if CMS is installed
302
+ - `initializeServices()` - Initialize all services
303
+
304
+ ### Frontend Class
305
+ - `initialize()` - Setup frontend routes and templates
306
+ - `handleRequest(req, res)` - Main request handler
307
+ - `findRouteByPath(path)` - Database route lookup
308
+ - `findEntityByPath(path)` - Entity-based URL handling
309
+ - `processCustomTemplateFunctions(template)` - Process {{ entity() }} functions
310
+
311
+ ### AdminManager Class
312
+ - `setupAdmin()` - Initialize admin panel
313
+ - `generateListRouteContent()` - Entity list pages
314
+ - `generateEditRouteContent()` - Entity edit forms
315
+ - `processSaveEntity()` - Handle form submissions
316
+
317
+ ### Installer Class
318
+ - `prepareSetup()` - Setup installation routes
319
+ - `executeInstallProcess()` - Run installation
320
+ - `generateEntities()` - Create entity files
321
+
322
+ ## File Structure
101
323
 
102
- Reldens CMS uses templates for rendering content. Templates are stored in the `templates` directory and use Mustache for rendering.
324
+ ```
325
+ project/
326
+ ├── admin/
327
+ │ └── templates/ # Admin panel templates
328
+ ├── templates/
329
+ │ ├── layouts/ # Body content layouts
330
+ │ ├── domains/ # Domain-specific templates
331
+ │ ├── partials/ # Shared template partials
332
+ │ ├── page.html # Base HTML wrapper
333
+ │ └── 404.html # Error page
334
+ ├── public/
335
+ │ ├── css/ # Stylesheets
336
+ │ ├── js/ # Client scripts
337
+ │ └── assets/ # Static assets
338
+ ├── entities/ # Generated entity classes
339
+ ├── .env # Environment configuration
340
+ ├── install.lock # Installation lock file
341
+ └── index.js # Main application file
342
+ ```
103
343
 
104
- Default templates include:
105
- - `layout.html`: The main layout template
106
- - `page.html`: Default page template
107
- - `404.html`: Not found page
344
+ ---
108
345
 
109
- You can create custom templates for different content types or override the default ones.
346
+ ## Contributing
347
+
348
+ 1. Fork the repository
349
+ 2. Create a feature branch
350
+ 3. Follow the coding standards in the JavaScript rules
351
+ 4. Submit a pull request
110
352
 
111
353
  ---
112
354
 
@@ -120,6 +362,12 @@ Need something specific?
120
362
 
121
363
  ---
122
364
 
365
+ ## License
366
+
367
+ MIT License - see LICENSE file for details.
368
+
369
+ ---
370
+
123
371
  ### [Reldens](https://github.com/damian-pastorini/reldens/ "Reldens")
124
372
 
125
373
  ##### [By DwDeveloper](https://www.dwdeveloper.com/ "DwDeveloper")
@@ -1 +1 @@
1
- <input type="text" name="{{&fieldName}}" id="{{&fieldName}}" value="{{fieldValue}}"{{&required}}{{&fieldDisabled}}/>
1
+ <input type="{{&inputType}}" name="{{&fieldName}}" id="{{&fieldName}}" value="{{fieldValue}}"{{&required}}{{&fieldDisabled}}/>
@@ -40,6 +40,14 @@ body {
40
40
  margin-bottom: 15px;
41
41
  }
42
42
 
43
+ .input-checkbox {
44
+ display: flex;
45
+
46
+ label {
47
+ margin-bottom: 0;
48
+ }
49
+ }
50
+
43
51
  label {
44
52
  display: block;
45
53
  margin-bottom: 5px;
@@ -32,7 +32,7 @@
32
32
  </div>
33
33
  <div class="input-box app-admin-secret">
34
34
  <label for="app-admin-secret">Admin Panel Secret</label>
35
- <input type="text" name="app-admin-secret" id="app-admin-secret" value="{{app-admin-secret}}"/>
35
+ <input type="text" name="app-admin-secret" id="app-admin-secret" value="{{app-admin-secret}}" required/>
36
36
  </div>
37
37
  <div class="input-box app-error">
38
38
  <p class="error installation-process-failed">There was an error during the installation process.</p>
@@ -77,25 +77,33 @@
77
77
  <p class="error db-installation-process-failed">There was an error during the installation process.</p>
78
78
  </div>
79
79
  <h3 class="form-title">Installation Options</h3>
80
- <div class="input-box install-cms-tables">
80
+ <div class="input-box input-checkbox install-cms-tables">
81
81
  <input type="checkbox" name="install-cms-tables" id="install-cms-tables" checked/>
82
82
  <label for="install-cms-tables">Install default CMS pages tables</label>
83
83
  </div>
84
- <div class="input-box install-user-auth">
84
+ <div class="input-box input-checkbox install-user-auth">
85
85
  <input type="checkbox" name="install-user-auth" id="install-user-auth" checked/>
86
86
  <label for="install-user-auth">Install users authentication</label>
87
87
  </div>
88
- <div class="input-box install-default-user">
88
+ <div class="input-box input-checkbox install-default-user">
89
89
  <input type="checkbox" name="install-default-user" id="install-default-user" checked/>
90
90
  <label for="install-default-user">Install default admin user (root@cms-admin.com / root)</label>
91
91
  </div>
92
- <div class="input-box install-default-homepage">
92
+ <div class="input-box input-checkbox install-default-homepage">
93
93
  <input type="checkbox" name="install-default-homepage" id="install-default-homepage" checked/>
94
94
  <label for="install-default-homepage">Install default homepage</label>
95
95
  </div>
96
+ <div class="input-box input-checkbox install-default-blocks">
97
+ <input type="checkbox" name="install-default-blocks" id="install-default-blocks" checked/>
98
+ <label for="install-default-blocks">Install default content blocks</label>
99
+ </div>
100
+ <div class="input-box input-checkbox install-entity-access">
101
+ <input type="checkbox" name="install-entity-access" id="install-entity-access" checked/>
102
+ <label for="install-entity-access">Install entity access control rules</label>
103
+ </div>
96
104
  <div class="input-box submit-container">
97
- <img class="install-loading hidden" src="/install-assets/img/loading.gif"/>
98
105
  <input id="install-submit-button" type="submit" value="Install"/>
106
+ <img class="install-loading hidden" src="/install-assets/img/loading.gif"/>
99
107
  </div>
100
108
  <div class="input-box response-error"></div>
101
109
  </form>
@@ -662,10 +662,10 @@ class AdminManager
662
662
  async generateEditRouteContent(req, driverResource, entityPath)
663
663
  {
664
664
  let idProperty = this.fetchEntityIdPropertyKey(driverResource);
665
- let idValue = (req?.query[idProperty] || '').toString();
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
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
669
  ? this.rootPath+'/'+driverResource.entityPath
670
670
  : this.generateEntityRoute('viewPath', driverResource, idProperty, loadedEntity);
671
671
  let renderedEditProperties = {
@@ -677,32 +677,29 @@ class AdminManager
677
677
  };
678
678
  let propertiesKeys = Object.keys(driverResource.options.properties);
679
679
  for(let propertyKey of propertiesKeys){
680
- let resourceProperty = driverResource.options.properties[propertyKey];
680
+ let property = driverResource.options.properties[propertyKey];
681
681
  let fieldDisabled = -1 === driverResource.options.editProperties.indexOf(propertyKey);
682
- let isRequired = resourceProperty.isRequired ? ' required="required"' : '';
683
- if(resourceProperty.isUpload && loadedEntity){
684
- isRequired = '';
685
- }
686
- let inputType = this.getInputType(resourceProperty, fieldDisabled);
687
682
  renderedEditProperties[propertyKey] = await this.render(
688
- this.adminFilesContents.fields.edit[this.propertyType(resourceProperty, 'edit')],
683
+ this.adminFilesContents.fields.edit[this.propertyType(property, 'edit')],
689
684
  {
690
685
  fieldName: propertyKey,
691
686
  fieldValue: await this.generatePropertyEditRenderedValue(
692
687
  loadedEntity,
693
688
  propertyKey,
694
- resourceProperty,
689
+ property,
695
690
  fieldDisabled
696
691
  ),
697
692
  fieldDisabled: fieldDisabled ? ' disabled="disabled"' : '',
698
- required: isRequired,
699
- multiple: resourceProperty.isArray ? ' multiple="multiple"' : '',
700
- inputType
693
+ required: (!property.isUpload || !loadedEntity) && property.isRequired ? ' required="required"' : '',
694
+ multiple: property.isArray ? ' multiple="multiple"' : '',
695
+ inputType: this.getInputType(property, fieldDisabled)
701
696
  }
702
697
  );
703
698
  }
704
- let renderedView = await this.render(this.adminContents.entities[entityPath].edit, renderedEditProperties);
705
- return await this.renderRoute(renderedView, this.adminContents.sideBar);
699
+ return await this.renderRoute(
700
+ await this.render(this.adminContents.entities[entityPath].edit, renderedEditProperties),
701
+ this.adminContents.sideBar
702
+ );
706
703
  }
707
704
 
708
705
  getInputType(resourceProperty, fieldDisabled)
@@ -710,12 +707,18 @@ class AdminManager
710
707
  if('datetime' === resourceProperty.type && !fieldDisabled){
711
708
  return 'datetime-local';
712
709
  }
710
+ if('number' === resourceProperty.type){
711
+ return 'number';
712
+ }
713
713
  return 'text';
714
714
  }
715
715
 
716
716
  async loadEntityById(driverResource, id)
717
717
  {
718
718
  let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
719
+ if(!entityRepository){
720
+ return false;
721
+ }
719
722
  return await entityRepository.loadByIdWithRelations(id);
720
723
  }
721
724
 
@@ -735,24 +738,23 @@ class AdminManager
735
738
  };
736
739
  let entitySerializedData = {};
737
740
  for(let propertyKey of driverResource.options.showProperties){
738
- let resourceProperty = driverResource.options.properties[propertyKey];
741
+ let property = driverResource.options.properties[propertyKey];
739
742
  let {fieldValue, fieldName} = this.generatePropertyRenderedValueWithLabel(
740
743
  loadedEntity,
741
744
  propertyKey,
742
- resourceProperty
745
+ property
743
746
  );
744
747
  entitySerializedData[fieldName] = fieldValue;
745
- let renderedFieldValue = await this.generatePropertyRenderedValue(
746
- fieldValue,
747
- fieldName,
748
- resourceProperty,
749
- 'view'
750
- );
751
748
  renderedViewProperties[propertyKey] = await this.render(
752
- this.adminFilesContents.fields.view[this.propertyType(resourceProperty)],
749
+ this.adminFilesContents.fields.view[this.propertyType(property)],
753
750
  {
754
751
  fieldName: propertyKey,
755
- fieldValue: renderedFieldValue,
752
+ fieldValue: await this.generatePropertyRenderedValue(
753
+ fieldValue,
754
+ fieldName,
755
+ property,
756
+ 'view'
757
+ ),
756
758
  fieldOriginalValue: fieldValue,
757
759
  target: ' target="_blank"'
758
760
  }
@@ -762,13 +764,15 @@ class AdminManager
762
764
  await this.events.emit('adminEntityExtraData', extraDataEvent);
763
765
  entitySerializedData = extraDataEvent.entitySerializedData;
764
766
  renderedViewProperties.entitySerializedData = JSON.stringify(entitySerializedData).replace(/"/g, '&quot;');
765
- let renderedView = await this.render(this.adminContents.entities[entityPath].view, renderedViewProperties);
766
- return await this.renderRoute(renderedView, this.adminContents.sideBar);
767
+ return await this.renderRoute(
768
+ await this.render(this.adminContents.entities[entityPath].view, renderedViewProperties),
769
+ this.adminContents.sideBar
770
+ );
767
771
  }
768
772
 
769
773
  propertyType(resourceProperty, templateType)
770
774
  {
771
- let propertyType = resourceProperty.type || 'text';
775
+ let propertyType = sc.get(resourceProperty, 'type', 'text');
772
776
  if('reference' === propertyType && 'edit' === templateType){
773
777
  return 'select';
774
778
  }
@@ -805,7 +809,8 @@ class AdminManager
805
809
  });
806
810
  let entitiesRows = await this.loadEntitiesForList(driverResource, pageSize, page, req, filters);
807
811
  let listRawContent = this.adminContents.entities[entityPath].list.toString();
808
- let totalPages = Math.ceil(await this.countTotalEntities(driverResource, filters) / pageSize);
812
+ let totalEntities = await this.countTotalEntities(driverResource, filters);
813
+ let totalPages = totalEntities <= pageSize ? 1 : Math.ceil(totalEntities / pageSize);
809
814
  let pages = PageRangeProvider.fetch(page, totalPages);
810
815
  let renderedPagination = '';
811
816
  for(let page of pages){
@@ -858,6 +863,9 @@ class AdminManager
858
863
  {
859
864
  /** @type {BaseDriver|ObjectionJsDriver} entityRepository **/
860
865
  let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
866
+ if(!entityRepository){
867
+ return false;
868
+ }
861
869
  return await entityRepository.count(filters);
862
870
  }
863
871