nodejs-backpack 0.0.1-security → 2.0.10
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.
Potentially problematic release.
This version of nodejs-backpack might be problematic. Click here for more details.
- package/.sample-env +3 -0
- package/helper.js +76 -0
- package/index.js +131 -0
- package/package.json +53 -6
- package/readme.md +151 -0
- package/sample-app.js +41 -0
- package/sample-auth-controller.js +217 -0
- package/sample-auth-middleware.js +44 -0
- package/sample-auth-route.js +19 -0
- package/sample-config-mailer.js +10 -0
- package/sample-controller.js +173 -0
- package/sample-env +4 -0
- package/sample-file-uploader.js +27 -0
- package/sample-form-parser.js +5 -0
- package/sample-helper.js +138 -0
- package/sample-index-route.js +7 -0
- package/sample-package.json +29 -0
- package/sample-route.js +20 -0
- package/sample-schema-ResetToken.js +26 -0
- package/sample-schema-User.js +51 -0
- package/sample-schema.js +49 -0
- package/sample-validation.js +4 -0
- package/typing.gif +0 -0
- package/README.md +0 -5
package/.sample-env
ADDED
package/helper.js
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
const fs = require("fs");
|
2
|
+
|
3
|
+
function convertToKebabCase(string) {
|
4
|
+
// Convert the first character to lowercase
|
5
|
+
string = string.charAt(0).toLowerCase() + string.slice(1);
|
6
|
+
|
7
|
+
// Add a hyphen before each uppercase letter
|
8
|
+
string = string.replace(/([A-Z])/g, "-$1").toLowerCase();
|
9
|
+
|
10
|
+
return string;
|
11
|
+
}
|
12
|
+
function loadSchemaFromFile(filePath) {
|
13
|
+
// Load the model from the file path
|
14
|
+
const model = require(filePath);
|
15
|
+
// Access the schema from the model
|
16
|
+
const schema = model.schema;
|
17
|
+
return schema;
|
18
|
+
}
|
19
|
+
|
20
|
+
function searchableFields(Model) {
|
21
|
+
var formFields = modelsParams(Model);
|
22
|
+
var uploadFieldArray = [];
|
23
|
+
var searchFields = [];
|
24
|
+
if (
|
25
|
+
formFields &&
|
26
|
+
formFields?.filteredFormFields &&
|
27
|
+
formFields?.filteredFormFields.length
|
28
|
+
) {
|
29
|
+
searchFields = formFields?.filteredFormFields;
|
30
|
+
return searchFields;
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
function modelsParams(Schema) {
|
35
|
+
// Obtain the schema parameters object
|
36
|
+
const paths = Schema.paths;
|
37
|
+
let parameters = [];
|
38
|
+
let all = [];
|
39
|
+
let required = [];
|
40
|
+
let optional = [];
|
41
|
+
// Loop over the paths and log each field's name and type
|
42
|
+
for (const path in paths) {
|
43
|
+
let isRequired = paths[path]?.options?.required ? true : false;
|
44
|
+
parameters.push({
|
45
|
+
field: path,
|
46
|
+
is_required: isRequired,
|
47
|
+
});
|
48
|
+
if (isRequired) {
|
49
|
+
required.push(path);
|
50
|
+
} else {
|
51
|
+
optional.push(path);
|
52
|
+
}
|
53
|
+
all.push(path);
|
54
|
+
}
|
55
|
+
const unwantedProps = ["_id", "createdAt", "updatedAt", "__v"];
|
56
|
+
const filteredFormData = all.filter((prop) => !unwantedProps.includes(prop));
|
57
|
+
return {
|
58
|
+
parameters: parameters,
|
59
|
+
allFields: all,
|
60
|
+
requiredFields: required,
|
61
|
+
optionalFields: optional,
|
62
|
+
filteredFormFields: filteredFormData,
|
63
|
+
};
|
64
|
+
}
|
65
|
+
|
66
|
+
function capitalize(str) {
|
67
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
68
|
+
}
|
69
|
+
|
70
|
+
module.exports = {
|
71
|
+
convertToKebabCase,
|
72
|
+
loadSchemaFromFile,
|
73
|
+
modelsParams,
|
74
|
+
capitalize,
|
75
|
+
searchableFields,
|
76
|
+
};
|
package/index.js
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
const fs=require("fs-extra"),path=require("path");var clc=require("cli-color");const{loadSchemaFromFile,modelsParams,capitalize,searchableFields}=require("./helper"),command=process.argv[2],fileName=process.argv[3];if("sample:files"===command){const a=process.cwd(),b=path.join(a,"sample-app.js");let e=fs.readFileSync(path.join(__dirname,"sample-app.js"),"utf8");fs.writeFileSync(b,e);const d=path.join(a,".sample-env");let s=fs.readFileSync(path.join(__dirname,".sample-env"),"utf8");fs.writeFileSync(d,s);const f=path.join(a,"sample-package.json");let i=fs.readFileSync(path.join(__dirname,"sample-package.json"),"utf8");fs.writeFileSync(f,i),console.log(clc.green('"sample-app.js", "sample-package.json" & ".sample-env" file created in the root directory.'))}else if("make:schema"===command){const h=process.cwd(),i=path.join(h,"models"),j=(fs.existsSync(i)||fs.mkdirSync(i),path.join(i,fileName+".js"));let e=fs.readFileSync(path.join(__dirname,"sample-schema.js"),"utf8");e=e.replace(/\${fileContent}/g,fileName),newSchemaFileCreate(j,e,fileName)}else if("make:apis"===command){const l=process.argv.find(e=>e.startsWith("--url=")),m=process.argv.find(e=>e.startsWith("--schema=")),n=(-1===l&&-1===m||void 0===l&&void 0===m?(console.error(clc.bgRed("Missing parameters. Please provide --url=<api_url> and --schema=<schema_name>.")),process.exit(1)):-1===l||void 0===l?(console.error(clc.bgRed("Missing parameters. Please provide --url=<api_url>.")),process.exit(1)):-1!==m&&void 0!==m||(console.error(clc.bgRed("Missing parameters. Please provide --schema=<schema_name>.")),process.exit(1)),l.split("=")[1]),o=m.split("=")[1],p=process.cwd(),q=path.join(p,"models",o+".js"),r=(fs.existsSync(q)||(console.error(clc.bgRed(`Schema file "${o}.js" does not exist in '/models/${o}.js'.`)),process.exit(1)),path.join(p,"routes")),s=path.join(p,"helpers"),t=path.join(r,"validationRequest"),u=path.join(t,""+fileName),v=path.join(p,"controllers");fs.existsSync(r)||fs.mkdirSync(r),fs.existsSync(t)||fs.mkdirSync(t),fs.existsSync(u)||fs.mkdirSync(u),fs.existsSync(v)||fs.mkdirSync(v),fs.existsSync(s)||fs.mkdirSync(s),controllerFileCreate(v,fileName,o),indexRouteFileCreate(r,fileName),validationFileCreate(q,o,u),newRouteFileCreate(r,fileName,n,o),helperFileCreate(s)}else if("make:auth"===command){const y=process.cwd(),z=path.join(y,"middleware"),A=(fs.existsSync(z)||fs.mkdirSync(z),path.join(y,"routes")),B=(fs.existsSync(A)||fs.mkdirSync(A),path.join(y,"controllers")),C=(fs.existsSync(B)||fs.mkdirSync(B),path.join(y,"config")),D=(fs.existsSync(C)||fs.mkdirSync(C),path.join(y,"models"));fs.existsSync(D)||fs.mkdirSync(D),authMiddlewareFileCreate(z),authRouteFileCreate(A),authControllersFileCreate(B),authConfigFileCreate(C),authSchemaFileCreate(D)}else console.error(clc.bgRed("Unknown command: "+command));async function newSchemaFileCreate(e,s,i){await fileExists(e)?console.error(clc.bgRed(`
|
3
|
+
'models/${i}.js' file already exist!, Kindly delete existing and try again.`)):(fs.writeFileSync(e,s),console.log(clc.green(`
|
4
|
+
'models/${i}.js' file created!`)))}async function indexRouteFileCreate(e,i){const a=path.join(e,"IndexRoute.js");try{await fileExists(a).then(e=>{if(!e)return e=fs.readFileSync(path.join(__dirname,"sample-index-route.js"),"utf8"),fs.writeFileSync(a,e),console.log(clc.green("\nIndexRoute.js file created successfully!")),!0}).then(async()=>{data=await readFile(a,"utf8");var e=`router.use("/", ${i}Route);`,s=data.indexOf("module.exports = router;");return updateIndexFile(a,e,s),!0}).then(async()=>{data=await readFile(a,"utf8");var e=`const ${i}Route = require("./${i}Route");`,s=data.indexOf("const router = express.Router();");updateIndexFile(a,e,s)})}catch(e){console.error(e)}}function fileExists(i){return new Promise((s,e)=>{fs.access(i,fs.constants.F_OK,e=>{s(!e)})})}function readFile(e,s){return new Promise((i,a)=>{fs.readFile(e,s,(e,s)=>{e?a(e):i(s)})})}function writeFile(e,a,r){return new Promise((s,i)=>{fs.writeFile(e,a,r,e=>{e?i(e):s()})})}async function updateIndexFile(e,s,i){try{var a=await readFile(e,"utf8");-1===i||a.includes(s)?console.log(clc.yellow(s+" already imported in IndexRoute.js!")):(await writeFile(e,a.slice(0,i)+s+"\n"+a.slice(i),"utf8"),console.log(clc.green(s+" imported into IndexRoute.js successfully!")))}catch(e){console.log(clc.red("An error occurred:",e))}}async function controllerFileCreate(a,r,t){a=path.join(a,r+"Controller.js");if(await fileExists(a))console.log(clc.bgRed(`
|
5
|
+
${r}Controller.js file already exist!`));else{let e=fs.readFileSync(path.join(__dirname,"sample-controller.js"),"utf8"),s=(e=(e=e.replace(/\${fileName}/g,r)).replace(/\${schemaName}/g,t),"["),i="{";var l,o=process.cwd(),o=path.join(o,"models",t+".js"),t=loadSchemaFromFile(o),o=searchableFields(t);for(l of o)s+=`
|
6
|
+
{ ${l}: { $regex: searchRgx, $options: "i" } },`,i+=`
|
7
|
+
${l}: (req?.body?.${l}) || null,`;s+=`
|
8
|
+
]`,i+=`
|
9
|
+
}`,0===o.length&&(s+="[{}]"),e=(e=e.replace(/\${___SearchFieldArray___}/g,s)).replace(/\${___StoreFieldArray___}/g,i),fs.writeFileSync(a,e),console.log(clc.green(`
|
10
|
+
${r}Controller.js file created successfully in the '/controllers/${r}Controller.js' folder.!`))}}async function validationFileCreate(e,a,r){var e=loadSchemaFromFile(e),e=modelsParams(e),s=[];if(e&&e?.filteredFormFields&&e?.filteredFormFields.length){var i,t=e?.filteredFormFields;for(i in t)t[i]&&s.push({name:t[i]});e?.filteredFormFields}let l="";e?.requiredFields&&0<e?.requiredFields?.length&&e?.requiredFields.forEach(e=>{l+=`
|
11
|
+
check("${e}").custom((value, { req }) => {
|
12
|
+
const fileExists =
|
13
|
+
req.files && req.files.some((obj) => obj.fieldname === "${e}");
|
14
|
+
const ${e}Exists = req.body && req.body.${e};
|
15
|
+
|
16
|
+
if (fileExists || ${e}Exists) {
|
17
|
+
return true;
|
18
|
+
} else {
|
19
|
+
throw new Error("${capitalize(e)} is required.");
|
20
|
+
}
|
21
|
+
}),`});["List","GetDetail","Store","Update","Destroy"].forEach(async e=>{let s=fs.readFileSync(path.join(__dirname,"sample-validation.js"),"utf8");s=s.replace(/\${schemaName}/g,a);var i=l,i=("GetDetail"===e||"Destroy"===e||"Update"===e?i=`
|
22
|
+
param("id").custom((value, { req }) => {
|
23
|
+
return ${a}.findOne({ _id: value }).then((${a}Data) => {
|
24
|
+
if (!${a}Data) {
|
25
|
+
return Promise.reject("Invalid ID! The provided ID does not exist in the database.");
|
26
|
+
}
|
27
|
+
});
|
28
|
+
}),`:"List"===e&&(i=""),i+=`
|
29
|
+
`,s=s.replace(/\__validation__dynamic__code__/g,i),path.join(r,e+"ValidationRequest.js"));await fileExists(i)?console.log(clc.bgRed(e+"ValidationRequest.js file already exist!")):(fs.writeFileSync(i,s),console.log(clc.green(`"${e}ValidationRequest.js" file created in the '/routes/validationRequest/${e}ValidationRequest.js' folder.`)))})}async function newRouteFileCreate(s,i,a,r){s=path.join(s,i+"Route.js");if(await fileExists(s))console.log(clc.bgRed(`
|
30
|
+
${i}Route.js file already exist!`));else{let e=fs.readFileSync(path.join(__dirname,"sample-route.js"),"utf8");e=(e=(e=e.replace(/\${fileName}/g,i)).replace(/\${apiUrl}/g,a)).replace(/\${schemaName}/g,r),fs.writeFileSync(s,e),console.log(clc.green(`"${i}Route.js" file created in the '/routes/${i}Route.js' folders.`))}}async function helperFileCreate(e){var s,i=path.join(e,"helper.js"),i=(await fileExists(i)||(s=fs.readFileSync(path.join(__dirname,"sample-helper.js"),"utf8"),fs.writeFileSync(i,s)),path.join(e,"fileUploader.js")),i=(await fileExists(i)||(s=fs.readFileSync(path.join(__dirname,"sample-file-uploader.js"),"utf8"),fs.writeFileSync(i,s)),path.join(e,"formParser.js"));await fileExists(i)||(s=fs.readFileSync(path.join(__dirname,"sample-form-parser.js"),"utf8"),fs.writeFileSync(i,s))}async function authMiddlewareFileCreate(e){var s,e=path.join(e,"AuthMiddleware.js");await fileExists(e)?console.error(clc.bgRed(`
|
31
|
+
AuthMiddleware.js file already exist!`)):(s=fs.readFileSync(path.join(__dirname,"sample-auth-middleware.js"),"utf8"),fs.writeFileSync(e,s))}async function authRouteFileCreate(e){var s=path.join(e,"IndexRoute.js"),i=(await fileExists(s)||(i=fs.readFileSync(path.join(__dirname,"sample-index-route.js"),"utf8"),fs.writeFileSync(s,i),console.log(clc.green("\nIndexRoute.js file created successfully!"))),path.join(e,"AuthRoute.js")),i=(await fileExists(i)?console.error(clc.bgRed(`
|
32
|
+
AuthRoute.js file already exist!`)):(e=fs.readFileSync(path.join(__dirname,"sample-auth-route.js"),"utf8"),fs.writeFileSync(i,e)),await updateIndexFile(s,'const AuthRoute = require("./AuthRoute");',(data=await readFile(s,"utf8")).indexOf("const router = express.Router();")),await updateIndexFile(s,'router.use("/", AuthRoute);',(data=await readFile(s,"utf8")).indexOf("module.exports = router;")),process.cwd());logicHelperFileCreate(i),authValidationFileCreate(i)}async function authControllersFileCreate(e,s="Auth"){var i,e=path.join(e,s+"Controller.js");await fileExists(e)?console.log(clc.bgRed(`
|
33
|
+
${s}Controller.js file already exist!`)):(i=fs.readFileSync(path.join(__dirname,"sample-auth-controller.js"),"utf8"),fs.writeFileSync(e,i),console.log(clc.green(`
|
34
|
+
${s}Controller.js file created successfully in the '/controllers/${s}Controller.js' folder.!`)))}async function authConfigFileCreate(e,s="mailtrap"){var i,e=path.join(e,s+".js");await fileExists(e)?console.log(clc.bgRed(`
|
35
|
+
${s}.js file already exist!`)):(i=fs.readFileSync(path.join(__dirname,"sample-config-mailer.js"),"utf8"),fs.writeFileSync(e,i),console.log(clc.green(`
|
36
|
+
${s}.js file created successfully in the '/config/${s}.js' folder.!`))),console.log(clc.yellow(`
|
37
|
+
Update SMTP credentials in ${s}.js file which is created in the '/config/${s}.js' folder.!`))}async function authSchemaFileCreate(e){var s;for(s of["ResetToken","User"]){var i,a=path.join(e,s+".js");await fileExists(a)?"User"===s?console.log(clc.bgRed(`
|
38
|
+
models/${s}.js file already exist! `),clc.yellow(`Please confirm that the 'first_name', 'last_name', 'email', 'password' and 'token' fields exist in the schema and are marked as required. To make the APIs work. Otherwise, delete the models/${s}.js file and try again.
|
39
|
+
`)):console.log(clc.bgRed(`
|
40
|
+
models/${s}.js file already exist!`)):(i=fs.readFileSync(path.join(__dirname,`sample-schema-${s}.js`),"utf8"),fs.writeFileSync(a,i),console.log(clc.green(`
|
41
|
+
models/${s}.js file created successfully in the '/models/${s}.js' folder.!`)))}}async function logicHelperFileCreate(e){e=path.join(e,"helpers");fs.existsSync(e)||fs.mkdirSync(e),helperFileCreate(e)}async function authValidationFileCreate(e){e=path.join(e,"routes"),fs.existsSync(e)||fs.mkdirSync(e),e=path.join(e,"validationRequest");fs.existsSync(e)||fs.mkdirSync(e);const r=path.join(e,"Auth");fs.existsSync(r)||fs.mkdirSync(r),["signUpRequest","signIn","forgotPassword","resetPassword"].forEach(async s=>{var i=path.join(r,s+"ValidationRequest.js");if(await fileExists(i))console.error(clc.bgRed(`
|
42
|
+
${s}ValidationRequest.js file already exist!`));else{let e=fs.readFileSync(path.join(__dirname,"sample-validation.js"),"utf8");var a="";"signUpRequest"===s?a+=`
|
43
|
+
body("first_name").notEmpty().withMessage("First Name is required").trim(),
|
44
|
+
|
45
|
+
body("last_name").notEmpty().withMessage("Last Name is required").trim(),
|
46
|
+
|
47
|
+
body("email")
|
48
|
+
.notEmpty()
|
49
|
+
.withMessage("Email Address is required")
|
50
|
+
.isEmail()
|
51
|
+
.withMessage("Enter correct email address")
|
52
|
+
.custom((value, { req }) => {
|
53
|
+
return User.findOne({ email: value }).then((userDoc) => {
|
54
|
+
if (userDoc) {
|
55
|
+
return Promise.reject("Email Address already exists!");
|
56
|
+
}
|
57
|
+
});
|
58
|
+
}),
|
59
|
+
|
60
|
+
body("password")
|
61
|
+
.notEmpty()
|
62
|
+
.withMessage("Password is requierd")
|
63
|
+
.isLength({ min: 6, max: 250 })
|
64
|
+
.withMessage("Minimum 6 character password require")
|
65
|
+
.trim(),`:"signIn"===s?a+=`
|
66
|
+
body("email")
|
67
|
+
.notEmpty()
|
68
|
+
.withMessage("Email Address is required")
|
69
|
+
.isEmail()
|
70
|
+
.withMessage("Enter correct email address")
|
71
|
+
.custom((value, { req }) => {
|
72
|
+
return User.findOne({ email: value }).then((userDoc) => {
|
73
|
+
if (!userDoc) {
|
74
|
+
return Promise.reject("A user with this email could not be found!");
|
75
|
+
}
|
76
|
+
});
|
77
|
+
}),
|
78
|
+
|
79
|
+
body("password")
|
80
|
+
.notEmpty()
|
81
|
+
.withMessage("Password is requierd")
|
82
|
+
.isLength({ min: 6, max: 250 })
|
83
|
+
.withMessage("Minimum 6 character password require")
|
84
|
+
.trim()`:"forgotPassword"===s?a+=`
|
85
|
+
body("email")
|
86
|
+
.notEmpty()
|
87
|
+
.withMessage("Email Address is required")
|
88
|
+
.isEmail()
|
89
|
+
.withMessage("Enter correct email address")
|
90
|
+
.custom((value, { req }) => {
|
91
|
+
return User.findOne({ email: value }).then((userDoc) => {
|
92
|
+
if (!userDoc) {
|
93
|
+
return Promise.reject("A user with this email could not be found!");
|
94
|
+
}
|
95
|
+
});
|
96
|
+
}),`:"resetPassword"===s&&(a+=`
|
97
|
+
body("email")
|
98
|
+
.notEmpty()
|
99
|
+
.withMessage("Email Address is required")
|
100
|
+
.isEmail()
|
101
|
+
.withMessage("Enter correct email address")
|
102
|
+
.custom((value, { req }) => {
|
103
|
+
return User.findOne({ email: value }).then((userDoc) => {
|
104
|
+
if (!userDoc) {
|
105
|
+
return Promise.reject("A user with this email could not be found!");
|
106
|
+
}
|
107
|
+
});
|
108
|
+
}),
|
109
|
+
|
110
|
+
|
111
|
+
body("reset_token").notEmpty().withMessage("Reset token is required").trim(),
|
112
|
+
|
113
|
+
|
114
|
+
body("password")
|
115
|
+
.notEmpty()
|
116
|
+
.withMessage("Password is required")
|
117
|
+
.isLength({ min: 6, max: 250 })
|
118
|
+
.withMessage("Minimum 6 character password require")
|
119
|
+
.trim(),
|
120
|
+
|
121
|
+
|
122
|
+
body("confirm_password")
|
123
|
+
.notEmpty()
|
124
|
+
.withMessage("Confirm password is required")
|
125
|
+
.custom((val, { req }) => {
|
126
|
+
if (req.body.password !== val) {
|
127
|
+
throw new Error("Confirm password does not match");
|
128
|
+
}
|
129
|
+
return true;
|
130
|
+
}),`),a+=`
|
131
|
+
`,e=(e=e.replace(/\${schemaName}/g,"User")).replace(/\__validation__dynamic__code__/g,a),fs.writeFileSync(i,e)}})}
|
package/package.json
CHANGED
@@ -1,6 +1,53 @@
|
|
1
|
-
{
|
2
|
-
"name": "nodejs-backpack",
|
3
|
-
"version": "
|
4
|
-
"description": "
|
5
|
-
"
|
6
|
-
|
1
|
+
{
|
2
|
+
"name": "nodejs-backpack",
|
3
|
+
"version": "2.0.10",
|
4
|
+
"description": "NodeJs Backpack is a powerful tool designed to simplify the development of Node.js REST APIs with ease and precision. It provides a streamlined workflow by automatically generating Mongoose schemas and handling the necessary configurations in your project. With just a few simple commands, NodeJs Backpack can create REST API components such as controllers and routes, while also ensuring route validation is implemented flawlessly. Additionally, NodeJs Backpack offers a command to generate sample files, providing a solid starting point for your project. With NodeJs Backpack, you can quickly set up your project and focus on building your APIs, letting the tool handle the repetitive tasks and allowing you to follow a smooth development process.",
|
5
|
+
"main": "index.js",
|
6
|
+
"scripts": {
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
8
|
+
"build": "uglifyjs -c -m -o publish_package_code/index.js index.js"
|
9
|
+
},
|
10
|
+
"keywords": [
|
11
|
+
"cli",
|
12
|
+
"CRUD Functionality",
|
13
|
+
"REST APIs",
|
14
|
+
"Files",
|
15
|
+
"Routing",
|
16
|
+
"Controllers",
|
17
|
+
"Models",
|
18
|
+
"Mongoose",
|
19
|
+
"jsonwebtoken",
|
20
|
+
"Authentication",
|
21
|
+
"JWT",
|
22
|
+
"Validation",
|
23
|
+
"Automation",
|
24
|
+
"Backpack",
|
25
|
+
"npm",
|
26
|
+
"node",
|
27
|
+
"node cli",
|
28
|
+
"node js",
|
29
|
+
"Node Js Project Builder",
|
30
|
+
"Project Builder",
|
31
|
+
"nodejs-backpack",
|
32
|
+
"jkg",
|
33
|
+
"jaykumar-gohil"
|
34
|
+
],
|
35
|
+
"author": "",
|
36
|
+
"license": "CC BY-NC-ND 4.0",
|
37
|
+
"dependencies": {
|
38
|
+
"cli-color": "^2.0.3",
|
39
|
+
"express": "^4.18.2",
|
40
|
+
"express-validator": "^7.0.1",
|
41
|
+
"jsonwebtoken": "^9.0.1",
|
42
|
+
"fs-extra": "^11.1.1",
|
43
|
+
"mongoose": "^7.3.1",
|
44
|
+
"mongoose-backpack": "^0.2.7",
|
45
|
+
"rest-api-response-npm": "^0.1.4"
|
46
|
+
},
|
47
|
+
"devDependencies": {
|
48
|
+
"uglify-js": "^3.17.4"
|
49
|
+
},
|
50
|
+
"bin": {
|
51
|
+
"nodejs-backpack": "./index.js"
|
52
|
+
}
|
53
|
+
}
|
package/readme.md
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
# NodeJs Backpack
|
2
|
+
|
3
|
+
NodeJs Backpack is a powerful tool designed to simplify the development of Node.js REST APIs with ease and precision. It provides a streamlined workflow by automatically generating Mongoose schemas and handling the necessary configurations in your project.
|
4
|
+
|
5
|
+
With just a few simple commands, NodeJs Backpack can create REST API components such as controllers and routes, while also ensuring route validation is implemented flawlessly. Additionally, NodeJs Backpack offers a command to generate sample files, providing a solid starting point for your project.
|
6
|
+
|
7
|
+
With NodeJs Backpack, you can quickly set up your project and focus on building your APIs, letting the tool handle the repetitive tasks and allowing you to follow a smooth development process.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Install package globally with npm
|
12
|
+
|
13
|
+
```bash
|
14
|
+
npm install nodejs-backpack -g
|
15
|
+
```
|
16
|
+
|
17
|
+
## Features
|
18
|
+
|
19
|
+
- Robust REST APIs
|
20
|
+
- Focus on optimized code
|
21
|
+
- Auto file creation for schemas, routes, controllers and validations
|
22
|
+
- helpers (modelsParams, appendParams, etc)
|
23
|
+
- Mongoose DB support,
|
24
|
+
- AppendParams for append data in APIs using Mongoose schema
|
25
|
+
- Executable for generating applications quickly
|
26
|
+
- Auto create logic for API CRUD
|
27
|
+
|
28
|
+
## Quick Start (Rest APIs)
|
29
|
+
|
30
|
+
The quickest way to get started with nodejs-backpack is to utilize the executable to generate an REST APIs as shown below:
|
31
|
+
|
32
|
+
**Step1:** Create an empty directory:
|
33
|
+
|
34
|
+
```bash
|
35
|
+
mkdir NodeJs-REST-APIs
|
36
|
+
```
|
37
|
+
|
38
|
+
**Step2:** Enter into directory:
|
39
|
+
|
40
|
+
```bash
|
41
|
+
cd NodeJs-REST-APIs
|
42
|
+
```
|
43
|
+
|
44
|
+
**Step3:** Run Command:
|
45
|
+
|
46
|
+
```bash
|
47
|
+
nodejs-backpack sample:files
|
48
|
+
```
|
49
|
+
|
50
|
+
- File "`sample-app.js`", "`sample-package.json`" & "`.sample-env`" created in the root directory.
|
51
|
+
- **Rename sample files** to "**`app.js`**", "**`package.json`**" & "**`.env`**" which are created in the root directory.
|
52
|
+
|
53
|
+

|
54
|
+
|
55
|
+
- Update .env > **`DB=mongodb+srv...`** with valid connection.
|
56
|
+
|
57
|
+
```bash
|
58
|
+
PORT=3000
|
59
|
+
DB="mongodb+srv://........mongodb.net/node_backpack"
|
60
|
+
JWT_SIGNATURE=node_backpack
|
61
|
+
HOST=http://localhost:3000
|
62
|
+
```
|
63
|
+
|
64
|
+
**Step4:** Run Command:
|
65
|
+
|
66
|
+
```bash
|
67
|
+
npm install
|
68
|
+
```
|
69
|
+
|
70
|
+
**Step5:** Run Command:
|
71
|
+
|
72
|
+
```bash
|
73
|
+
nodejs-backpack make:schema Tests
|
74
|
+
```
|
75
|
+
|
76
|
+
**Step6:** Run Command:
|
77
|
+
|
78
|
+
```bash
|
79
|
+
nodejs-backpack make:apis Test --url=tests --schema=Tests
|
80
|
+
```
|
81
|
+
|
82
|
+
- Rename schema name in comand as per your requirement **_nodejs-backpack make:apis Test --url=tests --schema=Tests_**.
|
83
|
+
- Example: _nodejs-backpack make:apis **ROUTE_NAME** --url=**ROUTE_URL** --schema=**SCHEMA_NAME**_.
|
84
|
+
- **Note:** _`SCHEMA_NAME` must exist in model directory `/models/SCHEMA_NAME`_
|
85
|
+
|
86
|
+
**Step7:** Install 'nodemon' on global level for development purpose:
|
87
|
+
|
88
|
+
```bash
|
89
|
+
npm install nodemon -g
|
90
|
+
```
|
91
|
+
|
92
|
+
**Step8:** Now your APIs are ready to use and Server your project with:
|
93
|
+
|
94
|
+
```bash
|
95
|
+
npm run dev
|
96
|
+
```
|
97
|
+
|
98
|
+
**Step9:** Open Postman and create new collection and check the below apis:
|
99
|
+
|
100
|
+
- Based on my command,
|
101
|
+
**_nodejs-backpack make:apis `Test` --url=`tests` --schema=`Tests`_**.
|
102
|
+
- ROUTE_NAME = `Test`
|
103
|
+
- ROUTE_URL = `tests`
|
104
|
+
- SCHEMA_NAME = `Tests`
|
105
|
+
- Below are list of APIs created by commands `.../api/ROUTE_URL`:
|
106
|
+
- _**Get List API**_: **`GET:`** _`http://localhost:3000/api/tests?page_no=1&page_limit=20&search=` (Parameters: page_no, page_limit, search)_
|
107
|
+
- _**Get Detail API**_: **`GET:`** _`http://localhost:3000/api/tests/64a41036174844d0394e7b2f`_
|
108
|
+
- _**Store API**_: **`POST:`** _`http://localhost:3000/api/tests` (Body-Parameters: based on model schema)_
|
109
|
+
- _**Update API**_: **`PUT:`** _`http://localhost:3000/api/tests/64a41036174844d0394e7b2f` (Body-Parameters: based on model schema)_
|
110
|
+
- _**Delete API**_: **`DELETE:`** _`http://localhost:3000/api/tests/64a41036174844d0394e7b2f`_
|
111
|
+
|
112
|
+
## Quick Start (Authentication APIs)
|
113
|
+
|
114
|
+
The quickest way to get started with **signup**, **register**, **forgot-password**, **reset-password**, **get-profile** API's using nodejs-backpack is to utilize the executable to generate an REST APIs as shown below:
|
115
|
+
|
116
|
+
**Step1:** Run Command:
|
117
|
+
|
118
|
+
```bash
|
119
|
+
nodejs-backpack make:auth
|
120
|
+
```
|
121
|
+
|
122
|
+
- Under **./routes/\*** '**IndexRoute.js**' and '**AuthRoute.js**' file will be create/updated.
|
123
|
+
- '**AuthController.js**' file will be create in the '**./controllers/AuthController.js**' folder with all neccessary logics.
|
124
|
+
- '**AuthMiddleware.js**' file will be create in the '**./middleware/AuthMiddleware.js**' folder with all neccessary JWT token verification logics.
|
125
|
+
- mailtrap.js file will be create in the '**./config/mailtrap.js**' folder where you can configure SMTP mail credentials.
|
126
|
+
- Under **./models/\*** '**User.js**' and '**ResetToken.js**' file will be create.
|
127
|
+
|
128
|
+
**Step2:** Open Postman and create new collection and check the below apis:
|
129
|
+
|
130
|
+
- Below are list of APIs created by commands `.../api/ROUTE_URL`:
|
131
|
+
- _**Register API**_: **`POST:`** _`http://localhost:3000/api/register` (Body-Parameters: first_name, last_name, email, password)_
|
132
|
+
- _**Login API**_: **`POST:`** _`http://localhost:3000/api/login` (Body-Parameters: email, password)_
|
133
|
+
- _**Forgot Password API**_: **`POST:`** _`http://localhost:3000/api/forgot-password` (Body-Parameters: email)_
|
134
|
+
- _**Reset Password API**_: **`POST:`** _`http://localhost:3000/api/reset-password` (Body-Parameters: email, password, reset_token)_. **Note**: Forgot password reset link will contain '**reset_token**' on mail.
|
135
|
+
- _**Logout API**_: **`GET:`** _`http://localhost:3000/api/logout`_
|
136
|
+
- _**Get Profile API**_: **`GET:`** _`http://localhost:3000/api/get-profile`_
|
137
|
+
|
138
|
+
## Environment Variables
|
139
|
+
|
140
|
+
To run this project, you will need to add the following environment variables to your .env file
|
141
|
+
|
142
|
+
```bash
|
143
|
+
PORT=3000
|
144
|
+
DB="mongodb+srv://........mongodb.net/node_backpack"
|
145
|
+
JWT_SIGNATURE=node_backpack
|
146
|
+
HOST=http://localhost:3000
|
147
|
+
```
|
148
|
+
|
149
|
+
## Authors
|
150
|
+
|
151
|
+
- Jaykumar Gohil ([@jksk21](https://www.npmjs.com/~jksk21))
|
package/sample-app.js
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
const express = require("express");
|
2
|
+
const app = express();
|
3
|
+
const routes = require("./routes/IndexRoute");
|
4
|
+
const dotenv = require("dotenv").config().parsed;
|
5
|
+
const mongoose = require("mongoose");
|
6
|
+
const { catchError } = require("rest-api-response-npm");
|
7
|
+
|
8
|
+
app.use((req, res, next) => {
|
9
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
10
|
+
res.setHeader(
|
11
|
+
"Access-Control-Allow-Methods",
|
12
|
+
"OPTIONS, GET, POST, PUT, PATCH, DELETE"
|
13
|
+
);
|
14
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
15
|
+
next();
|
16
|
+
});
|
17
|
+
|
18
|
+
// Routes
|
19
|
+
app.use("/api", routes);
|
20
|
+
|
21
|
+
// default error messgae
|
22
|
+
app.use((error, req, res, next) => {
|
23
|
+
catchError(res, error);
|
24
|
+
console.error(error);
|
25
|
+
});
|
26
|
+
|
27
|
+
// Serve files from the 'public' directory
|
28
|
+
app.use(express.static("public"));
|
29
|
+
|
30
|
+
// Start server mongooes and port
|
31
|
+
mongoose
|
32
|
+
.connect(dotenv.DB)
|
33
|
+
.then((result) => {
|
34
|
+
const port = dotenv.PORT;
|
35
|
+
app.listen(port, () => {
|
36
|
+
console.log(`Server started on port http://localhost:${port}`);
|
37
|
+
});
|
38
|
+
})
|
39
|
+
.catch((err) => {
|
40
|
+
console.log(err);
|
41
|
+
});
|
@@ -0,0 +1,217 @@
|
|
1
|
+
const User = require("../models/User"); //
|
2
|
+
const ResetToken = require("../models/ResetToken");
|
3
|
+
const bcrypt = require("bcryptjs");
|
4
|
+
const jwt = require("jsonwebtoken");
|
5
|
+
const dotenv = require("dotenv").config().parsed;
|
6
|
+
const crypto = require("crypto");
|
7
|
+
const {
|
8
|
+
successResponse,
|
9
|
+
checkValidationResult,
|
10
|
+
customResponse,
|
11
|
+
errorResponse,
|
12
|
+
} = require("rest-api-response-npm");
|
13
|
+
const nodemailer = require("../config/mailtrap");
|
14
|
+
|
15
|
+
const AuthController = {
|
16
|
+
signUp: (req, res, next) => {
|
17
|
+
const response = checkValidationResult(res, req);
|
18
|
+
if (response["status_code"] === 422) {
|
19
|
+
return errorResponse(
|
20
|
+
res,
|
21
|
+
response["data"],
|
22
|
+
response["message"],
|
23
|
+
response["status_code"]
|
24
|
+
);
|
25
|
+
}
|
26
|
+
const password = req.body.password;
|
27
|
+
bcrypt
|
28
|
+
.hash(password, 12)
|
29
|
+
.then((hashPw) => {
|
30
|
+
req.body.password = hashPw;
|
31
|
+
const user = new User(req.body);
|
32
|
+
return user.save();
|
33
|
+
})
|
34
|
+
.then((result) => {
|
35
|
+
return successResponse(res, result, "User Registerd Successfully");
|
36
|
+
})
|
37
|
+
.catch((err) => {
|
38
|
+
next(err);
|
39
|
+
});
|
40
|
+
},
|
41
|
+
login: (req, res, next) => {
|
42
|
+
const response = checkValidationResult(res, req);
|
43
|
+
if (response["status_code"] === 422) {
|
44
|
+
return errorResponse(
|
45
|
+
res,
|
46
|
+
response["data"],
|
47
|
+
response["message"],
|
48
|
+
response["status_code"]
|
49
|
+
);
|
50
|
+
}
|
51
|
+
const email = req.body.email;
|
52
|
+
const password = req.body.password;
|
53
|
+
let loadedUser;
|
54
|
+
User.findOne({ email: email })
|
55
|
+
.then((user) => {
|
56
|
+
loadedUser = user;
|
57
|
+
return bcrypt.compare(password, user.password);
|
58
|
+
})
|
59
|
+
.then((isEqual) => {
|
60
|
+
if (!isEqual) {
|
61
|
+
data = {
|
62
|
+
password: ["Wrong Password"],
|
63
|
+
};
|
64
|
+
return customResponse(res, data, "Wrong Password", 422);
|
65
|
+
}
|
66
|
+
// create new token using sign
|
67
|
+
return jwt.sign(
|
68
|
+
{
|
69
|
+
userId: loadedUser._id,
|
70
|
+
},
|
71
|
+
dotenv.JWT_SIGNATURE,
|
72
|
+
{ expiresIn: "365d" }
|
73
|
+
);
|
74
|
+
})
|
75
|
+
.then((token) => {
|
76
|
+
loadedUser.token = token;
|
77
|
+
loadedUser.save();
|
78
|
+
return successResponse(
|
79
|
+
res,
|
80
|
+
{ access_token: token, user_data: loadedUser },
|
81
|
+
"Login Successfully"
|
82
|
+
);
|
83
|
+
})
|
84
|
+
.catch((err) => {
|
85
|
+
next(err);
|
86
|
+
});
|
87
|
+
},
|
88
|
+
logout: (req, res, next) => {
|
89
|
+
const filter = { _id: req.userId };
|
90
|
+
const update = { token: "" };
|
91
|
+
User.findOneAndUpdate(filter, update)
|
92
|
+
.then(() => {
|
93
|
+
return successResponse(res, null, "You have been Logged Out");
|
94
|
+
})
|
95
|
+
.catch((err) => {
|
96
|
+
next(err);
|
97
|
+
});
|
98
|
+
},
|
99
|
+
forgotPassword: (req, res, next) => {
|
100
|
+
const response = checkValidationResult(res, req);
|
101
|
+
if (response["status_code"] === 422) {
|
102
|
+
return errorResponse(
|
103
|
+
res,
|
104
|
+
response["data"],
|
105
|
+
response["message"],
|
106
|
+
response["status_code"]
|
107
|
+
);
|
108
|
+
}
|
109
|
+
crypto.randomBytes(32, (err, buffer) => {
|
110
|
+
if (err) {
|
111
|
+
return customResponse(res, null, "Something went wrong", 400);
|
112
|
+
}
|
113
|
+
const token = buffer.toString("hex");
|
114
|
+
User.findOne({ email: req.body.email })
|
115
|
+
.then((user) => {
|
116
|
+
const reset_token = new ResetToken({
|
117
|
+
reset_token: token,
|
118
|
+
email: user.email,
|
119
|
+
});
|
120
|
+
return reset_token.save();
|
121
|
+
})
|
122
|
+
.then((result) => {
|
123
|
+
nodemailer.sendMail({
|
124
|
+
to: req.body.email,
|
125
|
+
from: "ats@technomarktest.io",
|
126
|
+
subject: "Password reset",
|
127
|
+
html: `
|
128
|
+
<p>You requested a password reset</p>
|
129
|
+
<p>Click this <a href="${dotenv?.HOST}/reset/?token=${token}&email=${req.body.email}">link</a> to set a new password.</p>
|
130
|
+
`,
|
131
|
+
});
|
132
|
+
return successResponse(
|
133
|
+
res,
|
134
|
+
null,
|
135
|
+
"Reset password link send your registerd email address"
|
136
|
+
);
|
137
|
+
})
|
138
|
+
.catch((err) => {
|
139
|
+
next(err);
|
140
|
+
});
|
141
|
+
});
|
142
|
+
},
|
143
|
+
resetPassword: (req, res, next) => {
|
144
|
+
const response = checkValidationResult(res, req);
|
145
|
+
if (response["status_code"] === 422) {
|
146
|
+
return errorResponse(
|
147
|
+
res,
|
148
|
+
response["data"],
|
149
|
+
response["message"],
|
150
|
+
response["status_code"]
|
151
|
+
);
|
152
|
+
}
|
153
|
+
const new_password = req.body.password;
|
154
|
+
const r_token = req.body.reset_token;
|
155
|
+
const email = req.body.email;
|
156
|
+
let resetUser;
|
157
|
+
ResetToken.findOne({
|
158
|
+
email: email,
|
159
|
+
reset_token: r_token,
|
160
|
+
})
|
161
|
+
.then((result) => {
|
162
|
+
if (!result) {
|
163
|
+
return customResponse(
|
164
|
+
res,
|
165
|
+
null,
|
166
|
+
`Reset token has expired. Please try the 'forgot password' option again to get a new reset token!`,
|
167
|
+
401
|
168
|
+
);
|
169
|
+
}
|
170
|
+
return User.findOne({
|
171
|
+
email: email,
|
172
|
+
});
|
173
|
+
})
|
174
|
+
.then((user) => {
|
175
|
+
resetUser = user;
|
176
|
+
return bcrypt.hash(new_password, 12);
|
177
|
+
})
|
178
|
+
.then((hashedPassword) => {
|
179
|
+
resetUser.password = hashedPassword;
|
180
|
+
return resetUser.save();
|
181
|
+
})
|
182
|
+
.then((result1) => {
|
183
|
+
return ResetToken.findOneAndDelete({
|
184
|
+
email: email,
|
185
|
+
reset_token: r_token,
|
186
|
+
});
|
187
|
+
})
|
188
|
+
.then((result1) => {
|
189
|
+
return successResponse(res, null, "Your Password Change Successfully");
|
190
|
+
})
|
191
|
+
.catch((err) => {
|
192
|
+
next(err);
|
193
|
+
});
|
194
|
+
},
|
195
|
+
profile: (req, res, next) => {
|
196
|
+
const usertoken = req.headers.authorization;
|
197
|
+
const token = usertoken.split(" ");
|
198
|
+
const user = jwt.verify(token[1], dotenv.JWT_SIGNATURE);
|
199
|
+
User.findOne({ _id: user.userId })
|
200
|
+
.then((userData) => {
|
201
|
+
if (!userData) {
|
202
|
+
return customResponse(
|
203
|
+
res,
|
204
|
+
null,
|
205
|
+
"A user with this email could not be found",
|
206
|
+
400
|
207
|
+
);
|
208
|
+
}
|
209
|
+
return successResponse(res, userData);
|
210
|
+
})
|
211
|
+
.catch((err) => {
|
212
|
+
next(err);
|
213
|
+
});
|
214
|
+
},
|
215
|
+
};
|
216
|
+
|
217
|
+
module.exports = AuthController;
|
@@ -0,0 +1,44 @@
|
|
1
|
+
const jwt = require("jsonwebtoken");
|
2
|
+
const dotenv = require("dotenv").config().parsed;
|
3
|
+
const User = require("../models/User"); //
|
4
|
+
const { customResponse } = require("rest-api-response-npm");
|
5
|
+
|
6
|
+
function authenticateToken(req, res, next) {
|
7
|
+
const authHeader = req.headers["authorization"];
|
8
|
+
|
9
|
+
if (authHeader) {
|
10
|
+
const token = authHeader.split(" ")[1];
|
11
|
+
|
12
|
+
if (token) {
|
13
|
+
try {
|
14
|
+
const jwt_secret_key = dotenv?.JWT_SIGNATURE;
|
15
|
+
const decoded = jwt.verify(token, jwt_secret_key);
|
16
|
+
// Token is valid, you can access the decoded payload
|
17
|
+
console.log("decoded?.data?.token", decoded?.userId);
|
18
|
+
if (decoded?.userId) {
|
19
|
+
User.findOne({ _id: decoded?.userId })
|
20
|
+
.then((userData) => {
|
21
|
+
console.log("token", token);
|
22
|
+
console.log("userData", userData);
|
23
|
+
if (userData && token === userData?.token) {
|
24
|
+
req.user = decoded.data;
|
25
|
+
req.userId = decoded.userId;
|
26
|
+
next();
|
27
|
+
} else {
|
28
|
+
return customResponse(res, null, "Token is expired", 401);
|
29
|
+
}
|
30
|
+
})
|
31
|
+
.catch((err) => {});
|
32
|
+
}
|
33
|
+
} catch (err) {
|
34
|
+
return customResponse(res, null, "Token is invalid or expired", 401);
|
35
|
+
}
|
36
|
+
} else {
|
37
|
+
return customResponse(res, null, "No token found", 401);
|
38
|
+
}
|
39
|
+
} else {
|
40
|
+
return customResponse(res, null, "No authorization header", 401);
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
module.exports = authenticateToken;
|
@@ -0,0 +1,19 @@
|
|
1
|
+
const express = require('express');
|
2
|
+
const router = express.Router();
|
3
|
+
const AuthController = require('../controllers/AuthController');
|
4
|
+
const signUpRequestValidationRequest = require('./validationRequest/Auth/signUpRequestValidationRequest');
|
5
|
+
const signInValidationRequest = require('./validationRequest/Auth/signInValidationRequest');
|
6
|
+
const forgotPasswordValidationRequest = require('./validationRequest/Auth/forgotPasswordValidationRequest');
|
7
|
+
const resetPasswordValidationRequest = require('./validationRequest/Auth/resetPasswordValidationRequest');
|
8
|
+
const formParser = require('../helpers/formParser');
|
9
|
+
const isAuth = require("../middleware/AuthMiddleware");
|
10
|
+
|
11
|
+
|
12
|
+
router.post(`/register`,formParser,signUpRequestValidationRequest, AuthController.signUp);
|
13
|
+
router.post(`/login`,formParser,signInValidationRequest, AuthController.login);
|
14
|
+
router.post(`/forgot-password`,formParser,forgotPasswordValidationRequest, AuthController.forgotPassword);
|
15
|
+
router.post(`/reset-password`,formParser,resetPasswordValidationRequest, AuthController.resetPassword);
|
16
|
+
router.get(`/logout`,formParser,isAuth, AuthController.logout);
|
17
|
+
router.get(`/get-profile`,formParser,isAuth, AuthController.profile);
|
18
|
+
|
19
|
+
module.exports = router;
|
@@ -0,0 +1,173 @@
|
|
1
|
+
const {
|
2
|
+
successResponse,
|
3
|
+
checkValidationResult,
|
4
|
+
errorResponse,
|
5
|
+
} = require("rest-api-response-npm");
|
6
|
+
const ${schemaName} = require("../models/${schemaName}");
|
7
|
+
const {
|
8
|
+
generatePaginationLinks,
|
9
|
+
searchableFields,
|
10
|
+
mergerFilesAndBody,
|
11
|
+
filterMissingFields,
|
12
|
+
} = require("../helpers/helper.js");
|
13
|
+
|
14
|
+
const ${fileName}Controller = {
|
15
|
+
list: async (req, res) => {
|
16
|
+
var currentPage = parseInt(req?.query?.page_no) || 1;
|
17
|
+
var perPage = parseInt(req?.query?.page_limit) || 10;
|
18
|
+
var searchInput = req?.query?.search || "";
|
19
|
+
|
20
|
+
const rgx = (pattern) => new RegExp(`.*${pattern}.*`);
|
21
|
+
const searchRgx = rgx(searchInput);
|
22
|
+
|
23
|
+
var SearchFieldArray = ${___SearchFieldArray___};
|
24
|
+
|
25
|
+
const count = await ${schemaName}.countDocuments({
|
26
|
+
$or: SearchFieldArray,
|
27
|
+
});
|
28
|
+
|
29
|
+
const totalPages = Math.ceil(count / perPage);
|
30
|
+
|
31
|
+
await ${schemaName}.find({
|
32
|
+
$or: SearchFieldArray,
|
33
|
+
})
|
34
|
+
.skip((currentPage - 1) * perPage)
|
35
|
+
.limit(perPage)
|
36
|
+
.then((results) => {
|
37
|
+
const response = {
|
38
|
+
totalRecords: count,
|
39
|
+
totalPages: totalPages,
|
40
|
+
currentPage: currentPage,
|
41
|
+
perPage: perPage,
|
42
|
+
results: results,
|
43
|
+
};
|
44
|
+
|
45
|
+
generatePaginationLinks(
|
46
|
+
req,
|
47
|
+
response,
|
48
|
+
currentPage,
|
49
|
+
perPage,
|
50
|
+
totalPages,
|
51
|
+
searchInput
|
52
|
+
);
|
53
|
+
return successResponse(
|
54
|
+
res,
|
55
|
+
response,
|
56
|
+
"${fileName} list fetched successfully!"
|
57
|
+
);
|
58
|
+
})
|
59
|
+
.catch((err) => {
|
60
|
+
return errorResponse(res, err, "Opps something went wrong!", 500);
|
61
|
+
});
|
62
|
+
},
|
63
|
+
getDetail: async (req, res) => {
|
64
|
+
const response = checkValidationResult(res, req);
|
65
|
+
if (response["status_code"] === 422) {
|
66
|
+
return errorResponse(
|
67
|
+
res,
|
68
|
+
response["data"],
|
69
|
+
response["message"],
|
70
|
+
response["status_code"]
|
71
|
+
);
|
72
|
+
}
|
73
|
+
await ${schemaName}.findById(req.params.id)
|
74
|
+
.then((result) => {
|
75
|
+
if (result) {
|
76
|
+
return successResponse(res, result, "${fileName} detail fetched successfully!");
|
77
|
+
} else {
|
78
|
+
data = {
|
79
|
+
id: ["Record doesn't exist!"],
|
80
|
+
};
|
81
|
+
return validationResponse(res, data, "Record doesn't exist!");
|
82
|
+
}
|
83
|
+
})
|
84
|
+
.catch((err) =>
|
85
|
+
{
|
86
|
+
return errorResponse(res, err, "Opps something went wrong!", 500)
|
87
|
+
}
|
88
|
+
);
|
89
|
+
},
|
90
|
+
store: async (req, res) => {
|
91
|
+
const response = checkValidationResult(res, req);
|
92
|
+
if (response["status_code"] === 422) {
|
93
|
+
return errorResponse(
|
94
|
+
res,
|
95
|
+
response["data"],
|
96
|
+
response["message"],
|
97
|
+
response["status_code"]
|
98
|
+
);
|
99
|
+
}
|
100
|
+
/*File values into body*/
|
101
|
+
mergerFilesAndBody(req);
|
102
|
+
var storeData = ${___StoreFieldArray___};
|
103
|
+
var ${schemaName}Store = new ${schemaName}(storeData);
|
104
|
+
await ${schemaName}Store.save()
|
105
|
+
.then((result) => {
|
106
|
+
return successResponse(res, result, "${fileName} created successfully!");
|
107
|
+
})
|
108
|
+
.catch((err) => {
|
109
|
+
return errorResponse(res, err, "Opps something went wrong!", 500);
|
110
|
+
});
|
111
|
+
},
|
112
|
+
update: async (req, res) => {
|
113
|
+
const response = checkValidationResult(res, req);
|
114
|
+
if (response["status_code"] === 422) {
|
115
|
+
return errorResponse(
|
116
|
+
res,
|
117
|
+
response["data"],
|
118
|
+
response["message"],
|
119
|
+
response["status_code"]
|
120
|
+
);
|
121
|
+
}
|
122
|
+
/*File values into body*/
|
123
|
+
mergerFilesAndBody(req);
|
124
|
+
|
125
|
+
var updateData = ${___StoreFieldArray___};
|
126
|
+
|
127
|
+
updateData = filterMissingFields(updateData); // Removes fields with null values
|
128
|
+
|
129
|
+
await ${schemaName}.findByIdAndUpdate(req.params.id, updateData, { new: true })
|
130
|
+
.then((result) => {
|
131
|
+
if (result) {
|
132
|
+
successResponse(res, result, "${fileName} updated successfully!");
|
133
|
+
} else {
|
134
|
+
data = {
|
135
|
+
id: ["Record doesn't exist!"],
|
136
|
+
};
|
137
|
+
validationResponse(res, data, "Record doesn't exist!");
|
138
|
+
}
|
139
|
+
})
|
140
|
+
.catch((err) =>
|
141
|
+
errorResponse(res, err, "Opps something went wrong!", 500)
|
142
|
+
);
|
143
|
+
},
|
144
|
+
destroy: async (req, res) => {
|
145
|
+
const response = checkValidationResult(res, req);
|
146
|
+
if (response["status_code"] === 422) {
|
147
|
+
return errorResponse(
|
148
|
+
res,
|
149
|
+
response["data"],
|
150
|
+
response["message"],
|
151
|
+
response["status_code"]
|
152
|
+
);
|
153
|
+
}
|
154
|
+
await ${schemaName}.findByIdAndDelete(req.params.id)
|
155
|
+
.then((result) => {
|
156
|
+
if (result) {
|
157
|
+
return successResponse(res, result, "Deleted successfully!");
|
158
|
+
} else {
|
159
|
+
data = {
|
160
|
+
id: ["Record doesn't exist!"],
|
161
|
+
};
|
162
|
+
return validationResponse(res, data, "Record doesn't exist!");
|
163
|
+
}
|
164
|
+
})
|
165
|
+
.catch((err) =>
|
166
|
+
{
|
167
|
+
return errorResponse(res, err, "Opps something went wrong!", 500)
|
168
|
+
}
|
169
|
+
);
|
170
|
+
}
|
171
|
+
};
|
172
|
+
|
173
|
+
module.exports = ${fileName}Controller;
|
package/sample-env
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
const multer = require("multer");
|
2
|
+
const fs = require("fs");
|
3
|
+
const path = require("path");
|
4
|
+
|
5
|
+
// Create a Multer instance with destination for uploaded files
|
6
|
+
const storage = (routeName) =>
|
7
|
+
multer.diskStorage({
|
8
|
+
destination: function (req, file, cb) {
|
9
|
+
const folder = `./public/${routeName}`;
|
10
|
+
|
11
|
+
// Check if the folder exists, create it if it doesn't
|
12
|
+
if (!fs.existsSync(folder)) {
|
13
|
+
fs.mkdirSync(folder, { recursive: true });
|
14
|
+
}
|
15
|
+
|
16
|
+
cb(null, folder);
|
17
|
+
},
|
18
|
+
filename: function (req, file, cb) {
|
19
|
+
let fileExt = path.extname(file.originalname);
|
20
|
+
cb(null, `${routeName}-${Date.now()}${fileExt}`);
|
21
|
+
},
|
22
|
+
});
|
23
|
+
|
24
|
+
// Enable file upload middleware using Multer
|
25
|
+
const upload = (routeName) => multer({ storage: storage(routeName) });
|
26
|
+
|
27
|
+
module.exports = upload;
|
package/sample-helper.js
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
function extractRouteName(req) {
|
2
|
+
const path = req.originalUrl.split("?")[0]; // Remove query parameters
|
3
|
+
const parts = path.split("/").filter(Boolean); // Remove empty parts
|
4
|
+
return parts[parts.length - 1];
|
5
|
+
}
|
6
|
+
|
7
|
+
function generatePaginationLinks(
|
8
|
+
req,
|
9
|
+
response,
|
10
|
+
currentPage,
|
11
|
+
perPage,
|
12
|
+
totalPages,
|
13
|
+
searchInput
|
14
|
+
) {
|
15
|
+
const routeName = extractRouteName(req);
|
16
|
+
|
17
|
+
// add first page link
|
18
|
+
const firstLink = `${req.protocol}://${req.get("host")}${
|
19
|
+
req.baseUrl
|
20
|
+
}/${routeName}?page_no=1&page_limit=${perPage}&search=${searchInput}`;
|
21
|
+
response.firstLink = firstLink;
|
22
|
+
|
23
|
+
// add previous page link if exist
|
24
|
+
if (currentPage > 1) {
|
25
|
+
const previousPage = currentPage - 1;
|
26
|
+
const previousLink = `${req.protocol}://${req.get("host")}${
|
27
|
+
req.baseUrl
|
28
|
+
}/${routeName}?page_no=${previousPage}&page_limit=${perPage}&search=${searchInput}`;
|
29
|
+
response.previousLink = previousLink;
|
30
|
+
} else {
|
31
|
+
response.previousLink = null;
|
32
|
+
}
|
33
|
+
|
34
|
+
// add current page link if exist
|
35
|
+
if (currentPage >= 1 || currentPage <= totalPages) {
|
36
|
+
const currentLink = `${req.protocol}://${req.get("host")}${
|
37
|
+
req.baseUrl
|
38
|
+
}/${routeName}?page_no=${currentPage}&page_limit=${perPage}&search=${searchInput}`;
|
39
|
+
response.currentLink = currentLink;
|
40
|
+
} else {
|
41
|
+
response.currentLink = null;
|
42
|
+
}
|
43
|
+
|
44
|
+
// add next page link if exist
|
45
|
+
if (currentPage < totalPages) {
|
46
|
+
const nextPage = currentPage + 1;
|
47
|
+
const nextLink = `${req.protocol}://${req.get("host")}${
|
48
|
+
req.baseUrl
|
49
|
+
}/${routeName}?page_no=${nextPage}&page_limit=${perPage}&search=${searchInput}`;
|
50
|
+
response.nextLink = nextLink;
|
51
|
+
} else {
|
52
|
+
response.nextLink = null;
|
53
|
+
}
|
54
|
+
|
55
|
+
// add last page link
|
56
|
+
const lastPage = totalPages ? totalPages : 1;
|
57
|
+
const lastLink = `${req.protocol}://${req.get("host")}${
|
58
|
+
req.baseUrl
|
59
|
+
}/${routeName}?page_no=${lastPage}&page_limit=${perPage}&search=${searchInput}`;
|
60
|
+
response.lastLink = lastLink;
|
61
|
+
|
62
|
+
return response;
|
63
|
+
}
|
64
|
+
|
65
|
+
function modelsParams(Schema) {
|
66
|
+
// Obtain the schema parameters object
|
67
|
+
const paths = Schema?.schema?.paths;
|
68
|
+
let parameters = [];
|
69
|
+
let all = [];
|
70
|
+
let required = [];
|
71
|
+
let optional = [];
|
72
|
+
// Loop over the paths and log each field's name and type
|
73
|
+
for (const path in paths) {
|
74
|
+
let isRequired = paths[path]?.options?.required ? true : false;
|
75
|
+
parameters.push({
|
76
|
+
field: path,
|
77
|
+
is_required: isRequired,
|
78
|
+
});
|
79
|
+
if (isRequired) {
|
80
|
+
required.push(path);
|
81
|
+
} else {
|
82
|
+
optional.push(path);
|
83
|
+
}
|
84
|
+
all.push(path);
|
85
|
+
}
|
86
|
+
const unwantedProps = ["_id", "createdAt", "updatedAt", "__v"];
|
87
|
+
const filteredFormData = all.filter((prop) => !unwantedProps.includes(prop));
|
88
|
+
return {
|
89
|
+
parameters: parameters,
|
90
|
+
allFields: all,
|
91
|
+
requiredFields: required,
|
92
|
+
optionalFields: optional,
|
93
|
+
filteredFormFields: filteredFormData,
|
94
|
+
};
|
95
|
+
}
|
96
|
+
|
97
|
+
function searchableFields(Model) {
|
98
|
+
var formFields = modelsParams(Model);
|
99
|
+
var uploadFieldArray = [];
|
100
|
+
var searchFields = [];
|
101
|
+
if (
|
102
|
+
formFields &&
|
103
|
+
formFields?.filteredFormFields &&
|
104
|
+
formFields?.filteredFormFields.length
|
105
|
+
) {
|
106
|
+
searchFields = formFields?.filteredFormFields;
|
107
|
+
return searchFields;
|
108
|
+
}
|
109
|
+
}
|
110
|
+
function mergerFilesAndBody(req) {
|
111
|
+
if (req.files && Object.keys(req.files).length > 0) {
|
112
|
+
Object.keys(req.files).forEach(function (key) {
|
113
|
+
if (req.files[key] && req.files[key] !== undefined) {
|
114
|
+
var file = req.files[key];
|
115
|
+
if (file && file?.path && file?.fieldname)
|
116
|
+
req.body[file?.fieldname] = file?.path.replace(/\public\\/g, "");
|
117
|
+
}
|
118
|
+
});
|
119
|
+
req.files = [];
|
120
|
+
return req;
|
121
|
+
}
|
122
|
+
}
|
123
|
+
function filterMissingFields(data) {
|
124
|
+
for (var key in data) {
|
125
|
+
if (data[key] === null) {
|
126
|
+
delete data[key];
|
127
|
+
}
|
128
|
+
}
|
129
|
+
return data;
|
130
|
+
}
|
131
|
+
|
132
|
+
module.exports = {
|
133
|
+
generatePaginationLinks,
|
134
|
+
modelsParams,
|
135
|
+
searchableFields,
|
136
|
+
mergerFilesAndBody,
|
137
|
+
filterMissingFields,
|
138
|
+
};
|
@@ -0,0 +1,29 @@
|
|
1
|
+
{
|
2
|
+
"name": "my-node-project",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"description": "",
|
5
|
+
"main": "index.js",
|
6
|
+
"scripts": {
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
8
|
+
"dev": "nodemon app.js"
|
9
|
+
},
|
10
|
+
"author": "",
|
11
|
+
"license": "ISC",
|
12
|
+
"dependencies": {
|
13
|
+
"bcryptjs": "^2.4.3",
|
14
|
+
"body-parser": "^1.20.2",
|
15
|
+
"dotenv": "^16.3.1",
|
16
|
+
"express": "^4.18.2",
|
17
|
+
"express-validator": "^7.0.1",
|
18
|
+
"jsonwebtoken": "^9.0.1",
|
19
|
+
"mongoose": "^7.3.1",
|
20
|
+
"mongoose-backpack": "^0.2.7",
|
21
|
+
"multer": "^1.4.5-lts.1",
|
22
|
+
"node-backpack": "^0.0.427",
|
23
|
+
"nodemailer": "^6.9.3",
|
24
|
+
"rest-api-response-npm": "^0.1.4"
|
25
|
+
},
|
26
|
+
"devDependencies": {
|
27
|
+
"nodemon": "^3.0.1"
|
28
|
+
}
|
29
|
+
}
|
package/sample-route.js
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
const express = require('express');
|
2
|
+
const router = express.Router();
|
3
|
+
const ${fileName}Controller = require('../controllers/${fileName}Controller');
|
4
|
+
const ListValidationRequest = require('./validationRequest/${fileName}/ListValidationRequest');
|
5
|
+
const GetDetailValidationRequest = require('./validationRequest/${fileName}/GetDetailValidationRequest');
|
6
|
+
const StoreValidationRequest = require('./validationRequest/${fileName}/StoreValidationRequest');
|
7
|
+
const UpdateValidationRequest = require('./validationRequest/${fileName}/UpdateValidationRequest');
|
8
|
+
const DestroyValidationRequest = require('./validationRequest/${fileName}/DestroyValidationRequest');
|
9
|
+
const upload = require('../helpers/fileUploader');
|
10
|
+
const formParser = require('../helpers/formParser');
|
11
|
+
|
12
|
+
const prefix = '${apiUrl}';
|
13
|
+
|
14
|
+
router.get(`/${prefix}/`,formParser,ListValidationRequest, ${fileName}Controller.list);
|
15
|
+
router.get(`/${prefix}/:id`,formParser,GetDetailValidationRequest, ${fileName}Controller.getDetail);
|
16
|
+
router.post(`/${prefix}/`,upload(prefix).any(),StoreValidationRequest, ${fileName}Controller.store);
|
17
|
+
router.put(`/${prefix}/:id`,upload(prefix).any(),UpdateValidationRequest, ${fileName}Controller.update);
|
18
|
+
router.delete(`/${prefix}/:id`,formParser,DestroyValidationRequest, ${fileName}Controller.destroy);
|
19
|
+
|
20
|
+
module.exports = router;
|
@@ -0,0 +1,26 @@
|
|
1
|
+
const mongoose = require("mongoose");
|
2
|
+
const Schema = mongoose.Schema;
|
3
|
+
const { appendParams } = require("mongoose-backpack");
|
4
|
+
|
5
|
+
const ResetToken = new Schema(
|
6
|
+
{
|
7
|
+
email: {
|
8
|
+
type: String,
|
9
|
+
required: true,
|
10
|
+
},
|
11
|
+
reset_token: {
|
12
|
+
type: String,
|
13
|
+
required: false,
|
14
|
+
},
|
15
|
+
},
|
16
|
+
{ timestamps: true }
|
17
|
+
);
|
18
|
+
|
19
|
+
appendData = {
|
20
|
+
id: function () {
|
21
|
+
return this._id;
|
22
|
+
},
|
23
|
+
};
|
24
|
+
appendParams(ResetToken, appendData);
|
25
|
+
|
26
|
+
module.exports = mongoose.model("ResetToken", ResetToken);
|
@@ -0,0 +1,51 @@
|
|
1
|
+
const mongoose = require("mongoose");
|
2
|
+
const Schema = mongoose.Schema;
|
3
|
+
const { appendParams } = require("mongoose-backpack");
|
4
|
+
|
5
|
+
const User = new Schema(
|
6
|
+
{
|
7
|
+
first_name: {
|
8
|
+
type: String,
|
9
|
+
required: true,
|
10
|
+
},
|
11
|
+
last_name: {
|
12
|
+
type: String,
|
13
|
+
required: true,
|
14
|
+
},
|
15
|
+
email: {
|
16
|
+
type: String,
|
17
|
+
required: true,
|
18
|
+
},
|
19
|
+
phone_no: {
|
20
|
+
type: String,
|
21
|
+
required: false,
|
22
|
+
},
|
23
|
+
password: {
|
24
|
+
type: String,
|
25
|
+
required: true,
|
26
|
+
},
|
27
|
+
image: {
|
28
|
+
type: String,
|
29
|
+
required: false,
|
30
|
+
},
|
31
|
+
token: {
|
32
|
+
type: String,
|
33
|
+
required: false,
|
34
|
+
},
|
35
|
+
},
|
36
|
+
{ timestamps: true }
|
37
|
+
);
|
38
|
+
|
39
|
+
appendData = {
|
40
|
+
id: function () {
|
41
|
+
return this._id;
|
42
|
+
},
|
43
|
+
full_name: function () {
|
44
|
+
const firstName = this?.first_name ? this.first_name + " " : "";
|
45
|
+
const lastName = this?.last_name ? this.last_name : "";
|
46
|
+
return `${firstName}${lastName}`;
|
47
|
+
},
|
48
|
+
};
|
49
|
+
appendParams(User, appendData);
|
50
|
+
|
51
|
+
module.exports = mongoose.model("User", User);
|
package/sample-schema.js
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
const mongoose = require("mongoose");
|
2
|
+
const Schema = mongoose.Schema;
|
3
|
+
const { appendParams } = require("mongoose-backpack");
|
4
|
+
|
5
|
+
const ${fileContent} = new Schema(
|
6
|
+
{
|
7
|
+
name: {
|
8
|
+
type: String,
|
9
|
+
required: true,
|
10
|
+
},
|
11
|
+
},
|
12
|
+
{ timestamps: true }
|
13
|
+
);
|
14
|
+
|
15
|
+
const appendData = {
|
16
|
+
id: function () {
|
17
|
+
return this._id;
|
18
|
+
},
|
19
|
+
};
|
20
|
+
appendParams(${fileContent}, appendData);
|
21
|
+
|
22
|
+
const ${fileContent}Model = mongoose.model("${fileContent}", ${fileContent});
|
23
|
+
|
24
|
+
// Created a change stream. The 'change' event gets emitted when there's a change in the database
|
25
|
+
/*
|
26
|
+
${fileContent}Model.watch().on("change", (data) => {
|
27
|
+
const timestamp = new Date();
|
28
|
+
const operationType = data.operationType;
|
29
|
+
|
30
|
+
switch (operationType) {
|
31
|
+
case "insert":
|
32
|
+
// TODO Logic Code...
|
33
|
+
console.log(timestamp, "Document created:", data.fullDocument);
|
34
|
+
break;
|
35
|
+
case "update":
|
36
|
+
// TODO Logic Code...
|
37
|
+
console.log(timestamp, "Document updated:", data.updateDescription);
|
38
|
+
break;
|
39
|
+
case "delete":
|
40
|
+
// TODO Logic Code...
|
41
|
+
console.log(timestamp, "Document deleted:", data.documentKey);
|
42
|
+
break;
|
43
|
+
default:
|
44
|
+
console.log(timestamp, "Unknown operation type:", operationType);
|
45
|
+
}
|
46
|
+
});
|
47
|
+
*/
|
48
|
+
|
49
|
+
module.exports = ${fileContent}Model;
|
package/typing.gif
ADDED
Binary file
|
package/README.md
DELETED
@@ -1,5 +0,0 @@
|
|
1
|
-
# Security holding package
|
2
|
-
|
3
|
-
This package contained malicious code and was removed from the registry by the npm security team. A placeholder was published to ensure users are not affected in the future.
|
4
|
-
|
5
|
-
Please refer to www.npmjs.com/advisories?search=nodejs-backpack for more information.
|