tss-stack 1.2.2 → 1.3.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.
@@ -1,358 +1,404 @@
1
- const fs = require("fs-extra");
2
- const path = require("path");
3
-
4
- const { toPascal, toRoute } = require("./utils");
5
-
6
- async function generateBackend(config) {
7
- const { dbName, port, tables, needsAuth, targetDir } = config;
8
- const root = path.join(targetDir, "backend-project");
9
-
10
- const dependencies = {
11
- express: "^4.18.2",
12
- mysql2: "^3.6.0",
13
- cors: "^2.8.5",
14
- dotenv: "^16.3.1",
15
- helmet: "^7.1.0",
16
- "express-session": "^1.17.3",
17
- bcryptjs: "^2.4.3",
18
- "express-rate-limit": "^7.1.5",
19
- };
20
-
21
- await fs.outputFile(
22
- path.join(root, "package.json"),
23
- JSON.stringify(
24
- {
25
- name: "backend-project",
26
- version: "1.0.0",
27
- scripts: {
28
- dev: "nodemon server.js",
29
- start: "node server.js",
30
- },
31
- dependencies: needsAuth
32
- ? dependencies
33
- : {
34
- express: dependencies.express,
35
- mysql2: dependencies.mysql2,
36
- cors: dependencies.cors,
37
- dotenv: dependencies.dotenv,
38
- helmet: dependencies.helmet,
39
- },
40
- devDependencies: {
41
- nodemon: "^3.0.1",
42
- },
43
- },
44
- null,
45
- 2
46
- )
47
- );
48
-
49
- await fs.outputFile(
50
- path.join(root, ".env.example"),
51
- `DB_HOST=localhost
52
- DB_USER=root
53
- DB_PASSWORD=your_password_here
54
- DB_NAME=${dbName}
55
- PORT=${port}
56
- SESSION_SECRET=change_me_to_random_string
57
- CLIENT_URL=http://localhost:5173
58
- NODE_ENV=development
59
- `
60
- );
61
-
62
- await fs.outputFile(
63
- path.join(root, ".gitignore"),
64
- `node_modules/
65
- .env
66
- .env.local
67
- *.log
68
- npm-debug.log*
69
- .DS_Store
70
- .idea/
71
- .vscode/
72
- `
73
- );
74
-
75
- await fs.outputFile(
76
- path.join(root, "config", "db.js"),
77
- `const mysql = require("mysql2");
78
- require("dotenv").config();
79
-
80
- const pool = mysql.createPool({
81
- host: process.env.DB_HOST,
82
- user: process.env.DB_USER,
83
- password: process.env.DB_PASSWORD,
84
- database: process.env.DB_NAME,
85
- waitForConnections: true,
86
- connectionLimit: 10,
87
- });
88
-
89
- pool.getConnection((err, connection) => {
90
- if (err) {
91
- console.error("[ERROR] MySQL connection failed:", err.message);
92
- process.exit(1);
93
- }
94
-
95
- console.log("[✓] MySQL connected");
96
- connection.release();
97
- });
98
-
99
- module.exports = pool.promise();
100
- `
101
- );
102
-
103
- if (needsAuth) {
104
- await fs.outputFile(
105
- path.join(root, "middleware", "auth.js"),
106
- `module.exports = (req, res, next) => {
107
- if (req.session && req.session.user) return next();
108
- return res.status(401).json({ message: "Unauthorized" });
109
- };
110
- `
111
- );
112
-
113
- await fs.outputFile(
114
- path.join(root, "routes", "auth.js"),
115
- `const express = require("express");
116
- const bcrypt = require("bcryptjs");
117
- const rateLimit = require("express-rate-limit");
118
- const router = express.Router();
119
- const db = require("../config/db");
120
- const isAuthenticated = require("../middleware/auth");
121
-
122
- const authLimiter = rateLimit({
123
- windowMs: 15 * 60 * 1000,
124
- max: 50,
125
- });
126
-
127
- router.post("/register", authLimiter, async (req, res) => {
128
- try {
129
- const { username, password } = req.body;
130
-
131
- if (!username || !password) {
132
- return res.status(400).json({ message: "Username and password required" });
133
- }
134
-
135
- if (password.length < 6) {
136
- return res.status(400).json({ message: "Password must be at least 6 characters" });
137
- }
138
-
139
- const hash = await bcrypt.hash(password, 10);
140
-
141
- try {
142
- await db.query("INSERT INTO users (username, password) VALUES (?, ?)", [username, hash]);
143
- res.json({ message: "User registered successfully" });
144
- } catch (err) {
145
- if (err.code === "ER_DUP_ENTRY") {
146
- return res.status(400).json({ message: "Username already exists" });
147
- }
148
- throw err;
149
- }
150
- } catch (err) {
151
- res.status(500).json({ error: err.message });
152
- }
153
- });
154
-
155
- router.post("/login", authLimiter, async (req, res) => {
156
- try {
157
- const { username, password } = req.body;
158
-
159
- if (!username || !password) {
160
- return res.status(400).json({ message: "Username and password required" });
161
- }
162
-
163
- const [results] = await db.query("SELECT id, username FROM users WHERE username = ? LIMIT 1", [username]);
164
-
165
- if (results.length === 0) {
166
- return res.status(401).json({ message: "Invalid credentials" });
167
- }
168
-
169
- const user = results[0];
170
- const [userWithPassword] = await db.query("SELECT password FROM users WHERE id = ?", [user.id]);
171
- const passwordMatch = await bcrypt.compare(password, userWithPassword[0].password);
172
-
173
- if (!passwordMatch) {
174
- return res.status(401).json({ message: "Invalid credentials" });
175
- }
176
-
177
- req.session.user = {
178
- id: user.id,
179
- username: user.username,
180
- };
181
-
182
- res.json({
183
- message: "Login successful",
184
- user: req.session.user,
185
- });
186
- } catch (err) {
187
- res.status(500).json({ error: err.message });
188
- }
189
- });
190
-
191
- router.get("/me", isAuthenticated, (req, res) => {
192
- res.json(req.session.user);
193
- });
194
-
195
- router.post("/logout", (req, res) => {
196
- req.session.destroy((err) => {
197
- if (err) return res.status(500).json({ error: "Logout failed" });
198
- res.json({ message: "Logged out" });
199
- });
200
- });
201
-
202
- module.exports = router;
203
- `
204
- );
205
- }
206
-
207
- for (const table of tables) {
208
- const routeName = toRoute(table.name);
209
- const insertFields = table.fields.join(", ");
210
- const placeholders = table.fields.map(() => "?").join(", ");
211
- const values = table.fields.map((field) => `req.body.${field}`).join(", ");
212
- const updateSet = table.fields.map((field) => `${field} = ?`).join(", ");
213
- const updateValues = [...table.fields.map((field) => `req.body.${field}`), "req.params.id"].join(", ");
214
-
215
- let route = `const express = require("express");
216
- const router = express.Router();
217
- const db = require("../config/db");
218
- ${needsAuth ? 'const isAuthenticated = require("../middleware/auth");' : ""}
219
-
220
- `;
221
-
222
- if (table.operations.includes("insert")) {
223
- route += `router.post(
224
- "/",
225
- ${needsAuth ? "isAuthenticated," : ""}
226
- async (req, res) => {
227
- try {
228
- const sql = "INSERT INTO ${table.name} (${insertFields}) VALUES (${placeholders})";
229
- await db.query(sql, [${values}]);
230
- res.json({ message: "${toPascal(table.name)} created" });
231
- } catch (err) {
232
- res.status(500).json({ error: err.message });
233
- }
234
- }
235
- );
236
-
237
- `;
238
- }
239
-
240
- if (table.operations.includes("select")) {
241
- route += `router.get(
242
- "/",
243
- ${needsAuth ? "isAuthenticated," : ""}
244
- async (req, res) => {
245
- try {
246
- const [rows] = await db.query("SELECT * FROM ${table.name}");
247
- res.json(rows);
248
- } catch (err) {
249
- res.status(500).json({ error: err.message });
250
- }
251
- }
252
- );
253
-
254
- `;
255
- }
256
-
257
- if (table.operations.includes("update")) {
258
- route += `router.put(
259
- "/:id",
260
- ${needsAuth ? "isAuthenticated," : ""}
261
- async (req, res) => {
262
- try {
263
- const sql = "UPDATE ${table.name} SET ${updateSet} WHERE id = ?";
264
- await db.query(sql, [${updateValues}]);
265
- res.json({ message: "${toPascal(table.name)} updated" });
266
- } catch (err) {
267
- res.status(500).json({ error: err.message });
268
- }
269
- }
270
- );
271
-
272
- `;
273
- }
274
-
275
- if (table.operations.includes("delete")) {
276
- route += `router.delete(
277
- "/:id",
278
- ${needsAuth ? "isAuthenticated," : ""}
279
- async (req, res) => {
280
- try {
281
- await db.query("DELETE FROM ${table.name} WHERE id = ?", [req.params.id]);
282
- res.json({ message: "${toPascal(table.name)} deleted" });
283
- } catch (err) {
284
- res.status(500).json({ error: err.message });
285
- }
286
- }
287
- );
288
-
289
- `;
290
- }
291
-
292
- route += `module.exports = router;
293
- `;
294
-
295
- await fs.outputFile(path.join(root, "routes", `${routeName}.js`), route);
296
- }
297
-
298
- const routeImports = tables
299
- .map((t) => `const ${toPascal(t.name)}Route = require("./routes/${toRoute(t.name)}");`)
300
- .join("\n");
301
-
302
- const routeMounts = tables
303
- .map((t) => `app.use("/${toRoute(t.name)}", ${toPascal(t.name)}Route);`)
304
- .join("\n");
305
-
306
- const authImport = needsAuth ? 'const authRoutes = require("./routes/auth");' : "";
307
- const authMount = needsAuth ? 'app.use("/auth", authRoutes);' : "";
308
- const authMiddleware = needsAuth
309
- ? `const session = require("express-session");
310
-
311
- app.use(session({
312
- secret: process.env.SESSION_SECRET || "change_me",
313
- resave: false,
314
- saveUninitialized: false,
315
- cookie: {
316
- httpOnly: true,
317
- sameSite: "lax",
318
- secure: false,
319
- },
320
- }));
321
- `
322
- : "";
323
-
324
- const server = `const express = require("express");
325
- const cors = require("cors");
326
- const helmet = require("helmet");
327
- require("dotenv").config();
328
-
329
- const app = express();
330
- const db = require("./config/db");
331
- ${authImport}
332
- ${routeImports}
333
-
334
- app.use(helmet());
335
- app.use(cors({
336
- origin: process.env.CLIENT_URL || "http://localhost:5173",
337
- credentials: true,
338
- }));
339
- app.use(express.json());
340
- ${authMiddleware}
341
- app.get("/health", (req, res) => res.json({ ok: true }));
342
- ${authMount}
343
- ${routeMounts}
344
-
345
- const port = process.env.PORT || 5000;
346
-
347
- app.listen(port, () => {
348
- console.log(\`[✓] Server running on port \${port}\`);
349
- });
350
- `;
351
-
352
- await fs.outputFile(path.join(root, "server.js"), server);
353
- console.log(" [✓] backend files");
354
- }
355
-
356
- module.exports = {
357
- generateBackend,
358
- };
1
+ const fs = require("fs-extra");
2
+ const path = require("path");
3
+
4
+ const { toPascal, toRoute, inferReportConfig } = require("./utils");
5
+
6
+ async function generateBackend(config) {
7
+ const { dbName, port, tables, needsAuth, needsReports, targetDir } = config;
8
+ const root = path.join(targetDir, "backend-project");
9
+
10
+ // ── package.json ───────────────────────────────────────────────────────
11
+ const dependencies = {
12
+ express: "^4.18.2",
13
+ mysql2: "^3.6.0",
14
+ cors: "^2.8.5",
15
+ dotenv: "^16.3.1",
16
+ helmet: "^7.1.0",
17
+ "express-session": "^1.17.3",
18
+ bcryptjs: "^2.4.3",
19
+ "express-rate-limit": "^7.1.5",
20
+ };
21
+
22
+ await fs.outputFile(
23
+ path.join(root, "package.json"),
24
+ JSON.stringify(
25
+ {
26
+ name: "backend-project",
27
+ version: "1.0.0",
28
+ scripts: { dev: "nodemon server.js", start: "node server.js" },
29
+ dependencies: needsAuth
30
+ ? dependencies
31
+ : {
32
+ express: dependencies.express,
33
+ mysql2: dependencies.mysql2,
34
+ cors: dependencies.cors,
35
+ dotenv: dependencies.dotenv,
36
+ helmet: dependencies.helmet,
37
+ },
38
+ devDependencies: { nodemon: "^3.0.1" },
39
+ },
40
+ null,
41
+ 2
42
+ )
43
+ );
44
+
45
+ // ── .env.example ───────────────────────────────────────────────────────
46
+ await fs.outputFile(
47
+ path.join(root, ".env.example"),
48
+ `DB_HOST=localhost
49
+ DB_USER=root
50
+ DB_PASSWORD=your_password_here
51
+ DB_NAME=${dbName}
52
+ PORT=${port}
53
+ SESSION_SECRET=change_me_to_random_string
54
+ CLIENT_URL=http://localhost:5173
55
+ NODE_ENV=development
56
+ `
57
+ );
58
+
59
+ // ── .gitignore ─────────────────────────────────────────────────────────
60
+ await fs.outputFile(
61
+ path.join(root, ".gitignore"),
62
+ `node_modules/\n.env\n.env.local\n*.log\nnpm-debug.log*\n.DS_Store\n`
63
+ );
64
+
65
+ // ── config/db.js — promise-based pool ─────────────────────────────────
66
+ await fs.outputFile(
67
+ path.join(root, "config", "db.js"),
68
+ `const mysql = require("mysql2");
69
+ require("dotenv").config();
70
+
71
+ // Promise-based pool — use "await db.query(...)" everywhere
72
+ const pool = mysql.createPool({
73
+ host: process.env.DB_HOST,
74
+ user: process.env.DB_USER,
75
+ password: process.env.DB_PASSWORD,
76
+ database: process.env.DB_NAME,
77
+ waitForConnections: true,
78
+ connectionLimit: 10,
79
+ });
80
+
81
+ pool.getConnection((err, connection) => {
82
+ if (err) {
83
+ console.error("[ERROR] MySQL connection failed:", err.message);
84
+ process.exit(1);
85
+ }
86
+ console.log("[✓] MySQL connected");
87
+ connection.release();
88
+ });
89
+
90
+ module.exports = pool.promise();
91
+ `
92
+ );
93
+
94
+ // ── middleware/auth.js ─────────────────────────────────────────────────
95
+ if (needsAuth) {
96
+ await fs.outputFile(
97
+ path.join(root, "middleware", "auth.js"),
98
+ `module.exports = (req, res, next) => {
99
+ if (req.session && req.session.user) return next();
100
+ return res.status(401).json({ message: "Unauthorized" });
101
+ };
102
+ `
103
+ );
104
+
105
+ // ── routes/auth.js ─────────────────────────────────────────────────
106
+ await fs.outputFile(
107
+ path.join(root, "routes", "auth.js"),
108
+ `const express = require("express");
109
+ const bcrypt = require("bcryptjs");
110
+ const rateLimit = require("express-rate-limit");
111
+ const router = express.Router();
112
+ const db = require("../config/db");
113
+ const isAuthenticated = require("../middleware/auth");
114
+
115
+ const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 50 });
116
+
117
+ router.post("/register", authLimiter, async (req, res) => {
118
+ try {
119
+ const { username, password } = req.body;
120
+ if (!username || !password)
121
+ return res.status(400).json({ message: "Username and password required" });
122
+ if (password.length < 6)
123
+ return res.status(400).json({ message: "Password must be at least 6 characters" });
124
+
125
+ const hash = await bcrypt.hash(password, 10);
126
+ try {
127
+ await db.query("INSERT INTO users (username, password) VALUES (?, ?)", [username, hash]);
128
+ res.json({ message: "User registered successfully" });
129
+ } catch (err) {
130
+ if (err.code === "ER_DUP_ENTRY")
131
+ return res.status(400).json({ message: "Username already exists" });
132
+ throw err;
133
+ }
134
+ } catch (err) {
135
+ res.status(500).json({ error: err.message });
136
+ }
137
+ });
138
+
139
+ router.post("/login", authLimiter, async (req, res) => {
140
+ try {
141
+ const { username, password } = req.body;
142
+ if (!username || !password)
143
+ return res.status(400).json({ message: "Username and password required" });
144
+
145
+ const [results] = await db.query(
146
+ "SELECT id, username, password FROM users WHERE username = ? LIMIT 1",
147
+ [username]
148
+ );
149
+ if (results.length === 0)
150
+ return res.status(401).json({ message: "Invalid credentials" });
151
+
152
+ const user = results[0];
153
+ const match = await bcrypt.compare(password, user.password);
154
+ if (!match) return res.status(401).json({ message: "Invalid credentials" });
155
+
156
+ req.session.user = { id: user.id, username: user.username };
157
+ res.json({ message: "Login successful", user: req.session.user });
158
+ } catch (err) {
159
+ res.status(500).json({ error: err.message });
160
+ }
161
+ });
162
+
163
+ router.get("/me", isAuthenticated, (req, res) => res.json(req.session.user));
164
+
165
+ router.post("/logout", (req, res) => {
166
+ req.session.destroy((err) => {
167
+ if (err) return res.status(500).json({ error: "Logout failed" });
168
+ res.json({ message: "Logged out" });
169
+ });
170
+ });
171
+
172
+ module.exports = router;
173
+ `
174
+ );
175
+ }
176
+
177
+ // ── One route file per table ───────────────────────────────────────────
178
+ for (const table of tables) {
179
+ const routeName = toRoute(table.name);
180
+ const insertFields = table.fields.join(", ");
181
+ const placeholders = table.fields.map(() => "?").join(", ");
182
+ const values = table.fields.map((f) => `req.body.${f}`).join(", ");
183
+ const updateSet = table.fields.map((f) => `${f} = ?`).join(", ");
184
+ const updateValues = [...table.fields.map((f) => `req.body.${f}`), "req.params.id"].join(", ");
185
+
186
+ let route = `const express = require("express");
187
+ const router = express.Router();
188
+ const db = require("../config/db");
189
+ ${needsAuth ? 'const isAuthenticated = require("../middleware/auth");' : ""}
190
+
191
+ `;
192
+
193
+ if (table.operations.includes("insert")) {
194
+ route += `router.post("/", ${needsAuth ? "isAuthenticated, " : ""}async (req, res) => {
195
+ try {
196
+ const sql = "INSERT INTO ${table.name} (${insertFields}) VALUES (${placeholders})";
197
+ await db.query(sql, [${values}]);
198
+ res.json({ message: "${toPascal(table.name)} created" });
199
+ } catch (err) {
200
+ res.status(500).json({ error: err.message });
201
+ }
202
+ });
203
+
204
+ `;
205
+ }
206
+
207
+ if (table.operations.includes("select")) {
208
+ route += `router.get("/", ${needsAuth ? "isAuthenticated, " : ""}async (req, res) => {
209
+ try {
210
+ const [rows] = await db.query("SELECT * FROM ${table.name}");
211
+ res.json(rows);
212
+ } catch (err) {
213
+ res.status(500).json({ error: err.message });
214
+ }
215
+ });
216
+
217
+ `;
218
+ }
219
+
220
+ if (table.operations.includes("update")) {
221
+ route += `router.put("/:id", ${needsAuth ? "isAuthenticated, " : ""}async (req, res) => {
222
+ try {
223
+ const sql = "UPDATE ${table.name} SET ${updateSet} WHERE id = ?";
224
+ await db.query(sql, [${updateValues}]);
225
+ res.json({ message: "${toPascal(table.name)} updated" });
226
+ } catch (err) {
227
+ res.status(500).json({ error: err.message });
228
+ }
229
+ });
230
+
231
+ `;
232
+ }
233
+
234
+ if (table.operations.includes("delete")) {
235
+ route += `router.delete("/:id", ${needsAuth ? "isAuthenticated, " : ""}async (req, res) => {
236
+ try {
237
+ await db.query("DELETE FROM ${table.name} WHERE id = ?", [req.params.id]);
238
+ res.json({ message: "${toPascal(table.name)} deleted" });
239
+ } catch (err) {
240
+ res.status(500).json({ error: err.message });
241
+ }
242
+ });
243
+
244
+ `;
245
+ }
246
+
247
+ route += `module.exports = router;\n`;
248
+ await fs.outputFile(path.join(root, "routes", `${routeName}.js`), route);
249
+ }
250
+
251
+ // ── STEP 6 — reports.js route ──────────────────────────────────────────
252
+ // Generated only when needsReports is true AND at least one table opted in.
253
+ // Uses table-specific aggregate SELECT queries built from inferReportConfig,
254
+ // so each endpoint returns real KPI data rather than a raw SELECT *.
255
+ // The table whitelist prevents SQL injection via the :table param.
256
+ const reportTables = needsReports ? tables.filter((t) => t.reports) : [];
257
+
258
+ if (reportTables.length > 0) {
259
+ const allowedTablesList = reportTables.map((t) => `"${t.name}"`).join(", ");
260
+
261
+ // Build one SQL SELECT per table that aggregates metric fields
262
+ const tableQueryMap = reportTables.map((t) => {
263
+ const rc = inferReportConfig(t);
264
+
265
+ // Build SELECT clause: SUM/COUNT per metric + COUNT(*) as total_records
266
+ const selectParts = ["COUNT(*) AS total_records"];
267
+
268
+ for (const metric of rc.metrics) {
269
+ selectParts.push(`SUM(${metric}) AS ${metric}_sum`);
270
+ selectParts.push(`AVG(${metric}) AS ${metric}_avg`);
271
+ }
272
+
273
+ for (const dim of rc.dimensions) {
274
+ selectParts.push(`${dim}`);
275
+ }
276
+
277
+ // If there are dimensions, group by them; otherwise a single aggregate row
278
+ const groupBy = rc.dimensions.length > 0
279
+ ? `GROUP BY ${rc.dimensions.join(", ")}`
280
+ : "";
281
+
282
+ return ` "${t.name}": \`SELECT ${selectParts.join(", ")} FROM ${t.name} ${groupBy}\`.trim(),`;
283
+ }).join("\n");
284
+
285
+ await fs.outputFile(
286
+ path.join(root, "routes", "reports.js"),
287
+ `const express = require("express");
288
+ const router = express.Router();
289
+ const db = require("../config/db");
290
+ ${needsAuth ? 'const isAuthenticated = require("../middleware/auth");' : ""}
291
+
292
+ // Whitelist of tables that have reporting enabled.
293
+ // This prevents arbitrary table names from reaching the database.
294
+ const ALLOWED_TABLES = [${allowedTablesList}];
295
+
296
+ // Pre-built aggregate queries per table — generated from your field names.
297
+ // Edit the SQL here if you need different aggregations.
298
+ const TABLE_QUERIES = {
299
+ ${tableQueryMap}
300
+ };
301
+
302
+ // GET /reports/:table
303
+ // Returns aggregated KPI data for the requested table.
304
+ // Example: GET /reports/stock_out → { total_records, unit_price_sum, ... }
305
+ router.get("/:table", ${needsAuth ? "isAuthenticated, " : ""}async (req, res) => {
306
+ const { table } = req.params;
307
+
308
+ if (!ALLOWED_TABLES.includes(table)) {
309
+ return res.status(400).json({ message: "Report not available for this table" });
310
+ }
311
+
312
+ try {
313
+ const [rows] = await db.query(TABLE_QUERIES[table]);
314
+ res.json({ table, data: rows });
315
+ } catch (err) {
316
+ res.status(500).json({ error: err.message });
317
+ }
318
+ });
319
+
320
+ // GET /reports
321
+ // Returns a summary across all reportable tables in one request.
322
+ // The frontend Reports page uses this single endpoint.
323
+ router.get("/", ${needsAuth ? "isAuthenticated, " : ""}async (req, res) => {
324
+ try {
325
+ const results = {};
326
+
327
+ for (const table of ALLOWED_TABLES) {
328
+ const [rows] = await db.query(TABLE_QUERIES[table]);
329
+ results[table] = rows;
330
+ }
331
+
332
+ res.json(results);
333
+ } catch (err) {
334
+ res.status(500).json({ error: err.message });
335
+ }
336
+ });
337
+
338
+ module.exports = router;
339
+ `
340
+ );
341
+ }
342
+
343
+ // ── server.js ──────────────────────────────────────────────────────────
344
+ const routeImports = tables
345
+ .map((t) => `const ${toPascal(t.name)}Route = require("./routes/${toRoute(t.name)}");`)
346
+ .join("\n");
347
+
348
+ const routeMounts = tables
349
+ .map((t) => `app.use("/${toRoute(t.name)}", ${toPascal(t.name)}Route);`)
350
+ .join("\n");
351
+
352
+ const authImport = needsAuth ? 'const authRoutes = require("./routes/auth");' : "";
353
+ const authMount = needsAuth ? 'app.use("/auth", authRoutes);' : "";
354
+ const reportsImport = reportTables.length > 0 ? 'const reportsRoute = require("./routes/reports");' : "";
355
+ const reportsMount = reportTables.length > 0 ? 'app.use("/reports", reportsRoute);' : "";
356
+
357
+ const sessionMiddleware = needsAuth
358
+ ? `const session = require("express-session");
359
+
360
+ app.use(session({
361
+ secret: process.env.SESSION_SECRET || "change_me",
362
+ resave: false,
363
+ saveUninitialized: false,
364
+ cookie: { httpOnly: true, sameSite: "lax", secure: false },
365
+ }));
366
+ `
367
+ : "";
368
+
369
+ await fs.outputFile(
370
+ path.join(root, "server.js"),
371
+ `const express = require("express");
372
+ const cors = require("cors");
373
+ const helmet = require("helmet");
374
+ require("dotenv").config();
375
+
376
+ const app = express();
377
+ const db = require("./config/db");
378
+ ${authImport}
379
+ ${reportsImport}
380
+ ${routeImports}
381
+
382
+ app.use(helmet());
383
+ app.use(cors({
384
+ origin: process.env.CLIENT_URL || "http://localhost:5173",
385
+ credentials: true,
386
+ }));
387
+ app.use(express.json());
388
+ ${sessionMiddleware}
389
+ app.get("/health", (req, res) => res.json({ ok: true }));
390
+ ${authMount}
391
+ ${reportsMount}
392
+ ${routeMounts}
393
+
394
+ const port = process.env.PORT || ${port};
395
+ app.listen(port, () => {
396
+ console.log(\`[✓] Server running on port \${port}\`);
397
+ });
398
+ `
399
+ );
400
+
401
+ console.log(" [✓] backend files");
402
+ }
403
+
404
+ module.exports = { generateBackend };