create-stackflow 1.0.4 → 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/README.md +4 -6
- package/cli.js +1 -1
- package/package.json +1 -1
- package/src/commands/create.js +9 -8
- package/src/generators/backend.js +264 -192
- package/src/generators/chat.js +661 -0
- package/src/generators/frontend.js +227 -118
- package/src/generators/root.js +6 -58
- package/src/utils/install.js +42 -0
- package/src/utils/prompts.js +26 -1
|
@@ -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,15 +108,19 @@ 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);
|
|
117
|
+
const files = backendTemplates({ ...context, language: backendContext.language, clientUrl, esm }, e);
|
|
118
|
+
|
|
119
|
+
if (!context.cloudinary) delete files[`src/config/cloudinary.${e}`];
|
|
120
|
+
if (!context.multer) delete files[`src/middleware/uploadMiddleware.${e}`];
|
|
121
|
+
if (!context.winston) delete files[`src/utils/logger.${e}`];
|
|
122
|
+
if (!context.swagger) delete files[`src/config/swagger.${e}`];
|
|
123
|
+
|
|
128
124
|
await Promise.all(Object.entries(files).map(([file, content]) =>
|
|
129
125
|
writeTemplate(path.join(context.backendDir, file), content, context)
|
|
130
126
|
));
|
|
@@ -136,61 +132,65 @@ function compact(object) {
|
|
|
136
132
|
|
|
137
133
|
function backendTemplates(context, e) {
|
|
138
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
|
+
};
|
|
139
150
|
const type = (name) => isTs ? `: ${name}` : "";
|
|
140
151
|
const reqRes = isTs ? "import type { Request, Response, NextFunction } from \"express\";\n" : "";
|
|
141
152
|
const imports = {
|
|
142
|
-
logger: context.winston ?
|
|
143
|
-
morgan: context.morgan ? "
|
|
144
|
-
swagger: context.swagger ?
|
|
145
|
-
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") : "",
|
|
146
157
|
};
|
|
147
158
|
|
|
148
|
-
|
|
149
|
-
[`src/server.${e}`]:
|
|
150
|
-
|
|
151
|
-
|
|
159
|
+
const templates = {
|
|
160
|
+
[`src/server.${e}`]: `${im("http", "node:http")}
|
|
161
|
+
${imDefault("app", "./app", true)}
|
|
162
|
+
${imn("connectDB", "./config/db", true)}
|
|
152
163
|
${imports.socket}
|
|
153
164
|
${imports.logger}
|
|
154
165
|
|
|
155
166
|
const PORT = Number(process.env.PORT) || 5000;
|
|
156
167
|
|
|
157
|
-
async function
|
|
168
|
+
async function startServer() {
|
|
158
169
|
await connectDB();
|
|
159
170
|
const server = http.createServer(app);
|
|
160
|
-
${context.
|
|
161
|
-
const io = new Server(server, {
|
|
162
|
-
cors: {
|
|
163
|
-
origin: process.env.CLIENT_URL || "${context.clientUrl}",
|
|
164
|
-
credentials: true
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
io.on("connection", (socket) => {
|
|
169
|
-
socket.emit("connected", { message: "Socket.IO connected" });
|
|
170
|
-
});
|
|
171
|
-
` : ""}
|
|
171
|
+
${chatServerSocketBlock(context, context.clientUrl, { imn })}
|
|
172
172
|
server.listen(PORT, () => {
|
|
173
173
|
${context.winston ? "logger.info(`API running on http://localhost:${PORT}`);" : "console.log(`API running on http://localhost:${PORT}`);"}
|
|
174
174
|
});
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
|
|
177
|
+
startServer().catch((error) => {
|
|
178
178
|
${context.winston ? "logger.error(error);" : "console.error(error);"}
|
|
179
179
|
process.exit(1);
|
|
180
180
|
});
|
|
181
181
|
`,
|
|
182
182
|
[`src/app.${e}`]: `import "dotenv/config";
|
|
183
183
|
import express from "express";
|
|
184
|
-
${context.cookieParser === false ? "" : "import cookieParser from \"cookie-parser\";"}
|
|
185
184
|
${context.cors === false ? "" : "import cors from \"cors\";"}
|
|
186
185
|
${context.helmet === false ? "" : "import helmet from \"helmet\";"}
|
|
187
186
|
${context.rateLimit === false ? "" : "import rateLimit from \"express-rate-limit\";"}
|
|
188
187
|
${context.hpp ? "import hpp from \"hpp\";" : ""}
|
|
189
188
|
${imports.morgan}
|
|
190
189
|
${imports.swagger}
|
|
191
|
-
import
|
|
192
|
-
import
|
|
193
|
-
import {
|
|
190
|
+
${context.multer ? "import { uploadsStatic } from \"./middleware/uploadMiddleware.js\";" : ""}
|
|
191
|
+
import apiRoutes from "./routes/mainRoute.js";
|
|
192
|
+
import { errorHandler } from "./middleware/errorMiddleware.js";
|
|
193
|
+
import { notFound } from "./middleware/notFoundMiddleware.js";
|
|
194
194
|
|
|
195
195
|
const app = express();
|
|
196
196
|
|
|
@@ -201,17 +201,12 @@ ${context.cors === false ? "" : `app.use(cors({
|
|
|
201
201
|
credentials: true
|
|
202
202
|
}));`}
|
|
203
203
|
${context.rateLimit === false ? "" : "app.use(rateLimit({ windowMs: 15 * 60 * 1000, limit: 300 }));"}
|
|
204
|
-
app.use("/uploads",
|
|
204
|
+
${context.multer ? 'app.use("/uploads", uploadsStatic);' : ""}
|
|
205
205
|
app.use(express.json({ limit: "1mb" }));
|
|
206
206
|
app.use(express.urlencoded({ extended: true }));
|
|
207
|
-
${context.cookieParser === false ? "" : "app.use(cookieParser());"}
|
|
208
207
|
${context.morgan ? "app.use(morgan(\"dev\"));" : ""}
|
|
209
208
|
${context.swagger ? "setupSwagger(app);" : ""}
|
|
210
209
|
|
|
211
|
-
app.get("/api/health", (_req, res) => {
|
|
212
|
-
res.json({ status: "ok", service: "stackflow-api" });
|
|
213
|
-
});
|
|
214
|
-
|
|
215
210
|
app.use("/api", apiRoutes);
|
|
216
211
|
app.use(notFound);
|
|
217
212
|
app.use(errorHandler);
|
|
@@ -228,7 +223,7 @@ export async function connectDB() {
|
|
|
228
223
|
${context.winston ? "logger.info(\"MongoDB connected\");" : "console.log(\"MongoDB connected\");"}
|
|
229
224
|
}
|
|
230
225
|
`,
|
|
231
|
-
[`src/models/
|
|
226
|
+
[`src/models/userModel.${e}`]: `import mongoose from "mongoose";
|
|
232
227
|
import bcrypt from "bcryptjs";
|
|
233
228
|
|
|
234
229
|
const userSchema = new mongoose.Schema({
|
|
@@ -248,9 +243,9 @@ userSchema.methods.comparePassword = function(candidatePassword) {
|
|
|
248
243
|
|
|
249
244
|
export const User = mongoose.model("User", userSchema);
|
|
250
245
|
`,
|
|
251
|
-
[`src/models/
|
|
246
|
+
[`src/models/todoModel.${e}`]: `import mongoose from "mongoose";
|
|
252
247
|
|
|
253
|
-
const
|
|
248
|
+
const todoSchema = new mongoose.Schema({
|
|
254
249
|
title: { type: String, required: true, trim: true },
|
|
255
250
|
description: { type: String, default: "" },
|
|
256
251
|
imageUrl: { type: String, default: "" },
|
|
@@ -258,7 +253,7 @@ const taskSchema = new mongoose.Schema({
|
|
|
258
253
|
owner: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true }
|
|
259
254
|
}, { timestamps: true });
|
|
260
255
|
|
|
261
|
-
export const
|
|
256
|
+
export const Todo = mongoose.model("Todo", todoSchema);
|
|
262
257
|
`,
|
|
263
258
|
[`src/utils/jwt.${e}`]: `import jwt from "jsonwebtoken";
|
|
264
259
|
|
|
@@ -267,41 +262,34 @@ export function signToken(userId) {
|
|
|
267
262
|
expiresIn: process.env.JWT_EXPIRES_IN || "7d"
|
|
268
263
|
});
|
|
269
264
|
}
|
|
270
|
-
|
|
271
|
-
export function setAuthCookie(res, token) {
|
|
272
|
-
res.cookie(process.env.COOKIE_NAME || "stackflow_token", token, {
|
|
273
|
-
httpOnly: true,
|
|
274
|
-
sameSite: "lax",
|
|
275
|
-
secure: process.env.NODE_ENV === "production",
|
|
276
|
-
maxAge: 7 * 24 * 60 * 60 * 1000
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
265
|
`,
|
|
280
|
-
[`src/middleware/
|
|
281
|
-
import { User } from "../models/
|
|
266
|
+
[`src/middleware/authMiddleware.${e}`]: `${reqRes}import jwt from "jsonwebtoken";
|
|
267
|
+
import { User } from "../models/userModel.js";
|
|
282
268
|
|
|
283
269
|
export async function protect(req${type("Request & { user?: unknown }")}, res${type("Response")}, next${type("NextFunction")}) {
|
|
284
270
|
try {
|
|
285
|
-
const
|
|
286
|
-
const headerToken = req.headers.authorization?.startsWith("Bearer ")
|
|
271
|
+
const token = req.headers.authorization?.startsWith("Bearer ")
|
|
287
272
|
? req.headers.authorization.split(" ")[1]
|
|
288
273
|
: undefined;
|
|
289
|
-
|
|
290
|
-
|
|
274
|
+
if (!token) {
|
|
275
|
+
return res.status(401).json({ success: false, message: "Authentication required" });
|
|
276
|
+
}
|
|
291
277
|
|
|
292
278
|
const decoded = jwt.verify(token, process.env.JWT_SECRET || "dev_secret");
|
|
293
279
|
const userId = typeof decoded === "object" && "userId" in decoded ? decoded.userId : null;
|
|
294
280
|
const user = await User.findById(userId).select("-password");
|
|
295
|
-
if (!user)
|
|
281
|
+
if (!user) {
|
|
282
|
+
return res.status(401).json({ success: false, message: "Invalid authentication" });
|
|
283
|
+
}
|
|
296
284
|
|
|
297
285
|
req.user = user;
|
|
298
286
|
next();
|
|
299
287
|
} catch {
|
|
300
|
-
res.status(401).json({ message: "Invalid or expired token" });
|
|
288
|
+
res.status(401).json({ success: false, message: "Invalid or expired token" });
|
|
301
289
|
}
|
|
302
290
|
}
|
|
303
291
|
`,
|
|
304
|
-
[`src/middleware/
|
|
292
|
+
[`src/middleware/errorMiddleware.${e}`]: `${reqRes}export function errorHandler(error${type("Error & { statusCode?: number }")}, _req${type("Request")}, res${type("Response")}, _next${type("NextFunction")}) {
|
|
305
293
|
const status = error.statusCode || 500;
|
|
306
294
|
res.status(status).json({
|
|
307
295
|
message: error.message || "Server error",
|
|
@@ -309,169 +297,234 @@ export async function protect(req${type("Request & { user?: unknown }")}, res${t
|
|
|
309
297
|
});
|
|
310
298
|
}
|
|
311
299
|
`,
|
|
312
|
-
[`src/middleware/
|
|
300
|
+
[`src/middleware/notFoundMiddleware.${e}`]: `${reqRes}export function notFound(req${type("Request")}, res${type("Response")}) {
|
|
313
301
|
res.status(404).json({ message: \`Route not found: \${req.originalUrl}\` });
|
|
314
302
|
}
|
|
315
303
|
`,
|
|
316
|
-
[`src/controllers/
|
|
317
|
-
import { signToken
|
|
304
|
+
[`src/controllers/authController.${e}`]: `import { User } from "../models/userModel.js";
|
|
305
|
+
import { signToken } from "../utils/jwt.js";
|
|
318
306
|
|
|
319
307
|
function publicUser(user) {
|
|
320
308
|
return { id: user._id, name: user.name, email: user.email };
|
|
321
309
|
}
|
|
322
310
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
+
},
|
|
336
331
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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 });
|
|
343
348
|
}
|
|
344
|
-
|
|
345
|
-
setAuthCookie(res, token);
|
|
346
|
-
res.json({ user: publicUser(user), token });
|
|
347
|
-
} catch (error) {
|
|
348
|
-
next(error);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
349
|
+
},
|
|
351
350
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
+
},
|
|
355
363
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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;
|
|
360
374
|
`,
|
|
361
|
-
[`src/controllers/
|
|
375
|
+
[`src/controllers/todoController.${e}`]: `import { Todo } from "../models/todoModel.js";
|
|
362
376
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
377
|
+
const todoController = {
|
|
378
|
+
// Get All Todos
|
|
379
|
+
getTodos: async (req, res) => {
|
|
380
|
+
try {
|
|
381
|
+
const todos = await Todo.find({ owner: req.user._id }).sort({ createdAt: -1 });
|
|
382
|
+
res.status(200).json({
|
|
383
|
+
success: true,
|
|
384
|
+
message: "Todos fetched successfully",
|
|
385
|
+
data: todos
|
|
386
|
+
});
|
|
387
|
+
} catch (error) {
|
|
388
|
+
res.status(500).json({
|
|
389
|
+
success: false,
|
|
390
|
+
message: error.message
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
},
|
|
371
394
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
395
|
+
// Create Todo
|
|
396
|
+
createTodo: async (req, res) => {
|
|
397
|
+
try {
|
|
398
|
+
const imageUrl = req.file ? \`/uploads/\${req.file.filename}\` : "";
|
|
399
|
+
const newTodo = await Todo.create({ ...req.body, imageUrl, owner: req.user._id });
|
|
400
|
+
res.status(201).json({
|
|
401
|
+
success: true,
|
|
402
|
+
message: "Todo created successfully",
|
|
403
|
+
data: newTodo
|
|
404
|
+
});
|
|
405
|
+
} catch (error) {
|
|
406
|
+
res.status(500).json({
|
|
407
|
+
success: false,
|
|
408
|
+
message: error.message
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
},
|
|
381
412
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
413
|
+
// Update Todo
|
|
414
|
+
updateTodo: async (req, res) => {
|
|
415
|
+
try {
|
|
416
|
+
const { id } = req.params;
|
|
417
|
+
const updates = { ...req.body };
|
|
418
|
+
if (req.file) updates.imageUrl = \`/uploads/\${req.file.filename}\`;
|
|
419
|
+
const todo = await Todo.findOneAndUpdate(
|
|
420
|
+
{ _id: id, owner: req.user._id },
|
|
421
|
+
updates,
|
|
422
|
+
{ new: true, runValidators: true }
|
|
423
|
+
);
|
|
424
|
+
if (!todo) return res.status(404).json({ success: false, message: "Todo not found" });
|
|
425
|
+
res.status(200).json({
|
|
426
|
+
success: true,
|
|
427
|
+
message: \`Todo \${id} updated successfully\`,
|
|
428
|
+
data: todo
|
|
429
|
+
});
|
|
430
|
+
} catch (error) {
|
|
431
|
+
res.status(500).json({
|
|
432
|
+
success: false,
|
|
433
|
+
message: error.message
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
},
|
|
397
437
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
438
|
+
// Delete Todo
|
|
439
|
+
deleteTodo: async (req, res) => {
|
|
440
|
+
try {
|
|
441
|
+
const { id } = req.params;
|
|
442
|
+
const todo = await Todo.findOneAndDelete({ _id: id, owner: req.user._id });
|
|
443
|
+
if (!todo) return res.status(404).json({ success: false, message: "Todo not found" });
|
|
444
|
+
res.status(200).json({
|
|
445
|
+
success: true,
|
|
446
|
+
message: \`Todo \${id} deleted successfully\`
|
|
447
|
+
});
|
|
448
|
+
} catch (error) {
|
|
449
|
+
res.status(500).json({
|
|
450
|
+
success: false,
|
|
451
|
+
message: error.message
|
|
452
|
+
});
|
|
453
|
+
}
|
|
405
454
|
}
|
|
406
|
-
}
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
export default todoController;
|
|
407
458
|
`,
|
|
408
|
-
[`src/routes/
|
|
409
|
-
import
|
|
410
|
-
import { protect } from "../middleware/
|
|
459
|
+
[`src/routes/authRoute.${e}`]: `import { Router } from "express";
|
|
460
|
+
import authController from "../controllers/authController.js";
|
|
461
|
+
import { protect } from "../middleware/authMiddleware.js";
|
|
411
462
|
|
|
412
463
|
const router = Router();
|
|
413
464
|
|
|
414
|
-
router.post("/register", register);
|
|
415
|
-
router.post("/login", login);
|
|
416
|
-
router.get("/me", protect, me);
|
|
417
|
-
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);
|
|
418
469
|
|
|
419
470
|
export default router;
|
|
420
471
|
`,
|
|
421
|
-
[`src/routes/
|
|
422
|
-
import authRoutes from "./
|
|
423
|
-
import
|
|
472
|
+
[`src/routes/mainRoute.${e}`]: `import { Router } from "express";
|
|
473
|
+
import authRoutes from "./authRoute.js";
|
|
474
|
+
import todoRoutes from "./todoRoute.js";
|
|
424
475
|
|
|
425
476
|
const router = Router();
|
|
426
477
|
|
|
427
478
|
router.use("/auth", authRoutes);
|
|
428
|
-
router.use("/
|
|
479
|
+
router.use("/todos", todoRoutes);
|
|
429
480
|
|
|
430
481
|
export default router;
|
|
431
482
|
`,
|
|
432
|
-
[`src/routes/
|
|
433
|
-
import
|
|
434
|
-
import { protect } from "../middleware/
|
|
435
|
-
${context.multer ? "import { upload } from \"../
|
|
483
|
+
[`src/routes/todoRoute.${e}`]: `import { Router } from "express";
|
|
484
|
+
import todoController from "../controllers/todoController.js";
|
|
485
|
+
import { protect } from "../middleware/authMiddleware.js";
|
|
486
|
+
${context.multer ? "import { upload } from \"../middleware/uploadMiddleware.js\";" : ""}
|
|
436
487
|
|
|
437
488
|
const router = Router();
|
|
438
489
|
|
|
439
490
|
router.use(protect);
|
|
440
|
-
|
|
441
|
-
router.
|
|
442
|
-
router.
|
|
443
|
-
router.
|
|
491
|
+
|
|
492
|
+
router.get("/", todoController.getTodos);
|
|
493
|
+
router.post("/", ${context.multer ? "upload.single(\"image\"), " : ""}todoController.createTodo);
|
|
494
|
+
router.put("/:id", ${context.multer ? "upload.single(\"image\"), " : ""}todoController.updateTodo);
|
|
495
|
+
router.delete("/:id", todoController.deleteTodo);
|
|
444
496
|
|
|
445
497
|
export default router;
|
|
446
498
|
`,
|
|
447
|
-
[`src/
|
|
448
|
-
|
|
449
|
-
|
|
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"}({
|
|
450
504
|
destination: "uploads/",
|
|
451
505
|
filename: (_req, file, callback) => {
|
|
452
506
|
callback(null, \`\${Date.now()}-\${file.originalname}\`);
|
|
453
507
|
}
|
|
454
508
|
});
|
|
455
509
|
|
|
456
|
-
export const upload = multer({ storage });
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
463
|
-
`,
|
|
464
|
-
[`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")}
|
|
465
517
|
|
|
466
|
-
|
|
518
|
+
const logger = winston.createLogger({
|
|
467
519
|
level: "info",
|
|
468
520
|
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
|
|
469
521
|
transports: [new winston.transports.Console()]
|
|
470
522
|
});
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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")}
|
|
475
528
|
|
|
476
529
|
export function setupSwagger(app) {
|
|
477
530
|
const spec = swaggerJSDoc({
|
|
@@ -484,9 +537,11 @@ export function setupSwagger(app) {
|
|
|
484
537
|
|
|
485
538
|
app.use("/api/docs", swaggerUi.serve, swaggerUi.setup(spec));
|
|
486
539
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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");'}
|
|
490
545
|
|
|
491
546
|
cloudinary.config({
|
|
492
547
|
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
|
|
@@ -494,8 +549,25 @@ cloudinary.config({
|
|
|
494
549
|
api_secret: process.env.CLOUDINARY_API_SECRET
|
|
495
550
|
});
|
|
496
551
|
|
|
497
|
-
export { cloudinary };
|
|
498
|
-
` : `export {};
|
|
552
|
+
${esm ? "export { cloudinary };" : "module.exports = { cloudinary };"}
|
|
499
553
|
`
|
|
554
|
+
: undefined,
|
|
500
555
|
};
|
|
556
|
+
|
|
557
|
+
const merged = {
|
|
558
|
+
...templates,
|
|
559
|
+
...chatBackendFiles(context, e, { im, imn, esm }),
|
|
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
|
+
);
|
|
501
573
|
}
|