@reldens/cms 0.28.0 → 0.30.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/installer.js CHANGED
@@ -4,9 +4,11 @@
4
4
  *
5
5
  */
6
6
 
7
+ const { fork, execSync } = require('child_process');
8
+ const { EntitiesLoader } = require('./entities-loader');
9
+ const { MySQLInstaller } = require('./mysql-installer');
7
10
  const { FileHandler, Encryptor } = require('@reldens/server-utils');
8
11
  const { DriversMap, DriversClassMap, EntitiesGenerator, PrismaSchemaGenerator } = require('@reldens/storage');
9
- const { EntitiesLoader } = require('./entities-loader');
10
12
  const { Logger, sc } = require('@reldens/utils');
11
13
 
12
14
  class Installer
@@ -32,11 +34,12 @@ class Installer
32
34
  this.defaultTemplatesPath = FileHandler.joinPaths(this.modulePath, 'templates');
33
35
  this.moduleAdminPath = FileHandler.joinPaths(this.modulePath, 'admin');
34
36
  this.moduleAdminAssetsPath = FileHandler.joinPaths(this.moduleAdminPath, 'assets');
35
- this.moduleAdminTemplatesPath = FileHandler.joinPaths(this.moduleAdminPath, 'templates')
37
+ this.moduleAdminTemplatesPath = FileHandler.joinPaths(this.moduleAdminPath, 'templates');
36
38
  this.indexTemplatePath = FileHandler.joinPaths(this.defaultTemplatesPath, 'index.js.dist');
37
39
  this.postInstallCallback = sc.get(props, 'postInstallCallback', false);
38
40
  this.prismaClient = sc.get(props, 'prismaClient', false);
39
41
  this.entitiesLoader = new EntitiesLoader({projectRoot: this.projectRoot});
42
+ this.subprocessMaxAttempts = sc.get(props, 'subprocessMaxAttempts', 1800);
40
43
  }
41
44
 
42
45
  isInstalled()
@@ -44,22 +47,22 @@ class Installer
44
47
  return FileHandler.exists(this.installLockPath);
45
48
  }
46
49
 
47
- async prepareSetup(app, appServer, appServerFactory, renderEngine)
50
+ async configureAppServerRoutes(app, appServer, appServerFactory, renderEngine)
48
51
  {
49
52
  if(!app){
50
- Logger.error('Missing app on prepareSetup for Installer.');
53
+ Logger.error('Missing app on configureAppServerRoutes for Installer.');
51
54
  return false;
52
55
  }
53
56
  if(!appServer){
54
- Logger.error('Missing appServer on prepareSetup for Installer.');
57
+ Logger.error('Missing appServer on configureAppServerRoutes for Installer.');
55
58
  return false;
56
59
  }
57
60
  if(!appServerFactory){
58
- Logger.error('Missing appServerFactory on prepareSetup for Installer.');
61
+ Logger.error('Missing appServerFactory on configureAppServerRoutes for Installer.');
59
62
  return false;
60
63
  }
61
64
  if(!renderEngine){
62
- Logger.error('Missing renderEngine on prepareSetup for Installer.');
65
+ Logger.error('Missing renderEngine on configureAppServerRoutes for Installer.');
63
66
  return false;
64
67
  }
65
68
  this.app = app;
@@ -110,6 +113,7 @@ class Installer
110
113
  {
111
114
  let errorMessages = {
112
115
  'invalid-driver': 'Invalid storage driver selected.',
116
+ 'installation-dependencies-failed': 'Required dependencies failed to install.',
113
117
  'connection-failed': 'Database connection failed. Please check your credentials.',
114
118
  'raw-query-not-found': 'Query method not found in driver.',
115
119
  'sql-file-not-found': 'SQL installation file not found.',
@@ -120,6 +124,8 @@ class Installer
120
124
  'installation-entities-generation-failed': 'Failed to generate entities.',
121
125
  'installation-entities-callback-failed': 'Failed to process entities for callback.',
122
126
  'configuration-error': 'Configuration error while completing installation.',
127
+ 'prisma-generation-subprocess-failed': 'Prisma generation subprocess failed.',
128
+ 'temporal-mysql-only-supported': 'Only MySQL is supported at installation time.',
123
129
  'already-installed': 'The application is already installed.'
124
130
  };
125
131
  return sc.get(errorMessages, errorCode, 'An unknown error occurred during installation.');
@@ -130,6 +136,7 @@ class Installer
130
136
  if(this.isInstalled()){
131
137
  return res.redirect('/?redirect=already-installed');
132
138
  }
139
+ // map database configuration variables:
133
140
  let templateVariables = req.body;
134
141
  req.session.templateVariables = templateVariables;
135
142
  let selectedDriver = templateVariables['db-storage-driver'];
@@ -139,7 +146,7 @@ class Installer
139
146
  return res.redirect('/?error=invalid-driver');
140
147
  }
141
148
  let dbConfig = {
142
- client: templateVariables['db-client'],
149
+ client: sc.get(templateVariables, 'db-client', 'mysql'),
143
150
  config: {
144
151
  host: templateVariables['db-host'],
145
152
  port: Number(templateVariables['db-port']),
@@ -150,41 +157,42 @@ class Installer
150
157
  },
151
158
  debug: false
152
159
  };
153
- if('prisma' === selectedDriver && this.prismaClient){
154
- dbConfig.prismaClient = this.prismaClient;
160
+ if(!await this.checkAndInstallPackages(['@reldens/cms'])){
161
+ Logger.error('Required @reldens/cms dependency installation failed.');
162
+ return res.redirect('/?error=installation-dependencies-failed');
163
+ }
164
+ if('prisma' === selectedDriver){
165
+ if(!await this.checkAndInstallPackages(['@prisma/client'])){
166
+ Logger.error('Required @prisma/client dependency installation failed.');
167
+ return res.redirect('/?error=installation-dependencies-failed');
168
+ }
155
169
  }
156
170
  let dbDriver = new driverClass(dbConfig);
157
- if(!await dbDriver.connect()){
158
- Logger.error('Connection failed');
159
- return res.redirect('/?error=connection-failed');
160
- }
161
171
  if(!sc.isObjectFunction(dbDriver, 'rawQuery')){
162
- Logger.error('Method "rawQuery" not found.');
172
+ Logger.error('Method "rawQuery" not found in driver.', driverClass);
163
173
  return res.redirect('/?error=raw-query-not-found');
164
174
  }
165
- let executeFiles = {
166
- 'install-cms-tables': 'install.sql',
167
- 'install-user-auth': 'users-authentication.sql',
168
- 'install-default-user': 'default-user.sql',
169
- 'install-default-homepage': 'default-homepage.sql',
170
- 'install-default-blocks': 'default-blocks.sql',
171
- 'install-entity-access': 'default-entity-access.sql',
172
- 'install-dynamic-forms': 'default-forms.sql'
173
- };
174
- for(let checkboxName of Object.keys(executeFiles)){
175
- let fileName = executeFiles[checkboxName];
176
- let redirectError = await this.executeQueryFile(
177
- sc.get(templateVariables, checkboxName, 'off'),
178
- fileName,
179
- dbDriver
180
- );
181
- if('' !== redirectError){
182
- return res.redirect(redirectError);
175
+ let queryFilesResult = await this.executeQueryFiles(selectedDriver, dbDriver, dbConfig, templateVariables);
176
+ if('' !== queryFilesResult){
177
+ Logger.critical('Invalid query result: ' + queryFilesResult);
178
+ if('prisma' === selectedDriver){
179
+ res.redirect('/?'+queryFilesResult);
180
+ res.on('finish', () => { process.exit(); });
181
+ return;
183
182
  }
183
+ return res.redirect('/?'+queryFilesResult);
184
184
  }
185
- let entitiesGenerationResult = await this.generateEntities(dbDriver, false, true);
185
+ // @IMPORTANT: if the selectedDriver is 'prisma', then at this point the executeQueryFiles already created the
186
+ // client through the sub-process worker.
187
+ // Search: FileHandler.joinPaths(this.projectRoot, 'prisma', 'client')
188
+ let entitiesGenerationResult = await this.generateEntities(dbDriver, false, true, false, dbConfig);
186
189
  if(!entitiesGenerationResult){
187
190
  Logger.error('Entities generation error.');
191
+ if('prisma' === selectedDriver){
192
+ res.redirect('/?error=installation-entities-generation-failed');
193
+ res.on('finish', () => { process.exit(); });
194
+ return;
195
+ }
188
196
  return res.redirect('/?error=installation-entities-generation-failed');
189
197
  }
190
198
  Logger.info('Generated entities.');
@@ -196,15 +204,22 @@ class Installer
196
204
  await this.createIndexJsFile(templateVariables);
197
205
  if(sc.isFunction(this.postInstallCallback)){
198
206
  if(this.appServer && sc.isFunction(this.appServer.close)){
207
+ // @TODO - CHECK IF THIS SHOULD BE REMOVED.
199
208
  await this.appServer.close();
200
209
  }
201
210
  Logger.debug('Running postInstallCallback.');
202
211
  let callbackResult = await this.postInstallCallback({
203
212
  loadedEntities: this.entitiesLoader.loadEntities(selectedDriver),
204
- mappedVariablesForConfig
213
+ mappedVariablesForConfig,
214
+ dataServer: dbDriver
205
215
  });
206
216
  if(false === callbackResult){
207
217
  Logger.error('Post-install callback failed.');
218
+ if('prisma' === selectedDriver){
219
+ res.redirect('/?error=installation-entities-callback-failed');
220
+ res.on('finish', () => { process.exit(); });
221
+ return;
222
+ }
208
223
  return res.redirect('/?error=installation-entities-callback-failed');
209
224
  }
210
225
  }
@@ -215,43 +230,69 @@ class Installer
215
230
  if(successFileContent){
216
231
  successContent = this.renderEngine.render(
217
232
  successFileContent,
218
- {adminPath: templateVariables['app-admin-path']},
233
+ {adminPath: templateVariables['app-admin-path']}
219
234
  );
220
235
  }
221
236
  return res.send(successContent);
222
237
  } catch (error) {
223
238
  Logger.critical('Configuration error: '+error.message);
224
- return res.redirect('/?error=installation-error');
239
+ if('prisma' === selectedDriver){
240
+ res.redirect('/?error=configuration-error');
241
+ res.on('finish', () => { process.exit(); });
242
+ return;
243
+ }
244
+ return res.redirect('/?error=configuration-error');
225
245
  }
226
246
  }
227
247
 
228
- async executeQueryFile(isMarked, fileName, dbDriver)
248
+ async executeQueryFiles(selectedDriver, dbDriver, dbConfig, templateVariables)
229
249
  {
230
- if('on' !== isMarked){
250
+ if('prisma' === selectedDriver){
251
+ let subProcessResult = await this.runSubprocessInstallation(dbConfig, templateVariables);
252
+ if(!subProcessResult){
253
+ return 'error=prisma-generation-subprocess-failed';
254
+ }
231
255
  return '';
232
256
  }
233
- let sqlFileContent = FileHandler.readFile(FileHandler.joinPaths(this.migrationsPath, fileName));
234
- if(!sqlFileContent){
235
- Logger.error('SQL file "'+fileName+'" not found.');
236
- return '/?error=sql-file-not-found&file-name='+fileName;
257
+ if(!await dbDriver.connect()){
258
+ Logger.error('Connection failed');
259
+ return 'error=connection-failed';
237
260
  }
238
- let queryExecutionResult = await dbDriver.rawQuery(sqlFileContent);
239
- if(!queryExecutionResult){
240
- Logger.error('SQL file "'+fileName+'" raw execution failed.');
241
- return '/?error=sql-file-execution-error&file-name='+fileName;
261
+ if(-1 === dbConfig.client.indexOf('mysql')){
262
+ return 'error=temporal-mysql-only-supported';
263
+ }
264
+ let migrationFiles = MySQLInstaller.migrationFiles();
265
+ for(let checkboxName of Object.keys(migrationFiles)){
266
+ let fileName = migrationFiles[checkboxName];
267
+ let redirectError = await MySQLInstaller.executeQueryFile(
268
+ sc.get(templateVariables, checkboxName, 'off'),
269
+ fileName,
270
+ dbDriver,
271
+ this.migrationsPath
272
+ );
273
+ if('' !== redirectError){
274
+ return redirectError;
275
+ }
242
276
  }
243
- Logger.info('SQL file "'+fileName+'" raw execution successfully.');
244
277
  return '';
245
278
  }
246
279
 
247
- async generateEntities(server, isOverride = false, isInstallationMode = false, isDryPrisma = false)
248
- {
280
+ async generateEntities(
281
+ server,
282
+ isOverride = false,
283
+ isInstallationMode = false,
284
+ isDryPrisma = false,
285
+ dbConfig = null
286
+ ){
249
287
  let driverType = sc.get(DriversClassMap, server.constructor.name, '');
250
288
  Logger.debug('Driver type detected: '+driverType+', Server constructor: '+server.constructor.name);
251
- if('prisma' === driverType && !isInstallationMode && !isDryPrisma){
289
+ if('prisma' === driverType && !isDryPrisma){
252
290
  Logger.info('Running prisma introspect "npx prisma db pull"...');
253
- let dbConfig = this.extractDbConfigFromServer(server);
254
- Logger.debug('Extracted DB config:', dbConfig);
291
+ if(!dbConfig){
292
+ dbConfig = this.extractDbConfigFromServer(server);
293
+ Logger.debug('Extracted DB config.');
294
+ }
295
+ Logger.debug('DB config:', dbConfig);
255
296
  if(dbConfig){
256
297
  let generatedPrismaSchema = await this.generatePrismaSchema(dbConfig);
257
298
  if(!generatedPrismaSchema){
@@ -259,16 +300,20 @@ class Installer
259
300
  return false;
260
301
  }
261
302
  Logger.info('Generated Prisma schema for entities generation.');
303
+ if(isInstallationMode){
304
+ Logger.info('Creating local Prisma client for entities generation...');
305
+ let localPrismaClient = await MySQLInstaller.createPrismaClient(this.projectRoot);
306
+ if(localPrismaClient){
307
+ this.prismaClient = localPrismaClient;
308
+ server.prisma = localPrismaClient;
309
+ }
310
+ }
262
311
  }
263
312
  }
264
313
  if('prisma' === driverType && isDryPrisma){
265
314
  Logger.info('Skipping Prisma schema generation due to --dry-prisma flag.');
266
315
  }
267
- let generatorConfig = {
268
- server,
269
- projectPath: this.projectRoot,
270
- isOverride
271
- };
316
+ let generatorConfig = {server, projectPath: this.projectRoot, isOverride};
272
317
  if('prisma' === driverType && this.prismaClient){
273
318
  generatorConfig.prismaClient = this.prismaClient;
274
319
  }
@@ -387,8 +432,7 @@ class Installer
387
432
  let driverKey = templateVariables['db-storage-driver'];
388
433
  let templateParams = {driverKey};
389
434
  if('prisma' === driverKey){
390
- let prismaClientPath = FileHandler.joinPaths(this.projectRoot, 'prisma', 'client');
391
- templateParams.prismaClientImports = 'const { PrismaClient } = require(\'' + prismaClientPath + '\');';
435
+ templateParams.prismaClientImports = 'const { PrismaClient } = require(\'./prisma/client\');';
392
436
  templateParams.prismaClientParam = ',\n prismaClient: new PrismaClient()';
393
437
  }
394
438
  if('prisma' !== driverKey){
@@ -425,11 +469,11 @@ class Installer
425
469
  FileHandler.copyFolderSync(this.moduleAdminAssetsPath, this.projectPublicAssetsPath);
426
470
  FileHandler.copyFile(
427
471
  FileHandler.joinPaths(this.moduleAdminPath, 'reldens-admin-client.css'),
428
- FileHandler.joinPaths(this.projectCssPath, 'reldens-admin-client.css'),
472
+ FileHandler.joinPaths(this.projectCssPath, 'reldens-admin-client.css')
429
473
  );
430
474
  FileHandler.copyFile(
431
475
  FileHandler.joinPaths(this.moduleAdminPath, 'reldens-admin-client.js'),
432
- FileHandler.joinPaths(this.projectJsPath, 'reldens-admin-client.js'),
476
+ FileHandler.joinPaths(this.projectJsPath, 'reldens-admin-client.js')
433
477
  );
434
478
  Logger.info('Admin folder copied to project root.');
435
479
  return true;
@@ -460,14 +504,8 @@ class Installer
460
504
  FileHandler.joinPaths(this.defaultTemplatesPath, 'layouts', 'default.html'),
461
505
  FileHandler.joinPaths(this.projectTemplatesPath, 'layouts', 'default.html')
462
506
  );
463
- FileHandler.copyFile(
464
- FileHandler.joinPaths(this.defaultTemplatesPath, 'css', 'styles.css'),
465
- FileHandler.joinPaths(this.projectCssPath, 'styles.css')
466
- );
467
- FileHandler.copyFile(
468
- FileHandler.joinPaths(this.defaultTemplatesPath, 'js', 'scripts.js'),
469
- FileHandler.joinPaths(this.projectJsPath, 'scripts.js')
470
- );
507
+ FileHandler.copyFolderSync(FileHandler.joinPaths(this.defaultTemplatesPath, 'css'), this.projectCssPath);
508
+ FileHandler.copyFolderSync(FileHandler.joinPaths(this.defaultTemplatesPath, 'js'), this.projectJsPath);
471
509
  FileHandler.copyFolderSync(
472
510
  FileHandler.joinPaths(this.defaultTemplatesPath, 'partials'),
473
511
  FileHandler.joinPaths(this.projectTemplatesPath, 'partials')
@@ -476,6 +514,10 @@ class Installer
476
514
  FileHandler.joinPaths(this.defaultTemplatesPath, 'domains'),
477
515
  FileHandler.joinPaths(this.projectTemplatesPath, 'domains')
478
516
  );
517
+ FileHandler.copyFolderSync(
518
+ FileHandler.joinPaths(this.defaultTemplatesPath, 'assets'),
519
+ this.projectPublicAssetsPath
520
+ );
479
521
  return true;
480
522
  }
481
523
 
@@ -495,6 +537,100 @@ class Installer
495
537
  'db-password': process.env.RELDENS_DB_PASSWORD || ''
496
538
  };
497
539
  }
540
+
541
+ async checkAndInstallPackages(requiredPackages)
542
+ {
543
+ let missingPackages = [];
544
+ for(let packageName of requiredPackages){
545
+ let packagePath = FileHandler.joinPaths(this.projectRoot, 'node_modules', packageName);
546
+ if(!FileHandler.exists(packagePath)){
547
+ missingPackages.push(packageName);
548
+ }
549
+ }
550
+ if(0 === missingPackages.length){
551
+ return true;
552
+ }
553
+ Logger.info('Missing required packages: ' + missingPackages.join(', '));
554
+ Logger.info('These packages are required for the CMS to function properly.');
555
+ Logger.info('Would you like to install them automatically? (This may take a few minutes)');
556
+ Logger.info('Installing packages: npm install ' + missingPackages.join(' '));
557
+ try {
558
+ let installCommand = 'npm install ' + missingPackages.join(' ');
559
+ execSync(installCommand, {stdio: 'inherit', cwd: this.projectRoot});
560
+ Logger.info('Dependencies installed successfully.');
561
+ return true;
562
+ } catch (error) {
563
+ Logger.error('Failed to install dependencies: ' + error.message);
564
+ Logger.error('Please run manually: npm install ' + missingPackages.join(' '));
565
+ return false;
566
+ }
567
+ }
568
+
569
+ async runSubprocessInstallation(dbConfig, templateVariables)
570
+ {
571
+ Logger.info('Subprocess Prisma installation - Starting...');
572
+ let workerPath = FileHandler.joinPaths(__dirname, 'prisma-subprocess-worker.js');
573
+ let worker = fork(workerPath, [], {
574
+ cwd: this.projectRoot,
575
+ stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
576
+ env: {...process.env}
577
+ });
578
+ let message = {
579
+ dbConfig: dbConfig,
580
+ templateVariables: templateVariables,
581
+ migrationsPath: this.migrationsPath,
582
+ projectRoot: this.projectRoot
583
+ };
584
+ worker.stdout.on('data', (data) => {
585
+ Logger.info('Subprocess: '+data.toString().trim());
586
+ });
587
+ worker.stderr.on('data', (data) => {
588
+ Logger.error('Subprocess error: '+data.toString().trim());
589
+ });
590
+ worker.send(message);
591
+ let subprocessCompleted = false;
592
+ let subprocessSuccess = false;
593
+ let workerExited = false;
594
+ worker.on('message', (message) => {
595
+ subprocessCompleted = true;
596
+ subprocessSuccess = sc.get(message, 'success', false);
597
+ if(!subprocessSuccess){
598
+ Logger.error('Subprocess failed: '+sc.get(message, 'error', 'Unknown'));
599
+ }
600
+ });
601
+ worker.on('error', (error) => {
602
+ subprocessCompleted = true;
603
+ subprocessSuccess = false;
604
+ Logger.error('Subprocess error: '+error.message);
605
+ });
606
+ worker.on('exit', (code, signal) => {
607
+ workerExited = true;
608
+ if(!subprocessCompleted){
609
+ subprocessCompleted = true;
610
+ subprocessSuccess = false;
611
+ }
612
+ });
613
+ let attempts = 0;
614
+ while(!subprocessCompleted && attempts < this.subprocessMaxAttempts){
615
+ attempts++;
616
+ await this.waitMilliseconds(100);
617
+ }
618
+ if(!workerExited){
619
+ worker.kill('SIGTERM');
620
+ await this.waitMilliseconds(1000);
621
+ if(!workerExited){
622
+ worker.kill('SIGKILL');
623
+ }
624
+ }
625
+ Logger.info('Subprocess Prisma installation - Ended.');
626
+ return subprocessSuccess;
627
+ }
628
+
629
+ async waitMilliseconds(ms)
630
+ {
631
+ return new Promise(resolve => setTimeout(resolve, ms));
632
+ }
633
+
498
634
  }
499
635
 
500
636
  module.exports.Installer = Installer;
package/lib/manager.js CHANGED
@@ -75,10 +75,9 @@ class Manager
75
75
  this.frontend = sc.get(props, 'frontend', false);
76
76
  this.renderEngine = sc.get(props, 'renderEngine', mustache);
77
77
  this.prismaClient = sc.get(props, 'prismaClient', false);
78
- this.defaultDevelopmentPatterns = ['localhost', '127.0.0.1', '.local', '.test', '.dev'];
79
- this.developmentPatterns = sc.get(props, 'developmentPatterns', this.defaultDevelopmentPatterns);
80
- this.developmentEnvironments = sc.get(props, 'developmentEnvironments', ['development', 'dev', 'test']);
81
- this.developmentPorts = sc.get(props, 'developmentPorts', [3000, 8080, 8081]);
78
+ this.developmentPatterns = sc.get(props, 'developmentPatterns', []);
79
+ this.developmentEnvironments = sc.get(props, 'developmentEnvironments', []);
80
+ this.developmentPorts = sc.get(props, 'developmentPorts', []);
82
81
  this.developmentMultiplier = sc.get(props, 'developmentMultiplier', 10);
83
82
  this.appServerConfig = sc.get(props, 'appServerConfig', {});
84
83
  this.developmentExternalDomains = sc.get(props, 'developmentExternalDomains', {});
@@ -207,7 +206,12 @@ class Manager
207
206
  }
208
207
  if(!this.isInstalled()){
209
208
  Logger.info('CMS not installed, preparing setup');
210
- await this.installer.prepareSetup(this.app, this.appServer, this.appServerFactory, this.renderEngine);
209
+ await this.installer.configureAppServerRoutes(
210
+ this.app,
211
+ this.appServer,
212
+ this.appServerFactory,
213
+ this.renderEngine
214
+ );
211
215
  if(!this.useProvidedServer){
212
216
  await this.appServer.listen(this.config.port);
213
217
  }
@@ -226,9 +230,14 @@ class Manager
226
230
 
227
231
  buildAppServerConfiguration()
228
232
  {
233
+ let useHelmet = true;
234
+ if(!this.isInstalled()){
235
+ useHelmet = false;
236
+ }
229
237
  let baseConfig = {
230
238
  port: this.config.port,
231
- useHttps: this.config.host.startsWith('https://'),
239
+ useHttps: this.determineHttpsUsage(),
240
+ useHelmet,
232
241
  domainMapping: this.domainMapping || {},
233
242
  defaultDomain: this.defaultDomain,
234
243
  developmentPatterns: this.developmentPatterns,
@@ -248,14 +257,37 @@ class Manager
248
257
  return appServerConfig;
249
258
  }
250
259
 
260
+ determineHttpsUsage()
261
+ {
262
+ if(this.config.publicUrl && this.config.publicUrl.startsWith('https://')){
263
+ return true;
264
+ }
265
+ return this.config.host.startsWith('https://');
266
+ }
267
+
251
268
  async initializeCmsAfterInstall(props)
252
269
  {
253
270
  try {
271
+ this.config = props.mappedVariablesForConfig;
272
+ let appServerConfig = this.buildAppServerConfiguration();
273
+ Object.assign(this.appServerFactory, appServerConfig);
274
+ this.appServerFactory.addHttpDomainsAsDevelopment();
275
+ this.appServerFactory.detectDevelopmentMode();
276
+ this.appServerFactory.setupSecurity();
277
+ //Logger.debug('Development mode installation: '+this.appServerFactory.isDevelopmentMode);
254
278
  this.rawRegisteredEntities = props.loadedEntities.rawRegisteredEntities;
255
279
  this.entitiesTranslations = props.loadedEntities.entitiesTranslations;
256
280
  this.entitiesConfig = props.loadedEntities.entitiesConfig;
257
281
  this.config = props.mappedVariablesForConfig;
258
- await this.initializeServices();
282
+ if(props.dataServer){
283
+ this.dataServer = props.dataServer;
284
+ this.useProvidedDataServer = true;
285
+ }
286
+ let servicesResult = await this.initializeServices();
287
+ if(!servicesResult){
288
+ Logger.critical('Failed to initialize services after installation.');
289
+ return false;
290
+ }
259
291
  Logger.info('CMS initialized after installation on '+this.config.host+':'+this.config.port);
260
292
  return true;
261
293
  } catch (error) {
@@ -357,7 +389,8 @@ class Manager
357
389
  this.dataServer.entityManager.entities
358
390
  );
359
391
  if(0 === Object.keys(this.adminEntities).length){
360
- Logger.warning('Admin entities not found.');
392
+ Logger.critical('Admin entities generation failed - no entities available.');
393
+ Logger.critical('CMS cannot start without admin entities.');
361
394
  return false;
362
395
  }
363
396
  return true;
@@ -0,0 +1,92 @@
1
+ /**
2
+ *
3
+ * Reldens - CMS - MySQLInstaller
4
+ *
5
+ */
6
+
7
+ const { execSync } = require('child_process');
8
+ const { FileHandler } = require('@reldens/server-utils');
9
+ const { Logger } = require('@reldens/utils');
10
+
11
+ class MySQLInstaller
12
+ {
13
+
14
+ static migrationFiles()
15
+ {
16
+ return {
17
+ 'install-cms-tables': 'install.sql',
18
+ 'install-user-auth': 'users-authentication.sql',
19
+ 'install-default-user': 'default-user.sql',
20
+ 'install-default-homepage': 'default-homepage.sql',
21
+ 'install-default-blocks': 'default-blocks.sql',
22
+ 'install-entity-access': 'default-entity-access.sql',
23
+ 'install-dynamic-forms': 'default-forms.sql'
24
+ };
25
+ }
26
+
27
+ static async executeQueryFile(isMarked, fileName, dbDriver, migrationsPath)
28
+ {
29
+ if('on' !== isMarked){
30
+ return '';
31
+ }
32
+ let sqlFileContent = FileHandler.readFile(FileHandler.joinPaths(migrationsPath, fileName));
33
+ if(!sqlFileContent){
34
+ Logger.error('SQL file "'+fileName+'" not found.');
35
+ return '/?error=sql-file-not-found&file-name='+fileName;
36
+ }
37
+ let queryExecutionResult = await dbDriver.rawQuery(sqlFileContent);
38
+ if(!queryExecutionResult){
39
+ Logger.error('SQL file "'+fileName+'" raw execution failed.');
40
+ return '/?error=sql-file-execution-error&file-name='+fileName;
41
+ }
42
+ Logger.info('SQL file "'+fileName+'" raw execution successfully.');
43
+ return '';
44
+ }
45
+
46
+ static async generateMinimalPrismaClient(dbConfig, projectRoot)
47
+ {
48
+ let prismaPath = FileHandler.joinPaths(projectRoot, 'prisma');
49
+ let schemaPath = FileHandler.joinPaths(prismaPath, 'schema.prisma');
50
+ FileHandler.createFolder(prismaPath);
51
+ let schemaContent = 'generator client {\n'
52
+ + ' provider = "prisma-client-js"\n'
53
+ + ' output = "./client"\n'
54
+ + '}\n'
55
+ + '\n'
56
+ + 'datasource db {\n'
57
+ + ' provider = "' + dbConfig.client + '"\n'
58
+ + ' url = env("RELDENS_DB_URL")\n'
59
+ + '}';
60
+ FileHandler.writeFile(schemaPath, schemaContent);
61
+ Logger.info('Running prisma generate...');
62
+ try {
63
+ execSync('npx prisma generate', { stdio: 'inherit', cwd: projectRoot });
64
+ let clientPath = FileHandler.joinPaths(projectRoot, 'prisma', 'client');
65
+ let { PrismaClient } = require(clientPath);
66
+ return new PrismaClient();
67
+ } catch(error) {
68
+ Logger.error('Prisma generate failed: '+error.message);
69
+ return false;
70
+ }
71
+ }
72
+
73
+ static async createPrismaClient(projectRoot)
74
+ {
75
+ try {
76
+ let clientPath = FileHandler.joinPaths(projectRoot, 'prisma', 'client');
77
+ if(!FileHandler.exists(clientPath)){
78
+ return false;
79
+ }
80
+ const { PrismaClient } = require(clientPath);
81
+ let client = new PrismaClient();
82
+ await client.$connect();
83
+ return client;
84
+ } catch(error){
85
+ Logger.error('Prisma client creation failed: '+error.message);
86
+ return false;
87
+ }
88
+ }
89
+
90
+ }
91
+
92
+ module.exports.MySQLInstaller = MySQLInstaller;