@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/LICENSE +21 -0
- package/README.md +125 -0
- package/bin/reldens-cms.js +21 -0
- package/index.js +15 -0
- package/install/css/installer.css +117 -0
- package/install/index.html +100 -0
- package/install/js/installer.js +32 -0
- package/lib/admin-dist-helper.js +56 -0
- package/lib/admin-entities-generator.js +32 -0
- package/lib/admin-manager-validator.js +37 -0
- package/lib/admin-manager.js +1059 -0
- package/lib/admin-translations.js +262 -0
- package/lib/installer.js +283 -0
- package/lib/manager.js +171 -0
- package/lib/storefront.js +228 -0
- package/lib/templates-list.js +50 -0
- package/migrations/default-user.sql +11 -0
- package/migrations/install.sql +84 -0
- package/package.json +42 -0
- package/templates/.env.dist +16 -0
- package/templates/404.html +30 -0
- package/templates/css/styles.css +102 -0
- package/templates/js/scripts.js +12 -0
- package/templates/page.html +38 -0
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>© {{current_year}} - Built with Reldens CMS</p>
|
|
27
|
+
</footer>
|
|
28
|
+
</body>
|
|
29
|
+
</html>
|
|
30
|
+
|