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
|
@@ -0,0 +1,231 @@
|
|
|
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 fieldTypes = [
|
|
14
|
+
"String",
|
|
15
|
+
"Number",
|
|
16
|
+
"Date",
|
|
17
|
+
"Boolean",
|
|
18
|
+
"Array",
|
|
19
|
+
"Buffer",
|
|
20
|
+
"Mixed",
|
|
21
|
+
"ObjectId",
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
function toCamelCase(str) {
|
|
25
|
+
return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function toPascalCase(str) {
|
|
29
|
+
const camelCaseStr = toCamelCase(str);
|
|
30
|
+
return camelCaseStr.charAt(0).toUpperCase() + camelCaseStr.slice(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function convertDefaultValue(fieldType, value) {
|
|
34
|
+
try {
|
|
35
|
+
switch (fieldType) {
|
|
36
|
+
case "Number":
|
|
37
|
+
if (isNaN(value)) throw new Error("Invalid number");
|
|
38
|
+
return parseFloat(value);
|
|
39
|
+
case "Date":
|
|
40
|
+
const date = new Date(value);
|
|
41
|
+
if (isNaN(date.getTime())) throw new Error("Invalid date");
|
|
42
|
+
return date;
|
|
43
|
+
case "Boolean":
|
|
44
|
+
if (value.toLowerCase() !== "true" && value.toLowerCase() !== "false") {
|
|
45
|
+
throw new Error("Invalid boolean");
|
|
46
|
+
}
|
|
47
|
+
return value.toLowerCase() === "true";
|
|
48
|
+
case "Array":
|
|
49
|
+
return JSON.parse(value);
|
|
50
|
+
case "ObjectId":
|
|
51
|
+
return value; // Keep as string; Mongoose will cast it
|
|
52
|
+
case "Buffer":
|
|
53
|
+
return Buffer.from(value);
|
|
54
|
+
default:
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
} catch (e) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function promptSchemaDetails(schemaName) {
|
|
63
|
+
const fields = [];
|
|
64
|
+
|
|
65
|
+
function askField() {
|
|
66
|
+
rl.question(
|
|
67
|
+
chalk.yellow("\nEnter field name (or press enter to finish): "),
|
|
68
|
+
(fieldName) => {
|
|
69
|
+
if (!fieldName.trim()) {
|
|
70
|
+
generateSchemaFile(schemaName, fields);
|
|
71
|
+
rl.close();
|
|
72
|
+
} else {
|
|
73
|
+
console.log(chalk.cyan("Choose field type:"));
|
|
74
|
+
fieldTypes.forEach((type, index) => {
|
|
75
|
+
console.log(chalk.cyan(`${index + 1}. ${type}`));
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
rl.question(
|
|
79
|
+
chalk.yellow("Enter the number for the field type: "),
|
|
80
|
+
(fieldTypeIndex) => {
|
|
81
|
+
const fieldType = fieldTypes[parseInt(fieldTypeIndex, 10) - 1];
|
|
82
|
+
if (!fieldType) {
|
|
83
|
+
console.error(
|
|
84
|
+
chalk.red("Invalid selection. Please choose a valid number.")
|
|
85
|
+
);
|
|
86
|
+
askField();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
rl.question(
|
|
91
|
+
chalk.yellow("Is this field required? (y/n): "),
|
|
92
|
+
(isRequired) => {
|
|
93
|
+
const required = isRequired.trim().toLowerCase();
|
|
94
|
+
if (required !== "y" && required !== "n") {
|
|
95
|
+
console.error(
|
|
96
|
+
chalk.red("Invalid selection. Please enter 'y' or 'n'.")
|
|
97
|
+
);
|
|
98
|
+
askField();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
rl.question(
|
|
103
|
+
chalk.yellow("Is this field unique? (y/n): "),
|
|
104
|
+
(isUnique) => {
|
|
105
|
+
const unique = isUnique.trim().toLowerCase();
|
|
106
|
+
if (unique !== "y" && unique !== "n") {
|
|
107
|
+
console.error(
|
|
108
|
+
chalk.red(
|
|
109
|
+
"Invalid selection. Please enter 'y' or 'n'."
|
|
110
|
+
)
|
|
111
|
+
);
|
|
112
|
+
askField();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function askDefaultValue() {
|
|
117
|
+
rl.question(
|
|
118
|
+
chalk.yellow(
|
|
119
|
+
`Enter default value${
|
|
120
|
+
fieldType === "Boolean" ? " (true/false)" : ""
|
|
121
|
+
}${
|
|
122
|
+
fieldType === "Date"
|
|
123
|
+
? " (e.g., 2023-01-01T00:00:00Z)"
|
|
124
|
+
: ""
|
|
125
|
+
} (or press enter to skip): `
|
|
126
|
+
),
|
|
127
|
+
(defaultValue) => {
|
|
128
|
+
if (defaultValue.trim()) {
|
|
129
|
+
const convertedDefaultValue = convertDefaultValue(
|
|
130
|
+
fieldType,
|
|
131
|
+
defaultValue.trim()
|
|
132
|
+
);
|
|
133
|
+
if (convertedDefaultValue === null) {
|
|
134
|
+
console.error(
|
|
135
|
+
chalk.red(
|
|
136
|
+
"Invalid default value. Please enter a valid value."
|
|
137
|
+
)
|
|
138
|
+
);
|
|
139
|
+
askDefaultValue();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
fields.push({
|
|
143
|
+
name: fieldName.trim(),
|
|
144
|
+
type: fieldType,
|
|
145
|
+
required: required === "y",
|
|
146
|
+
unique: unique === "y",
|
|
147
|
+
default: convertedDefaultValue,
|
|
148
|
+
});
|
|
149
|
+
} else {
|
|
150
|
+
fields.push({
|
|
151
|
+
name: fieldName.trim(),
|
|
152
|
+
type: fieldType,
|
|
153
|
+
required: required === "y",
|
|
154
|
+
unique: unique === "y",
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
askField();
|
|
158
|
+
}
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
askDefaultValue();
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
askField();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function generateSchemaFile(schemaName, fields) {
|
|
178
|
+
const projectRoot = process.cwd();
|
|
179
|
+
const modelsDir = path.join(projectRoot, "src", "models");
|
|
180
|
+
|
|
181
|
+
if (!fs.existsSync(modelsDir)) {
|
|
182
|
+
fs.mkdirSync(modelsDir, { recursive: true });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const schemaFilePath = path.join(modelsDir, `${toCamelCase(schemaName)}.js`);
|
|
186
|
+
const pascalSchemaName = toPascalCase(schemaName);
|
|
187
|
+
const schemaContent = `
|
|
188
|
+
import mongoose from 'mongoose';
|
|
189
|
+
|
|
190
|
+
const ${pascalSchemaName}Schema = new mongoose.Schema({
|
|
191
|
+
${fields
|
|
192
|
+
.map((field) => {
|
|
193
|
+
let fieldDef = `${field.name}: { type: ${
|
|
194
|
+
field.type === "ObjectId"
|
|
195
|
+
? "mongoose.Schema.Types.ObjectId"
|
|
196
|
+
: field.type
|
|
197
|
+
}`;
|
|
198
|
+
if (field.required) {
|
|
199
|
+
fieldDef += `, required: true`;
|
|
200
|
+
}
|
|
201
|
+
if (field.unique) {
|
|
202
|
+
fieldDef += `, unique: true`;
|
|
203
|
+
}
|
|
204
|
+
if (field.default !== undefined) {
|
|
205
|
+
fieldDef += `, default: ${JSON.stringify(field.default)}`;
|
|
206
|
+
}
|
|
207
|
+
fieldDef += ` }`;
|
|
208
|
+
return fieldDef;
|
|
209
|
+
})
|
|
210
|
+
.join(",\n ")}
|
|
211
|
+
}, { timestamps: true });
|
|
212
|
+
|
|
213
|
+
const ${pascalSchemaName} = mongoose.model('${pascalSchemaName}', ${pascalSchemaName}Schema);
|
|
214
|
+
|
|
215
|
+
export default ${pascalSchemaName};
|
|
216
|
+
`;
|
|
217
|
+
|
|
218
|
+
fs.writeFileSync(schemaFilePath, schemaContent.trim(), "utf8");
|
|
219
|
+
console.log(
|
|
220
|
+
chalk.green(`MongoDB schema ${schemaName} created successfully.`)
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (process.argv.length !== 3) {
|
|
225
|
+
console.error(chalk.red("Usage: add-mongo-schema <schemaName>"));
|
|
226
|
+
process.exit(0);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const schemaName = process.argv[2];
|
|
230
|
+
|
|
231
|
+
promptSchemaDetails(schemaName);
|
|
@@ -0,0 +1,229 @@
|
|
|
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
|
+
import { execSync } from "child_process";
|
|
8
|
+
|
|
9
|
+
const rl = readline.createInterface({
|
|
10
|
+
input: process.stdin,
|
|
11
|
+
output: process.stdout,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
function listRoutes() {
|
|
15
|
+
const projectRoot = process.cwd();
|
|
16
|
+
const routesDir = path.join(projectRoot, "src", "routes");
|
|
17
|
+
if (!fs.existsSync(routesDir)) {
|
|
18
|
+
console.error(chalk.red("No routes directory found in src/routes"));
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const routeFiles = fs
|
|
22
|
+
.readdirSync(routesDir)
|
|
23
|
+
.filter((file) => file.endsWith(".js"));
|
|
24
|
+
if (routeFiles.length === 0) {
|
|
25
|
+
console.error(chalk.red("No route files found in src/routes"));
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return routeFiles;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function listModels() {
|
|
32
|
+
const projectRoot = process.cwd();
|
|
33
|
+
const modelsDir = path.join(projectRoot, "src", "models");
|
|
34
|
+
if (!fs.existsSync(modelsDir)) {
|
|
35
|
+
console.error(chalk.red("No models directory found in src/models"));
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const modelFiles = fs
|
|
39
|
+
.readdirSync(modelsDir)
|
|
40
|
+
.filter((file) => file.endsWith(".js"));
|
|
41
|
+
if (modelFiles.length === 0) {
|
|
42
|
+
console.error(chalk.red("No model files found in src/models"));
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return modelFiles;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getModelSchema(modelName) {
|
|
49
|
+
const projectRoot = process.cwd();
|
|
50
|
+
const modelFilePath = path.join(
|
|
51
|
+
projectRoot,
|
|
52
|
+
"src",
|
|
53
|
+
"models",
|
|
54
|
+
modelName + ".js"
|
|
55
|
+
);
|
|
56
|
+
const modelFileContent = fs.readFileSync(modelFilePath, "utf8");
|
|
57
|
+
|
|
58
|
+
// Improved regex to handle beautified code
|
|
59
|
+
const schemaRegex = /new\s+mongoose\.Schema\s*\(\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*\{/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
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
console.error(chalk.red("Schema not found in model file."));
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function addUpdateTemplateToRoute(routeFile, fields, modelName) {
|
|
83
|
+
const projectRoot = process.cwd();
|
|
84
|
+
const routeFilePath = path.join(projectRoot, "src", "routes", routeFile);
|
|
85
|
+
const pascalModelName =
|
|
86
|
+
modelName.charAt(0).toUpperCase() + modelName.slice(1);
|
|
87
|
+
|
|
88
|
+
let updateTemplateImport = `import ${pascalModelName} from '../models/${modelName}.js';\n`;
|
|
89
|
+
|
|
90
|
+
let passwordHashingLogic = "";
|
|
91
|
+
if (fields.includes("password")) {
|
|
92
|
+
updateTemplateImport =
|
|
93
|
+
`import bcrypt from 'bcrypt';\n` + updateTemplateImport;
|
|
94
|
+
passwordHashingLogic = `
|
|
95
|
+
if (updatedData.password) {
|
|
96
|
+
const salt = await bcrypt.genSalt(10);
|
|
97
|
+
updatedData.password = await bcrypt.hash(updatedData.password, salt);
|
|
98
|
+
}
|
|
99
|
+
`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!fs.existsSync(routeFilePath)) {
|
|
103
|
+
console.error(
|
|
104
|
+
chalk.red(`The specified route file does not exist: ${routeFilePath}`)
|
|
105
|
+
);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let routeContent = fs.readFileSync(routeFilePath, "utf8");
|
|
110
|
+
|
|
111
|
+
if (!routeContent.includes(`import ${pascalModelName}`)) {
|
|
112
|
+
routeContent = updateTemplateImport + routeContent;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Detect HTTP method type
|
|
116
|
+
const methodTypeRegex =
|
|
117
|
+
/router\.(get|post|put|patch|delete)\((['"`])\/.*?\2,\s*async\s*\(req,\s*res/;
|
|
118
|
+
const methodMatch = routeContent.match(methodTypeRegex);
|
|
119
|
+
const methodType = methodMatch ? methodMatch[1].toLowerCase() : "put";
|
|
120
|
+
|
|
121
|
+
// Determine whether to use req.body or req.query based on method type
|
|
122
|
+
const requestDataSource =
|
|
123
|
+
methodType === "get" || methodType === "delete" ? "req.query" : "req.body";
|
|
124
|
+
|
|
125
|
+
// Generate update logic
|
|
126
|
+
const updateTemplateFunction = `
|
|
127
|
+
// Data update logic
|
|
128
|
+
const updatedData = {
|
|
129
|
+
${fields
|
|
130
|
+
.map((field) => `${field}: ${requestDataSource}.${field}`)
|
|
131
|
+
.join(",\n ")}
|
|
132
|
+
};
|
|
133
|
+
${passwordHashingLogic}
|
|
134
|
+
try {
|
|
135
|
+
const updatedDocument = await ${pascalModelName}.findByIdAndUpdate(req.params.id, updatedData, { new: true });
|
|
136
|
+
if (!updatedDocument) {
|
|
137
|
+
return res.status(404).json({ message: "Document not found" });
|
|
138
|
+
}
|
|
139
|
+
res.json({ message: "Data updated successfully", data: updatedDocument });
|
|
140
|
+
} catch (err) {
|
|
141
|
+
console.error(err);
|
|
142
|
+
res.status(500).json({ message: "Server error" });
|
|
143
|
+
}
|
|
144
|
+
`;
|
|
145
|
+
|
|
146
|
+
// Insert the logic before the last closing `});`
|
|
147
|
+
const lastCloseIndex = routeContent.lastIndexOf("});");
|
|
148
|
+
if (lastCloseIndex !== -1) {
|
|
149
|
+
const updatedContent = [
|
|
150
|
+
routeContent.slice(0, lastCloseIndex),
|
|
151
|
+
updateTemplateFunction,
|
|
152
|
+
routeContent.slice(lastCloseIndex),
|
|
153
|
+
].join("\n");
|
|
154
|
+
|
|
155
|
+
routeContent = updatedContent;
|
|
156
|
+
} else {
|
|
157
|
+
routeContent += `\n${updateTemplateFunction}\n`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
fs.writeFileSync(routeFilePath, routeContent, "utf8");
|
|
161
|
+
console.log(chalk.green(`Update template added to ${routeFile}`));
|
|
162
|
+
|
|
163
|
+
// Install bcrypt if needed
|
|
164
|
+
if (fields.includes("password")) {
|
|
165
|
+
console.log(chalk.green("Installing bcrypt..."));
|
|
166
|
+
execSync("npm install bcrypt", { stdio: "inherit" });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const routeFiles = listRoutes();
|
|
171
|
+
const modelFiles = listModels();
|
|
172
|
+
|
|
173
|
+
if (!routeFiles || !modelFiles) {
|
|
174
|
+
rl.close();
|
|
175
|
+
process.exit(0);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
console.log(chalk.blue("Select the route file to modify:"));
|
|
179
|
+
routeFiles.forEach((file, index) => {
|
|
180
|
+
console.log(chalk.yellow(`${index + 1}. ${file}`));
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
rl.question(
|
|
184
|
+
chalk.cyan("Enter the number of the route file: "),
|
|
185
|
+
(routeAnswer) => {
|
|
186
|
+
const routeIndex = parseInt(routeAnswer, 10) - 1;
|
|
187
|
+
if (routeIndex >= 0 && routeIndex < routeFiles.length) {
|
|
188
|
+
const selectedRoute = routeFiles[routeIndex];
|
|
189
|
+
|
|
190
|
+
console.log(chalk.blue("Select the model file to use:"));
|
|
191
|
+
modelFiles.forEach((file, index) => {
|
|
192
|
+
console.log(chalk.yellow(`${index + 1}. ${file}`));
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
rl.question(
|
|
196
|
+
chalk.cyan("Enter the number of the model file: "),
|
|
197
|
+
(modelAnswer) => {
|
|
198
|
+
const modelIndex = parseInt(modelAnswer, 10) - 1;
|
|
199
|
+
if (modelIndex >= 0 && modelIndex < modelFiles.length) {
|
|
200
|
+
const selectedModel = path.basename(modelFiles[modelIndex], ".js");
|
|
201
|
+
const schemaFields = getModelSchema(selectedModel);
|
|
202
|
+
|
|
203
|
+
if (!schemaFields) {
|
|
204
|
+
rl.close();
|
|
205
|
+
process.exit(0);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
addUpdateTemplateToRoute(
|
|
209
|
+
selectedRoute,
|
|
210
|
+
schemaFields,
|
|
211
|
+
selectedModel
|
|
212
|
+
);
|
|
213
|
+
rl.close();
|
|
214
|
+
} else {
|
|
215
|
+
console.error(
|
|
216
|
+
chalk.red("Invalid selection. Please choose a valid number.")
|
|
217
|
+
);
|
|
218
|
+
rl.close();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
);
|
|
222
|
+
} else {
|
|
223
|
+
console.error(
|
|
224
|
+
chalk.red("Invalid selection. Please choose a valid number.")
|
|
225
|
+
);
|
|
226
|
+
rl.close();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
);
|