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 +157 -6
- package/package.json +1 -1
- package/templates/.env.example +2 -2
- package/templates/docker-compose.yml +1 -0
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',
|
|
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',
|
|
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();
|
|
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':
|
|
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
package/templates/.env.example
CHANGED
|
@@ -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
|