create-stackflow 1.0.5 → 1.0.8
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/README.md +4 -6
- package/cli.js +1 -1
- package/package.json +1 -1
- package/src/commands/create.js +10 -9
- package/src/generators/backend.js +156 -129
- package/src/generators/chat.js +661 -0
- package/src/generators/frontend.js +184 -73
- package/src/generators/root.js +6 -58
- package/src/utils/install.js +42 -0
- package/src/utils/prompts.js +26 -1
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ Features:
|
|
|
65
65
|
- React Router DOM
|
|
66
66
|
- Zustand / Redux Toolkit / Context API
|
|
67
67
|
- React Hook Form
|
|
68
|
-
- Zod / Yup validation
|
|
68
|
+
- Zod / Yup validation (Joi is backend-only)
|
|
69
69
|
- Axios API layer
|
|
70
70
|
- TanStack Query
|
|
71
71
|
- Protected routes
|
|
@@ -141,14 +141,12 @@ StackFlow automatically generates:
|
|
|
141
141
|
my-app/
|
|
142
142
|
│
|
|
143
143
|
├── frontend/
|
|
144
|
+
│ └── package.json
|
|
144
145
|
│
|
|
145
146
|
├── backend/
|
|
147
|
+
│ └── package.json
|
|
146
148
|
│
|
|
147
|
-
|
|
148
|
-
│
|
|
149
|
-
├── .gitignore
|
|
150
|
-
│
|
|
151
|
-
└── README.md
|
|
149
|
+
└── node_modules/ (hoisted at project root after install)
|
|
152
150
|
```
|
|
153
151
|
|
|
154
152
|
---
|
package/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ const program = new Command();
|
|
|
8
8
|
program
|
|
9
9
|
.name("create-stackflow")
|
|
10
10
|
.description("Generate a production-minded MERN starter with frontend, backend, auth, CRUD, and dashboard UI.")
|
|
11
|
-
.version("1.0.
|
|
11
|
+
.version("1.0.8")
|
|
12
12
|
.argument("[project-name]", "project folder name")
|
|
13
13
|
.option("--skip-install", "generate files without installing dependencies")
|
|
14
14
|
.option("--yes", "use recommended defaults")
|
package/package.json
CHANGED
package/src/commands/create.js
CHANGED
|
@@ -10,6 +10,7 @@ import { createRootFiles } from "../generators/root.js";
|
|
|
10
10
|
import { createBackend } from "../generators/backend.js";
|
|
11
11
|
import { createFrontend } from "../generators/frontend.js";
|
|
12
12
|
import { formatName } from "../utils/names.js";
|
|
13
|
+
import { installHoistedWorkspace } from "../utils/install.js";
|
|
13
14
|
|
|
14
15
|
export async function createStackFlow(options) {
|
|
15
16
|
printBanner();
|
|
@@ -46,13 +47,13 @@ export async function createStackFlow(options) {
|
|
|
46
47
|
|
|
47
48
|
await fs.ensureDir(projectDir);
|
|
48
49
|
|
|
49
|
-
await step("Creating root workspace", () => createRootFiles(context));
|
|
50
50
|
await createFrontend(context);
|
|
51
51
|
await createBackend(context);
|
|
52
|
+
await step("Writing project gitignore files", () => createRootFiles(context));
|
|
52
53
|
|
|
53
54
|
if (!context.skipInstall) {
|
|
54
|
-
await step("Installing root
|
|
55
|
-
|
|
55
|
+
await step("Installing dependencies (root node_modules)", () =>
|
|
56
|
+
installHoistedWorkspace(context),
|
|
56
57
|
);
|
|
57
58
|
}
|
|
58
59
|
|
|
@@ -61,7 +62,7 @@ export async function createStackFlow(options) {
|
|
|
61
62
|
if (context.runProject) {
|
|
62
63
|
console.log(chalk.cyan("\n🚀 Starting the project..."));
|
|
63
64
|
try {
|
|
64
|
-
await execa("npm", ["run", "dev"], { cwd:
|
|
65
|
+
await execa("npm", ["run", "dev"], { cwd: context.frontendDir, stdio: "inherit" });
|
|
65
66
|
} catch (error) {
|
|
66
67
|
console.error(chalk.red("\nFailed to start the project."));
|
|
67
68
|
console.error(chalk.dim(error.message));
|
|
@@ -77,6 +78,7 @@ function recommendedDefaults(projectName) {
|
|
|
77
78
|
frontend: "react",
|
|
78
79
|
language: "typescript",
|
|
79
80
|
backendLanguage: "javascript",
|
|
81
|
+
backendModule: "esm",
|
|
80
82
|
styling: "tailwind",
|
|
81
83
|
tailwind: true,
|
|
82
84
|
uiLibrary: "shadcn",
|
|
@@ -103,17 +105,16 @@ function recommendedDefaults(projectName) {
|
|
|
103
105
|
orm: "Mongoose",
|
|
104
106
|
authStrategy: "JWT",
|
|
105
107
|
passwordHashing: "bcryptjs",
|
|
106
|
-
fileUpload: "
|
|
108
|
+
fileUpload: "None",
|
|
107
109
|
backendValidation: "None",
|
|
108
110
|
securityPackages: ["helmet", "cors", "express-rate-limit", "hpp"],
|
|
109
|
-
cookieParser: true,
|
|
110
111
|
logging: "Morgan",
|
|
111
|
-
swagger:
|
|
112
|
+
swagger: false,
|
|
112
113
|
socketio: false,
|
|
113
114
|
winston: false,
|
|
114
115
|
morgan: true,
|
|
115
116
|
cloudinary: false,
|
|
116
|
-
multer:
|
|
117
|
+
multer: false,
|
|
117
118
|
helmet: true,
|
|
118
119
|
cors: true,
|
|
119
120
|
rateLimit: true,
|
|
@@ -163,7 +164,7 @@ function printSuccess(context) {
|
|
|
163
164
|
console.log(chalk.green.bold("StackFlow app created successfully."));
|
|
164
165
|
console.log();
|
|
165
166
|
console.log(chalk.white("Next steps:"));
|
|
166
|
-
console.log(chalk.cyan(` cd ${context.projectName}`));
|
|
167
|
+
console.log(chalk.cyan(` cd ${context.projectName}/${frontend}`));
|
|
167
168
|
console.log(chalk.cyan(" npm run dev"));
|
|
168
169
|
console.log();
|
|
169
170
|
console.log(chalk.dim(`Frontend: ${frontend}`));
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import fs from "fs-extra";
|
|
3
3
|
import ora from "ora";
|
|
4
|
-
import { execa } from "execa";
|
|
5
4
|
import { ext, writeTemplate } from "../utils/template.js";
|
|
5
|
+
import {
|
|
6
|
+
chatBackendFiles,
|
|
7
|
+
chatServerSocketBlock,
|
|
8
|
+
patchMainRouteForChat,
|
|
9
|
+
} from "./chat.js";
|
|
6
10
|
|
|
7
11
|
export async function createBackend(context) {
|
|
8
12
|
const spinner = ora("Generating backend API").start();
|
|
@@ -15,26 +19,16 @@ export async function createBackend(context) {
|
|
|
15
19
|
throw error;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
if (!context.skipInstall) {
|
|
19
|
-
const install = ora("Installing backend dependencies").start();
|
|
20
|
-
try {
|
|
21
|
-
await execa("npm", ["install"], { cwd: context.backendDir, stdio: "ignore" });
|
|
22
|
-
install.succeed("Installing backend dependencies");
|
|
23
|
-
} catch (error) {
|
|
24
|
-
install.fail("Installing backend dependencies");
|
|
25
|
-
throw error;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
22
|
}
|
|
29
23
|
|
|
30
24
|
async function writeBackendFiles(context) {
|
|
31
25
|
const backendContext = { ...context, language: context.backendLanguage || context.language };
|
|
32
26
|
const e = ext(backendContext);
|
|
33
27
|
const isTs = backendContext.language === "typescript";
|
|
28
|
+
const esm = isTs || context.backendModule !== "cjs";
|
|
34
29
|
const clientUrl = context.frontend === "next" ? "http://localhost:3000" : "http://localhost:5173";
|
|
35
30
|
const deps = {
|
|
36
31
|
bcryptjs: "latest",
|
|
37
|
-
"cookie-parser": context.cookieParser === false ? undefined : "latest",
|
|
38
32
|
cors: context.cors === false ? undefined : "latest",
|
|
39
33
|
dotenv: "latest",
|
|
40
34
|
express: "latest",
|
|
@@ -54,7 +48,6 @@ async function writeBackendFiles(context) {
|
|
|
54
48
|
const devDeps = isTs
|
|
55
49
|
? {
|
|
56
50
|
"@types/bcryptjs": "latest",
|
|
57
|
-
"@types/cookie-parser": "latest",
|
|
58
51
|
"@types/cors": "latest",
|
|
59
52
|
"@types/express": "latest",
|
|
60
53
|
"@types/jsonwebtoken": "latest",
|
|
@@ -70,7 +63,7 @@ async function writeBackendFiles(context) {
|
|
|
70
63
|
await writeTemplate(path.join(context.backendDir, "package.json"), JSON.stringify({
|
|
71
64
|
name: path.basename(context.backendDir),
|
|
72
65
|
version: "1.0.0",
|
|
73
|
-
type: "module",
|
|
66
|
+
...(esm ? { type: "module" } : {}),
|
|
74
67
|
private: true,
|
|
75
68
|
scripts: {
|
|
76
69
|
dev: isTs ? "tsx watch src/server.ts" : "nodemon src/server.js",
|
|
@@ -85,8 +78,8 @@ async function writeBackendFiles(context) {
|
|
|
85
78
|
await writeTemplate(path.join(context.backendDir, "tsconfig.json"), `{
|
|
86
79
|
"compilerOptions": {
|
|
87
80
|
"target": "ES2022",
|
|
88
|
-
"module": "NodeNext",
|
|
89
|
-
"moduleResolution": "NodeNext",
|
|
81
|
+
"module": "${context.backendModule === "cjs" ? "CommonJS" : "NodeNext"}",
|
|
82
|
+
"moduleResolution": "${context.backendModule === "cjs" ? "Node" : "NodeNext"}",
|
|
90
83
|
"strict": false,
|
|
91
84
|
"noImplicitAny": false,
|
|
92
85
|
"noCheck": true,
|
|
@@ -106,7 +99,6 @@ CLIENT_URL={{clientUrl}}
|
|
|
106
99
|
MONGODB_URI=mongodb://127.0.0.1:27017/{{databaseName}}
|
|
107
100
|
JWT_SECRET=change_this_super_secret_value
|
|
108
101
|
JWT_EXPIRES_IN=7d
|
|
109
|
-
COOKIE_NAME=stackflow_token
|
|
110
102
|
{{cloudinaryEnv}}
|
|
111
103
|
`, { ...context, clientUrl, cloudinaryEnv: context.cloudinary ? "CLOUDINARY_CLOUD_NAME=\nCLOUDINARY_API_KEY=\nCLOUDINARY_API_SECRET=" : "" });
|
|
112
104
|
|
|
@@ -116,19 +108,16 @@ COOKIE_NAME=stackflow_token
|
|
|
116
108
|
"src/middleware",
|
|
117
109
|
"src/models",
|
|
118
110
|
"src/routes",
|
|
119
|
-
"src/services",
|
|
120
111
|
"src/utils",
|
|
121
|
-
"src/validations"
|
|
122
112
|
];
|
|
123
113
|
if (context.multer) dirs.push("uploads");
|
|
124
114
|
await Promise.all(dirs.map((dir) => fs.ensureDir(path.join(context.backendDir, dir))));
|
|
125
115
|
if (context.multer) await fs.outputFile(path.join(context.backendDir, "uploads", ".gitkeep"), "");
|
|
126
116
|
|
|
127
|
-
const files = backendTemplates({ ...context, language: backendContext.language, clientUrl }, e);
|
|
128
|
-
|
|
129
|
-
// Filter out unused configuration files
|
|
117
|
+
const files = backendTemplates({ ...context, language: backendContext.language, clientUrl, esm }, e);
|
|
118
|
+
|
|
130
119
|
if (!context.cloudinary) delete files[`src/config/cloudinary.${e}`];
|
|
131
|
-
if (!context.multer) delete files[`src/
|
|
120
|
+
if (!context.multer) delete files[`src/middleware/uploadMiddleware.${e}`];
|
|
132
121
|
if (!context.winston) delete files[`src/utils/logger.${e}`];
|
|
133
122
|
if (!context.swagger) delete files[`src/config/swagger.${e}`];
|
|
134
123
|
|
|
@@ -143,58 +132,62 @@ function compact(object) {
|
|
|
143
132
|
|
|
144
133
|
function backendTemplates(context, e) {
|
|
145
134
|
const isTs = context.language === "typescript";
|
|
135
|
+
const esm = context.esm !== false;
|
|
136
|
+
const rel = (p) => (esm ? `${p}.js` : p);
|
|
137
|
+
const im = (binding, from, relative = false) => {
|
|
138
|
+
const src = relative ? rel(from) : from;
|
|
139
|
+
return esm
|
|
140
|
+
? `import ${binding} from "${src}";`
|
|
141
|
+
: `const ${binding} = require("${src}");`;
|
|
142
|
+
};
|
|
143
|
+
const imDefault = (binding, from, relative = false) => im(binding, from, relative);
|
|
144
|
+
const imn = (names, from, relative = false) => {
|
|
145
|
+
const src = relative ? rel(from) : from;
|
|
146
|
+
return esm
|
|
147
|
+
? `import { ${names} } from "${src}";`
|
|
148
|
+
: `const { ${names} } = require("${src}");`;
|
|
149
|
+
};
|
|
146
150
|
const type = (name) => isTs ? `: ${name}` : "";
|
|
147
151
|
const reqRes = isTs ? "import type { Request, Response, NextFunction } from \"express\";\n" : "";
|
|
148
152
|
const imports = {
|
|
149
|
-
logger: context.winston ?
|
|
150
|
-
morgan: context.morgan ? "
|
|
151
|
-
swagger: context.swagger ?
|
|
152
|
-
socket: context.socketio ? "
|
|
153
|
+
logger: context.winston ? (esm ? imn("logger", "./utils/logger", true) : imn("logger", "./utils/logger", true)) : "",
|
|
154
|
+
morgan: context.morgan ? im("morgan", "morgan") : "",
|
|
155
|
+
swagger: context.swagger ? (esm ? imn("setupSwagger", "./config/swagger", true) : imn("setupSwagger", "./config/swagger", true)) : "",
|
|
156
|
+
socket: context.socketio ? imn("Server", "socket.io") : "",
|
|
153
157
|
};
|
|
154
158
|
|
|
155
|
-
|
|
156
|
-
[`src/server.${e}`]:
|
|
157
|
-
|
|
158
|
-
|
|
159
|
+
const templates = {
|
|
160
|
+
[`src/server.${e}`]: `${im("http", "node:http")}
|
|
161
|
+
${imDefault("app", "./app", true)}
|
|
162
|
+
${imn("connectDB", "./config/db", true)}
|
|
159
163
|
${imports.socket}
|
|
160
164
|
${imports.logger}
|
|
161
165
|
|
|
162
166
|
const PORT = Number(process.env.PORT) || 5000;
|
|
163
167
|
|
|
164
|
-
async function
|
|
168
|
+
async function startServer() {
|
|
165
169
|
await connectDB();
|
|
166
170
|
const server = http.createServer(app);
|
|
167
|
-
${context.
|
|
168
|
-
const io = new Server(server, {
|
|
169
|
-
cors: {
|
|
170
|
-
origin: process.env.CLIENT_URL || "${context.clientUrl}",
|
|
171
|
-
credentials: true
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
io.on("connection", (socket) => {
|
|
176
|
-
socket.emit("connected", { message: "Socket.IO connected" });
|
|
177
|
-
});
|
|
178
|
-
` : ""}
|
|
171
|
+
${chatServerSocketBlock(context, context.clientUrl, { imn })}
|
|
179
172
|
server.listen(PORT, () => {
|
|
180
173
|
${context.winston ? "logger.info(`API running on http://localhost:${PORT}`);" : "console.log(`API running on http://localhost:${PORT}`);"}
|
|
181
174
|
});
|
|
182
175
|
}
|
|
183
176
|
|
|
184
|
-
|
|
177
|
+
startServer().catch((error) => {
|
|
185
178
|
${context.winston ? "logger.error(error);" : "console.error(error);"}
|
|
186
179
|
process.exit(1);
|
|
187
180
|
});
|
|
188
181
|
`,
|
|
189
182
|
[`src/app.${e}`]: `import "dotenv/config";
|
|
190
183
|
import express from "express";
|
|
191
|
-
${context.cookieParser === false ? "" : "import cookieParser from \"cookie-parser\";"}
|
|
192
184
|
${context.cors === false ? "" : "import cors from \"cors\";"}
|
|
193
185
|
${context.helmet === false ? "" : "import helmet from \"helmet\";"}
|
|
194
186
|
${context.rateLimit === false ? "" : "import rateLimit from \"express-rate-limit\";"}
|
|
195
187
|
${context.hpp ? "import hpp from \"hpp\";" : ""}
|
|
196
188
|
${imports.morgan}
|
|
197
189
|
${imports.swagger}
|
|
190
|
+
${context.multer ? "import { uploadsStatic } from \"./middleware/uploadMiddleware.js\";" : ""}
|
|
198
191
|
import apiRoutes from "./routes/mainRoute.js";
|
|
199
192
|
import { errorHandler } from "./middleware/errorMiddleware.js";
|
|
200
193
|
import { notFound } from "./middleware/notFoundMiddleware.js";
|
|
@@ -208,17 +201,12 @@ ${context.cors === false ? "" : `app.use(cors({
|
|
|
208
201
|
credentials: true
|
|
209
202
|
}));`}
|
|
210
203
|
${context.rateLimit === false ? "" : "app.use(rateLimit({ windowMs: 15 * 60 * 1000, limit: 300 }));"}
|
|
211
|
-
app.use("/uploads",
|
|
204
|
+
${context.multer ? 'app.use("/uploads", uploadsStatic);' : ""}
|
|
212
205
|
app.use(express.json({ limit: "1mb" }));
|
|
213
206
|
app.use(express.urlencoded({ extended: true }));
|
|
214
|
-
${context.cookieParser === false ? "" : "app.use(cookieParser());"}
|
|
215
207
|
${context.morgan ? "app.use(morgan(\"dev\"));" : ""}
|
|
216
208
|
${context.swagger ? "setupSwagger(app);" : ""}
|
|
217
209
|
|
|
218
|
-
app.get("/api/health", (_req, res) => {
|
|
219
|
-
res.json({ status: "ok", service: "stackflow-api" });
|
|
220
|
-
});
|
|
221
|
-
|
|
222
210
|
app.use("/api", apiRoutes);
|
|
223
211
|
app.use(notFound);
|
|
224
212
|
app.use(errorHandler);
|
|
@@ -274,37 +262,30 @@ export function signToken(userId) {
|
|
|
274
262
|
expiresIn: process.env.JWT_EXPIRES_IN || "7d"
|
|
275
263
|
});
|
|
276
264
|
}
|
|
277
|
-
|
|
278
|
-
export function setAuthCookie(res, token) {
|
|
279
|
-
res.cookie(process.env.COOKIE_NAME || "stackflow_token", token, {
|
|
280
|
-
httpOnly: true,
|
|
281
|
-
sameSite: "lax",
|
|
282
|
-
secure: process.env.NODE_ENV === "production",
|
|
283
|
-
maxAge: 7 * 24 * 60 * 60 * 1000
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
265
|
`,
|
|
287
266
|
[`src/middleware/authMiddleware.${e}`]: `${reqRes}import jwt from "jsonwebtoken";
|
|
288
267
|
import { User } from "../models/userModel.js";
|
|
289
268
|
|
|
290
269
|
export async function protect(req${type("Request & { user?: unknown }")}, res${type("Response")}, next${type("NextFunction")}) {
|
|
291
270
|
try {
|
|
292
|
-
const
|
|
293
|
-
const headerToken = req.headers.authorization?.startsWith("Bearer ")
|
|
271
|
+
const token = req.headers.authorization?.startsWith("Bearer ")
|
|
294
272
|
? req.headers.authorization.split(" ")[1]
|
|
295
273
|
: undefined;
|
|
296
|
-
|
|
297
|
-
|
|
274
|
+
if (!token) {
|
|
275
|
+
return res.status(401).json({ success: false, message: "Authentication required" });
|
|
276
|
+
}
|
|
298
277
|
|
|
299
278
|
const decoded = jwt.verify(token, process.env.JWT_SECRET || "dev_secret");
|
|
300
279
|
const userId = typeof decoded === "object" && "userId" in decoded ? decoded.userId : null;
|
|
301
280
|
const user = await User.findById(userId).select("-password");
|
|
302
|
-
if (!user)
|
|
281
|
+
if (!user) {
|
|
282
|
+
return res.status(401).json({ success: false, message: "Invalid authentication" });
|
|
283
|
+
}
|
|
303
284
|
|
|
304
285
|
req.user = user;
|
|
305
286
|
next();
|
|
306
287
|
} catch {
|
|
307
|
-
res.status(401).json({ message: "Invalid or expired token" });
|
|
288
|
+
res.status(401).json({ success: false, message: "Invalid or expired token" });
|
|
308
289
|
}
|
|
309
290
|
}
|
|
310
291
|
`,
|
|
@@ -321,49 +302,75 @@ export async function protect(req${type("Request & { user?: unknown }")}, res${t
|
|
|
321
302
|
}
|
|
322
303
|
`,
|
|
323
304
|
[`src/controllers/authController.${e}`]: `import { User } from "../models/userModel.js";
|
|
324
|
-
import { signToken
|
|
305
|
+
import { signToken } from "../utils/jwt.js";
|
|
325
306
|
|
|
326
307
|
function publicUser(user) {
|
|
327
308
|
return { id: user._id, name: user.name, email: user.email };
|
|
328
309
|
}
|
|
329
310
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
311
|
+
const authController = {
|
|
312
|
+
// Register
|
|
313
|
+
register: async (req, res) => {
|
|
314
|
+
try {
|
|
315
|
+
const { name, email, password } = req.body;
|
|
316
|
+
const existing = await User.findOne({ email });
|
|
317
|
+
if (existing) {
|
|
318
|
+
return res.status(409).json({ success: false, message: "Email already registered" });
|
|
319
|
+
}
|
|
320
|
+
const user = await User.create({ name, email, password });
|
|
321
|
+
const token = signToken(user._id);
|
|
322
|
+
res.status(201).json({
|
|
323
|
+
success: true,
|
|
324
|
+
message: "Account created successfully",
|
|
325
|
+
data: { user: publicUser(user), token }
|
|
326
|
+
});
|
|
327
|
+
} catch (error) {
|
|
328
|
+
res.status(500).json({ success: false, message: error.message });
|
|
329
|
+
}
|
|
330
|
+
},
|
|
343
331
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
332
|
+
// Login
|
|
333
|
+
login: async (req, res) => {
|
|
334
|
+
try {
|
|
335
|
+
const { email, password } = req.body;
|
|
336
|
+
const user = await User.findOne({ email });
|
|
337
|
+
if (!user || !(await user.comparePassword(password))) {
|
|
338
|
+
return res.status(401).json({ success: false, message: "Invalid email or password" });
|
|
339
|
+
}
|
|
340
|
+
const token = signToken(user._id);
|
|
341
|
+
res.status(200).json({
|
|
342
|
+
success: true,
|
|
343
|
+
message: "Logged in successfully",
|
|
344
|
+
data: { user: publicUser(user), token }
|
|
345
|
+
});
|
|
346
|
+
} catch (error) {
|
|
347
|
+
res.status(500).json({ success: false, message: error.message });
|
|
350
348
|
}
|
|
351
|
-
|
|
352
|
-
setAuthCookie(res, token);
|
|
353
|
-
res.json({ user: publicUser(user), token });
|
|
354
|
-
} catch (error) {
|
|
355
|
-
next(error);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
349
|
+
},
|
|
358
350
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
351
|
+
// Current user
|
|
352
|
+
me: async (req, res) => {
|
|
353
|
+
try {
|
|
354
|
+
res.status(200).json({
|
|
355
|
+
success: true,
|
|
356
|
+
message: "Profile fetched successfully",
|
|
357
|
+
data: { user: publicUser(req.user) }
|
|
358
|
+
});
|
|
359
|
+
} catch (error) {
|
|
360
|
+
res.status(500).json({ success: false, message: error.message });
|
|
361
|
+
}
|
|
362
|
+
},
|
|
362
363
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
364
|
+
// Logout (client clears token)
|
|
365
|
+
logout: async (_req, res) => {
|
|
366
|
+
res.status(200).json({
|
|
367
|
+
success: true,
|
|
368
|
+
message: "Logged out successfully"
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
export default authController;
|
|
367
374
|
`,
|
|
368
375
|
[`src/controllers/todoController.${e}`]: `import { Todo } from "../models/todoModel.js";
|
|
369
376
|
|
|
@@ -450,15 +457,15 @@ const todoController = {
|
|
|
450
457
|
export default todoController;
|
|
451
458
|
`,
|
|
452
459
|
[`src/routes/authRoute.${e}`]: `import { Router } from "express";
|
|
453
|
-
import
|
|
460
|
+
import authController from "../controllers/authController.js";
|
|
454
461
|
import { protect } from "../middleware/authMiddleware.js";
|
|
455
462
|
|
|
456
463
|
const router = Router();
|
|
457
464
|
|
|
458
|
-
router.post("/register", register);
|
|
459
|
-
router.post("/login", login);
|
|
460
|
-
router.get("/me", protect, me);
|
|
461
|
-
router.post("/logout", logout);
|
|
465
|
+
router.post("/register", authController.register);
|
|
466
|
+
router.post("/login", authController.login);
|
|
467
|
+
router.get("/me", protect, authController.me);
|
|
468
|
+
router.post("/logout", protect, authController.logout);
|
|
462
469
|
|
|
463
470
|
export default router;
|
|
464
471
|
`,
|
|
@@ -476,7 +483,7 @@ export default router;
|
|
|
476
483
|
[`src/routes/todoRoute.${e}`]: `import { Router } from "express";
|
|
477
484
|
import todoController from "../controllers/todoController.js";
|
|
478
485
|
import { protect } from "../middleware/authMiddleware.js";
|
|
479
|
-
${context.multer ? "import { upload } from \"../
|
|
486
|
+
${context.multer ? "import { upload } from \"../middleware/uploadMiddleware.js\";" : ""}
|
|
480
487
|
|
|
481
488
|
const router = Router();
|
|
482
489
|
|
|
@@ -489,34 +496,35 @@ router.delete("/:id", todoController.deleteTodo);
|
|
|
489
496
|
|
|
490
497
|
export default router;
|
|
491
498
|
`,
|
|
492
|
-
[`src/
|
|
493
|
-
|
|
494
|
-
|
|
499
|
+
[`src/middleware/uploadMiddleware.${e}`]: context.multer
|
|
500
|
+
? `${im("multer", "multer")}
|
|
501
|
+
${im("express", "express")}
|
|
502
|
+
${esm ? "" : "const { diskStorage } = multer;\n"}
|
|
503
|
+
const storage = ${esm ? "multer.diskStorage" : "diskStorage"}({
|
|
495
504
|
destination: "uploads/",
|
|
496
505
|
filename: (_req, file, callback) => {
|
|
497
506
|
callback(null, \`\${Date.now()}-\${file.originalname}\`);
|
|
498
507
|
}
|
|
499
508
|
});
|
|
500
509
|
|
|
501
|
-
export const upload = multer({ storage });
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
}
|
|
508
|
-
`,
|
|
509
|
-
[`src/utils/logger.${e}`]: context.winston ? `import winston from "winston";
|
|
510
|
+
${esm ? "export const upload = multer({ storage });" : "const upload = multer({ storage });\n"}
|
|
511
|
+
${esm ? "export const uploadsStatic = express.static(\"uploads\");" : "const uploadsStatic = express.static(\"uploads\");\n"}
|
|
512
|
+
${esm ? "" : "module.exports = { upload, uploadsStatic };"}
|
|
513
|
+
`
|
|
514
|
+
: undefined,
|
|
515
|
+
[`src/utils/logger.${e}`]: context.winston
|
|
516
|
+
? `${im("winston", "winston")}
|
|
510
517
|
|
|
511
|
-
|
|
518
|
+
const logger = winston.createLogger({
|
|
512
519
|
level: "info",
|
|
513
520
|
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
|
|
514
521
|
transports: [new winston.transports.Console()]
|
|
515
522
|
});
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
523
|
+
${esm ? "export { logger };" : "module.exports = { logger };"}
|
|
524
|
+
`
|
|
525
|
+
: undefined,
|
|
526
|
+
[`src/config/swagger.${e}`]: context.swagger ? `${im("swaggerJSDoc", "swagger-jsdoc")}
|
|
527
|
+
${im("swaggerUi", "swagger-ui-express")}
|
|
520
528
|
|
|
521
529
|
export function setupSwagger(app) {
|
|
522
530
|
const spec = swaggerJSDoc({
|
|
@@ -529,9 +537,11 @@ export function setupSwagger(app) {
|
|
|
529
537
|
|
|
530
538
|
app.use("/api/docs", swaggerUi.serve, swaggerUi.setup(spec));
|
|
531
539
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
540
|
+
${esm ? "" : "\nmodule.exports = { setupSwagger };"}
|
|
541
|
+
`
|
|
542
|
+
: undefined,
|
|
543
|
+
[`src/config/cloudinary.${e}`]: context.cloudinary
|
|
544
|
+
? `${esm ? 'import { v2 as cloudinary } from "cloudinary";' : 'const { v2: cloudinary } = require("cloudinary");'}
|
|
535
545
|
|
|
536
546
|
cloudinary.config({
|
|
537
547
|
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
|
|
@@ -539,8 +549,25 @@ cloudinary.config({
|
|
|
539
549
|
api_secret: process.env.CLOUDINARY_API_SECRET
|
|
540
550
|
});
|
|
541
551
|
|
|
542
|
-
export { cloudinary };
|
|
543
|
-
` : `export {};
|
|
552
|
+
${esm ? "export { cloudinary };" : "module.exports = { cloudinary };"}
|
|
544
553
|
`
|
|
554
|
+
: undefined,
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
const merged = {
|
|
558
|
+
...templates,
|
|
559
|
+
...chatBackendFiles(context, e, { im, imn, esm }),
|
|
545
560
|
};
|
|
561
|
+
|
|
562
|
+
if (merged[`src/routes/mainRoute.${e}`]) {
|
|
563
|
+
merged[`src/routes/mainRoute.${e}`] = patchMainRouteForChat(
|
|
564
|
+
merged[`src/routes/mainRoute.${e}`],
|
|
565
|
+
context,
|
|
566
|
+
e,
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return Object.fromEntries(
|
|
571
|
+
Object.entries(merged).filter(([, value]) => value !== undefined),
|
|
572
|
+
);
|
|
546
573
|
}
|