4bnode 3.5.3 → 4.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 +138 -113
- package/add-api.js +288 -156
- package/add-crud.js +226 -0
- package/add-login.js +152 -319
- package/add-mongo-delete.js +67 -138
- package/add-mongo-insert.js +87 -204
- package/add-mongo-read.js +100 -216
- package/add-mongo-schema.js +431 -199
- package/add-mongo-update.js +89 -202
- package/add-mongo.js +125 -218
- package/add-serialport.js +53 -98
- package/add-socket.js +43 -66
- package/add-websocket.js +68 -140
- package/index.js +288 -155
- package/lib/env.js +18 -0
- package/lib/indexFile.js +87 -0
- package/lib/names.js +21 -0
- package/lib/project.js +122 -0
- package/lib/routeFile.js +56 -0
- package/lib/ui.js +161 -0
- package/package.json +9 -16
- package/skeleton/dev-api.js +2785 -0
- package/skeleton/index.js +10 -3
- package/skeleton/package.json +9 -5
- package/skeleton/public/fonts/NotoSans-Bold.woff2 +0 -0
- package/skeleton/public/fonts/NotoSans-Light.woff2 +0 -0
- package/skeleton/public/fonts/NotoSans-Medium.woff2 +0 -0
- package/skeleton/public/fonts/NotoSans-Regular.woff2 +0 -0
- package/skeleton/public/fonts/NotoSans-SemiBold.woff2 +0 -0
- package/skeleton/public/images/mongo.png +0 -0
- package/skeleton/public/images/mysql.png +0 -0
- package/skeleton/public/images/postgresql.png +0 -0
- package/skeleton/public/images/sqlite.png +0 -0
- package/skeleton/public/index.html +7562 -64
- package/README_4bnode.md +0 -197
- package/src/auth/passport.js +0 -21
- package/src/db.js +0 -13
- package/test-server/.env.development +0 -1
- package/test-server/.env.production +0 -1
- package/test-server/index.js +0 -82
- package/test-server/package-lock.json +0 -1267
- package/test-server/package.json +0 -20
- package/test-server/public/index.html +0 -90
package/add-crud.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { select, checkbox } from "@inquirer/prompts";
|
|
5
|
+
import { toPascalCase } from "./lib/names.js";
|
|
6
|
+
import { listRoutes, listModels, getModelSchema } from "./lib/project.js";
|
|
7
|
+
import {
|
|
8
|
+
getRouteFilePath,
|
|
9
|
+
addImportToRoute,
|
|
10
|
+
insertCodeIntoRoute,
|
|
11
|
+
detectHttpMethod,
|
|
12
|
+
getRequestDataSource,
|
|
13
|
+
} from "./lib/routeFile.js";
|
|
14
|
+
import {
|
|
15
|
+
showHeader,
|
|
16
|
+
showTaskDone,
|
|
17
|
+
showError,
|
|
18
|
+
showFileAction,
|
|
19
|
+
installDeps,
|
|
20
|
+
} from "./lib/ui.js";
|
|
21
|
+
|
|
22
|
+
function buildInsertCode(pascalName, fields, dataSource) {
|
|
23
|
+
const assignments = fields
|
|
24
|
+
.map((f) => ` ${f}: ${dataSource}.${f}`)
|
|
25
|
+
.join(",\n");
|
|
26
|
+
|
|
27
|
+
let passwordLogic = "";
|
|
28
|
+
if (fields.includes("password")) {
|
|
29
|
+
passwordLogic = `
|
|
30
|
+
if (newData.password) {
|
|
31
|
+
const salt = await bcrypt.genSalt(10);
|
|
32
|
+
newData.password = await bcrypt.hash(newData.password, salt);
|
|
33
|
+
}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return `
|
|
37
|
+
router.post('/create', async (req, res) => {
|
|
38
|
+
const newData = {
|
|
39
|
+
${assignments}
|
|
40
|
+
};
|
|
41
|
+
${passwordLogic}
|
|
42
|
+
try {
|
|
43
|
+
const newDocument = new ${pascalName}(newData);
|
|
44
|
+
await newDocument.save();
|
|
45
|
+
res.json({ message: 'Data inserted successfully', data: newDocument });
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error(err);
|
|
48
|
+
res.status(500).json({ message: 'Server error' });
|
|
49
|
+
}
|
|
50
|
+
});`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function buildReadCode(pascalName) {
|
|
54
|
+
return `
|
|
55
|
+
router.get('/', async (req, res) => {
|
|
56
|
+
try {
|
|
57
|
+
const documents = await ${pascalName}.find();
|
|
58
|
+
res.json({ data: documents });
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.error(err);
|
|
61
|
+
res.status(500).json({ message: 'Server error' });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
router.get('/:id', async (req, res) => {
|
|
66
|
+
try {
|
|
67
|
+
const document = await ${pascalName}.findById(req.params.id);
|
|
68
|
+
if (!document) {
|
|
69
|
+
return res.status(404).json({ message: 'Document not found' });
|
|
70
|
+
}
|
|
71
|
+
res.json({ data: document });
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.error(err);
|
|
74
|
+
res.status(500).json({ message: 'Server error' });
|
|
75
|
+
}
|
|
76
|
+
});`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function buildUpdateCode(pascalName, fields, dataSource) {
|
|
80
|
+
const assignments = fields
|
|
81
|
+
.map((f) => ` ${f}: ${dataSource}.${f}`)
|
|
82
|
+
.join(",\n");
|
|
83
|
+
|
|
84
|
+
let passwordLogic = "";
|
|
85
|
+
if (fields.includes("password")) {
|
|
86
|
+
passwordLogic = `
|
|
87
|
+
if (updatedData.password) {
|
|
88
|
+
const salt = await bcrypt.genSalt(10);
|
|
89
|
+
updatedData.password = await bcrypt.hash(updatedData.password, salt);
|
|
90
|
+
}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return `
|
|
94
|
+
router.put('/:id', async (req, res) => {
|
|
95
|
+
const updatedData = {
|
|
96
|
+
${assignments}
|
|
97
|
+
};
|
|
98
|
+
${passwordLogic}
|
|
99
|
+
try {
|
|
100
|
+
const updatedDocument = await ${pascalName}.findByIdAndUpdate(req.params.id, updatedData, { new: true });
|
|
101
|
+
if (!updatedDocument) {
|
|
102
|
+
return res.status(404).json({ message: 'Document not found' });
|
|
103
|
+
}
|
|
104
|
+
res.json({ message: 'Data updated successfully', data: updatedDocument });
|
|
105
|
+
} catch (err) {
|
|
106
|
+
console.error(err);
|
|
107
|
+
res.status(500).json({ message: 'Server error' });
|
|
108
|
+
}
|
|
109
|
+
});`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function buildDeleteCode(pascalName) {
|
|
113
|
+
return `
|
|
114
|
+
router.delete('/:id', async (req, res) => {
|
|
115
|
+
try {
|
|
116
|
+
const deletedDocument = await ${pascalName}.findByIdAndDelete(req.params.id);
|
|
117
|
+
if (!deletedDocument) {
|
|
118
|
+
return res.status(404).json({ message: 'Document not found' });
|
|
119
|
+
}
|
|
120
|
+
res.json({ message: 'Data deleted successfully', data: deletedDocument });
|
|
121
|
+
} catch (err) {
|
|
122
|
+
console.error(err);
|
|
123
|
+
res.status(500).json({ message: 'Server error' });
|
|
124
|
+
}
|
|
125
|
+
});`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function main() {
|
|
129
|
+
showHeader("add-crud", "Add CRUD operations to a route");
|
|
130
|
+
|
|
131
|
+
const routeFiles = listRoutes();
|
|
132
|
+
if (routeFiles.length === 0) {
|
|
133
|
+
showError("No route files found in src/routes.");
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const modelFiles = listModels();
|
|
138
|
+
if (modelFiles.length === 0) {
|
|
139
|
+
showError("No model files found in src/models.");
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const selectedRoute = await select({
|
|
144
|
+
message: "Select route file:",
|
|
145
|
+
choices: routeFiles.map((f) => ({ name: f, value: f })),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const selectedModelFile = await select({
|
|
149
|
+
message: "Select model file:",
|
|
150
|
+
choices: modelFiles.map((f) => ({ name: f, value: f })),
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const operations = await checkbox({
|
|
154
|
+
message: "Select CRUD operations:",
|
|
155
|
+
choices: [
|
|
156
|
+
{ name: "Create (POST /create)", value: "create", checked: true },
|
|
157
|
+
{ name: "Read (GET / and GET /:id)", value: "read", checked: true },
|
|
158
|
+
{ name: "Update (PUT /:id)", value: "update", checked: true },
|
|
159
|
+
{ name: "Delete (DELETE /:id)", value: "delete", checked: true },
|
|
160
|
+
],
|
|
161
|
+
validate: (v) => (v.length > 0 ? true : "Select at least one operation."),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const modelName = path.basename(selectedModelFile, ".js");
|
|
165
|
+
const pascalName = toPascalCase(modelName);
|
|
166
|
+
const fields = getModelSchema(modelName);
|
|
167
|
+
|
|
168
|
+
if (fields.length === 0) {
|
|
169
|
+
showError("No fields found in the selected model.");
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log();
|
|
174
|
+
|
|
175
|
+
const routeFilePath = getRouteFilePath(selectedRoute);
|
|
176
|
+
const method = detectHttpMethod(routeFilePath);
|
|
177
|
+
const dataSource = getRequestDataSource(method);
|
|
178
|
+
const needsBcrypt = fields.includes("password") && (operations.includes("create") || operations.includes("update"));
|
|
179
|
+
|
|
180
|
+
// Build import
|
|
181
|
+
let importBlock = `import ${pascalName} from '../models/${modelName}.js';`;
|
|
182
|
+
if (needsBcrypt) {
|
|
183
|
+
importBlock = `import bcrypt from 'bcrypt';\n${importBlock}`;
|
|
184
|
+
}
|
|
185
|
+
addImportToRoute(routeFilePath, importBlock);
|
|
186
|
+
|
|
187
|
+
// Add selected operations
|
|
188
|
+
const added = [];
|
|
189
|
+
|
|
190
|
+
if (operations.includes("create")) {
|
|
191
|
+
insertCodeIntoRoute(routeFilePath, buildInsertCode(pascalName, fields, dataSource));
|
|
192
|
+
added.push("POST /create");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (operations.includes("read")) {
|
|
196
|
+
insertCodeIntoRoute(routeFilePath, buildReadCode(pascalName));
|
|
197
|
+
added.push("GET / and GET /:id");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (operations.includes("update")) {
|
|
201
|
+
insertCodeIntoRoute(routeFilePath, buildUpdateCode(pascalName, fields, dataSource));
|
|
202
|
+
added.push("PUT /:id");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (operations.includes("delete")) {
|
|
206
|
+
insertCodeIntoRoute(routeFilePath, buildDeleteCode(pascalName));
|
|
207
|
+
added.push("DELETE /:id");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
showFileAction("updated", `src/routes/${selectedRoute}`);
|
|
211
|
+
|
|
212
|
+
if (needsBcrypt) {
|
|
213
|
+
await installDeps("bcrypt");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
showTaskDone(`CRUD operations added to ${selectedRoute}`, [
|
|
217
|
+
`Model: ${modelName}`,
|
|
218
|
+
...added.map((a) => `Route: ${a}`),
|
|
219
|
+
]);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
main().catch((err) => {
|
|
223
|
+
if (err.name === "ExitPromptError") process.exit(0);
|
|
224
|
+
showError(err.message);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
});
|