lolo-backend
Version:
Lolo Backend is a REST Framework and provides an automatic asynchronous CRUD backend server generator based on your models' definitions and validation rules for Node.js and MongoDB.
432 lines (365 loc) • 20 kB
JavaScript
/*!
* lolo-backend
* Copyright(c) 2021 Camilo Pifano Herrera
* MIT Licensed
*/
//--------------------------------------------------------------------------------------------------------------------//
// DYNAMIC CRUD:
// Generate dynamic CRUD routes (most common routes), based on the specified models.
//--------------------------------------------------------------------------------------------------------------------//
module.exports = function(current, currentLang, verboseMode) {
const express = require('express'); //Express module.
const { validationResult } = require('express-validator'); //Express-validator Middleware.
const bcrypt = require('bcrypt'); //bcrypt module.
const services = require('./services'); //Services module.
const crudMiddleware = require('./crud_middleware'); //CRUD Middleware.
//Create a Router:
const router = express.Router();
//Get keys from current schema:
const allSchemaKeys = services.getSchemaKeys(current); //All.
const allowedSchemaKeys = services.getSchemaKeys(current, true); //No parameters that cannot be modified.
//----------------------------------------------------------------------------------------------------------------//
// GET - DESCRIBE:
// Returns all the keys of the model indicated in the creation of the route.
//----------------------------------------------------------------------------------------------------------------//
router.get('/describe', crudMiddleware.checkJWT, (req, res) => {
//Send console log message:
if(verboseMode === true) { services.sendConsoleMessage(req); }
//Return All Schema Keys (HTML Response):
res.status(200).send({ success: true, keys: allSchemaKeys });
});
//----------------------------------------------------------------------------------------------------------------//
//----------------------------------------------------------------------------------------------------------------//
// GET - COUNT:
// Returns number of records in the collection that match the filters.
//----------------------------------------------------------------------------------------------------------------//
router.get('/count', crudMiddleware.checkJWT, async (req, res) => {
//Send console log message:
if(verboseMode === true) { services.sendConsoleMessage(req); }
//Count using query params:
await current.Model.countDocuments(req.query.filter, (err, count) => {
if (err) {
//Send error message to console:
console.error({ success: false, error: err.message });
res.status(500).send({ success: false, error: err.message });
}
//Return the result (HTML Response):
res.status(200).send({ success: true, count: count });
});
});
//----------------------------------------------------------------------------------------------------------------//
//----------------------------------------------------------------------------------------------------------------//
// GET - FIND:
// Finds all the records in the collection that match the filters, the projection and the requested sort.
//----------------------------------------------------------------------------------------------------------------//
router.get('/find', crudMiddleware.checkJWT, async (req, res) => {
//Send console log message:
if(verboseMode === true) { services.sendConsoleMessage(req); }
//Get query params:
let { filter, proj, skip, limit, sort, pager } = req.query;
//Parse skip and limit value (string) to integer (base 10):
skip = parseInt(skip, 10);
limit = parseInt(limit, 10);
//Validate and format data projection:
let formatted_proj = services.validateFormattedProj(proj);
//Check if Pager was requested:
if(pager){
//Parse page_number and page_limit value (string) to integer (base 10):
pager.page_number = parseInt(pager.page_number, 10);
pager.page_limit = parseInt(pager.page_limit, 10);
//If dosn't exist, set page number:
if(!pager.page_number){
pager.page_number = 1;
}
//Set page limit and and replace limit param:
if(pager.page_limit){
limit = pager.page_limit;
} else {
//Set default page limit value:
limit = 10;
}
//Calculate and replace skip value (Paginate):
skip = (pager.page_number-1)*limit;
}
//Build find query:
const queryMongoDB = current.Model.find(filter, formatted_proj).skip(skip).limit(limit).sort(sort);
//Count using query params:
await current.Model.countDocuments(req.query.filter, async(err, count) => {
if (err) {
//Send error message to console:
console.error({ success: false, error: err.message });
res.status(500).send({ success: false, error: err.message });
}
//Execute find query:
await queryMongoDB.exec((err, data) => {
//Validate errors in the query:
if(err){
//Set error message:
errorMessage = { success: false, message: currentLang.db.query_error, error: err.message };
//Send error message to console:
console.error(errorMessage);
//Return error message (HTML Response):
res.status(500).send(errorMessage);
} else if(data){
//Validate and set paginator:
let pager_data;
if(pager){
pager_data = {
total_items: count,
items_per_page: limit,
viewed_items: data.length,
number_of_pages: Math.ceil(count / limit),
actual_page: pager.page_number
};
} else {
pager_data = 'Pager is disabled';
}
//Return result (HTML Response):
res.status(200).send({
success: true,
data: data,
pager: pager_data,
});
} else {
//Return result NO data (HTML Response):
res.status(200).send({ success: true, message: currentLang.db.query_no_data, data: {} });
}
});
});
});
//----------------------------------------------------------------------------------------------------------------//
//----------------------------------------------------------------------------------------------------------------//
// GET - FIND BY ID:
// Find an element based on an ID.
//----------------------------------------------------------------------------------------------------------------//
router.get('/findById', crudMiddleware.checkJWT, async (req, res) => {
//Send console log message:
if(verboseMode === true) { services.sendConsoleMessage(req); }
//Validate ID request:
if(!services.validateRequestID(req, res, currentLang)) return;
//Get query params:
let { id, proj } = req.query;
//Validate and format data projection:
let formatted_proj = services.validateFormattedProj(proj);
//Build query:
const queryMongoDB = current.Model.findById(id, formatted_proj);
//Execute query:
await queryMongoDB.exec((err, data) => {
//Evaluate query result:
const result = services.commonResponse(err, data, currentLang);
//Return the result (HTML Response):
if (result.success === true) {
res.status(200).send(result);
} else {
res.status(500).send(result);
}
});
});
//----------------------------------------------------------------------------------------------------------------//
//----------------------------------------------------------------------------------------------------------------//
// GET - FIND ONE:
// Find for a single item (first occurrence), in the collection that match the filters, the projection and the
// requested sort.
//----------------------------------------------------------------------------------------------------------------//
router.get('/findOne', crudMiddleware.checkJWT, async (req, res) => {
//Send console log message:
if(verboseMode === true) { services.sendConsoleMessage(req); }
//Get query params:
let { filter, proj, sort } = req.query;
//Validate and format data projection:
let formatted_proj = services.validateFormattedProj(proj);
//Build query:
const queryMongoDB = current.Model.findOne(filter, formatted_proj).sort(sort);
//Execute query:
await queryMongoDB.exec((err, data) => {
//Evaluate query result:
const result = services.commonResponse(err, data, currentLang);
//Return the result (HTML Response):
if (result.success === true) {
res.status(200).send(result);
} else {
res.status(500).send(result);
}
});
});
//----------------------------------------------------------------------------------------------------------------//
//----------------------------------------------------------------------------------------------------------------//
// POST - INSERT:
// Creates a new record in the database.
//----------------------------------------------------------------------------------------------------------------//
router.post('/insert', crudMiddleware.checkJWT, current.Validator, async (req, res) => {
//Send console log message:
if(verboseMode === true) { services.sendConsoleMessage(req); }
//Get validation result:
const errors = validationResult(req);
//Check validation result (express-validator):
if(!errors.isEmpty()){
//Initialize container array of validation messages:
let validate_messages = [];
//Walk through validation errors and load them into the message array:
errors.array().forEach(element => {
validate_messages.push(element.msg);
});
//Return the result (HTML Response):
res.status(422).send({ success: false, message: currentLang.db.validate_error, validate_errors: validate_messages });
} else {
//Create Mongoose object to insert validated data from POST:
const objData = new current.Model(req.body);
//Save to MongoDB:
const result = await objData.save(objData)
.then(data => {
return { success: true, message: currentLang.db.insert_success, data: data };
})
.catch(err => {
//Send error message to console:
console.error({ success: false, message: currentLang.db.insert_error, error: err.message });
return { success: false, message: currentLang.db.insert_error, error: err.message };
});
//Return the result (HTML Response):
if (result.success === true) {
res.status(200).send(result);
} else {
res.status(500).send(result);
}
}
});
//----------------------------------------------------------------------------------------------------------------//
//----------------------------------------------------------------------------------------------------------------//
// POST - UPDATE:
// Validate against the current model and if positive, updates an existing record in the database according to the
// ID and specified parameters.
//----------------------------------------------------------------------------------------------------------------//
router.post('/update', crudMiddleware.checkJWT, crudMiddleware.allowedValidate(allowedSchemaKeys), current.Validator, async (req, res) => {
//Send console log message:
if(verboseMode === true) { services.sendConsoleMessage(req); }
//Validate ID request:
if(!services.validateRequestID(req, res, currentLang)) return;
//Get validation result (global: all elements):
const errors = validationResult(req);
//Initialize container array of validation messages:
let validate_messages = [];
//Check validation result (express-validator):
if(!errors.isEmpty()){
//Get keys of the elements allowed to set:
const setKeys = Object.keys(req.validatedResult.set)
//Check if the elements to be set contain errors:
errors.array().forEach(element => {
if(setKeys.includes(element.param)){
validate_messages.push(element.msg);
}
});
}
//Check only the validation of the fields allowed to set:
if(Object.keys(validate_messages).length === 0){
//Initialize result variable (HTML Response):
let result;
//Save to MongoDB:
//{new:true} - If success, return the modified document instead of the original, before modification.
result = await current.Model.findOneAndUpdate({_id: req.body.id },{$set: req.validatedResult.set }, {new:true})
.then((data)=>{
if(data) {
return { success: true, data: data, blocked_attributes: req.validatedResult.blocked };
} else {
return { success: true, message: currentLang.db.id_no_results };
}
})
.catch((err)=>{
//Send error message to console:
console.error({ success: false, message: currentLang.db.update_error, error: err.message });
//Return error message (HTML Response):
return { success: false, message: currentLang.db.update_error, error: err.message };
});
//Return the result (HTML Response):
if (result.success === true && result.data) {
res.status(200).send(result);
} else if (result.success === true){
res.status(404).send(result);
} else {
res.status(500).send(result);
}
} else {
//Return the result (HTML Response):
res.status(422).send({ success: false, message: currentLang.db.validate_error, validate_errors: validate_messages });
}
});
//----------------------------------------------------------------------------------------------------------------//
//----------------------------------------------------------------------------------------------------------------//
// POST - DELETE:
// Delete an item from the database based on an ID.
//----------------------------------------------------------------------------------------------------------------//
router.post('/delete', crudMiddleware.checkJWT, async (req, res) => {
//Send console log message:
if(verboseMode === true) { services.sendConsoleMessage(req); }
//Validate ID request:
if(!services.validateRequestID(req, res, currentLang)) return;
//Build query:
const queryMongoDB = current.Model.findOneAndDelete({ _id: req.body.id });
//Execute query:
await queryMongoDB.exec((err, data) => {
if(err){
//Send error message to console:
console.error({ success: false, message: currentLang.db.delete_error, error: err.message });
//Return error message (HTML Response):
res.status(500).send({ success: false, message: currentLang.db.delete_error, error: err.message });
} else if(data) {
//Return the result (HTML Response):
res.status(200).send({ success: true, message: currentLang.db.delete_success, data: data });
} else {
//Return the result (HTML Response):
res.status(404).send({ success: false, message: currentLang.db.delete_id_no_results, id: req.body.id });
}
});
});
//----------------------------------------------------------------------------------------------------------------//
//----------------------------------------------------------------------------------------------------------------//
// POST - CHECK PASSWORD:
//----------------------------------------------------------------------------------------------------------------//
router.post('/checkPassById', crudMiddleware.checkJWT, async (req, res) => {
//Send console log message:
if(verboseMode === true) { services.sendConsoleMessage(req); }
//Validate ID request:
if(!services.validateRequestID(req, res, currentLang)) return;
//Validate Password request:
if(req.body.password){
//Get POST data:
let {id, password} = req.body;
//Build query:
const queryMongoDB = current.Model.findById(id, {'password': 1});
//Execute query:
await queryMongoDB.exec((err, data) => {
//Validate errors in the query:
if(err){
//Set error message:
errorMessage = { success: false, message: currentLang.db.query_error, error: err.message };
//Send error message to console:
console.error(errorMessage);
//Return error message (HTML Response):
res.status(500).send(errorMessage);
} else if(data){
//Compare the passwords:
bcrypt.compare(password, data.password, (err, same) => {
if(same){
//Passwords match:
res.status(200).send({ success: true, message: currentLang.db.password_match });
} else {
//Passwords don't match:
res.status(200).send({ success: false, message: currentLang.db.password_dont_match, error: err });
}
});
} else {
//Return result NO data (HTML Response):
res.status(404).send({ success: false, message: currentLang.db.id_no_results });
}
});
} else {
//Return result NO data (HTML Response):
res.status(404).send({ success: false, message: currentLang.db.password_empty });
}
});
//----------------------------------------------------------------------------------------------------------------//
//----------------------------------------------------------------------------------------------------------------//
//Return the current router:
return router;
//----------------------------------------------------------------------------------------------------------------//
};
//--------------------------------------------------------------------------------------------------------------------//