4bnode 4.0.7 → 4.0.9
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/add-login.js +3 -6
- package/add-mongo.js +7 -36
- package/add-serialport.js +3 -6
- package/add-websocket.js +2 -4
- package/lib/project.js +1 -1
- package/package.json +1 -1
- package/skeleton/.4bnode/assets/js/pages/EnvironmentPage.js +52 -90
- package/skeleton/.4bnode/dev-api.js +39 -75
- package/skeleton/_gitignore +4 -0
package/add-login.js
CHANGED
|
@@ -197,12 +197,9 @@ router.post('/login', async (req, res) => {
|
|
|
197
197
|
showFileAction("updated", `src/routes/${selectedRoute}`);
|
|
198
198
|
|
|
199
199
|
const projectRoot = getProjectRoot();
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
showFileAction("updated", ".env.development");
|
|
204
|
-
updateEnvFile(path.join(projectRoot, ".env.production"), "JWT_SECRET", prodSecret);
|
|
205
|
-
showFileAction("updated", ".env.production");
|
|
200
|
+
const secret = crypto.randomBytes(32).toString("hex");
|
|
201
|
+
updateEnvFile(path.join(projectRoot, ".env"), "JWT_SECRET", secret);
|
|
202
|
+
showFileAction("updated", ".env");
|
|
206
203
|
|
|
207
204
|
await installDeps(["jsonwebtoken", "bcrypt"]);
|
|
208
205
|
|
package/add-mongo.js
CHANGED
|
@@ -17,19 +17,11 @@ import {
|
|
|
17
17
|
} from "./lib/ui.js";
|
|
18
18
|
|
|
19
19
|
function isDatabaseConfigured() {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
const prodEnvPath = path.join(projectRoot, ".env.production");
|
|
23
|
-
const hasDev =
|
|
24
|
-
fs.existsSync(devEnvPath) &&
|
|
25
|
-
fs.readFileSync(devEnvPath, "utf8").includes("MONGO_URI=");
|
|
26
|
-
const hasProd =
|
|
27
|
-
fs.existsSync(prodEnvPath) &&
|
|
28
|
-
fs.readFileSync(prodEnvPath, "utf8").includes("MONGO_URI=");
|
|
29
|
-
return hasDev || hasProd;
|
|
20
|
+
const envPath = path.join(getProjectRoot(), ".env");
|
|
21
|
+
return fs.existsSync(envPath) && fs.readFileSync(envPath, "utf8").includes("MONGO_URI=");
|
|
30
22
|
}
|
|
31
23
|
|
|
32
|
-
function addMongoDB(
|
|
24
|
+
function addMongoDB(uri) {
|
|
33
25
|
const projectRoot = getProjectRoot();
|
|
34
26
|
|
|
35
27
|
const dbContent = `import mongoose from 'mongoose';
|
|
@@ -65,10 +57,8 @@ export default connectDB;`;
|
|
|
65
57
|
}
|
|
66
58
|
showFileAction("updated", "index.js");
|
|
67
59
|
|
|
68
|
-
updateEnvFile(path.join(projectRoot, ".env
|
|
69
|
-
showFileAction("updated", ".env
|
|
70
|
-
updateEnvFile(path.join(projectRoot, ".env.production"), "MONGO_URI", prodUri);
|
|
71
|
-
showFileAction("updated", ".env.production");
|
|
60
|
+
updateEnvFile(path.join(projectRoot, ".env"), "MONGO_URI", uri);
|
|
61
|
+
showFileAction("updated", ".env");
|
|
72
62
|
}
|
|
73
63
|
|
|
74
64
|
async function main() {
|
|
@@ -128,32 +118,13 @@ async function main() {
|
|
|
128
118
|
devUri = devUri.trim();
|
|
129
119
|
}
|
|
130
120
|
|
|
131
|
-
const useSameForProd = await confirm({
|
|
132
|
-
message: "Use the same URI for production?",
|
|
133
|
-
default: true,
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
let prodUri = devUri;
|
|
137
|
-
if (!useSameForProd) {
|
|
138
|
-
prodUri = await input({
|
|
139
|
-
message: "Production MongoDB URI:",
|
|
140
|
-
default: devUri,
|
|
141
|
-
validate: (v) =>
|
|
142
|
-
v.trim().startsWith("mongodb://") || v.trim().startsWith("mongodb+srv://")
|
|
143
|
-
? true
|
|
144
|
-
: "Must start with mongodb:// or mongodb+srv://",
|
|
145
|
-
});
|
|
146
|
-
prodUri = prodUri.trim();
|
|
147
|
-
}
|
|
148
|
-
|
|
149
121
|
console.log();
|
|
150
|
-
addMongoDB(devUri
|
|
122
|
+
addMongoDB(devUri);
|
|
151
123
|
|
|
152
124
|
await installDeps("mongoose");
|
|
153
125
|
|
|
154
126
|
showTaskDone("MongoDB integration complete", [
|
|
155
|
-
`
|
|
156
|
-
`Prod: ${prodUri}`,
|
|
127
|
+
`URI: ${devUri}`,
|
|
157
128
|
]);
|
|
158
129
|
}
|
|
159
130
|
|
package/add-serialport.js
CHANGED
|
@@ -56,12 +56,9 @@ try {
|
|
|
56
56
|
insertBeforeListen(serialportSetup);
|
|
57
57
|
|
|
58
58
|
const projectRoot = getProjectRoot();
|
|
59
|
-
updateEnvFile(path.join(projectRoot, ".env
|
|
60
|
-
updateEnvFile(path.join(projectRoot, ".env
|
|
61
|
-
|
|
62
|
-
updateEnvFile(path.join(projectRoot, ".env.production"), "BAUDRATE", baudRate);
|
|
63
|
-
showFileAction("updated", ".env.development");
|
|
64
|
-
showFileAction("updated", ".env.production");
|
|
59
|
+
updateEnvFile(path.join(projectRoot, ".env"), "SERIALPORT_PATH", serialPortPath);
|
|
60
|
+
updateEnvFile(path.join(projectRoot, ".env"), "BAUDRATE", baudRate);
|
|
61
|
+
showFileAction("updated", ".env");
|
|
65
62
|
|
|
66
63
|
await installDeps(["serialport", "@serialport/parser-readline"]);
|
|
67
64
|
|
package/add-websocket.js
CHANGED
|
@@ -105,10 +105,8 @@ server.listen(port, () => {
|
|
|
105
105
|
|
|
106
106
|
if (!samePort) {
|
|
107
107
|
const projectRoot = getProjectRoot();
|
|
108
|
-
updateEnvFile(path.join(projectRoot, ".env
|
|
109
|
-
|
|
110
|
-
showFileAction("updated", ".env.development");
|
|
111
|
-
showFileAction("updated", ".env.production");
|
|
108
|
+
updateEnvFile(path.join(projectRoot, ".env"), "WS_PORT", wsPort);
|
|
109
|
+
showFileAction("updated", ".env");
|
|
112
110
|
}
|
|
113
111
|
|
|
114
112
|
await installDeps("ws");
|
package/lib/project.js
CHANGED
|
@@ -105,7 +105,7 @@ export function getModelSchemaDetailed(modelName) {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
export function checkMongoConfig() {
|
|
108
|
-
const envPath = path.join(getProjectRoot(), ".env
|
|
108
|
+
const envPath = path.join(getProjectRoot(), ".env");
|
|
109
109
|
return (
|
|
110
110
|
fs.existsSync(envPath) &&
|
|
111
111
|
fs.readFileSync(envPath, "utf8").includes("MONGO_URI")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "4bnode",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.9",
|
|
4
4
|
"description": "4bnode is a CLI-powered backend development platform with a built-in visual dashboard to generate, manage, and test Node.js/Express APIs faster.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -1,33 +1,27 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
|
-
import { motion
|
|
2
|
+
import { motion } from "framer-motion";
|
|
3
3
|
import { html, api } from "../lib.js";
|
|
4
4
|
import { useApp } from "../context.js";
|
|
5
5
|
|
|
6
6
|
export function EnvironmentPage() {
|
|
7
7
|
const { toast } = useApp();
|
|
8
|
-
const [
|
|
9
|
-
const [devContent, setDevContent] = useState("");
|
|
10
|
-
const [prodContent, setProdContent] = useState("");
|
|
8
|
+
const [content, setContent] = useState("");
|
|
11
9
|
const [loading, setLoading] = useState(true);
|
|
12
10
|
const [saving, setSaving] = useState(false);
|
|
13
|
-
const [dirty, setDirty] = useState(
|
|
11
|
+
const [dirty, setDirty] = useState(false);
|
|
14
12
|
|
|
15
13
|
useEffect(() => {
|
|
16
14
|
api("/env")
|
|
17
15
|
.then((res) => {
|
|
18
|
-
|
|
19
|
-
setProdContent(res.production || "");
|
|
16
|
+
setContent(res.content || "");
|
|
20
17
|
setLoading(false);
|
|
21
18
|
})
|
|
22
19
|
.catch(() => setLoading(false));
|
|
23
20
|
}, []);
|
|
24
21
|
|
|
25
|
-
const currentContent = selected === "development" ? devContent : prodContent;
|
|
26
|
-
const setCurrentContent = selected === "development" ? setDevContent : setProdContent;
|
|
27
|
-
|
|
28
22
|
function handleChange(e) {
|
|
29
|
-
|
|
30
|
-
setDirty(
|
|
23
|
+
setContent(e.target.value);
|
|
24
|
+
setDirty(true);
|
|
31
25
|
}
|
|
32
26
|
|
|
33
27
|
async function handleSave() {
|
|
@@ -35,26 +29,21 @@ export function EnvironmentPage() {
|
|
|
35
29
|
try {
|
|
36
30
|
const result = await api("/env", {
|
|
37
31
|
method: "PUT",
|
|
38
|
-
body: {
|
|
32
|
+
body: { content },
|
|
39
33
|
});
|
|
40
34
|
toast(result.message, "success");
|
|
41
|
-
setDirty(
|
|
35
|
+
setDirty(false);
|
|
42
36
|
} catch (err) {
|
|
43
37
|
toast(err.message, "error");
|
|
44
38
|
}
|
|
45
39
|
setSaving(false);
|
|
46
40
|
}
|
|
47
41
|
|
|
48
|
-
function countVars(
|
|
49
|
-
if (!
|
|
50
|
-
return
|
|
42
|
+
function countVars(c) {
|
|
43
|
+
if (!c) return 0;
|
|
44
|
+
return c.split('\n').filter(l => l.trim() && !l.trim().startsWith('#') && l.includes('=')).length;
|
|
51
45
|
}
|
|
52
46
|
|
|
53
|
-
const files = [
|
|
54
|
-
{ id: "development", label: ".env.development", content: devContent },
|
|
55
|
-
{ id: "production", label: ".env.production", content: prodContent },
|
|
56
|
-
];
|
|
57
|
-
|
|
58
47
|
return html`<div>
|
|
59
48
|
<div className="flex items-center justify-between mb-4">
|
|
60
49
|
<div>
|
|
@@ -63,79 +52,52 @@ export function EnvironmentPage() {
|
|
|
63
52
|
</div>
|
|
64
53
|
</div>
|
|
65
54
|
|
|
66
|
-
<div className="
|
|
67
|
-
<div className="
|
|
68
|
-
<div
|
|
69
|
-
<
|
|
55
|
+
<div className="card mb-4">
|
|
56
|
+
<div className="flex items-center justify-between mb-3">
|
|
57
|
+
<div>
|
|
58
|
+
<div style=${{ fontWeight: 600, fontSize: '16px', marginBottom: '2px' }}>.env</div>
|
|
59
|
+
<div className="text-sm text-muted">${() => countVars(content)} variable${() => countVars(content) !== 1 ? 's' : ''} defined</div>
|
|
70
60
|
</div>
|
|
71
|
-
<div className="
|
|
72
|
-
${
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
<span className="ri-file">${countVars(f.content)} variable${countVars(f.content) !== 1 ? 's' : ''}</span>
|
|
78
|
-
</div>
|
|
79
|
-
${dirty[f.id] && html`<span className="badge badge-warning" style=${{ fontSize: '10px' }}>Modified</span>`}
|
|
80
|
-
</div>`)}
|
|
61
|
+
<div className="flex gap-2 items-center">
|
|
62
|
+
${() => dirty && html`<span className="text-sm text-muted" style=${{ fontSize: '11px' }}>Unsaved</span>`}
|
|
63
|
+
<button className="btn btn-sm btn-primary" onClick=${handleSave} disabled=${saving || !dirty}>
|
|
64
|
+
${saving ? html`<span className="spinner" />` : null}
|
|
65
|
+
${saving ? "Saving..." : "Save"}
|
|
66
|
+
</button>
|
|
81
67
|
</div>
|
|
82
68
|
</div>
|
|
69
|
+
</div>
|
|
83
70
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
<div style=${{ fontWeight: 600, fontSize: '16px', marginBottom: '2px' }}>.env.${selected}</div>
|
|
89
|
-
<div className="text-sm text-muted">${countVars(currentContent)} variable${countVars(currentContent) !== 1 ? 's' : ''} defined</div>
|
|
90
|
-
</div>
|
|
91
|
-
<div className="flex gap-2 items-center">
|
|
92
|
-
${dirty[selected] && html`<span className="text-sm text-muted" style=${{ fontSize: '11px' }}>Unsaved</span>`}
|
|
93
|
-
<button className="btn btn-sm btn-primary" onClick=${handleSave} disabled=${saving || !dirty[selected]}>
|
|
94
|
-
${saving ? html`<span className="spinner" />` : null}
|
|
95
|
-
${saving ? "Saving..." : "Save"}
|
|
96
|
-
</button>
|
|
97
|
-
</div>
|
|
98
|
-
</div>
|
|
99
|
-
</div>
|
|
100
|
-
|
|
101
|
-
<div className="card" style=${{ padding: 0, overflow: 'hidden' }}>
|
|
102
|
-
<div style=${{ padding: '12px 16px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
103
|
-
<span style=${{ fontSize: '12px', fontWeight: 600, color: 'var(--text-secondary)' }}>Editor</span>
|
|
104
|
-
<span className="field-count" style=${{ fontSize: '11px' }}>Cmd+S to save</span>
|
|
105
|
-
</div>
|
|
106
|
-
<${AnimatePresence} mode="wait">
|
|
107
|
-
<${motion.div} key=${selected}
|
|
108
|
-
initial=${{ opacity: 0, y: 6 }} animate=${{ opacity: 1, y: 0 }}
|
|
109
|
-
exit=${{ opacity: 0, y: -6 }} transition=${{ duration: 0.12 }}>
|
|
110
|
-
${loading
|
|
111
|
-
? html`<div className="text-sm text-muted" style=${{ padding: '40px 0', textAlign: 'center' }}>Loading...</div>`
|
|
112
|
-
: html`<textarea
|
|
113
|
-
className="env-editor"
|
|
114
|
-
value=${currentContent}
|
|
115
|
-
onInput=${handleChange}
|
|
116
|
-
placeholder=${"# Add environment variables\n# KEY=value"}
|
|
117
|
-
spellCheck=${false}
|
|
118
|
-
style=${{ borderRadius: 0, border: 'none' }}
|
|
119
|
-
onKeyDown=${(e) => {
|
|
120
|
-
if (e.key === "Tab") {
|
|
121
|
-
e.preventDefault();
|
|
122
|
-
const start = e.target.selectionStart;
|
|
123
|
-
const end = e.target.selectionEnd;
|
|
124
|
-
const val = e.target.value;
|
|
125
|
-
setCurrentContent(val.substring(0, start) + " " + val.substring(end));
|
|
126
|
-
setTimeout(() => { e.target.selectionStart = e.target.selectionEnd = start + 2; }, 0);
|
|
127
|
-
}
|
|
128
|
-
if ((e.metaKey || e.ctrlKey) && e.key === "s") {
|
|
129
|
-
e.preventDefault();
|
|
130
|
-
if (dirty[selected]) handleSave();
|
|
131
|
-
}
|
|
132
|
-
}}
|
|
133
|
-
/>`}
|
|
134
|
-
<//>
|
|
135
|
-
<//>
|
|
136
|
-
</div>
|
|
71
|
+
<div className="card" style=${{ padding: 0, overflow: 'hidden' }}>
|
|
72
|
+
<div style=${{ padding: '12px 16px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
73
|
+
<span style=${{ fontSize: '12px', fontWeight: 600, color: 'var(--text-secondary)' }}>Editor</span>
|
|
74
|
+
<span className="field-count" style=${{ fontSize: '11px' }}>Cmd+S to save</span>
|
|
137
75
|
</div>
|
|
76
|
+
<${motion.div} initial=${{ opacity: 0, y: 6 }} animate=${{ opacity: 1, y: 0 }} transition=${{ duration: 0.12 }}>
|
|
77
|
+
${loading
|
|
78
|
+
? html`<div className="text-sm text-muted" style=${{ padding: '40px 0', textAlign: 'center' }}>Loading...</div>`
|
|
79
|
+
: html`<textarea
|
|
80
|
+
className="env-editor"
|
|
81
|
+
value=${content}
|
|
82
|
+
onInput=${handleChange}
|
|
83
|
+
placeholder=${"# Add environment variables\n# KEY=value"}
|
|
84
|
+
spellCheck=${false}
|
|
85
|
+
style=${{ borderRadius: 0, border: 'none' }}
|
|
86
|
+
onKeyDown=${(e) => {
|
|
87
|
+
if (e.key === "Tab") {
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
const start = e.target.selectionStart;
|
|
90
|
+
const end = e.target.selectionEnd;
|
|
91
|
+
setContent(e.target.value.substring(0, start) + " " + e.target.value.substring(end));
|
|
92
|
+
setTimeout(() => { e.target.selectionStart = e.target.selectionEnd = start + 2; }, 0);
|
|
93
|
+
}
|
|
94
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "s") {
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
if (dirty) handleSave();
|
|
97
|
+
}
|
|
98
|
+
}}
|
|
99
|
+
/>`}
|
|
100
|
+
<//>
|
|
138
101
|
</div>
|
|
139
102
|
</div>`;
|
|
140
103
|
}
|
|
141
|
-
|
|
@@ -134,7 +134,7 @@ function listRoutes() {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
function checkMongoConfig() {
|
|
137
|
-
const envPath = path.join(process.cwd(), ".env
|
|
137
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
138
138
|
return (
|
|
139
139
|
fs.existsSync(envPath) &&
|
|
140
140
|
fs.readFileSync(envPath, "utf8").includes("MONGO_URI")
|
|
@@ -485,15 +485,12 @@ router.get('/api/dashboard', (req, res) => {
|
|
|
485
485
|
|
|
486
486
|
const authConfigured = fs.existsSync(path.join(process.cwd(), 'src', 'middleware', 'auth.js'));
|
|
487
487
|
|
|
488
|
-
let mongoUri = ''
|
|
488
|
+
let mongoUri = '';
|
|
489
489
|
if (mongoConfigured) {
|
|
490
|
-
|
|
491
|
-
const prodEnv = path.join(process.cwd(), '.env.production');
|
|
492
|
-
mongoUri = getEnvValue(devEnv, 'MONGO_URI');
|
|
493
|
-
mongoProdUri = getEnvValue(prodEnv, 'MONGO_URI');
|
|
490
|
+
mongoUri = getEnvValue(path.join(process.cwd(), '.env'), 'MONGO_URI');
|
|
494
491
|
}
|
|
495
492
|
|
|
496
|
-
res.json({ models, routes, mongoConfigured, dbType, routesDirExists, authConfigured, mongoUri
|
|
493
|
+
res.json({ models, routes, mongoConfigured, dbType, routesDirExists, authConfigured, mongoUri });
|
|
497
494
|
} catch (err) {
|
|
498
495
|
res.status(500).json({ error: err.message });
|
|
499
496
|
}
|
|
@@ -539,8 +536,7 @@ router.get('/api/models/:name/data', async (req, res) => {
|
|
|
539
536
|
|
|
540
537
|
// Connect if not already connected
|
|
541
538
|
if (mongoose.connection.readyState === 0) {
|
|
542
|
-
const
|
|
543
|
-
const uri = getEnvValue(devEnv, 'MONGO_URI');
|
|
539
|
+
const uri = getEnvValue(path.join(process.cwd(), '.env'), 'MONGO_URI');
|
|
544
540
|
if (!uri) return res.status(400).json({ error: 'MONGO_URI not configured.' });
|
|
545
541
|
await mongoose.connect(uri);
|
|
546
542
|
}
|
|
@@ -896,10 +892,8 @@ router.post('/api/routes/:name/add-method', async (req, res) => {
|
|
|
896
892
|
}
|
|
897
893
|
|
|
898
894
|
// Ensure JWT_SECRET
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
if (fs.existsSync(devEnv) && !fs.readFileSync(devEnv, 'utf8').includes('JWT_SECRET')) updateEnvFile(devEnv, 'JWT_SECRET', crypto.randomBytes(32).toString('hex'));
|
|
902
|
-
if (fs.existsSync(prodEnv) && !fs.readFileSync(prodEnv, 'utf8').includes('JWT_SECRET')) updateEnvFile(prodEnv, 'JWT_SECRET', crypto.randomBytes(32).toString('hex'));
|
|
895
|
+
const envFile = path.join(process.cwd(), '.env');
|
|
896
|
+
if (!fs.existsSync(envFile) || !fs.readFileSync(envFile, 'utf8').includes('JWT_SECRET')) updateEnvFile(envFile, 'JWT_SECRET', crypto.randomBytes(32).toString('hex'));
|
|
903
897
|
|
|
904
898
|
} else if (routeType === 'register' && modelName) {
|
|
905
899
|
addImport(`import ${pascalModel} from '../models/${modelName}.js';`);
|
|
@@ -917,10 +911,8 @@ router.post('/api/routes/:name/add-method', async (req, res) => {
|
|
|
917
911
|
handlers += `\n// Register\nrouter.post('/', async (req, res) => {\n try {\n const { ${allDestructured} } = req.body;\n const existing = await ${pascalModel}.findOne({ ${findField} });\n if (existing) return res.status(400).json({ message: '${pascalModel} already exists' });\n\n const item = new ${pascalModel}({ ${allDestructured} });\n const saved = await item.save();\n\n const token = jwt.sign({ id: saved._id }, process.env.JWT_SECRET, { expiresIn: '7d' });\n res.status(201).json({ token, ${modelName}: { id: saved._id, ${findField}: saved.${findField} } });\n } catch (err) {\n res.status(400).json({ message: err.message });\n }\n});\n`;
|
|
918
912
|
}
|
|
919
913
|
|
|
920
|
-
const
|
|
921
|
-
|
|
922
|
-
if (fs.existsSync(devEnv) && !fs.readFileSync(devEnv, 'utf8').includes('JWT_SECRET')) updateEnvFile(devEnv, 'JWT_SECRET', crypto.randomBytes(32).toString('hex'));
|
|
923
|
-
if (fs.existsSync(prodEnv) && !fs.readFileSync(prodEnv, 'utf8').includes('JWT_SECRET')) updateEnvFile(prodEnv, 'JWT_SECRET', crypto.randomBytes(32).toString('hex'));
|
|
914
|
+
const envFile = path.join(process.cwd(), '.env');
|
|
915
|
+
if (!fs.existsSync(envFile) || !fs.readFileSync(envFile, 'utf8').includes('JWT_SECRET')) updateEnvFile(envFile, 'JWT_SECRET', crypto.randomBytes(32).toString('hex'));
|
|
924
916
|
}
|
|
925
917
|
|
|
926
918
|
if (!handlers) {
|
|
@@ -1137,11 +1129,9 @@ router.post('/api/crud/generate', (req, res) => {
|
|
|
1137
1129
|
// GET /api/env
|
|
1138
1130
|
router.get('/api/env', (req, res) => {
|
|
1139
1131
|
try {
|
|
1140
|
-
const
|
|
1141
|
-
const
|
|
1142
|
-
|
|
1143
|
-
const prodContent = fs.existsSync(prodEnv) ? fs.readFileSync(prodEnv, 'utf8') : '';
|
|
1144
|
-
res.json({ development: devContent, production: prodContent });
|
|
1132
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
1133
|
+
const content = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf8') : '';
|
|
1134
|
+
res.json({ content });
|
|
1145
1135
|
} catch (err) {
|
|
1146
1136
|
res.status(500).json({ error: err.message });
|
|
1147
1137
|
}
|
|
@@ -1150,13 +1140,10 @@ router.get('/api/env', (req, res) => {
|
|
|
1150
1140
|
// PUT /api/env
|
|
1151
1141
|
router.put('/api/env', (req, res) => {
|
|
1152
1142
|
try {
|
|
1153
|
-
const {
|
|
1154
|
-
|
|
1155
|
-
return res.status(400).json({ error: 'Invalid environment. Must be "development" or "production".' });
|
|
1156
|
-
}
|
|
1157
|
-
const envPath = path.join(process.cwd(), `.env.${environment}`);
|
|
1143
|
+
const { content } = req.body;
|
|
1144
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
1158
1145
|
fs.writeFileSync(envPath, content, 'utf8');
|
|
1159
|
-
res.json({ message:
|
|
1146
|
+
res.json({ message: '.env updated.' });
|
|
1160
1147
|
} catch (err) {
|
|
1161
1148
|
res.status(500).json({ error: err.message });
|
|
1162
1149
|
}
|
|
@@ -1670,15 +1657,9 @@ export default router;
|
|
|
1670
1657
|
|
|
1671
1658
|
// Ensure JWT_SECRET exists in env files for login/register routes
|
|
1672
1659
|
if (type === 'login' || type === 'register') {
|
|
1673
|
-
const
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
const prodContent = fs.existsSync(prodEnv) ? fs.readFileSync(prodEnv, 'utf8') : '';
|
|
1677
|
-
if (!devContent.includes('JWT_SECRET')) {
|
|
1678
|
-
updateEnvFile(devEnv, 'JWT_SECRET', crypto.randomBytes(32).toString('hex'));
|
|
1679
|
-
}
|
|
1680
|
-
if (!prodContent.includes('JWT_SECRET')) {
|
|
1681
|
-
updateEnvFile(prodEnv, 'JWT_SECRET', crypto.randomBytes(32).toString('hex'));
|
|
1660
|
+
const envFile = path.join(process.cwd(), '.env');
|
|
1661
|
+
if (!fs.existsSync(envFile) || !fs.readFileSync(envFile, 'utf8').includes('JWT_SECRET')) {
|
|
1662
|
+
updateEnvFile(envFile, 'JWT_SECRET', crypto.randomBytes(32).toString('hex'));
|
|
1682
1663
|
}
|
|
1683
1664
|
}
|
|
1684
1665
|
|
|
@@ -1713,24 +1694,19 @@ export default router;
|
|
|
1713
1694
|
router.post('/api/add-mongo', async (req, res) => {
|
|
1714
1695
|
try {
|
|
1715
1696
|
const uri = (req.body.uri || '').trim();
|
|
1716
|
-
const prodUri = (req.body.prodUri || '').trim();
|
|
1717
1697
|
const alreadyConfigured = checkMongoConfig();
|
|
1718
1698
|
|
|
1719
1699
|
if (!uri) {
|
|
1720
1700
|
return res.status(400).json({ error: 'MongoDB URI is required' });
|
|
1721
1701
|
}
|
|
1722
1702
|
|
|
1723
|
-
|
|
1724
|
-
const devEnv = path.join(process.cwd(), '.env.development');
|
|
1725
|
-
const prodEnv = path.join(process.cwd(), '.env.production');
|
|
1726
|
-
updateEnvFile(devEnv, 'MONGO_URI', uri);
|
|
1727
|
-
updateEnvFile(prodEnv, 'MONGO_URI', prodUri || uri);
|
|
1703
|
+
updateEnvFile(path.join(process.cwd(), '.env'), 'MONGO_URI', uri);
|
|
1728
1704
|
|
|
1729
1705
|
if (alreadyConfigured) {
|
|
1730
|
-
// Edit mode — just update env
|
|
1706
|
+
// Edit mode — just update .env, no need to reinstall or modify index.js
|
|
1731
1707
|
res.json({
|
|
1732
1708
|
message: 'MongoDB connection updated. Restart the server to apply changes.',
|
|
1733
|
-
files: ['.env
|
|
1709
|
+
files: ['.env']
|
|
1734
1710
|
});
|
|
1735
1711
|
return;
|
|
1736
1712
|
}
|
|
@@ -1751,7 +1727,7 @@ const connectDB = async () => {
|
|
|
1751
1727
|
console.log(\`MongoDB connected: \${conn.connection.host}\`);
|
|
1752
1728
|
} catch (err) {
|
|
1753
1729
|
console.error('MongoDB connection error:', err.message);
|
|
1754
|
-
console.error('Check your MONGO_URI in .env
|
|
1730
|
+
console.error('Check your MONGO_URI in .env and ensure MongoDB is running.');
|
|
1755
1731
|
}
|
|
1756
1732
|
};
|
|
1757
1733
|
|
|
@@ -1762,7 +1738,7 @@ export default connectDB;
|
|
|
1762
1738
|
// Send response BEFORE modifying index.js (nodemon restart kills connection)
|
|
1763
1739
|
res.json({
|
|
1764
1740
|
message: 'MongoDB setup complete.',
|
|
1765
|
-
files: ['src/db.js', '.env
|
|
1741
|
+
files: ['src/db.js', '.env', 'index.js']
|
|
1766
1742
|
});
|
|
1767
1743
|
|
|
1768
1744
|
// Modify index.js LAST (triggers nodemon restart)
|
|
@@ -1916,15 +1892,10 @@ export default router;
|
|
|
1916
1892
|
}
|
|
1917
1893
|
}
|
|
1918
1894
|
|
|
1919
|
-
// Generate JWT
|
|
1920
|
-
|
|
1921
|
-
const prodSecret = crypto.randomBytes(32).toString('hex');
|
|
1922
|
-
const devEnv = path.join(process.cwd(), '.env.development');
|
|
1923
|
-
const prodEnv = path.join(process.cwd(), '.env.production');
|
|
1924
|
-
updateEnvFile(devEnv, 'JWT_SECRET', devSecret);
|
|
1925
|
-
updateEnvFile(prodEnv, 'JWT_SECRET', prodSecret);
|
|
1895
|
+
// Generate JWT secret and update .env
|
|
1896
|
+
updateEnvFile(path.join(process.cwd(), '.env'), 'JWT_SECRET', crypto.randomBytes(32).toString('hex'));
|
|
1926
1897
|
|
|
1927
|
-
const filesModified = ['src/middleware/auth.js', `src/routes/${routeFile}`, '.env
|
|
1898
|
+
const filesModified = ['src/middleware/auth.js', `src/routes/${routeFile}`, '.env'];
|
|
1928
1899
|
if (tokenExpiry) filesModified.push('src/routes/refresh-token.js');
|
|
1929
1900
|
|
|
1930
1901
|
// Send response BEFORE index.js modifications (nodemon restart kills connection)
|
|
@@ -2218,18 +2189,15 @@ router.post('/api/add-serialport', async (req, res) => {
|
|
|
2218
2189
|
await execAsync('npm install serialport @serialport/parser-readline', { cwd: process.cwd() });
|
|
2219
2190
|
}
|
|
2220
2191
|
|
|
2221
|
-
// Update env
|
|
2222
|
-
const
|
|
2223
|
-
|
|
2224
|
-
updateEnvFile(
|
|
2225
|
-
updateEnvFile(devEnv, envBaudKey, String(baud));
|
|
2226
|
-
updateEnvFile(prodEnv, envPathKey, portPath);
|
|
2227
|
-
updateEnvFile(prodEnv, envBaudKey, String(baud));
|
|
2192
|
+
// Update .env
|
|
2193
|
+
const envFile = path.join(process.cwd(), '.env');
|
|
2194
|
+
updateEnvFile(envFile, envPathKey, portPath);
|
|
2195
|
+
updateEnvFile(envFile, envBaudKey, String(baud));
|
|
2228
2196
|
|
|
2229
2197
|
// Send response BEFORE modifying index.js
|
|
2230
2198
|
res.json({
|
|
2231
2199
|
message: 'Serial port "' + name + '" added.',
|
|
2232
|
-
files: ['index.js', '.env
|
|
2200
|
+
files: ['index.js', '.env']
|
|
2233
2201
|
});
|
|
2234
2202
|
|
|
2235
2203
|
// Modify index.js LAST
|
|
@@ -2295,14 +2263,12 @@ router.delete('/api/serialports/:name', (req, res) => {
|
|
|
2295
2263
|
// Remove env vars
|
|
2296
2264
|
const envPathKey = name.toUpperCase() + '_PATH';
|
|
2297
2265
|
const envBaudKey = name.toUpperCase() + '_BAUD';
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
fs.writeFileSync(envPath, envContent, 'utf8');
|
|
2305
|
-
}
|
|
2266
|
+
const envFilePath = path.join(process.cwd(), '.env');
|
|
2267
|
+
if (fs.existsSync(envFilePath)) {
|
|
2268
|
+
let envContent = fs.readFileSync(envFilePath, 'utf8');
|
|
2269
|
+
envContent = envContent.replace(new RegExp(`^${envPathKey}=.*\\n?`, 'm'), '');
|
|
2270
|
+
envContent = envContent.replace(new RegExp(`^${envBaudKey}=.*\\n?`, 'm'), '');
|
|
2271
|
+
fs.writeFileSync(envFilePath, envContent, 'utf8');
|
|
2306
2272
|
}
|
|
2307
2273
|
|
|
2308
2274
|
res.json({ message: 'Serial port "' + name + '" removed.' });
|
|
@@ -2636,8 +2602,7 @@ router.post('/api/reports/preview', async (req, res) => {
|
|
|
2636
2602
|
if (!fs.existsSync(projectMongoose)) return res.status(400).json({ error: 'Mongoose not installed.' });
|
|
2637
2603
|
const mongoose = (await import(pathToFileURL(projectMongoose).href)).default;
|
|
2638
2604
|
if (mongoose.connection.readyState === 0) {
|
|
2639
|
-
const
|
|
2640
|
-
const uri = getEnvValue(devEnv, 'MONGO_URI');
|
|
2605
|
+
const uri = getEnvValue(path.join(process.cwd(), '.env'), 'MONGO_URI');
|
|
2641
2606
|
if (!uri) return res.status(400).json({ error: 'MONGO_URI not configured.' });
|
|
2642
2607
|
await mongoose.connect(uri);
|
|
2643
2608
|
}
|
|
@@ -2753,8 +2718,7 @@ router.post('/api/reports/generate', async (req, res) => {
|
|
|
2753
2718
|
}
|
|
2754
2719
|
const mongoose = (await import(pathToFileURL(projectMongoose).href)).default;
|
|
2755
2720
|
if (mongoose.connection.readyState === 0) {
|
|
2756
|
-
const
|
|
2757
|
-
const uri = getEnvValue(devEnv, 'MONGO_URI');
|
|
2721
|
+
const uri = getEnvValue(path.join(process.cwd(), '.env'), 'MONGO_URI');
|
|
2758
2722
|
if (!uri) return res.status(400).json({ error: 'MONGO_URI not configured.' });
|
|
2759
2723
|
await mongoose.connect(uri);
|
|
2760
2724
|
}
|