freeschema 1.0.5 → 1.0.7

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/bin/freeschema.js CHANGED
@@ -6,6 +6,7 @@ const { spawnSync } = require('child_process');
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
8
  const https = require('https');
9
+ const http = require('http');
9
10
  const crypto = require('crypto');
10
11
  const readline = require('readline');
11
12
 
@@ -212,6 +213,11 @@ const COMPOSE_PATCHES = [
212
213
  replace: '$1 user: "1883:1883"\n',
213
214
  label: 'mosquitto user (1883:1883 to skip chown on Windows)',
214
215
  },
216
+ {
217
+ find: /(--default-authentication-plugin=mysql_native_password)(?!\s*\n\s*--log-bin-trust)/,
218
+ replace: '$1\n --log-bin-trust-function-creators=1',
219
+ label: 'MySQL log_bin_trust_function_creators (allows stored functions/triggers)',
220
+ },
215
221
  ];
216
222
 
217
223
  function patchComposeFile() {
@@ -505,7 +511,7 @@ function waitForMySQL(container, user, pass, maxAttempts = 30) {
505
511
  return false;
506
512
  }
507
513
 
508
- function cmdStart() {
514
+ async function cmdStart() {
509
515
  patchComposeFile();
510
516
  const cwd = process.cwd();
511
517
  const missing = ['.env', '.env.frontend'].filter(f => !fs.existsSync(path.join(cwd, f)));
@@ -537,13 +543,14 @@ function cmdStart() {
537
543
  const env = readEnv();
538
544
  const dbUser = env.DB_USER || env.MYSQL_USER || 'freeschema';
539
545
  const dbPass = env.DB_PASSWORD || env.MYSQL_PASSWORD || 'Freeschema@123';
546
+ const rootPass = env.MYSQL_ROOT_PASSWORD || ('Admin@' + dbPass);
540
547
  const dbName = env.DB_DATABASE || 'freeschema_db';
541
548
  const container = getMysqlContainer();
542
549
  if (!waitForMySQL(container, dbUser, dbPass)) die('MySQL did not become ready — check logs with `freeschema logs mysql-server`.');
543
550
  console.log(` Seeding ${dbName} from ${seedFile}…`);
544
551
  const result = spawnSync(
545
552
  'docker',
546
- ['exec', '-i', container, 'mysql', `-u${dbUser}`, `-p${dbPass}`, dbName],
553
+ ['exec', '-i', container, 'mysql', '-uroot', `-p${rootPass}`, dbName],
547
554
  { input: fs.readFileSync(seedFile), stdio: ['pipe', 'inherit', 'inherit'] }
548
555
  );
549
556
  if (result.error || result.status !== 0) die('Seed failed — check container logs.');
@@ -554,6 +561,18 @@ function cmdStart() {
554
561
  console.log(' Status: freeschema status');
555
562
  console.log(' Logs: freeschema logs');
556
563
  console.log(' Stop: freeschema stop');
564
+
565
+ // Auto-setup credentials if CLIENT_SECRET is missing or placeholder
566
+ const envAfterStart = readEnv();
567
+ const clientSecret = envAfterStart.CLIENT_SECRET || '';
568
+ const isPlaceholder = clientSecret === '' || clientSecret === 'sk_test_placeholder';
569
+ if (isPlaceholder) {
570
+ console.log('\n─────────────────────────────────────────');
571
+ console.log(' CLIENT_SECRET is not configured.');
572
+ console.log(' Setting up admin credentials now…');
573
+ console.log('─────────────────────────────────────────');
574
+ await cmdSetupCredentials();
575
+ }
557
576
  }
558
577
 
559
578
  // ── other commands ────────────────────────────────────────────────────────────
@@ -680,21 +699,150 @@ function cmdDbRestore() {
680
699
  if (!fs.existsSync(sqlFile)) die(`File not found: ${sqlFile}`);
681
700
 
682
701
  const env = readEnv();
683
- const dbUser = env.DB_USER || env.MYSQL_USER || 'freeschema';
684
702
  const dbPass = env.DB_PASSWORD || env.MYSQL_PASSWORD || 'Freeschema@123';
703
+ const rootPass = env.MYSQL_ROOT_PASSWORD || ('Admin@' + dbPass);
685
704
  const dbName = env.DB_DATABASE || 'freeschema_db';
686
705
  const container = getMysqlContainer();
687
706
 
688
707
  console.log(`Restoring ${sqlFile} → ${dbName}…`);
689
708
  const result = spawnSync(
690
709
  'docker',
691
- ['exec', '-i', container, 'mysql', `-u${dbUser}`, `-p${dbPass}`, dbName],
710
+ ['exec', '-i', container, 'mysql', '-uroot', `-p${rootPass}`, dbName],
692
711
  { input: fs.readFileSync(sqlFile), stdio: ['pipe', 'inherit', 'inherit'] }
693
712
  );
694
713
  if (result.error || result.status !== 0) die('Restore failed — check container logs.');
695
714
  console.log('Restore complete.');
696
715
  }
697
716
 
717
+ // ── setup-credentials ─────────────────────────────────────────────────────────
718
+
719
+ function httpPost(url, body) {
720
+ return new Promise((resolve, reject) => {
721
+ const parsed = new URL(url);
722
+ const payload = JSON.stringify(body);
723
+ const opts = {
724
+ hostname: parsed.hostname,
725
+ port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
726
+ path: parsed.pathname,
727
+ method: 'POST',
728
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
729
+ };
730
+ const lib = parsed.protocol === 'https:' ? https : http;
731
+ const req = lib.request(opts, res => {
732
+ let data = '';
733
+ res.on('data', chunk => { data += chunk; });
734
+ res.on('end', () => {
735
+ try { resolve({ status: res.statusCode, body: JSON.parse(data) }); }
736
+ catch { resolve({ status: res.statusCode, body: data }); }
737
+ });
738
+ });
739
+ req.on('error', reject);
740
+ req.write(payload);
741
+ req.end();
742
+ });
743
+ }
744
+
745
+ function httpPostWithToken(url, token) {
746
+ return new Promise((resolve, reject) => {
747
+ const parsed = new URL(url);
748
+ const opts = {
749
+ hostname: parsed.hostname,
750
+ port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
751
+ path: parsed.pathname,
752
+ method: 'POST',
753
+ headers: { 'Authorization': `Bearer ${token}`, 'Content-Length': 0 },
754
+ };
755
+ const lib = parsed.protocol === 'https:' ? https : http;
756
+ const req = lib.request(opts, res => {
757
+ let data = '';
758
+ res.on('data', chunk => { data += chunk; });
759
+ res.on('end', () => {
760
+ try { resolve({ status: res.statusCode, body: JSON.parse(data) }); }
761
+ catch { resolve({ status: res.statusCode, body: data }); }
762
+ });
763
+ });
764
+ req.on('error', reject);
765
+ req.end();
766
+ });
767
+ }
768
+
769
+ async function cmdSetupCredentials() {
770
+ const envPath = path.join(process.cwd(), '.env');
771
+ if (!fs.existsSync(envPath)) die('.env not found. Run `freeschema init` first.');
772
+
773
+ const env = readEnv();
774
+ const wicoPort = env.WICO_PORT || '7071';
775
+ const baseUrl = `http://localhost:${wicoPort}/api`;
776
+
777
+ console.log(`\nSetting up CLIENT_ID and CLIENT_SECRET`);
778
+ console.log(`Using WICO server at ${baseUrl}\n`);
779
+
780
+ // Step 1: login
781
+ const email = await prompt('Admin email');
782
+ const password = await promptSecret('Admin password');
783
+ if (!email || !password) die('Email and password are required.');
784
+
785
+ process.stdout.write('\n[1/3] Logging in… ');
786
+ let loginRes;
787
+ try {
788
+ loginRes = await httpPost(`${baseUrl}/auth/login`, { email, password, application: 'boomconsole' });
789
+ } catch (e) {
790
+ die(`Could not reach WICO server at ${baseUrl}/auth/login\nIs FreeSchema running? Try: freeschema start`);
791
+ }
792
+
793
+ if (loginRes.status !== 200 || !loginRes.body) {
794
+ die(`Login failed (HTTP ${loginRes.status}): ${JSON.stringify(loginRes.body)}`);
795
+ }
796
+
797
+ const token = loginRes.body.token || loginRes.body.access_token || (loginRes.body.data && loginRes.body.data.token);
798
+ if (!token) die(`No token in login response: ${JSON.stringify(loginRes.body)}`);
799
+
800
+ // Decode entity concept ID from JWT upn claim
801
+ let entityId;
802
+ try {
803
+ const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString());
804
+ entityId = payload.upn || payload.sub;
805
+ } catch {
806
+ die('Could not decode JWT token.');
807
+ }
808
+ if (!entityId) die('Could not determine entity ID from JWT.');
809
+ console.log(`done (entity ${entityId})`);
810
+
811
+ // Step 2: generate client-secret
812
+ process.stdout.write('[2/3] Generating client-secret… ');
813
+ let secretRes;
814
+ try {
815
+ secretRes = await httpPostWithToken(`${baseUrl}/client-secret`, token);
816
+ } catch (e) {
817
+ die(`Request to /api/client-secret failed: ${e.message}`);
818
+ }
819
+
820
+ if (secretRes.status !== 200 || !secretRes.body) {
821
+ die(`client-secret failed (HTTP ${secretRes.status}): ${JSON.stringify(secretRes.body)}`);
822
+ }
823
+
824
+ const secret = secretRes.body.message || secretRes.body.data || secretRes.body;
825
+ if (!secret || typeof secret !== 'string') die(`Empty secret in response: ${JSON.stringify(secretRes.body)}`);
826
+ console.log('done');
827
+
828
+ // Step 3: update .env
829
+ process.stdout.write('[3/3] Updating .env… ');
830
+ setEnvVar(envPath, 'CLIENT_ID', String(entityId));
831
+ setEnvVar(envPath, 'CLIENT_SECRET', secret);
832
+ console.log('done');
833
+
834
+ console.log(`
835
+ ─────────────────────────────────────────
836
+ Credentials saved to .env:
837
+ CLIENT_ID = ${entityId}
838
+ CLIENT_SECRET = ${secret}
839
+ ─────────────────────────────────────────
840
+
841
+ Restart the node-server to apply:
842
+ freeschema restart node-server
843
+ `);
844
+ }
845
+
698
846
  // ── help ──────────────────────────────────────────────────────────────────────
699
847
 
700
848
  function cmdHelp() {
@@ -719,6 +867,8 @@ Commands:
719
867
  update Update CLI + pull latest images and restart
720
868
  down [-v] Remove containers (-v also removes data volumes)
721
869
 
870
+ setup-credentials Login as admin, generate CLIENT_SECRET, save to .env
871
+
722
872
  db seed --url <url> Download SQL seed file into seed-db/
723
873
  db seed --file <path> Copy local SQL file into seed-db/
724
874
  db backup [file] Dump database to a .sql file
@@ -740,7 +890,7 @@ Examples:
740
890
 
741
891
  switch (command) {
742
892
  case 'init': cmdInit().catch(e => { console.error(e); process.exit(1); }); break;
743
- case 'start': cmdStart(); break;
893
+ case 'start': cmdStart().catch(e => { console.error(e); process.exit(1); }); break;
744
894
  case 'stop': cmdStop(); break;
745
895
  case 'restart': cmdRestart(); break;
746
896
  case 'status': cmdStatus(); break;
@@ -748,7 +898,8 @@ switch (command) {
748
898
  case 'pull': cmdPull(); break;
749
899
  case 'update': cmdUpdate(); break;
750
900
  case 'down': cmdDown(); break;
751
- case 'db': cmdDb(); break;
901
+ case 'db': cmdDb(); break;
902
+ case 'setup-credentials': cmdSetupCredentials().catch(e => { console.error(e); process.exit(1); }); break;
752
903
  case '--version': case '-v': console.log(VERSION); break;
753
904
  case '--help': case '-h': case undefined: cmdHelp(); break;
754
905
  default:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "freeschema",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "FreeSchema — self-hosted deployment CLI",
5
5
  "keywords": [
6
6
  "freeschema",
@@ -99,9 +99,9 @@ LINKEDIN_SECRET=""
99
99
  # ====================================
100
100
  # PAYMENT GATEWAY (STRIPE)
101
101
  # ====================================
102
- STRIPE_SECRET=""
102
+ STRIPE_SECRET="sk_test_placeholder"
103
103
  WEBHOOK_KEY=""
104
- STRIPE_TEST_SECRET=""
104
+ STRIPE_TEST_SECRET="sk_test_placeholder"
105
105
  WEBHOOK_TEST_KEY=""
106
106
 
107
107
  # ====================================
@@ -39,6 +39,7 @@ services:
39
39
  user: "999:999"
40
40
  command: >
41
41
  --default-authentication-plugin=mysql_native_password
42
+ --log-bin-trust-function-creators=1
42
43
  healthcheck:
43
44
  test: ["CMD-SHELL", "mysqladmin ping -h localhost -u${MYSQL_USER:-freeschema} -p${MYSQL_PASSWORD:-Freeschema@123} --silent"]
44
45
  interval: 10s