4bnode 1.0.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.
- package/README.md +199 -0
- package/README_4bnode.md +197 -0
- package/README_final_4bnode.md +199 -0
- package/add-api.js +200 -0
- package/add-login.js +395 -0
- package/add-mongo-delete.js +153 -0
- package/add-mongo-insert.js +231 -0
- package/add-mongo-read.js +235 -0
- package/add-mongo-schema.js +231 -0
- package/add-mongo-update.js +229 -0
- package/add-mongo.js +257 -0
- package/add-serialport.js +124 -0
- package/add-socket.js +110 -0
- package/add-websocket.js +199 -0
- package/index.js +226 -0
- package/package.json +40 -0
- package/skeleton/.env.development +1 -0
- package/skeleton/.env.production +1 -0
- package/skeleton/index.js +45 -0
- package/skeleton/package.json +19 -0
- package/skeleton/public/index.html +90 -0
- package/src/auth/passport.js +21 -0
- package/src/db.js +13 -0
- package/test-server/.env.development +1 -0
- package/test-server/.env.production +1 -0
- package/test-server/index.js +82 -0
- package/test-server/package-lock.json +1267 -0
- package/test-server/package.json +20 -0
- package/test-server/public/index.html +90 -0
package/add-api.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import readline from "readline";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
|
|
8
|
+
const rl = readline.createInterface({
|
|
9
|
+
input: process.stdin,
|
|
10
|
+
output: process.stdout,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const methods = ["get", "post", "put", "delete", "patch"];
|
|
14
|
+
|
|
15
|
+
function toCamelCase(str) {
|
|
16
|
+
return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function checkDuplicateApi(endpointName) {
|
|
20
|
+
const projectRoot = process.cwd();
|
|
21
|
+
const apiDir = path.join(projectRoot, "src", "routes");
|
|
22
|
+
const apiFile = path.join(apiDir, `${endpointName}.js`);
|
|
23
|
+
return fs.existsSync(apiFile);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function checkDuplicateMethod(apiFile, method) {
|
|
27
|
+
const apiContent = fs.readFileSync(apiFile, "utf8");
|
|
28
|
+
const methodPattern = new RegExp(`router\\.${method}\\(`);
|
|
29
|
+
return methodPattern.test(apiContent);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function createApi(endpointName, method, isAuthorized, selectedPath) {
|
|
33
|
+
const projectRoot = process.cwd();
|
|
34
|
+
const apiDir = path.join(projectRoot, "src", "routes");
|
|
35
|
+
const apiFile = path.join(apiDir, `${endpointName}.js`);
|
|
36
|
+
const variableName = toCamelCase(endpointName);
|
|
37
|
+
|
|
38
|
+
if (!fs.existsSync(apiDir)) {
|
|
39
|
+
fs.mkdirSync(apiDir, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (fs.existsSync(apiFile) && checkDuplicateMethod(apiFile, method)) {
|
|
43
|
+
console.error(
|
|
44
|
+
chalk.red(
|
|
45
|
+
`API endpoint ${selectedPath} with method ${method.toUpperCase()} already exists. Choose a different method or endpoint name.`
|
|
46
|
+
)
|
|
47
|
+
);
|
|
48
|
+
rl.close();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let apiContent;
|
|
53
|
+
const authMiddleware = isAuthorized ? "auth, " : "";
|
|
54
|
+
const methodContent = `
|
|
55
|
+
router.${method.toLowerCase()}('/', ${authMiddleware}async (req, res) => {
|
|
56
|
+
res.json({ msg: '${endpointName} ${method.toUpperCase()} endpoint works' });
|
|
57
|
+
});
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
if (fs.existsSync(apiFile)) {
|
|
61
|
+
// Read existing content and append the new method if not already present
|
|
62
|
+
apiContent = fs.readFileSync(apiFile, "utf8");
|
|
63
|
+
if (!apiContent.includes(methodContent.trim())) {
|
|
64
|
+
const insertionIndex = apiContent.lastIndexOf("export default router;");
|
|
65
|
+
apiContent =
|
|
66
|
+
apiContent.slice(0, insertionIndex) +
|
|
67
|
+
methodContent.trim() +
|
|
68
|
+
"\n\n" +
|
|
69
|
+
apiContent.slice(insertionIndex);
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
// Create new content for the API file
|
|
73
|
+
apiContent = `
|
|
74
|
+
import express from 'express';
|
|
75
|
+
${isAuthorized ? "import auth from '../middleware/auth.js';" : ""}
|
|
76
|
+
const router = express.Router();
|
|
77
|
+
|
|
78
|
+
router.${method.toLowerCase()}('/', ${authMiddleware}async (req, res) => {
|
|
79
|
+
res.json({ msg: '${endpointName} ${method.toUpperCase()} endpoint works' });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export default router;
|
|
83
|
+
`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
fs.writeFileSync(apiFile, apiContent.trim(), "utf8");
|
|
87
|
+
|
|
88
|
+
// Update index.js to include the new route
|
|
89
|
+
const indexPath = path.join(projectRoot, "index.js");
|
|
90
|
+
const routeImport = `import ${variableName} from './src/routes/${endpointName}.js';\n`;
|
|
91
|
+
const routeUse = `app.use('${selectedPath}', ${variableName});\n`;
|
|
92
|
+
|
|
93
|
+
let indexContent = fs.readFileSync(indexPath, "utf8");
|
|
94
|
+
if (!indexContent.includes(routeImport)) {
|
|
95
|
+
indexContent = routeImport + indexContent;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Ensure there's no duplicate app.use entry
|
|
99
|
+
if (!indexContent.includes(routeUse)) {
|
|
100
|
+
// Check if `app.listen` or `server.listen` exists and replace accordingly
|
|
101
|
+
if (indexContent.includes("app.listen(port")) {
|
|
102
|
+
indexContent = indexContent.replace(
|
|
103
|
+
"app.listen(port",
|
|
104
|
+
routeUse + "app.listen(port"
|
|
105
|
+
);
|
|
106
|
+
} else if (indexContent.includes("server.listen(port")) {
|
|
107
|
+
indexContent = indexContent.replace(
|
|
108
|
+
"server.listen(port",
|
|
109
|
+
routeUse + "server.listen(port"
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fs.writeFileSync(indexPath, indexContent, "utf8");
|
|
115
|
+
|
|
116
|
+
console.log(
|
|
117
|
+
chalk.green(
|
|
118
|
+
`API endpoint ${selectedPath} with method ${method.toUpperCase()} created/updated successfully.`
|
|
119
|
+
)
|
|
120
|
+
);
|
|
121
|
+
rl.close();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function exitWithError(message) {
|
|
125
|
+
console.error(chalk.red(message));
|
|
126
|
+
rl.close();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const apiPathOptions = [
|
|
130
|
+
(endpointName) => `/${endpointName}`,
|
|
131
|
+
(endpointName) => `/api/${endpointName}`,
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
function promptApiPath(endpointName, callback) {
|
|
135
|
+
console.log(chalk.cyan("Choose API path:"));
|
|
136
|
+
apiPathOptions.forEach((option, index) => {
|
|
137
|
+
console.log(chalk.cyan(`${index + 1}. ${option(endpointName)}`));
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
rl.question(
|
|
141
|
+
chalk.yellow("Enter the number for the API path: "),
|
|
142
|
+
(pathAnswer) => {
|
|
143
|
+
const pathIndex = parseInt(pathAnswer, 10) - 1;
|
|
144
|
+
if (pathIndex >= 0 && pathIndex < apiPathOptions.length) {
|
|
145
|
+
const selectedPath = apiPathOptions[pathIndex](endpointName);
|
|
146
|
+
callback(selectedPath);
|
|
147
|
+
} else {
|
|
148
|
+
exitWithError("Invalid selection. Please choose a valid number.");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (process.argv.length !== 3) {
|
|
155
|
+
exitWithError("Usage: add-api <endpointName>");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const endpointName = process.argv[2];
|
|
159
|
+
|
|
160
|
+
promptApiPath(endpointName, (selectedPath) => {
|
|
161
|
+
console.log(chalk.cyan("Choose HTTP method:"));
|
|
162
|
+
methods.forEach((method, index) => {
|
|
163
|
+
console.log(chalk.cyan(`${index + 1}. ${method}`));
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
rl.question(
|
|
167
|
+
chalk.yellow("Enter the number for the HTTP method: "),
|
|
168
|
+
(answer) => {
|
|
169
|
+
const methodIndex = parseInt(answer, 10) - 1;
|
|
170
|
+
if (methodIndex >= 0 && methodIndex < methods.length) {
|
|
171
|
+
const projectRoot = process.cwd();
|
|
172
|
+
const authFilePath = path.join(
|
|
173
|
+
projectRoot,
|
|
174
|
+
"src",
|
|
175
|
+
"middleware",
|
|
176
|
+
"auth.js"
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
if (fs.existsSync(authFilePath)) {
|
|
180
|
+
rl.question(
|
|
181
|
+
chalk.yellow("Should this API be authorized? (y/n): "),
|
|
182
|
+
(authAnswer) => {
|
|
183
|
+
const isAuthorized = authAnswer.trim().toLowerCase() === "y";
|
|
184
|
+
createApi(
|
|
185
|
+
endpointName,
|
|
186
|
+
methods[methodIndex],
|
|
187
|
+
isAuthorized,
|
|
188
|
+
selectedPath
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
} else {
|
|
193
|
+
createApi(endpointName, methods[methodIndex], false, selectedPath);
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
exitWithError("Invalid selection. Please choose a valid number.");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
});
|
package/add-login.js
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import readline from "readline";
|
|
6
|
+
import crypto from "crypto";
|
|
7
|
+
import { execSync } from "child_process";
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
|
|
10
|
+
const rl = readline.createInterface({
|
|
11
|
+
input: process.stdin,
|
|
12
|
+
output: process.stdout,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
function listRoutes() {
|
|
16
|
+
const projectRoot = process.cwd();
|
|
17
|
+
const routesDir = path.join(projectRoot, "src", "routes");
|
|
18
|
+
if (!fs.existsSync(routesDir)) {
|
|
19
|
+
console.error(chalk.red("No routes directory found in src/routes"));
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
const routeFiles = fs
|
|
23
|
+
.readdirSync(routesDir)
|
|
24
|
+
.filter((file) => file.endsWith(".js"));
|
|
25
|
+
if (routeFiles.length === 0) {
|
|
26
|
+
console.error(chalk.red("No route files found in src/routes"));
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
return routeFiles;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function listModels() {
|
|
33
|
+
const projectRoot = process.cwd();
|
|
34
|
+
const modelsDir = path.join(projectRoot, "src", "models");
|
|
35
|
+
if (!fs.existsSync(modelsDir)) {
|
|
36
|
+
console.error(chalk.red("No models directory found in src/models"));
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
const modelFiles = fs
|
|
40
|
+
.readdirSync(modelsDir)
|
|
41
|
+
.filter((file) => file.endsWith(".js"));
|
|
42
|
+
if (modelFiles.length === 0) {
|
|
43
|
+
console.error(chalk.red("No model files found in src/models"));
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
return modelFiles;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getModelSchema(modelName) {
|
|
50
|
+
const projectRoot = process.cwd();
|
|
51
|
+
const modelFilePath = path.join(
|
|
52
|
+
projectRoot,
|
|
53
|
+
"src",
|
|
54
|
+
"models",
|
|
55
|
+
modelName + ".js"
|
|
56
|
+
);
|
|
57
|
+
const modelFileContent = fs.readFileSync(modelFilePath, "utf8");
|
|
58
|
+
|
|
59
|
+
const schemaRegex = /new mongoose\.Schema\(\s*\{([\s\S]*?)\}\s*\)/m;
|
|
60
|
+
const match = modelFileContent.match(schemaRegex);
|
|
61
|
+
|
|
62
|
+
if (match) {
|
|
63
|
+
try {
|
|
64
|
+
const schemaContent = match[1];
|
|
65
|
+
const fieldRegex = /(\w+):\s*\{[\s\S]*?\}/g;
|
|
66
|
+
const schemaKeys = [];
|
|
67
|
+
let fieldMatch;
|
|
68
|
+
while ((fieldMatch = fieldRegex.exec(schemaContent)) !== null) {
|
|
69
|
+
schemaKeys.push(fieldMatch[1]);
|
|
70
|
+
}
|
|
71
|
+
return schemaKeys;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error(chalk.red("Error parsing schema: "), error);
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
console.error(chalk.red("Schema not found in model file."));
|
|
78
|
+
process.exit(0);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function promptSelectFields(fields, callback) {
|
|
83
|
+
console.log(
|
|
84
|
+
chalk.yellow(
|
|
85
|
+
"Select the fields to include in the data insertion (space-separated list of numbers):"
|
|
86
|
+
)
|
|
87
|
+
);
|
|
88
|
+
fields.forEach((field, index) => {
|
|
89
|
+
console.log(chalk.cyan(`${index + 1}. ${field}`));
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
rl.question(
|
|
93
|
+
chalk.yellow("Enter the numbers of the fields: "),
|
|
94
|
+
(fieldAnswer) => {
|
|
95
|
+
const selectedIndexes = fieldAnswer
|
|
96
|
+
.split(" ")
|
|
97
|
+
.map((index) => parseInt(index.trim(), 10) - 1);
|
|
98
|
+
const selectedFields = selectedIndexes
|
|
99
|
+
.map((index) => fields[index])
|
|
100
|
+
.filter((field) => field !== undefined);
|
|
101
|
+
callback(selectedFields);
|
|
102
|
+
rl.close();
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function generateJwtSecret() {
|
|
108
|
+
return crypto.randomBytes(32).toString("hex");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function updateEnvFile(filePath, jwtSecret) {
|
|
112
|
+
const jwtSecretLine = `JWT_SECRET=${jwtSecret}\n`;
|
|
113
|
+
|
|
114
|
+
if (fs.existsSync(filePath)) {
|
|
115
|
+
let envContent = fs.readFileSync(filePath, "utf8");
|
|
116
|
+
if (envContent.includes("JWT_SECRET=")) {
|
|
117
|
+
envContent = envContent.replace(/JWT_SECRET=.*/, jwtSecretLine.trim());
|
|
118
|
+
} else {
|
|
119
|
+
envContent = envContent.trim() + "\n" + jwtSecretLine;
|
|
120
|
+
}
|
|
121
|
+
fs.writeFileSync(filePath, envContent, "utf8");
|
|
122
|
+
} else {
|
|
123
|
+
fs.writeFileSync(filePath, jwtSecretLine, "utf8");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function installDependencies() {
|
|
128
|
+
console.log(chalk.green("Installing dependencies: jsonwebtoken, bcrypt..."));
|
|
129
|
+
execSync("npm install jsonwebtoken bcrypt", { stdio: "inherit" });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function addLoginTemplateToRoute(routeFile, fields, modelName, useExpiry) {
|
|
133
|
+
const projectRoot = process.cwd();
|
|
134
|
+
const routeFilePath = path.join(projectRoot, "src", "routes", routeFile);
|
|
135
|
+
const pascalModelName =
|
|
136
|
+
modelName.charAt(0).toUpperCase() + modelName.slice(1);
|
|
137
|
+
const loginTemplateImport = `
|
|
138
|
+
import bcrypt from 'bcrypt';
|
|
139
|
+
import jwt from 'jsonwebtoken';
|
|
140
|
+
import ${pascalModelName} from '../models/${modelName}.js';
|
|
141
|
+
`;
|
|
142
|
+
|
|
143
|
+
const loginTemplateFunction = `
|
|
144
|
+
// Login logic
|
|
145
|
+
const { ${fields.join(", ")} } = req.body;
|
|
146
|
+
try {
|
|
147
|
+
const user = await ${pascalModelName}.findOne({ ${fields[0]}: ${fields[0]} });
|
|
148
|
+
if (!user) {
|
|
149
|
+
return res.status(404).json({ message: 'User not found' });
|
|
150
|
+
}
|
|
151
|
+
${
|
|
152
|
+
fields.includes("password")
|
|
153
|
+
? `
|
|
154
|
+
const isMatch = await bcrypt.compare(password, user.password);
|
|
155
|
+
if (!isMatch) {
|
|
156
|
+
return res.status(400).json({ message: 'Invalid credentials' });
|
|
157
|
+
}`
|
|
158
|
+
: ""
|
|
159
|
+
}
|
|
160
|
+
const payload = { id: user._id };
|
|
161
|
+
${
|
|
162
|
+
useExpiry
|
|
163
|
+
? `
|
|
164
|
+
const token = jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '15m' });
|
|
165
|
+
const refreshToken = jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '7d' });
|
|
166
|
+
res.json({ token, refreshToken });`
|
|
167
|
+
: `
|
|
168
|
+
const token = jwt.sign(payload, process.env.JWT_SECRET);
|
|
169
|
+
res.json({ token });`
|
|
170
|
+
}
|
|
171
|
+
} catch (err) {
|
|
172
|
+
console.error(err);
|
|
173
|
+
res.status(500).json({ message: 'Server error' });
|
|
174
|
+
}
|
|
175
|
+
`;
|
|
176
|
+
|
|
177
|
+
if (!fs.existsSync(routeFilePath)) {
|
|
178
|
+
console.error(
|
|
179
|
+
chalk.red(`The specified route file does not exist: ${routeFilePath}`)
|
|
180
|
+
);
|
|
181
|
+
process.exit(0);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let routeContent = fs.readFileSync(routeFilePath, "utf8");
|
|
185
|
+
|
|
186
|
+
if (!routeContent.includes("import bcrypt")) {
|
|
187
|
+
routeContent = loginTemplateImport + routeContent;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const lastCloseIndex = routeContent.lastIndexOf("});");
|
|
191
|
+
if (lastCloseIndex !== -1) {
|
|
192
|
+
const updatedContent = [
|
|
193
|
+
routeContent.slice(0, lastCloseIndex),
|
|
194
|
+
loginTemplateFunction,
|
|
195
|
+
routeContent.slice(lastCloseIndex),
|
|
196
|
+
].join("\n");
|
|
197
|
+
|
|
198
|
+
routeContent = updatedContent;
|
|
199
|
+
} else {
|
|
200
|
+
routeContent += `\n${loginTemplateFunction}\n`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
fs.writeFileSync(routeFilePath, routeContent, "utf8");
|
|
204
|
+
console.log(chalk.green(`Login template added to ${routeFile}`));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function addAuthMiddleware() {
|
|
208
|
+
const projectRoot = process.cwd();
|
|
209
|
+
const authFilePath = path.join(projectRoot, "src", "middleware", "auth.js");
|
|
210
|
+
|
|
211
|
+
if (fs.existsSync(authFilePath)) {
|
|
212
|
+
console.log(chalk.yellow("Auth middleware already exists."));
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const authMiddlewareContent = `
|
|
217
|
+
import jwt from 'jsonwebtoken';
|
|
218
|
+
|
|
219
|
+
const auth = (req, res, next) => {
|
|
220
|
+
const token = req.header('Authorization')?.replace('Bearer ', '');
|
|
221
|
+
if (!token) {
|
|
222
|
+
return res.status(401).json({ message: 'No token, authorization denied' });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
|
227
|
+
req.user = decoded;
|
|
228
|
+
next();
|
|
229
|
+
} catch (err) {
|
|
230
|
+
res.status(401).json({ message: 'Token is not valid' });
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
export default auth;
|
|
235
|
+
`;
|
|
236
|
+
|
|
237
|
+
fs.mkdirSync(path.join(projectRoot, "src", "middleware"), {
|
|
238
|
+
recursive: true,
|
|
239
|
+
});
|
|
240
|
+
fs.writeFileSync(authFilePath, authMiddlewareContent.trim(), "utf8");
|
|
241
|
+
console.log(chalk.green("Auth middleware created successfully."));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function addRefreshTokenRoute() {
|
|
245
|
+
const projectRoot = process.cwd();
|
|
246
|
+
const routesDir = path.join(projectRoot, "src", "routes");
|
|
247
|
+
const refreshTokenFilePath = path.join(routesDir, "refresh-token.js");
|
|
248
|
+
|
|
249
|
+
if (fs.existsSync(refreshTokenFilePath)) {
|
|
250
|
+
console.log(chalk.yellow("Refresh token route already exists."));
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const refreshTokenContent = `
|
|
255
|
+
import express from 'express';
|
|
256
|
+
import jwt from 'jsonwebtoken';
|
|
257
|
+
|
|
258
|
+
const router = express.Router();
|
|
259
|
+
|
|
260
|
+
router.post('/', (req, res) => {
|
|
261
|
+
const { refreshToken } = req.body;
|
|
262
|
+
if (!refreshToken) {
|
|
263
|
+
return res.status(401).json({ message: 'Refresh token not provided' });
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const decoded = jwt.verify(refreshToken, process.env.JWT_SECRET);
|
|
268
|
+
const payload = { id: decoded.id };
|
|
269
|
+
const token = jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '15m' });
|
|
270
|
+
res.json({ token });
|
|
271
|
+
} catch (err) {
|
|
272
|
+
console.error(err);
|
|
273
|
+
res.status(401).json({ message: 'Invalid refresh token' });
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
export default router;
|
|
278
|
+
`;
|
|
279
|
+
|
|
280
|
+
fs.mkdirSync(routesDir, { recursive: true });
|
|
281
|
+
fs.writeFileSync(refreshTokenFilePath, refreshTokenContent.trim(), "utf8");
|
|
282
|
+
|
|
283
|
+
// Update index.js to include the new route
|
|
284
|
+
const indexPath = path.join(projectRoot, "index.js");
|
|
285
|
+
const routeImport = `import refreshToken from './src/routes/refresh-token.js';\n`;
|
|
286
|
+
const routeUse = `app.use('/refresh-token', refreshToken);\n`;
|
|
287
|
+
|
|
288
|
+
let indexContent = fs.readFileSync(indexPath, "utf8");
|
|
289
|
+
if (!indexContent.includes(routeImport)) {
|
|
290
|
+
indexContent = routeImport + indexContent;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!indexContent.includes(routeUse)) {
|
|
294
|
+
if (indexContent.includes("app.listen(port")) {
|
|
295
|
+
indexContent = indexContent.replace(
|
|
296
|
+
"app.listen(port",
|
|
297
|
+
routeUse + "app.listen(port"
|
|
298
|
+
);
|
|
299
|
+
} else if (indexContent.includes("server.listen(port")) {
|
|
300
|
+
indexContent = indexContent.replace(
|
|
301
|
+
"server.listen(port",
|
|
302
|
+
routeUse + "server.listen(port"
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
fs.writeFileSync(indexPath, indexContent, "utf8");
|
|
308
|
+
|
|
309
|
+
console.log(chalk.green("Refresh token route created successfully."));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const routeFiles = listRoutes();
|
|
313
|
+
const modelFiles = listModels();
|
|
314
|
+
|
|
315
|
+
console.log(chalk.yellow("Select the route file to modify:"));
|
|
316
|
+
routeFiles.forEach((file, index) => {
|
|
317
|
+
console.log(chalk.cyan(`${index + 1}. ${file}`));
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
rl.question(
|
|
321
|
+
chalk.yellow("Enter the number of the route file: "),
|
|
322
|
+
(routeAnswer) => {
|
|
323
|
+
const routeIndex = parseInt(routeAnswer, 10) - 1;
|
|
324
|
+
if (routeIndex >= 0 && routeIndex < routeFiles.length) {
|
|
325
|
+
const selectedRoute = routeFiles[routeIndex];
|
|
326
|
+
|
|
327
|
+
console.log(chalk.yellow("Select the model file to use:"));
|
|
328
|
+
modelFiles.forEach((file, index) => {
|
|
329
|
+
console.log(chalk.cyan(`${index + 1}. ${file}`));
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
rl.question(
|
|
333
|
+
chalk.yellow("Enter the number of the model file: "),
|
|
334
|
+
(modelAnswer) => {
|
|
335
|
+
const modelIndex = parseInt(modelAnswer, 10) - 1;
|
|
336
|
+
if (modelIndex >= 0 && modelIndex < modelFiles.length) {
|
|
337
|
+
const selectedModel = path.basename(modelFiles[modelIndex], ".js");
|
|
338
|
+
const schemaFields = getModelSchema(selectedModel);
|
|
339
|
+
|
|
340
|
+
rl.question(
|
|
341
|
+
chalk.yellow(
|
|
342
|
+
"Do you want to use expiry date for JWT token? (y/n): "
|
|
343
|
+
),
|
|
344
|
+
(expiryAnswer) => {
|
|
345
|
+
const useExpiry = expiryAnswer.trim().toLowerCase() === "y";
|
|
346
|
+
promptSelectFields(schemaFields, (fields) => {
|
|
347
|
+
addLoginTemplateToRoute(
|
|
348
|
+
selectedRoute,
|
|
349
|
+
fields,
|
|
350
|
+
selectedModel,
|
|
351
|
+
useExpiry
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
const jwtSecret = generateJwtSecret();
|
|
355
|
+
const projectRoot = process.cwd();
|
|
356
|
+
updateEnvFile(
|
|
357
|
+
path.join(projectRoot, ".env.development"),
|
|
358
|
+
jwtSecret
|
|
359
|
+
);
|
|
360
|
+
updateEnvFile(
|
|
361
|
+
path.join(projectRoot, ".env.production"),
|
|
362
|
+
jwtSecret
|
|
363
|
+
);
|
|
364
|
+
console.log(
|
|
365
|
+
chalk.green(
|
|
366
|
+
"JWT_SECRET has been added to .env.development and .env.production files."
|
|
367
|
+
)
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
installDependencies();
|
|
371
|
+
addAuthMiddleware();
|
|
372
|
+
if (useExpiry) {
|
|
373
|
+
addRefreshTokenRoute();
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
);
|
|
378
|
+
} else {
|
|
379
|
+
console.error(
|
|
380
|
+
chalk.red("Invalid selection. Please choose a valid number.")
|
|
381
|
+
);
|
|
382
|
+
rl.close();
|
|
383
|
+
process.exit(0);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
);
|
|
387
|
+
} else {
|
|
388
|
+
console.error(
|
|
389
|
+
chalk.red("Invalid selection. Please choose a valid number.")
|
|
390
|
+
);
|
|
391
|
+
rl.close();
|
|
392
|
+
process.exit(0);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
);
|