@reldens/cms 0.7.0 → 0.8.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.
@@ -86,7 +86,6 @@
86
86
  }
87
87
 
88
88
  & .button {
89
-
90
89
  padding: 0.5rem 1rem;
91
90
  border: none;
92
91
  border-radius: 4px;
@@ -94,44 +93,44 @@
94
93
  cursor: pointer;
95
94
  text-decoration: none;
96
95
 
97
- &-primary {
98
- color: var(--white);
99
- background-color: var(--lightBlue);
100
-
101
- &:hover {
102
- background-color: var(--lightBlue);
103
- }
96
+ &:disabled {
97
+ background-color: var(--grey) !important;
104
98
  }
99
+ }
105
100
 
106
- &-secondary {
107
- color: var(--white);
108
- background-color: var(--grey);
101
+ & .button-primary {
102
+ color: var(--white);
103
+ background-color: var(--lightBlue);
109
104
 
110
- &:hover {
111
- background-color: var(--grey);
112
- }
105
+ &:hover {
106
+ background-color: var(--lightBlue);
113
107
  }
108
+ }
114
109
 
115
- &-warning {
116
- color: var(--white);
117
- background-color: var(--orange);
110
+ & .button-secondary {
111
+ color: var(--white);
112
+ background-color: var(--grey);
118
113
 
119
- &:hover {
120
- background-color: var(--orange);
121
- }
114
+ &:hover {
115
+ background-color: var(--grey);
122
116
  }
117
+ }
123
118
 
124
- &-danger {
125
- color: var(--white);
126
- background-color: var(--red);
119
+ & .button-warning {
120
+ color: var(--white);
121
+ background-color: var(--orange);
127
122
 
128
- &:hover {
129
- background-color: var(--red);
130
- }
123
+ &:hover {
124
+ background-color: var(--orange);
131
125
  }
126
+ }
132
127
 
133
- &:disabled {
134
- background-color: var(--grey) !important;
128
+ & .button-danger {
129
+ color: var(--white);
130
+ background-color: var(--red);
131
+
132
+ &:hover {
133
+ background-color: var(--red);
135
134
  }
136
135
  }
137
136
 
@@ -556,7 +555,7 @@
556
555
  padding: 0.5rem;
557
556
  border: 1px solid #ccc;
558
557
  border-radius: 4px;
559
- min-width: 150px; /* Minimum width for input fields */
558
+ min-width: 150px;
560
559
  }
561
560
  }
562
561
 
@@ -17,7 +17,7 @@
17
17
  <div class="forms-container">
18
18
  <div class="row">
19
19
  <form name="install-form" id="install-form" class="install-form" action="/install" method="post">
20
- <h3 class="form-title">- Server Configuration -</h3>
20
+ <h3 class="form-title">Server Configuration</h3>
21
21
  <div class="input-box app-host">
22
22
  <label for="app-host">Host</label>
23
23
  <input type="text" name="app-host" id="app-host" value="{{app-host}}" class="required" required/>
@@ -30,10 +30,14 @@
30
30
  <label for="app-admin-path">Admin Panel Path</label>
31
31
  <input type="text" name="app-admin-path" id="app-admin-path" value="{{app-admin-path}}"/>
32
32
  </div>
33
+ <div class="input-box app-admin-secret">
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}}"/>
36
+ </div>
33
37
  <div class="input-box app-error">
34
38
  <p class="error installation-process-failed">There was an error during the installation process.</p>
35
39
  </div>
36
- <h3 class="form-title">- Database Configuration -</h3>
40
+ <h3 class="form-title">Database Configuration</h3>
37
41
  <div class="input-box db-storage-driver">
38
42
  <label for="db-storage-driver">Storage Driver</label>
39
43
  <select name="db-storage-driver" id="db-storage-driver" class="required" required>
@@ -72,6 +76,23 @@
72
76
  <p class="error sql-file-not-found">SQL installation file not found.</p>
73
77
  <p class="error db-installation-process-failed">There was an error during the installation process.</p>
74
78
  </div>
79
+ <h3 class="form-title">Installation Options</h3>
80
+ <div class="input-box install-cms-tables">
81
+ <input type="checkbox" name="install-cms-tables" id="install-cms-tables" checked/>
82
+ <label for="install-cms-tables">Install default CMS pages tables</label>
83
+ </div>
84
+ <div class="input-box install-user-auth">
85
+ <input type="checkbox" name="install-user-auth" id="install-user-auth" checked/>
86
+ <label for="install-user-auth">Install users authentication</label>
87
+ </div>
88
+ <div class="input-box install-default-user">
89
+ <input type="checkbox" name="install-default-user" id="install-default-user" checked/>
90
+ <label for="install-default-user">Install default admin user (root@cms-admin.com / root)</label>
91
+ </div>
92
+ <div class="input-box install-default-homepage">
93
+ <input type="checkbox" name="install-default-homepage" id="install-default-homepage" checked/>
94
+ <label for="install-default-homepage">Install default homepage</label>
95
+ </div>
75
96
  <div class="input-box submit-container">
76
97
  <img class="install-loading hidden" src="/install-assets/img/loading.gif"/>
77
98
  <input id="install-submit-button" type="submit" value="Install"/>
@@ -17,6 +17,12 @@
17
17
  <div class="forms-container">
18
18
  <div class="row">
19
19
  Installation completed!
20
+ <p>
21
+ Default data<br/>
22
+ username: root
23
+ email: root@cms-admin.com
24
+ password: root
25
+ </p>
20
26
  </div>
21
27
  </div>
22
28
  </div>
@@ -380,6 +380,9 @@ class AdminManager
380
380
  this.adminRouter = this.applicationFramework.Router();
381
381
  // apply session middleware only to /admin routes:
382
382
  if(this.session){
383
+ if(!this.secret){
384
+ Logger.warning('Admin Manager "secret" key was not provided.');
385
+ }
383
386
  this.adminRouter.use(this.session({secret: this.secret, resave: false, saveUninitialized: true}));
384
387
  }
385
388
  this.adminRouter.use(this.bodyParser.json());
@@ -636,6 +639,26 @@ class AdminManager
636
639
  return fileNames.join(property.isArray);
637
640
  }
638
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
+
639
662
  async generateEditRouteContent(req, driverResource, entityPath)
640
663
  {
641
664
  let idProperty = this.fetchEntityIdPropertyKey(driverResource);
@@ -660,6 +683,7 @@ class AdminManager
660
683
  if(resourceProperty.isUpload && loadedEntity){
661
684
  isRequired = '';
662
685
  }
686
+ let inputType = this.getInputType(resourceProperty, fieldDisabled);
663
687
  renderedEditProperties[propertyKey] = await this.render(
664
688
  this.adminFilesContents.fields.edit[this.propertyType(resourceProperty, 'edit')],
665
689
  {
@@ -667,11 +691,13 @@ class AdminManager
667
691
  fieldValue: await this.generatePropertyEditRenderedValue(
668
692
  loadedEntity,
669
693
  propertyKey,
670
- resourceProperty
694
+ resourceProperty,
695
+ fieldDisabled
671
696
  ),
672
697
  fieldDisabled: fieldDisabled ? ' disabled="disabled"' : '',
673
698
  required: isRequired,
674
- multiple: resourceProperty.isArray ? ' multiple="multiple"' : ''
699
+ multiple: resourceProperty.isArray ? ' multiple="multiple"' : '',
700
+ inputType
675
701
  }
676
702
  );
677
703
  }
@@ -679,6 +705,14 @@ class AdminManager
679
705
  return await this.renderRoute(renderedView, this.adminContents.sideBar);
680
706
  }
681
707
 
708
+ getInputType(resourceProperty, fieldDisabled)
709
+ {
710
+ if('datetime' === resourceProperty.type && !fieldDisabled){
711
+ return 'datetime-local';
712
+ }
713
+ return 'text';
714
+ }
715
+
682
716
  async loadEntityById(driverResource, id)
683
717
  {
684
718
  let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
@@ -936,13 +970,16 @@ class AdminManager
936
970
  return {fieldValue, fieldName};
937
971
  }
938
972
 
939
- async generatePropertyEditRenderedValue(entity, propertyKey, resourceProperty)
973
+ async generatePropertyEditRenderedValue(entity, propertyKey, resourceProperty, isFieldDisabled)
940
974
  {
941
975
  let entityPropertyValue = sc.get(entity, propertyKey, null);
942
976
  let fieldValue = (0 === entityPropertyValue ? '0' : entityPropertyValue || '').toString();
943
977
  if('boolean' === resourceProperty.type){
944
978
  fieldValue = '1' === fieldValue || 'true' === fieldValue ? ' checked="checked"' : '';
945
979
  }
980
+ if('datetime' === resourceProperty.type){
981
+ fieldValue = this.formatDateTimeForEdit(entityPropertyValue, isFieldDisabled);
982
+ }
946
983
  if('reference' === resourceProperty.type){
947
984
  let relationDriverResource = this.resourcesByReference[resourceProperty.reference];
948
985
  let relation = this.relations[resourceProperty.reference];
@@ -4,7 +4,6 @@
4
4
  *
5
5
  */
6
6
 
7
- const { LoadedEntitiesProcessor } = require('./loaded-entities-processor');
8
7
  const { FileHandler } = require('@reldens/server-utils');
9
8
  const { Logger, sc } = require('@reldens/utils');
10
9
 
@@ -30,7 +29,7 @@ class EntitiesLoader
30
29
  }
31
30
  try {
32
31
  let {rawRegisteredEntities, entitiesConfig, entitiesTranslations} = require(entitiesPath);
33
- return LoadedEntitiesProcessor.process(rawRegisteredEntities, entitiesTranslations, entitiesConfig);
32
+ return {rawRegisteredEntities, entitiesTranslations, entitiesConfig};
34
33
  } catch(error){
35
34
  Logger.error('Failed to load generated entities: ' + error.message);
36
35
  }
package/lib/frontend.js CHANGED
@@ -64,12 +64,14 @@ class Frontend
64
64
  try {
65
65
  let route = await this.findRouteByPath(path);
66
66
  if(route){
67
+ Logger.debug('Found route for path: '+path, route);
67
68
  return await this.renderContentFromRoute(res, route);
68
69
  }
69
70
  let pathSegments = path.split('/').filter(segment => segment !== '');
70
71
  if(0 < pathSegments.length){
71
72
  let entityResult = await this.findEntityByPath(pathSegments);
72
73
  if(entityResult){
74
+ Logger.debug('Found entity for path segments: '+pathSegments.join('/'));
73
75
  return await this.renderContentFromEntity(
74
76
  res,
75
77
  entityResult.entity,
@@ -79,8 +81,10 @@ class Frontend
79
81
  }
80
82
  let templatePath = this.findTemplateByPath(path);
81
83
  if(templatePath){
84
+ Logger.debug('Found template for path: '+path+' at: '+templatePath);
82
85
  return await this.renderTemplateOnly(res, templatePath);
83
86
  }
87
+ Logger.debug('No template found for path: '+path+', rendering 404');
84
88
  return await this.renderNotFoundPage(res);
85
89
  } catch (error) {
86
90
  Logger.error('Request handling error: '+error.message);
@@ -90,32 +94,41 @@ class Frontend
90
94
 
91
95
  async findRouteByPath(path)
92
96
  {
93
- if('/' === path){
94
- path = '/home';
95
- }
96
97
  let routesEntity = this.dataServer.getEntity('routes');
97
98
  if(!routesEntity){
99
+ Logger.error('Routes entity not found in dataServer');
98
100
  return false;
99
101
  }
100
- return await routesEntity.loadOneBy('path', path);
102
+ let route = await routesEntity.loadOneBy('path', path);
103
+ if(route){
104
+ return route;
105
+ }
106
+ if('/' === path){
107
+ return await routesEntity.loadOneBy('path', '/home');
108
+ }
109
+ return false;
101
110
  }
102
111
 
103
112
  async findEntityByPath(pathSegments)
104
113
  {
105
114
  if(1 > pathSegments.length){
115
+ Logger.debug('No path segments provided');
106
116
  return false;
107
117
  }
108
118
  let entityName = pathSegments[0];
109
119
  let entityId = 2 > pathSegments.length ? false : pathSegments[1];
110
120
  if(!entityId){
121
+ Logger.debug('No entity ID in path segments');
111
122
  return false;
112
123
  }
113
124
  let entity = this.dataServer.getEntity(entityName);
114
125
  if(!entity){
126
+ Logger.debug('Entity not found: '+entityName);
115
127
  return false;
116
128
  }
117
129
  let loadedEntity = await entity.loadById(entityId);
118
130
  if(!loadedEntity){
131
+ Logger.debug('Entity not loaded by ID: '+entityId);
119
132
  return false;
120
133
  }
121
134
  return {
@@ -145,14 +158,17 @@ class Frontend
145
158
  async renderContentFromRoute(res, route)
146
159
  {
147
160
  if(!route.router || !route.content_id){
161
+ Logger.debug('Route missing router or content_id');
148
162
  return await this.renderNotFoundPage(res);
149
163
  }
150
164
  let entity = this.dataServer.getEntity(route.router);
151
165
  if(!entity){
166
+ Logger.debug('Entity not found: '+route.router);
152
167
  return await this.renderNotFoundPage(res);
153
168
  }
154
169
  let content = await entity.loadById(route.content_id);
155
170
  if(!content){
171
+ Logger.debug('Content not found for ID: '+route.content_id+' in entity: '+route.router);
156
172
  return await this.renderNotFoundPage(res);
157
173
  }
158
174
  let templateName = content.template || route.router;
@@ -160,6 +176,7 @@ class Frontend
160
176
  if(!FileHandler.exists(templatePath)){
161
177
  templatePath = FileHandler.joinPaths(this.templatesPath, 'page.html');
162
178
  if(!FileHandler.exists(templatePath)){
179
+ Logger.debug('Neither template found: '+templateName+'.html nor page.html');
163
180
  return await this.renderNotFoundPage(res);
164
181
  }
165
182
  }
package/lib/installer.js CHANGED
@@ -7,7 +7,6 @@
7
7
  const { FileHandler, Encryptor } = require('@reldens/server-utils');
8
8
  const { DriversMap, EntitiesGenerator, PrismaSchemaGenerator } = require('@reldens/storage');
9
9
  const { EntitiesLoader } = require('./entities-loader');
10
- const { AdminEntitiesGenerator } = require('./admin-entities-generator');
11
10
  const { Logger, sc } = require('@reldens/utils');
12
11
  const mustache = require('mustache');
13
12
 
@@ -104,10 +103,11 @@ class Installer
104
103
  'connection-failed': 'Database connection failed. Please check your credentials.',
105
104
  'raw-query-not-found': 'Query method not found in driver.',
106
105
  'sql-file-not-found': 'SQL installation file not found.',
107
- 'sql-tables-creation-failed': 'Failed to create database tables.',
106
+ 'sql-cms-tables-creation-failed': 'Failed to create CMS tables.',
107
+ 'sql-user-auth-creation-failed': 'Failed to create user authentication tables.',
108
108
  'sql-default-user-error': 'Failed to create default user.',
109
+ 'sql-default-homepage-error': 'Failed to create default homepage.',
109
110
  'installation-entities-generation-failed': 'Failed to generate entities.',
110
- 'installation-process-failed': 'Installation process failed.',
111
111
  'installation-entities-callback-failed': 'Failed to process entities for callback.',
112
112
  'configuration-error': 'Configuration error while completing installation.',
113
113
  'already-installed': 'The application is already installed.'
@@ -157,39 +157,32 @@ class Installer
157
157
  Logger.error('Method "rawQuery" not found.');
158
158
  return res.redirect('/?error=raw-query-not-found');
159
159
  }
160
- let installSqlPath = FileHandler.joinPaths(this.migrationsPath, 'install.sql');
161
- if(!FileHandler.exists(installSqlPath)){
162
- Logger.error('SQL installation file not found.');
163
- return res.redirect('/?error=sql-file-not-found');
164
- }
165
- let queryTablesResult = await this.executeQueryFile(dbDriver, installSqlPath);
166
- if(!queryTablesResult){
167
- Logger.error('Tables creation failed.');
168
- return res.redirect('/?error=sql-tables-creation-failed');
169
- }
170
- Logger.info('Installed tables.');
171
- let defaultUserSqlPath = FileHandler.joinPaths(this.migrationsPath, 'default-user.sql');
172
- try {
173
- if(FileHandler.exists(defaultUserSqlPath)){
174
- let queryUserResult = await this.executeQueryFile(dbDriver, defaultUserSqlPath);
175
- if(!queryUserResult){
176
- Logger.error('Default user creation failed.', queryUserResult);
177
- return res.redirect('/?error=sql-default-user-error');
178
- }
179
- Logger.info('Created default user.');
180
- }
181
- let entitiesGenerationResult = await this.generateEntities(dbDriver);
182
- if(!entitiesGenerationResult){
183
- Logger.error('Entities generation error.');
184
- return res.redirect('/?error=installation-entities-generation-failed');
160
+ let executeFiles = {
161
+ 'install-cms-tables': 'install.sql',
162
+ 'install-user-auth': 'users-authentication.sql',
163
+ 'install-default-user': 'default-user.sql',
164
+ 'install-default-homepage': 'default-homepage.sql'
165
+ };
166
+ for(let checkboxName of Object.keys(executeFiles)){
167
+ let fileName = executeFiles[checkboxName];
168
+ let redirectError = await this.executeQueryFile(
169
+ sc.get(templateVariables, checkboxName, 'off'),
170
+ fileName,
171
+ dbDriver
172
+ );
173
+ if('' !== redirectError){
174
+ return res.redirect(redirectError);
185
175
  }
186
- Logger.info('Generated entities.');
187
- } catch (error) {
188
- Logger.error('Installation error: '+error.message);
189
- return res.redirect('/?error=installation-process-failed');
190
176
  }
177
+ let entitiesGenerationResult = await this.generateEntities(dbDriver);
178
+ if(!entitiesGenerationResult){
179
+ Logger.error('Entities generation error.');
180
+ return res.redirect('/?error=installation-entities-generation-failed');
181
+ }
182
+ Logger.info('Generated entities.');
191
183
  try {
192
- await this.createEnvFile(templateVariables);
184
+ let mappedVariablesForConfig = this.mapVariablesForConfig(templateVariables);
185
+ await this.createEnvFile(this.mapVariablesForTemplate(mappedVariablesForConfig));
193
186
  await this.prepareProjectDirectories();
194
187
  await this.copyAdminDirectory();
195
188
  await this.createIndexJsFile(templateVariables);
@@ -198,7 +191,10 @@ class Installer
198
191
  await this.appServer.close();
199
192
  }
200
193
  Logger.debug('Running postInstallCallback.');
201
- await this.postInstallCallback(this.entitiesLoader.loadEntities(selectedDriver));
194
+ await this.postInstallCallback({
195
+ loadedEntities: this.entitiesLoader.loadEntities(selectedDriver),
196
+ mappedVariablesForConfig
197
+ });
202
198
  }
203
199
  await this.createLockFile();
204
200
  Logger.info('Installation successful!');
@@ -214,13 +210,23 @@ class Installer
214
210
  }
215
211
  }
216
212
 
217
- async executeQueryFile(dbDriver, filePath)
213
+ async executeQueryFile(isMarked, fileName, dbDriver)
218
214
  {
219
- let sqlContent = FileHandler.readFile(filePath);
220
- if(!sqlContent){
221
- throw new Error('Could not read SQL file: '+filePath);
215
+ if('on' !== isMarked){
216
+ return '';
222
217
  }
223
- return await dbDriver.rawQuery(sqlContent.toString());
218
+ let sqlFileContent = FileHandler.readFile(FileHandler.joinPaths(this.migrationsPath, fileName));
219
+ if(!sqlFileContent){
220
+ Logger.error('SQL file "'+fileName+'" not found.');
221
+ return '/?error=sql-file-not-found&file-name='+fileName;
222
+ }
223
+ let queryResult = await dbDriver.rawQuery(sqlFileContent.toString());
224
+ if(!queryResult){
225
+ Logger.error('SQL file "'+fileName+'" raw execution failed.');
226
+ return '/?error=sql-file-execution-error&file-name='+fileName;
227
+ }
228
+ Logger.info('SQL file "'+fileName+'" raw execution successfully.');
229
+ return '';
224
230
  }
225
231
 
226
232
  async generateEntities(server)
@@ -252,25 +258,48 @@ class Installer
252
258
  async createEnvFile(templateVariables)
253
259
  {
254
260
  let envTemplatePath = FileHandler.joinPaths(this.defaultTemplatesPath, '.env.dist');
255
- if(!FileHandler.exists(envTemplatePath)){
256
- Logger.error('ENV template not found: '+envTemplatePath);
261
+ let envTemplateContent = FileHandler.readFile(envTemplatePath);
262
+ if(!envTemplateContent){
263
+ Logger.error('Template ".env.dist" not found: '+envTemplatePath);
257
264
  return false;
258
265
  }
259
- let envTemplate = FileHandler.readFile(envTemplatePath);
260
- let envContent = mustache.render(envTemplate, {
261
- dbClient: templateVariables['db-client'],
262
- dbHost: templateVariables['db-host'],
263
- dbPort: templateVariables['db-port'],
264
- dbName: templateVariables['db-name'],
265
- dbUser: templateVariables['db-username'],
266
- dbPassword: templateVariables['db-password'],
267
- dbDriver: templateVariables['db-storage-driver'],
268
- adminPath: templateVariables['app-admin-path'],
269
- adminSecret: Encryptor.generateSecretKey(),
270
- host: templateVariables['app-host'],
271
- port: templateVariables['app-port']
272
- });
273
- return FileHandler.writeFile(this.envFilePath, envContent);
266
+ return FileHandler.writeFile(this.envFilePath, mustache.render(envTemplateContent, templateVariables));
267
+ }
268
+
269
+ mapVariablesForTemplate(configVariables)
270
+ {
271
+ return {
272
+ host: configVariables.host,
273
+ port: configVariables.port,
274
+ adminPath: configVariables.adminPath,
275
+ adminSecret: configVariables.adminSecret,
276
+ dbClient: configVariables.database.client,
277
+ dbHost: configVariables.database.host,
278
+ dbPort: configVariables.database.port,
279
+ dbName: configVariables.database.name,
280
+ dbUser: configVariables.database.user,
281
+ dbPassword: configVariables.database.password,
282
+ dbDriver: configVariables.database.driver
283
+ };
284
+ }
285
+
286
+ mapVariablesForConfig(templateVariables)
287
+ {
288
+ return {
289
+ host: sc.get(templateVariables, 'app-host', 'http://localhost'),
290
+ port: Number(sc.get(templateVariables, 'app-port', 8080)),
291
+ adminPath: sc.get(templateVariables, 'app-admin-path', '/reldens-admin'),
292
+ adminSecret: sc.get(templateVariables, 'app-admin-secret', Encryptor.generateSecretKey()),
293
+ database: {
294
+ client: sc.get(templateVariables, 'db-client', 'mysql'),
295
+ host: sc.get(templateVariables, 'db-host', 'localhost'),
296
+ port: Number(sc.get(templateVariables, 'db-port', 3306)),
297
+ name: sc.get(templateVariables, 'db-name', 'reldens_cms'),
298
+ user: sc.get(templateVariables, 'db-username', ''),
299
+ password: sc.get(templateVariables, 'db-password', ''),
300
+ driver: sc.get(templateVariables, 'db-storage-driver', 'prisma')
301
+ }
302
+ };
274
303
  }
275
304
 
276
305
  async createIndexJsFile(templateVariables)
@@ -328,18 +357,20 @@ class Installer
328
357
  FileHandler.createFolder(this.projectPublicAssetsPath);
329
358
  FileHandler.createFolder(this.projectCssPath);
330
359
  FileHandler.createFolder(this.projectJsPath);
331
- FileHandler.copyFile(
332
- FileHandler.joinPaths(this.defaultTemplatesPath, 'page.html'),
333
- FileHandler.joinPaths(this.projectTemplatesPath, 'page.html')
334
- );
335
- FileHandler.copyFile(
336
- FileHandler.joinPaths(this.defaultTemplatesPath, '404.html'),
337
- FileHandler.joinPaths(this.projectTemplatesPath, '404.html')
338
- );
339
- FileHandler.copyFile(
340
- FileHandler.joinPaths(this.defaultTemplatesPath, 'layout.html'),
341
- FileHandler.joinPaths(this.projectTemplatesPath, 'layout.html')
342
- );
360
+ let baseFiles = [
361
+ 'page.html',
362
+ '404.html',
363
+ 'layout.html',
364
+ 'browserconfig.xml',
365
+ 'favicon.ico',
366
+ 'site.webmanifest'
367
+ ];
368
+ for(let fileName of baseFiles){
369
+ FileHandler.copyFile(
370
+ FileHandler.joinPaths(this.defaultTemplatesPath, fileName),
371
+ FileHandler.joinPaths(this.projectTemplatesPath, fileName)
372
+ );
373
+ }
343
374
  FileHandler.copyFile(
344
375
  FileHandler.joinPaths(this.defaultTemplatesPath, 'css', 'styles.css'),
345
376
  FileHandler.joinPaths(this.projectCssPath, 'styles.css')
@@ -354,16 +385,16 @@ class Installer
354
385
  fetchDefaults()
355
386
  {
356
387
  return {
357
- 'app-host': process.env.RELDENS_CMS_HOST || 'http://localhost',
358
- 'app-port': process.env.RELDENS_CMS_PORT || '8000',
359
- 'app-admin-path': process.env.RELDENS_CMS_ADMIN_PATH || '/reldens-admin',
360
- 'db-storage-driver': 'prisma',
361
- 'db-client': process.env.RELDENS_CMS_DB_CLIENT || 'mysql',
362
- 'db-host': process.env.RELDENS_CMS_DB_HOST || 'localhost',
363
- 'db-port': process.env.RELDENS_CMS_DB_PORT || '3306',
364
- 'db-name': process.env.RELDENS_CMS_DB_NAME || 'reldens_cms',
365
- 'db-username': process.env.RELDENS_CMS_DB_USER || '',
366
- 'db-password': process.env.RELDENS_CMS_DB_PASSWORD || ''
388
+ 'app-host': process.env.RELDENS_APP_HOST || 'http://localhost',
389
+ 'app-port': process.env.RELDENS_APP_PORT || '8080',
390
+ 'app-admin-path': process.env.RELDENS_ADMIN_ROUTE_PATH || '/reldens-admin',
391
+ 'db-storage-driver': process.env.RELDENS_STORAGE_DRIVER || 'prisma',
392
+ 'db-client': process.env.RELDENS_DB_CLIENT || 'mysql',
393
+ 'db-host': process.env.RELDENS_DB_HOST || 'localhost',
394
+ 'db-port': process.env.RELDENS_DB_PORT || '3306',
395
+ 'db-name': process.env.RELDENS_DB_NAME || 'reldens_cms',
396
+ 'db-username': process.env.RELDENS_DB_USER || '',
397
+ 'db-password': process.env.RELDENS_DB_PASSWORD || ''
367
398
  };
368
399
  }
369
400
  }
package/lib/manager.js CHANGED
@@ -51,35 +51,98 @@ class Manager
51
51
  this.companyName = sc.get(props, 'companyName', 'Reldens - CMS');
52
52
  this.logo = sc.get(props, 'logo', '/assets/web/reldens-your-logo-mage.png');
53
53
  this.favicon = sc.get(props, 'favicon', '/assets/web/favicon.ico');
54
+ this.app = sc.get(props, 'app', false);
55
+ this.appServer = sc.get(props, 'appServer', false);
56
+ this.dataServer = sc.get(props, 'dataServer', false);
57
+ this.adminManager = sc.get(props, 'adminManager', false);
58
+ this.frontend = sc.get(props, 'frontend', false);
54
59
  this.appServerFactory = new AppServerFactory();
55
60
  this.adminEntitiesGenerator = new AdminEntitiesGenerator();
56
61
  this.installer = new Installer({
57
62
  projectRoot: this.projectRoot,
58
63
  postInstallCallback: this.initializeCmsAfterInstall.bind(this)
59
64
  });
60
- this.dataServer = false;
61
- this.app = false;
62
- this.appServer = false;
63
- this.adminManager = false;
64
- this.frontend = false;
65
+ this.useProvidedServer = this.validateProvidedServer();
66
+ this.useProvidedDataServer = this.validateProvidedDataServer();
67
+ this.useProvidedAdminManager = this.validateProvidedAdminManager();
68
+ this.useProvidedFrontend = this.validateProvidedFrontend();
69
+ }
70
+
71
+ validateProvidedServer()
72
+ {
73
+ if(!this.app){
74
+ return false;
75
+ }
76
+ if(!this.appServer){
77
+ return false;
78
+ }
79
+ if('function' !== typeof this.app.use){
80
+ Logger.critical('Invalid app instance provided - missing use method.');
81
+ return false;
82
+ }
83
+ if('function' !== typeof this.appServer.listen){
84
+ Logger.critical('Invalid appServer instance provided - missing listen method.');
85
+ return false;
86
+ }
87
+ return true;
88
+ }
89
+
90
+ validateProvidedDataServer()
91
+ {
92
+ if(!this.dataServer){
93
+ return false;
94
+ }
95
+ if('function' !== typeof this.dataServer.connect){
96
+ Logger.critical('Invalid dataServer instance provided - missing connect method.');
97
+ return false;
98
+ }
99
+ if('function' !== typeof this.dataServer.generateEntities){
100
+ Logger.critical('Invalid dataServer instance provided - missing generateEntities method.');
101
+ return false;
102
+ }
103
+ return true;
104
+ }
105
+
106
+ validateProvidedAdminManager()
107
+ {
108
+ if(!this.adminManager){
109
+ return false;
110
+ }
111
+ if('function' !== typeof this.adminManager.setupAdmin){
112
+ Logger.critical('Invalid adminManager instance provided - missing setupAdmin method.');
113
+ return false;
114
+ }
115
+ return true;
116
+ }
117
+
118
+ validateProvidedFrontend()
119
+ {
120
+ if(!this.frontend){
121
+ return false;
122
+ }
123
+ if('function' !== typeof this.frontend.initialize){
124
+ Logger.critical('Invalid frontend instance provided - missing initialize method.');
125
+ return false;
126
+ }
127
+ return true;
65
128
  }
66
129
 
67
130
  loadConfigFromEnv()
68
131
  {
69
132
  let envVars = process.env;
70
133
  return {
71
- host: sc.get(envVars, 'RELDENS_CMS_HOST', 'http://localhost'),
72
- port: Number(sc.get(envVars, 'RELDENS_CMS_PORT', 8000)),
73
- adminPath: sc.get(envVars, 'RELDENS_CMS_ADMIN_PATH', '/reldens-admin'),
74
- adminSecret: sc.get(envVars, 'RELDENS_CMS_ADMIN_SECRET', ''),
134
+ host: sc.get(envVars, 'RELDENS_APP_HOST', 'http://localhost'),
135
+ port: Number(sc.get(envVars, 'RELDENS_APP_PORT', 8080)),
136
+ adminPath: sc.get(envVars, 'RELDENS_ADMIN_ROUTE_PATH', '/reldens-admin'),
137
+ adminSecret: sc.get(envVars, 'RELDENS_ADMIN_SECRET', ''),
75
138
  database: {
76
- client: sc.get(envVars, 'RELDENS_CMS_DB_CLIENT', 'mysql'),
77
- host: sc.get(envVars, 'RELDENS_CMS_DB_HOST', 'localhost'),
78
- port: Number(sc.get(envVars, 'RELDENS_CMS_DB_PORT', 3306)),
79
- name: sc.get(envVars, 'RELDENS_CMS_DB_NAME', 'reldens_cms'),
80
- user: sc.get(envVars, 'RELDENS_CMS_DB_USER', ''),
81
- password: sc.get(envVars, 'RELDENS_CMS_DB_PASSWORD', ''),
82
- driver: sc.get(envVars, 'RELDENS_CMS_DB_DRIVER', 'prisma')
139
+ client: sc.get(envVars, 'RELDENS_DB_CLIENT', 'mysql'),
140
+ host: sc.get(envVars, 'RELDENS_DB_HOST', 'localhost'),
141
+ port: Number(sc.get(envVars, 'RELDENS_DB_PORT', 3306)),
142
+ name: sc.get(envVars, 'RELDENS_DB_NAME', 'reldens_cms'),
143
+ user: sc.get(envVars, 'RELDENS_DB_USER', ''),
144
+ password: sc.get(envVars, 'RELDENS_DB_PASSWORD', ''),
145
+ driver: sc.get(envVars, 'RELDENS_STORAGE_DRIVER', 'prisma')
83
146
  }
84
147
  };
85
148
  }
@@ -91,17 +154,21 @@ class Manager
91
154
 
92
155
  async start()
93
156
  {
94
- let createdAppServer = this.appServerFactory.createAppServer();
95
- if(this.appServerFactory.error.message){
96
- Logger.error('App server error: '+this.appServerFactory.error.message);
97
- return false;
157
+ if(!this.useProvidedServer){
158
+ let createdAppServer = this.appServerFactory.createAppServer();
159
+ if(this.appServerFactory.error.message){
160
+ Logger.error('App server error: '+this.appServerFactory.error.message);
161
+ return false;
162
+ }
163
+ this.app = createdAppServer.app;
164
+ this.appServer = createdAppServer.appServer;
98
165
  }
99
- this.app = createdAppServer.app;
100
- this.appServer = createdAppServer.appServer;
101
166
  if(!this.isInstalled()){
102
167
  Logger.info('CMS not installed, preparing setup');
103
168
  await this.installer.prepareSetup(this.app, this.appServer, this.appServerFactory);
104
- await this.appServer.listen(this.config.port);
169
+ if(!this.useProvidedServer){
170
+ await this.appServer.listen(this.config.port);
171
+ }
105
172
  Logger.info('Installer running on '+this.config.host+':'+this.config.port);
106
173
  return true;
107
174
  }
@@ -115,14 +182,14 @@ class Manager
115
182
  }
116
183
  }
117
184
 
118
- async initializeCmsAfterInstall(loadEntities)
185
+ async initializeCmsAfterInstall(props)
119
186
  {
120
187
  try {
121
- this.rawRegisteredEntities = loadEntities.rawRegisteredEntities;
122
- this.entitiesTranslations = loadEntities.entitiesTranslations;
123
- this.entitiesConfig = loadEntities.entitiesConfig;
124
- this.dataServer.rawEntities = loadEntities.rawRegisteredEntities;
125
- this.config = this.loadConfigFromEnv();
188
+ //Logger.debug('Loaded entities post install.', props.loadedEntities);
189
+ this.rawRegisteredEntities = props.loadedEntities.rawRegisteredEntities;
190
+ this.entitiesTranslations = props.loadedEntities.entitiesTranslations;
191
+ this.entitiesConfig = props.loadedEntities.entitiesConfig;
192
+ this.config = props.mappedVariablesForConfig;
126
193
  await this.initializeServices();
127
194
  Logger.info('CMS initialized after installation on '+this.config.host+':'+this.config.port);
128
195
  return true;
@@ -134,28 +201,68 @@ class Manager
134
201
 
135
202
  async initializeServices()
136
203
  {
137
- await this.initializeDataServer();
138
- if (0 === Object.keys(this.adminEntities).length){
139
- if(0 === Object.keys(this.processedEntities).length){
140
- this.processedEntities = LoadedEntitiesProcessor.process(
141
- this.rawRegisteredEntities,
142
- this.entitiesTranslations,
143
- this.entitiesConfig
144
- );
204
+ if(!this.useProvidedDataServer){
205
+ if(!await this.initializeDataServer()){
206
+ return false;
207
+ }
208
+ }
209
+ if(!this.loadProcessedEntities()){
210
+ return false;
211
+ }
212
+ if(!await this.generateAdminEntities()){
213
+ return false;
214
+ }
215
+ if(!this.useProvidedAdminManager){
216
+ if(!await this.initializeAdminManager()){
217
+ return false;
145
218
  }
146
- if(!this.processedEntities.entities){
147
- Logger.critical('Processed entities undefined.');
219
+ }
220
+ if(!this.useProvidedFrontend){
221
+ if(!await this.initializeFrontend()){
148
222
  return false;
149
223
  }
150
- await this.dataServer.generateEntities();
151
- this.adminEntities = this.adminEntitiesGenerator.generate(
152
- this.processedEntities.entities,
153
- this.dataServer.entityManager.entities
224
+ }
225
+ if(!this.useProvidedServer){
226
+ await this.appServer.listen(this.config.port);
227
+ }
228
+ return true;
229
+ }
230
+
231
+ loadProcessedEntities()
232
+ {
233
+ if(0 === Object.keys(this.processedEntities).length){
234
+ this.processedEntities = LoadedEntitiesProcessor.process(
235
+ this.rawRegisteredEntities,
236
+ this.entitiesTranslations,
237
+ this.entitiesConfig
154
238
  );
155
239
  }
156
- await this.initializeAdminManager();
157
- await this.initializeFrontend();
158
- await this.appServer.listen(this.config.port);
240
+ if(!this.processedEntities?.entities){
241
+ Logger.critical('Processed entities undefined.');
242
+ return false;
243
+ }
244
+ return true;
245
+ }
246
+
247
+ async generateAdminEntities()
248
+ {
249
+ if(0 < Object.keys(this.adminEntities).length){
250
+ return true;
251
+ }
252
+ if(!this.dataServer.rawEntities && this.rawRegisteredEntities){
253
+ this.dataServer.rawEntities = this.rawRegisteredEntities;
254
+ }
255
+ Logger.debug('Generate entities count: '+Object.keys(this.rawRegisteredEntities).length);
256
+ await this.dataServer.generateEntities();
257
+ this.adminEntities = this.adminEntitiesGenerator.generate(
258
+ this.processedEntities.entities,
259
+ this.dataServer.entityManager.entities
260
+ );
261
+ if(0 === Object.keys(this.adminEntities).length){
262
+ Logger.warning('Admin entities not found.');
263
+ return false;
264
+ }
265
+ return true;
159
266
  }
160
267
 
161
268
  async initializeDataServer()
@@ -181,6 +288,7 @@ class Manager
181
288
  Logger.critical('Failed to connect to database.');
182
289
  return false;
183
290
  }
291
+ Logger.debug('Entities count: '+Object.keys(this.rawRegisteredEntities).length);
184
292
  await this.dataServer.generateEntities();
185
293
  return true;
186
294
  }
@@ -0,0 +1,10 @@
1
+
2
+ -- Default homepage:
3
+
4
+ -- Create a default homepage route if not exists
5
+ REPLACE INTO `cms_pages` (`id`, `title`, `content`, `template`, `created_at`) VALUES
6
+ (1, 'Home', '<h1>Welcome to Reldens CMS</h1><p>This is your homepage. Edit this content in the admin panel.</p>', 'page', NOW());
7
+
8
+ -- Create a default route to the homepage
9
+ REPLACE INTO `routes` (`id`, `path`, `router`, `content_id`, `title`, `meta_description`, `status`, `created_at`) VALUES
10
+ (1, '/home', 'cmsPages', 1, 'Home', 'Welcome to Reldens CMS', 'published', NOW());
@@ -1,19 +1,5 @@
1
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;
2
+ -- Install Reldens CMS
17
3
 
18
4
  CREATE TABLE IF NOT EXISTS `routes` (
19
5
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
@@ -74,11 +60,3 @@ CREATE TABLE IF NOT EXISTS `entities_meta` (
74
60
  PRIMARY KEY (`id`) USING BTREE,
75
61
  UNIQUE KEY `entity_meta` (`entity_name`, `entity_id`, `meta_key`) USING BTREE
76
62
  ) 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());
@@ -0,0 +1,16 @@
1
+
2
+ -- Default db-users authentication:
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;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reldens/cms",
3
3
  "scope": "@reldens",
4
- "version": "0.7.0",
4
+ "version": "0.8.0",
5
5
  "description": "Reldens - CMS",
6
6
  "author": "Damian A. Pastorini",
7
7
  "license": "MIT",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@reldens/server-utils": "^0.16.0",
36
- "@reldens/storage": "^0.44.0",
36
+ "@reldens/storage": "^0.45.0",
37
37
  "@reldens/utils": "^0.47.0",
38
38
  "dotenv": "^16.5.0",
39
39
  "mustache": "^4.2.0"
@@ -1,16 +1,16 @@
1
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}}
2
+ RELDENS_DB_CLIENT={{&dbClient}}
3
+ RELDENS_DB_HOST={{&dbHost}}
4
+ RELDENS_DB_PORT={{&dbPort}}
5
+ RELDENS_DB_NAME={{&dbName}}
6
+ RELDENS_DB_USER={{&dbUser}}
7
+ RELDENS_DB_PASSWORD={{&dbPassword}}
8
+ RELDENS_DB_DRIVER={{&dbDriver}}
9
9
 
10
10
  # Admin Panel Configuration
11
- RELDENS_CMS_ADMIN_PATH={{&adminPath}}
12
- RELDENS_CMS_ADMIN_SECRET={{&adminSecret}}
11
+ RELDENS_ADMIN_ROUTE_PATH={{&adminPath}}
12
+ RELDENS_ADMIN_SECRET={{&adminSecret}}
13
13
 
14
14
  # Server Configuration
15
- RELDENS_CMS_HOST={{&host}}
16
- RELDENS_CMS_PORT={{&port}}
15
+ RELDENS_APP_HOST={{&host}}
16
+ RELDENS_APP_PORT={{&port}}
@@ -0,0 +1,121 @@
1
+ <?xml version="1.0" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
3
+ "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
4
+ <svg version="1.0" xmlns="http://www.w3.org/2000/svg"
5
+ width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
6
+ preserveAspectRatio="xMidYMid meet">
7
+ <metadata>
8
+ Created by potrace 1.14, written by Peter Selinger 2001-2017
9
+ </metadata>
10
+ <g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
11
+ fill="#000000" stroke="none">
12
+ <path d="M0 3883 l0 -1238 34 61 c49 92 94 149 203 255 121 119 204 185 372
13
+ 294 72 46 181 130 243 186 62 55 205 173 318 262 232 183 317 262 348 328 53
14
+ 111 4 231 -120 295 -90 46 -167 58 -363 58 -197 0 -282 -9 -545 -56 -198 -35
15
+ -232 -33 -285 13 -91 80 -34 207 172 379 181 151 379 270 593 357 l105 42
16
+ -537 1 -538 0 0 -1237z"/>
17
+ <path d="M2208 5100 c141 -51 465 -230 620 -344 85 -63 344 -321 447 -446 122
18
+ -147 247 -318 380 -520 68 -102 136 -197 151 -212 40 -37 83 -53 221 -83 236
19
+ -51 557 -156 713 -233 87 -42 210 -133 264 -193 21 -22 55 -68 77 -101 l39
20
+ -61 0 1106 0 1107 -1482 -1 c-1462 0 -1482 -1 -1430 -19z"/>
21
+ <path d="M1466 5070 c-357 -41 -721 -207 -1034 -470 -64 -54 -173 -180 -155
22
+ -180 4 0 76 13 158 29 292 58 365 66 600 65 195 0 229 -3 299 -22 164 -45 261
23
+ -113 313 -220 39 -80 45 -207 12 -277 -35 -76 -196 -230 -424 -405 -110 -85
24
+ -245 -198 -300 -252 -64 -63 -131 -117 -185 -149 -197 -119 -409 -284 -497
25
+ -387 -221 -261 -230 -536 -24 -742 74 -73 150 -117 261 -151 61 -19 99 -23
26
+ 202 -24 81 0 127 -4 125 -10 -9 -26 -95 -76 -182 -105 -112 -37 -178 -79 -220
27
+ -138 -27 -39 -30 -53 -36 -145 -3 -56 -8 -109 -12 -117 -4 -8 -64 -69 -133
28
+ -134 -69 -65 -150 -149 -180 -186 l-54 -67 0 -492 0 -491 2229 0 2230 0 98 62
29
+ c54 34 136 85 183 114 47 29 151 96 233 149 l147 96 0 50 0 51 -117 -63 c-65
30
+ -34 -250 -138 -412 -231 -162 -92 -298 -168 -302 -168 -4 0 -10 36 -14 81 -4
31
+ 44 -13 103 -20 130 -13 48 -13 49 8 49 19 1 94 27 322 114 54 20 204 96 452
32
+ 227 l83 44 0 108 c0 59 -2 107 -4 107 -2 0 -53 -31 -113 -68 -145 -92 -310
33
+ -182 -442 -242 -162 -73 -144 -74 -237 22 -208 217 -289 440 -301 828 l-6 185
34
+ 162 92 c280 160 523 331 616 435 176 195 265 406 238 564 -30 173 -138 301
35
+ -343 403 -188 95 -540 203 -829 256 -74 14 -135 26 -136 28 -1 1 -23 40 -50
36
+ 87 -70 124 -239 377 -358 536 -252 336 -503 586 -694 689 -32 18 -101 58 -153
37
+ 90 -182 112 -404 198 -590 229 -119 21 -310 28 -414 16z m-87 -435 c293 -51
38
+ 484 -158 526 -293 6 -20 -2 -17 -57 23 -194 144 -417 206 -898 251 -68 6 -72
39
+ 8 -42 16 112 30 311 31 471 3z m511 -677 c-15 -80 -40 -133 -87 -193 -42 -52
40
+ -126 -109 -178 -120 l-30 -7 35 28 c126 103 181 176 233 313 35 95 48 85 27
41
+ -21z m-285 -426 c-113 -110 -134 -122 -227 -138 -46 -8 -96 -19 -113 -24 l-30
42
+ -9 24 17 c35 25 394 221 406 222 5 0 -22 -31 -60 -68z m-80 -169 c-22 -14 -78
43
+ -57 -123 -94 -56 -46 -96 -71 -120 -75 -33 -6 -223 -61 -277 -80 -69 -24 276
44
+ 178 390 228 102 46 104 46 140 47 l30 1 -40 -27z m2033 -515 c-60 -74 -62 -81
45
+ -33 -135 37 -70 32 -114 -26 -250 -33 -78 -33 -39 1 48 37 97 40 155 11 203
46
+ -12 18 -21 42 -21 54 0 20 89 136 98 127 3 -2 -11 -24 -30 -47z m-811 -42 c89
47
+ 0 164 -4 167 -7 16 -16 -98 -250 -183 -374 -34 -50 -64 -106 -67 -125 -6 -34
48
+ -10 -37 -92 -70 -48 -19 -89 -36 -92 -38 -4 -1 -17 17 -30 40 -13 24 -48 66
49
+ -77 95 -40 39 -54 61 -59 91 l-6 39 52 7 c51 7 150 44 150 56 0 3 -27 12 -61
50
+ 19 -43 8 -71 21 -95 44 l-35 32 71 116 72 116 61 -20 c51 -16 92 -20 224 -21z
51
+ m563 -16 c35 -12 36 -14 54 -95 10 -46 15 -92 11 -105 -4 -13 -25 -42 -47 -66
52
+ l-40 -44 26 -10 c21 -8 25 -14 21 -37 -2 -15 -6 -53 -9 -84 -3 -31 -12 -71
53
+ -20 -90 -51 -121 -55 -129 -71 -123 -8 3 -15 12 -15 19 0 7 -13 24 -30 38 -26
54
+ 22 -27 25 -10 30 10 3 28 20 39 38 29 47 27 145 -3 306 -14 71 -28 151 -31
55
+ 180 l-7 52 39 4 c41 4 41 4 93 -13z m-206 -47 c4 -32 18 -121 32 -198 28 -150
56
+ 33 -220 17 -236 -12 -12 -73 0 -73 14 0 5 -12 35 -27 66 -27 54 -28 60 -27
57
+ 222 1 92 5 172 8 178 4 6 20 11 35 11 27 0 29 -3 35 -57z m506 -85 c16 -63 7
58
+ -217 -15 -256 l-14 -27 5 30 c21 125 21 217 -2 294 -5 16 -4 22 3 18 6 -4 17
59
+ -30 23 -59z m-1720 -141 c0 -8 27 -52 59 -98 74 -106 88 -150 72 -222 -15 -69
60
+ -51 -129 -154 -261 -105 -134 -140 -200 -145 -276 -5 -70 8 -113 55 -181 44
61
+ -64 37 -74 -12 -17 -61 71 -70 94 -70 183 0 71 4 88 36 155 20 41 73 125 119
62
+ 185 99 132 140 212 140 275 0 48 -14 79 -71 162 -38 55 -57 108 -39 108 5 0
63
+ 10 -6 10 -13z m-469 -285 c-11 -22 -61 -76 -110 -121 -95 -87 -111 -115 -111
64
+ -189 0 -38 -2 -43 -15 -32 -24 20 -28 72 -10 116 15 38 256 287 263 272 2 -3
65
+ -6 -24 -17 -46z m-270 -109 c-10 -21 -35 -54 -55 -72 l-36 -35 0 26 c0 18 17
66
+ 45 52 83 56 61 72 60 39 -2z m1963 -153 c43 0 168 -36 224 -64 51 -26 88 -57
67
+ 113 -96 l17 -25 7 38 c5 23 3 49 -5 67 -17 41 -11 44 30 12 18 -14 39 -27 46
68
+ -28 29 -4 78 -52 97 -94 12 -27 22 -77 25 -123 l5 -78 22 21 23 21 6 -24 c3
69
+ -14 42 -64 86 -112 101 -111 125 -159 124 -245 -1 -53 -8 -83 -42 -162 -67
70
+ -154 -51 -238 51 -283 l29 -12 -47 -41 c-58 -51 -130 -76 -215 -75 -72 0 -142
71
+ 25 -174 62 -22 24 -12 27 24 6 33 -19 114 -19 129 -1 10 12 7 18 -18 31 -88
72
+ 45 -78 147 29 305 79 117 91 164 58 230 -18 34 -43 58 -108 102 -93 63 -124
73
+ 95 -141 146 -18 54 -61 124 -70 115 -5 -5 -12 -30 -15 -56 -13 -90 65 -201
74
+ 194 -281 66 -40 86 -70 76 -112 -4 -14 -35 -68 -70 -120 -35 -52 -69 -114 -75
75
+ -137 -7 -23 -16 -46 -21 -52 -16 -18 -8 34 23 152 37 143 40 203 10 261 -28
76
+ 56 -82 93 -189 132 -175 63 -187 81 -179 254 l5 119 -41 -63 c-38 -58 -41 -67
77
+ -42 -134 0 -113 47 -179 158 -220 23 -9 69 -25 102 -37 76 -28 120 -71 130
78
+ -126 7 -44 -21 -181 -73 -351 -20 -68 -24 -97 -20 -165 5 -87 18 -123 71 -191
79
+ l31 -39 -39 7 c-154 27 -310 115 -362 203 -22 37 -24 50 -18 95 11 79 24 103
80
+ 24 47 1 -59 51 -160 96 -195 37 -28 90 -40 131 -30 26 7 26 7 -20 36 -90 58
81
+ -102 108 -64 283 43 205 25 286 -84 386 -156 141 -213 220 -226 313 -3 28 -8
82
+ 36 -16 28 -16 -16 -11 -186 7 -245 l16 -50 -25 30 c-13 17 -39 59 -57 95 -29
83
+ 59 -32 73 -32 156 0 103 -8 108 -45 23 -43 -101 -35 -241 19 -348 31 -61 110
84
+ -139 181 -180 36 -20 71 -49 79 -63 14 -24 13 -32 -2 -82 -10 -31 -21 -56 -26
85
+ -56 -4 0 -12 15 -19 34 -20 54 -69 115 -119 146 -26 17 -48 28 -51 26 -2 -3
86
+ 10 -22 27 -43 50 -63 85 -150 89 -225 2 -38 1 -68 -2 -68 -3 0 -20 15 -36 33
87
+ l-29 32 -1 -28 c0 -38 -21 -117 -30 -117 -14 0 -27 25 -40 78 -7 32 -20 58
88
+ -33 67 -21 15 -21 15 9 15 33 0 84 29 52 30 -21 0 -68 21 -68 30 0 4 12 13 28
89
+ 20 l27 13 -30 10 c-42 13 -82 54 -96 94 -7 23 -9 73 -4 152 9 148 -1 190 -61
90
+ 250 -24 24 -44 47 -44 52 0 4 -21 25 -46 47 -55 47 -55 62 3 138 l38 49 6 -42
91
+ c7 -57 15 -62 29 -19 15 47 63 94 203 199 65 49 125 100 134 114 l17 26 39
92
+ -47 c31 -37 45 -46 71 -46z m-2619 12 c87 -25 284 -19 423 15 17 4 22 1 22
93
+ -15 0 -28 -19 -33 -157 -39 -161 -8 -327 19 -368 60 -7 7 -3 7 10 2 11 -4 43
94
+ -14 70 -23z m955 -71 c0 -41 39 -93 93 -121 47 -25 62 -43 51 -62 -4 -5 -13
95
+ -4 -22 5 -10 8 -45 29 -78 46 -73 39 -89 65 -74 122 13 45 30 52 30 10z m-385
96
+ -156 c7 -96 2 -145 -15 -145 -6 0 -10 12 -10 26 0 15 -5 55 -12 89 -9 50 -8
97
+ 70 2 94 7 17 16 31 20 31 5 0 11 -43 15 -95z m946 -210 c53 -55 96 -131 83
98
+ -144 -5 -6 -142 142 -170 183 -26 38 -37 81 -28 109 6 18 10 14 30 -31 13 -30
99
+ 50 -81 85 -117z m-641 70 c0 -8 23 -58 50 -110 28 -52 50 -108 50 -123 l-1
100
+ -27 -16 30 c-8 17 -30 46 -49 65 -18 19 -38 48 -43 63 -14 36 -14 117 -1 117
101
+ 6 0 10 -7 10 -15z m435 -296 c4 -11 38 -52 77 -91 38 -38 67 -72 64 -75 -9 -9
102
+ -114 57 -140 88 -17 20 -26 43 -26 65 0 38 15 46 25 13z m263 -836 c-10 -2
103
+ -26 -2 -35 0 -10 3 -2 5 17 5 19 0 27 -2 18 -5z"/>
104
+ <path d="M5097 2386 c-34 -82 -138 -227 -231 -321 -105 -107 -232 -203 -431
105
+ -326 -284 -177 -281 -174 -279 -319 3 -219 60 -416 166 -576 82 -122 145 -136
106
+ 288 -64 117 59 274 150 403 233 l107 70 0 674 c0 370 -1 673 -2 673 -2 0 -11
107
+ -20 -21 -44z"/>
108
+ <path d="M1 1675 l-1 -490 109 111 c118 119 111 108 154 279 8 33 27 88 41
109
+ 123 36 85 27 113 -58 181 -87 69 -191 182 -220 238 l-25 48 0 -490z"/>
110
+ <path d="M5100 600 c-9 -6 -10 -10 -3 -10 6 0 15 5 18 10 8 12 4 12 -15 0z"/>
111
+ <path d="M5050 580 c-8 -5 -10 -10 -5 -10 6 0 17 5 25 10 8 5 11 10 5 10 -5 0
112
+ -17 -5 -25 -10z"/>
113
+ <path d="M4680 385 c-14 -8 -20 -14 -15 -14 6 0 21 6 35 14 14 8 21 14 15 14
114
+ -5 0 -21 -6 -35 -14z"/>
115
+ <path d="M5043 272 c-39 -26 -105 -67 -145 -91 -40 -24 -118 -74 -172 -110
116
+ l-100 -66 173 -3 174 -2 73 45 74 46 0 115 c0 63 -1 114 -2 114 -2 -1 -35 -22
117
+ -75 -48z"/>
118
+ <path d="M4420 270 c-19 -11 -30 -19 -25 -19 6 0 26 8 45 19 19 11 31 19 25
119
+ 19 -5 0 -26 -8 -45 -19z"/>
120
+ </g>
121
+ </svg>
@@ -0,0 +1,9 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <browserconfig>
3
+ <msapplication>
4
+ <tile>
5
+ <square150x150logo src="/assets/favicons/mstile-150x150.png"/>
6
+ <TileColor>#000000</TileColor>
7
+ </tile>
8
+ </msapplication>
9
+ </browserconfig>
Binary file
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "Reldens",
3
+ "short_name": "Reldens",
4
+ "start_url": "/",
5
+ "display": "standalone",
6
+ "theme_color": "#000000",
7
+ "background_color": "#000000",
8
+ "icons": [
9
+ {
10
+ "src": "assets/favicons/android-icon-144x144.png",
11
+ "sizes": "144x144",
12
+ "type": "image/png",
13
+ "purpose": "any"
14
+ },
15
+ {
16
+ "src": "assets/favicons/android-icon-192x192.png",
17
+ "sizes": "192x192",
18
+ "type": "image/png"
19
+ },
20
+ {
21
+ "src": "assets/favicons/android-icon-512x512.png",
22
+ "sizes": "512x512",
23
+ "type": "image/png",
24
+ "purpose": "maskable"
25
+ }
26
+ ]
27
+ }