@reldens/cms 0.1.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/lib/manager.js ADDED
@@ -0,0 +1,171 @@
1
+ /**
2
+ *
3
+ * Reldens - CMS - Manager
4
+ *
5
+ */
6
+
7
+ const { AppServerFactory, FileHandler, Encryptor } = require('@reldens/server-utils');
8
+ const { DriversMap } = require('@reldens/storage');
9
+ const { AdminManager } = require('./admin-manager');
10
+ const { Installer } = require('./installer');
11
+ const { Storefront } = require('./storefront');
12
+ const { Logger, sc } = require('@reldens/utils');
13
+ const dotenv = require('dotenv');
14
+ const mustache = require('mustache');
15
+
16
+ class Manager
17
+ {
18
+ constructor(props = {})
19
+ {
20
+ this.projectRoot = sc.get(props, 'projectRoot', './');
21
+ this.envFilePath = FileHandler.joinPaths(this.projectRoot, '.env');
22
+ this.installLockPath = FileHandler.joinPaths(this.projectRoot, 'install.lock');
23
+ dotenv.config({path: this.envFilePath});
24
+ this.config = this.loadConfigFromEnv();
25
+ this.entities = sc.get(props, 'entities', {});
26
+ this.authenticationMethod = sc.get(props, 'authenticationMethod', 'db-users');
27
+ this.authenticationCallback = sc.get(props, 'authenticationCallback', false);
28
+ this.appServerFactory = new AppServerFactory();
29
+ this.installer = new Installer({
30
+ projectRoot: this.projectRoot
31
+ });
32
+ this.dataServer = false;
33
+ this.app = false;
34
+ this.appServer = false;
35
+ this.adminManager = false;
36
+ this.storefront = false;
37
+ }
38
+
39
+ loadConfigFromEnv()
40
+ {
41
+ return {
42
+ host: process.env.RELDENS_CMS_HOST || 'http://localhost',
43
+ port: Number(process.env.RELDENS_CMS_PORT || 3000),
44
+ adminPath: process.env.RELDENS_CMS_ADMIN_PATH || '/reldens-admin',
45
+ adminSecret: process.env.RELDENS_CMS_ADMIN_SECRET || '',
46
+ database: {
47
+ client: process.env.RELDENS_CMS_DB_CLIENT || 'mysql2',
48
+ host: process.env.RELDENS_CMS_DB_HOST || 'localhost',
49
+ port: Number(process.env.RELDENS_CMS_DB_PORT || 3306),
50
+ name: process.env.RELDENS_CMS_DB_NAME || 'reldens_cms',
51
+ user: process.env.RELDENS_CMS_DB_USER || '',
52
+ password: process.env.RELDENS_CMS_DB_PASSWORD || '',
53
+ driver: process.env.RELDENS_CMS_DB_DRIVER || 'prisma'
54
+ }
55
+ };
56
+ }
57
+
58
+ isInstalled()
59
+ {
60
+ return FileHandler.exists(this.installLockPath);
61
+ }
62
+
63
+ async start()
64
+ {
65
+ let createdAppServer = this.appServerFactory.createAppServer();
66
+ if(this.appServerFactory.error.message){
67
+ Logger.error('App server error: '+this.appServerFactory.error.message);
68
+ return false;
69
+ }
70
+ this.app = createdAppServer.app;
71
+ this.appServer = createdAppServer.appServer;
72
+ if(!this.isInstalled()){
73
+ Logger.info('CMS not installed, preparing setup');
74
+ await this.installer.prepareSetup(this.app, this.appServerFactory);
75
+ await this.appServer.listen(this.config.port);
76
+ Logger.info('Installer running on '+this.config.host+':'+this.config.port);
77
+ return true;
78
+ }
79
+ try {
80
+ await this.initializeDataServer();
81
+ await this.initializeAdminManager();
82
+ await this.initializeStorefront();
83
+ await this.appServer.listen(this.config.port);
84
+ Logger.info('CMS running on '+this.config.host+':'+this.config.port);
85
+ return true;
86
+ } catch (error) {
87
+ Logger.error('Failed to start CMS: '+error.message);
88
+ return false;
89
+ }
90
+ }
91
+
92
+ async initializeDataServer()
93
+ {
94
+ let dbConfig = {
95
+ client: this.config.database.client,
96
+ config: {
97
+ host: this.config.database.host,
98
+ port: this.config.database.port,
99
+ database: this.config.database.name,
100
+ user: this.config.database.user,
101
+ password: this.config.database.password
102
+ }
103
+ };
104
+ let DriverClass = DriversMap[this.config.database.driver];
105
+ if(!DriverClass){
106
+ throw new Error('Invalid database driver: '+this.config.database.driver);
107
+ }
108
+ this.dataServer = new DriverClass(dbConfig);
109
+ if(!await this.dataServer.connect()){
110
+ throw new Error('Failed to connect to database');
111
+ }
112
+ await this.dataServer.generateEntities();
113
+ return true;
114
+ }
115
+
116
+ async initializeAdminManager()
117
+ {
118
+ let authenticationCallback = this.authenticationCallback;
119
+ if('db-users' === this.authenticationMethod){
120
+ authenticationCallback = async (email, password, roleId) => {
121
+ let usersEntity = this.dataServer.getEntity('users');
122
+ if(!usersEntity){
123
+ return false;
124
+ }
125
+ let user = await usersEntity.loadOneBy('email', email);
126
+ if(!user){
127
+ return false;
128
+ }
129
+ if(Number(user.role_id) !== Number(roleId)){
130
+ return false;
131
+ }
132
+ return Encryptor.validatePassword(password, user.password) ? user : false;
133
+ };
134
+ }
135
+ let adminConfig = {
136
+ events: false,
137
+ renderCallback: this.renderCallback.bind(this),
138
+ dataServer: this.dataServer,
139
+ authenticationCallback,
140
+ app: this.app,
141
+ appServerFactory: this.appServerFactory,
142
+ secret: this.config.adminSecret,
143
+ rootPath: this.config.adminPath,
144
+ adminRoleId: 99,
145
+ entities: this.entities
146
+ };
147
+ this.adminManager = new AdminManager(adminConfig);
148
+ await this.adminManager.setupAdmin();
149
+ return true;
150
+ }
151
+
152
+ async renderCallback(template, params)
153
+ {
154
+ if(!template){
155
+ return '';
156
+ }
157
+ return mustache.render(template, params);
158
+ }
159
+
160
+ async initializeStorefront()
161
+ {
162
+ this.storefront = new Storefront({
163
+ app: this.app,
164
+ dataServer: this.dataServer,
165
+ projectRoot: this.projectRoot
166
+ });
167
+ return await this.storefront.initialize();
168
+ }
169
+ }
170
+
171
+ module.exports.Manager = Manager;
@@ -0,0 +1,228 @@
1
+ /**
2
+ *
3
+ * Reldens - CMS - Storefront
4
+ *
5
+ */
6
+
7
+ const { FileHandler } = require('@reldens/server-utils');
8
+ const { Logger, sc } = require('@reldens/utils');
9
+ const mustache = require('mustache');
10
+
11
+ class Storefront
12
+ {
13
+
14
+ constructor(props)
15
+ {
16
+ this.app = sc.get(props, 'app', false);
17
+ this.dataServer = sc.get(props, 'dataServer', false);
18
+ this.projectRoot = sc.get(props, 'projectRoot', './');
19
+ this.templatesPath = FileHandler.joinPaths(this.projectRoot, 'templates');
20
+ this.publicPath = FileHandler.joinPaths(this.projectRoot, 'public');
21
+ this.encoding = sc.get(props, 'encoding', 'utf8');
22
+ this.error = false;
23
+ }
24
+
25
+ async initialize()
26
+ {
27
+ if(!this.app || !this.dataServer){
28
+ this.error = 'Missing app or dataServer';
29
+ return false;
30
+ }
31
+ if(!FileHandler.exists(this.templatesPath)){
32
+ Logger.error('Templates folder not found: '+this.templatesPath);
33
+ return false;
34
+ }
35
+ if(!FileHandler.exists(this.publicPath)){
36
+ Logger.error('Public folder not found: '+this.publicPath);
37
+ return false;
38
+ }
39
+ this.setupStaticAssets();
40
+ this.app.get('*', async (req, res) => {
41
+ return await this.handleRequest(req, res);
42
+ });
43
+ return true;
44
+ }
45
+
46
+ setupStaticAssets()
47
+ {
48
+ if(!this.app || !this.publicPath){
49
+ return false;
50
+ }
51
+ this.app.use(this.app._router.constructor.static(this.publicPath));
52
+ return true;
53
+ }
54
+
55
+ async handleRequest(req, res)
56
+ {
57
+ let path = req.path;
58
+ if('/favicon.ico' === path){
59
+ return res.status(404).send('');
60
+ }
61
+ try {
62
+ let route = await this.findRouteByPath(path);
63
+ if(route){
64
+ return await this.renderContentFromRoute(res, route);
65
+ }
66
+ let pathSegments = path.split('/').filter(segment => segment !== '');
67
+ if(0 < pathSegments.length){
68
+ let entityResult = await this.findEntityByPath(pathSegments);
69
+ if(entityResult){
70
+ return await this.renderContentFromEntity(
71
+ res,
72
+ entityResult.entity,
73
+ entityResult.entityName
74
+ );
75
+ }
76
+ }
77
+ let templatePath = this.findTemplateByPath(path);
78
+ if(templatePath){
79
+ return await this.renderTemplateOnly(res, templatePath);
80
+ }
81
+ return await this.renderNotFoundPage(res);
82
+ } catch (error) {
83
+ Logger.error('Request handling error: '+error.message);
84
+ return res.status(500).send('Internal server error');
85
+ }
86
+ }
87
+
88
+ async findRouteByPath(path)
89
+ {
90
+ if('/' === path){
91
+ path = '/home';
92
+ }
93
+ let routesEntity = this.dataServer.getEntity('routes');
94
+ if(!routesEntity){
95
+ return false;
96
+ }
97
+ return await routesEntity.loadOneBy('path', path);
98
+ }
99
+
100
+ async findEntityByPath(pathSegments)
101
+ {
102
+ if(1 > pathSegments.length){
103
+ return false;
104
+ }
105
+ let entityName = pathSegments[0];
106
+ let entityId = 2 > pathSegments.length ? false : pathSegments[1];
107
+ if(!entityId){
108
+ return false;
109
+ }
110
+ let entity = this.dataServer.getEntity(entityName);
111
+ if(!entity){
112
+ return false;
113
+ }
114
+ let loadedEntity = await entity.loadById(entityId);
115
+ if(!loadedEntity){
116
+ return false;
117
+ }
118
+ return {
119
+ entity: loadedEntity,
120
+ entityName
121
+ };
122
+ }
123
+
124
+ findTemplateByPath(path)
125
+ {
126
+ if('/' === path){
127
+ path = '/index';
128
+ }
129
+ let templatePath = path.endsWith('/')
130
+ ? path.slice(0, -1)
131
+ : path;
132
+ templatePath = templatePath.startsWith('/')
133
+ ? templatePath.substring(1)
134
+ : templatePath;
135
+ let fullPath = FileHandler.joinPaths(this.templatesPath, templatePath + '.html');
136
+ if(FileHandler.exists(fullPath)){
137
+ return fullPath;
138
+ }
139
+ return false;
140
+ }
141
+
142
+ async renderContentFromRoute(res, route)
143
+ {
144
+ if(!route.router || !route.content_id){
145
+ return await this.renderNotFoundPage(res);
146
+ }
147
+ let entity = this.dataServer.getEntity(route.router);
148
+ if(!entity){
149
+ return await this.renderNotFoundPage(res);
150
+ }
151
+ let content = await entity.loadById(route.content_id);
152
+ if(!content){
153
+ return await this.renderNotFoundPage(res);
154
+ }
155
+ let templateName = content.template || route.router;
156
+ let templatePath = FileHandler.joinPaths(this.templatesPath, templateName + '.html');
157
+ if(!FileHandler.exists(templatePath)){
158
+ templatePath = FileHandler.joinPaths(this.templatesPath, 'page.html');
159
+ if(!FileHandler.exists(templatePath)){
160
+ return await this.renderNotFoundPage(res);
161
+ }
162
+ }
163
+ let template = FileHandler.readFile(templatePath, {
164
+ encoding: this.encoding
165
+ }).toString();
166
+ let data = {
167
+ ...route,
168
+ ...content,
169
+ current_year: new Date().getFullYear()
170
+ };
171
+ let rendered = mustache.render(template, data);
172
+ return res.send(rendered);
173
+ }
174
+
175
+ async renderContentFromEntity(res, entity, entityName)
176
+ {
177
+ let templatePath = FileHandler.joinPaths(this.templatesPath, entityName + '.html');
178
+ if(!FileHandler.exists(templatePath)){
179
+ templatePath = FileHandler.joinPaths(this.templatesPath, 'page.html');
180
+ if(!FileHandler.exists(templatePath)){
181
+ return await this.renderNotFoundPage(res);
182
+ }
183
+ }
184
+ let template = FileHandler.readFile(templatePath, {
185
+ encoding: this.encoding
186
+ }).toString();
187
+ let data = {
188
+ ...entity,
189
+ title: entity.title || entity.name || entityName,
190
+ current_year: new Date().getFullYear()
191
+ };
192
+ let rendered = mustache.render(template, data);
193
+ return res.send(rendered);
194
+ }
195
+
196
+ async renderTemplateOnly(res, templatePath)
197
+ {
198
+ let template = FileHandler.readFile(templatePath, {
199
+ encoding: this.encoding
200
+ }).toString();
201
+ let data = {
202
+ title: 'Page Title',
203
+ current_year: new Date().getFullYear()
204
+ };
205
+ let rendered = mustache.render(template, data);
206
+ return res.send(rendered);
207
+ }
208
+
209
+ async renderNotFoundPage(res)
210
+ {
211
+ let templatePath = FileHandler.joinPaths(this.templatesPath, '404.html');
212
+ if(!FileHandler.exists(templatePath)){
213
+ return res.status(404).send('Page not found');
214
+ }
215
+ let template = FileHandler.readFile(templatePath, {
216
+ encoding: this.encoding
217
+ }).toString();
218
+ let data = {
219
+ title: '404 - Page Not Found',
220
+ current_year: new Date().getFullYear()
221
+ };
222
+ let rendered = mustache.render(template, data);
223
+ return res.status(404).send(rendered);
224
+ }
225
+
226
+ }
227
+
228
+ module.exports.Storefront = Storefront;
@@ -0,0 +1,50 @@
1
+ /**
2
+ *
3
+ * Reldens - TemplatesList
4
+ *
5
+ */
6
+
7
+ module.exports.TemplatesList = {
8
+ login: 'login.html',
9
+ dashboard: 'dashboard.html',
10
+ management: 'management.html',
11
+ mapsWizard: 'maps-wizard.html',
12
+ mapsWizardMapsSelection: 'maps-wizard-maps-selection.html',
13
+ objectsImport: 'objects-import.html',
14
+ skillsImport: 'skills-import.html',
15
+ list: 'list.html',
16
+ listContent: 'list-content.html',
17
+ view: 'view.html',
18
+ edit: 'edit.html',
19
+ layout: 'layout.html',
20
+ sideBar: 'sidebar.html',
21
+ sideBarHeader: 'sidebar-header.html',
22
+ sideBarItem: 'sidebar-item.html',
23
+ paginationLink: 'pagination-link.html',
24
+ defaultCopyRight: 'default-copyright.html',
25
+ fields: {
26
+ view: {
27
+ text: 'text.html',
28
+ image: 'image.html',
29
+ images: 'images.html',
30
+ link: 'link.html',
31
+ links: 'links.html',
32
+ boolean: 'boolean.html'
33
+ },
34
+ edit: {
35
+ text: 'text.html',
36
+ textarea: 'textarea.html',
37
+ select: 'select.html',
38
+ checkbox: 'checkbox.html',
39
+ boolean: 'checkbox.html',
40
+ radio: 'radio.html',
41
+ button: 'button.html',
42
+ file: 'file.html'
43
+ }
44
+ },
45
+ sections: {
46
+ view: {
47
+ rooms: 'rooms.html'
48
+ }
49
+ }
50
+ };
@@ -0,0 +1,11 @@
1
+
2
+ -- Default admin user:
3
+
4
+ INSERT IGNORE INTO `users` (`email`, `username`, `password`, `role_id`, `status`)
5
+ VALUES (
6
+ 'root@cms-admin.com',
7
+ 'root',
8
+ 'd35ed1c81c3ff00de15309fe40a90c32:a39a9231a69fefef274c13c1780a7447672a5fee8250ce22a51bb20275039dda63a54faa1e5fd775becb3ac424f571d5b996001305bb7d63e038111dce08d45b',
9
+ 99,
10
+ '1'
11
+ );
@@ -0,0 +1,84 @@
1
+
2
+ -- Install SQL for Reldens CMS
3
+
4
+ CREATE TABLE IF NOT EXISTS `users` (
5
+ `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
6
+ `email` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
7
+ `username` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
8
+ `password` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
9
+ `role_id` INT(10) UNSIGNED NOT NULL,
10
+ `status` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
11
+ `created_at` TIMESTAMP NOT NULL DEFAULT (NOW()),
12
+ `updated_at` TIMESTAMP NOT NULL DEFAULT (NOW()) ON UPDATE CURRENT_TIMESTAMP,
13
+ PRIMARY KEY (`id`) USING BTREE,
14
+ UNIQUE KEY `email` (`email`) USING BTREE,
15
+ UNIQUE KEY `username` (`username`) USING BTREE
16
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
17
+
18
+ CREATE TABLE IF NOT EXISTS `routes` (
19
+ `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
20
+ `path` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
21
+ `router` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
22
+ `content_id` INT(10) UNSIGNED NOT NULL,
23
+ `title` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
24
+ `meta_description` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
25
+ `canonical_url` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
26
+ `meta_robots` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'index,follow',
27
+ `og_title` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
28
+ `og_description` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
29
+ `og_image` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
30
+ `twitter_card_type` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'summary',
31
+ `status` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'published',
32
+ `publish_at` TIMESTAMP NULL,
33
+ `locale` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'en',
34
+ `cache_ttl_seconds` INT UNSIGNED NULL DEFAULT 3600,
35
+ `created_at` TIMESTAMP NOT NULL DEFAULT (NOW()),
36
+ `updated_at` TIMESTAMP NOT NULL DEFAULT (NOW()) ON UPDATE CURRENT_TIMESTAMP,
37
+ PRIMARY KEY (`id`) USING BTREE,
38
+ UNIQUE KEY `path` (`path`) USING BTREE
39
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
40
+
41
+ CREATE TABLE IF NOT EXISTS `cms_pages_meta` (
42
+ `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
43
+ `page_id` INT(10) UNSIGNED NOT NULL,
44
+ `layout` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
45
+ `publish_date` TIMESTAMP NULL,
46
+ `expire_date` TIMESTAMP NULL,
47
+ `author_id` INT(10) UNSIGNED NULL,
48
+ `created_at` TIMESTAMP NOT NULL DEFAULT (NOW()),
49
+ `updated_at` TIMESTAMP NOT NULL DEFAULT (NOW()) ON UPDATE CURRENT_TIMESTAMP,
50
+ PRIMARY KEY (`id`) USING BTREE,
51
+ KEY `page_id` (`page_id`) USING BTREE
52
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
53
+
54
+ CREATE TABLE IF NOT EXISTS `cms_pages` (
55
+ `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
56
+ `title` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
57
+ `content` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
58
+ `markdown` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
59
+ `json_data` JSON NULL,
60
+ `template` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
61
+ `created_at` TIMESTAMP NOT NULL DEFAULT (NOW()),
62
+ `updated_at` TIMESTAMP NOT NULL DEFAULT (NOW()) ON UPDATE CURRENT_TIMESTAMP,
63
+ PRIMARY KEY (`id`) USING BTREE
64
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
65
+
66
+ CREATE TABLE IF NOT EXISTS `entities_meta` (
67
+ `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
68
+ `entity_name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
69
+ `entity_id` INT(10) UNSIGNED NOT NULL,
70
+ `meta_key` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
71
+ `meta_value` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
72
+ `created_at` TIMESTAMP NOT NULL DEFAULT (NOW()),
73
+ `updated_at` TIMESTAMP NOT NULL DEFAULT (NOW()) ON UPDATE CURRENT_TIMESTAMP,
74
+ PRIMARY KEY (`id`) USING BTREE,
75
+ UNIQUE KEY `entity_meta` (`entity_name`, `entity_id`, `meta_key`) USING BTREE
76
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
77
+
78
+ -- Create a default homepage route if not exists
79
+ INSERT IGNORE INTO `cms_pages` (`id`, `title`, `content`, `template`, `created_at`) VALUES
80
+ (1, 'Home', '<h1>Welcome to Reldens CMS</h1><p>This is your homepage. Edit this content in the admin panel.</p>', 'page', NOW());
81
+
82
+ -- Create a default route to the homepage
83
+ INSERT IGNORE INTO `routes` (`path`, `router`, `content_id`, `title`, `meta_description`, `status`, `created_at`) VALUES
84
+ ('/home', 'cms_pages', 1, 'Home', 'Welcome to Reldens CMS', 'published', NOW());
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@reldens/cms",
3
+ "scope": "@reldens",
4
+ "version": "0.1.0",
5
+ "description": "Reldens - CMS",
6
+ "author": "Damian A. Pastorini",
7
+ "license": "MIT",
8
+ "homepage": "https://github.com/damian-pastorini/reldens-cms",
9
+ "source": true,
10
+ "engines": {
11
+ "node": ">=18.0.0"
12
+ },
13
+ "main": "index.js",
14
+ "bin": {
15
+ "reldens-cms": "./bin/reldens-cms"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/damian-pastorini/reldens-cms.git"
20
+ },
21
+ "keywords": [
22
+ "reldens",
23
+ "cms",
24
+ "system",
25
+ "content",
26
+ "management",
27
+ "dwd",
28
+ "nodejs",
29
+ "platform",
30
+ "framework"
31
+ ],
32
+ "bugs": {
33
+ "url": "https://github.com/damian-pastorini/reldens-cms/issues"
34
+ },
35
+ "dependencies": {
36
+ "@reldens/server-utils": "^0.16.0",
37
+ "@reldens/storage": "^0.34.0",
38
+ "@reldens/utils": "^0.47.0",
39
+ "dotenv": "^16.0.3",
40
+ "mustache": "^4.2.0"
41
+ }
42
+ }
@@ -0,0 +1,16 @@
1
+ # Database Configuration
2
+ RELDENS_CMS_DB_CLIENT={{dbClient}}
3
+ RELDENS_CMS_DB_HOST={{dbHost}}
4
+ RELDENS_CMS_DB_PORT={{dbPort}}
5
+ RELDENS_CMS_DB_NAME={{dbName}}
6
+ RELDENS_CMS_DB_USER={{dbUser}}
7
+ RELDENS_CMS_DB_PASSWORD={{dbPassword}}
8
+ RELDENS_CMS_DB_DRIVER={{dbDriver}}
9
+
10
+ # Admin Panel Configuration
11
+ RELDENS_CMS_ADMIN_PATH={{adminPath}}
12
+ RELDENS_CMS_ADMIN_SECRET={{adminSecret}}
13
+
14
+ # Server Configuration
15
+ RELDENS_CMS_HOST={{host}}
16
+ RELDENS_CMS_PORT={{port}}
@@ -0,0 +1,30 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>404 - Page Not Found</title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
6
+ <link rel="stylesheet" href="/css/styles.css">
7
+ <script src="/js/scripts.js"></script>
8
+ </head>
9
+ <body>
10
+ <header>
11
+ <div class="container">
12
+ <h1>Reldens CMS</h1>
13
+ <nav>
14
+ <a href="/">Home</a>
15
+ </nav>
16
+ </div>
17
+ </header>
18
+ <div class="container">
19
+ <main class="error-container">
20
+ <h1>404 - Page Not Found</h1>
21
+ <p>The page you are looking for might have been removed, had its name changed, or is temporarily unavailable.</p>
22
+ <a href="/" class="button">Return to Homepage</a>
23
+ </main>
24
+ </div>
25
+ <footer>
26
+ <p>&copy; {{current_year}} - Built with Reldens CMS</p>
27
+ </footer>
28
+ </body>
29
+ </html>
30
+