Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ mongorestore ./dump
To pass this exam all excercises must be resolved, every feature must be tested (with all tests passing) and there must be no linting errors.

Summission must be done through a single Pull Request.

4 changes: 2 additions & 2 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const bodyParser = require("body-parser");
const morgan = require("morgan");
const mongoose = require("mongoose");
const config = require("./config");
const {responseHelpers} = require("./middleware");
const {responseHelpers, cacheMiddleware} = require("./middleware");
const routes = require("./routes");
require("./models");

Expand All @@ -22,7 +22,7 @@ app.use(morgan("dev"));
app.use(responseHelpers);

// Add cache middleware
// app.use(cacheMiddleware);
app.use(cacheMiddleware);

// Setup mongoose and load models
mongoose.Promise = global.Promise;
Expand Down
90 changes: 88 additions & 2 deletions controllers/billing.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module.exports = (mongoose) => {
const Course = mongoose.model("Course");
const Evaluation = mongoose.model("Evaluation");
const Student = mongoose.model("Student");
const request = require("request");

// para cada curso dame la evaluacion
// para cada evaluacion dame los aprobados
Expand Down Expand Up @@ -67,7 +68,7 @@ module.exports = (mongoose) => {
firstName: student.firstName,
lastName: student.lastName,
address: student.billingAddress,
price: studentsWithPrice[id]
price: studentsWithPrice[id] / 100
});
});
});
Expand All @@ -81,8 +82,93 @@ module.exports = (mongoose) => {
res.response500(err, "Courses couldn't be found!");
});
}
function getStudentsBillingInfo() {
const options = {
url: "http://localhost:8000/api/admin/billing/getChargeableStudents",
headers: {
"user-agent": "node-exam"
}
};

return new Promise((resolve, reject) => {
request.get(options, (err, response, body) => {
if (err) {
reject(err);
} else if (response.statusCode === 200) {
try {
const studentsBillingInfo = JSON.parse(body).data.studentsBillingInfo;
resolve(studentsBillingInfo);
} catch (ex) {
// console.error(ex);
// Log the exception but returns an error without any sensitive data!
reject(new Error("Couldn't get node repos!"));
}
} else {
console.error(response);
// Log the conflictive response but returns an error without any sensitive data!
reject(new Error("Couldn't get billing student!"));
}
});
});
}
function formatObject(infoAfip, studentInfo) {
return ({
"BillingNumber": infoAfip.id,
"FirstAndLastName": studentInfo.nomYAp,
"Address": studentInfo.dir,
"price": studentInfo.importe
});
}
function getAfipInfo(studentInfo) {
const options = {
url: "http://localhost:8000/api/afip",
headers: {
"user-agent": "node-exam"
}
};
return new Promise((resolve, reject) => {
request.post(options.url, {json: studentInfo}, (_, response, body) => {
if (response.statusCode === 404) {
resolve(getAfipInfo(studentInfo));
} else if (response.statusCode === 200) {
try {
resolve(formatObject(body.data, studentInfo));
} catch (ex) {
console.error(ex);
// Log the exception but returns an error without any sensitive data!
reject(new Error("Couldn't get node repos!"));
}
} else {
console.error(response);
// Log the conflictive response but returns an error without any sensitive data!
reject(new Error("Couldn't get billing student!"));
}
});
});
}
function formatStudentForAfip(student) {
const {firstName, lastName, address, price} = student;
return {"nomYAp": `${firstName} ${lastName}`, "dir": address.street1, "importe": price};
}
function getAfipStudensInfo(students) {
const promiseArray = students.map((student) => {
const studentInfo = formatStudentForAfip(student);
return getAfipInfo(studentInfo);
});
return Promise.all(promiseArray);
}
async function getInvoices(req, res) {
try {
const students = await getStudentsBillingInfo(req, res);
const afipFinalInfo = await getAfipStudensInfo(students);
res.response200(afipFinalInfo, "Success");
} catch (error) {
res.response500(error, "Error");
}
}

return {
getChargeableStudents
getChargeableStudents,
getInvoices
};
};
26 changes: 13 additions & 13 deletions controllers/courses.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/* eslint-disable no-param-reassign */
module.exports = (mongoose) => {
var Course = mongoose.model("Course"),
filterFields = ["status"],
sortFields = ["status"];
const Course = mongoose.model("Course");
const filterFields = ["status", "technologyId"];

var buildFilterQuery = function(params) {
function buildFilterQuery(params) {
return filterFields.reduce((query, prop) => {
if (params[prop]) {
query[prop] = params[prop];
Expand All @@ -20,32 +20,32 @@ module.exports = (mongoose) => {

function list(req, res) {
Course.find(buildFilterQuery(req.query)).sort()
.then(function (courses) {
res.response200({courses}, "Found '" + courses.length + "' Courses.");
.then((courses) => {
res.response200({courses}, `Found '${courses.length}' Courses.`);
})
.catch(function (err) {
.catch((err) => {
res.response500(err, "Courses couldn't be found!");
});
}

function create(req, res) {
let courseDTO = getCourseDTO(req.body);
const courseDTO = getCourseDTO(req.body);
Course.create(courseDTO)
.then((course) => {
res.response200(course, `Course '${course._id}' successfully created.`);
})
.catch((err) => {
res.response500(err, "Course couldn't be created!");
res.response500(err, "Course couldn't be created!");
});
}

function read(req, res) {
Course.findById({ _id: req.params.id })
Course.findById({_id: req.params.id})
.then((course) => {
if (course) {
res.response200({course}, `Course '${course._id}' found.`);
res.response200({course}, `Course '${course._id}' found.`);
} else {
res.response404("Course not found!");
res.response404("Course not found!");
}
})
.catch((err) => {
Expand All @@ -59,7 +59,7 @@ module.exports = (mongoose) => {
if (course) {
res.response200({course}, `Course '${course._id}' successfully updated.`);
} else {
res.response404('Course not found!');
res.response404("Course not found!");
}
})
.catch((err) => {
Expand Down
2 changes: 2 additions & 0 deletions controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ const CoursesController = require("./courses");
const StudentsController = require("./students");
const EvaluationsController = require("./evaluations");
const TechnologiesController = require("./technologies");
const StatsController = require("./stats");

module.exports = {
BillingController,
EvaluationsController,
CoursesController,
StudentsController,
StatsController,
TechnologiesController
};
63 changes: 63 additions & 0 deletions controllers/stats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
module.exports = (mongoose) => {
const Evaluation = mongoose.model("Evaluation");
const Student = mongoose.model("Student");
const stateArray = {};
const addedStudents = [];
function verifyExistentStudent(studentId, array) {
let exist = false;
array.forEach((student) => {
if (studentId.toString() === student._id) {
exist = true;
}
});

return exist;
}

function incrementState(state) {
if (stateArray[state]) {
stateArray[state]++;
} else {
stateArray[state] = 1;
}
}

function addStateByStudent(state, studentId) {
if (!verifyExistentStudent(studentId, addedStudents)) {
addedStudents.push(studentId);
incrementState(state);
}
}

function addStudentsToList(array) {
array.forEach((student) => {
addStateByStudent(student.billingAddress.state, student._id);
});
}

function getFailuresByState(req, res) {
Evaluation.find()
.then((failedEvals) => {
const notes = failedEvals.reduce((notesFiltered, toConcat) => {
return notesFiltered.concat(toConcat.notes);
}, []).filter((note) => {
return note.status === "failed";
});

return Promise.all(notes);
})
.then((notes) => {
const studentsFiltered = notes.map((note) => {
return Student.findOne({_id: note.studentId});
});

return Promise.all(studentsFiltered);
})
.then((students) => {
addStudentsToList(students);
res.response200(stateArray);
});
}

return {getFailuresByState};
};
36 changes: 36 additions & 0 deletions middleware/cacheMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const memCache = {};

function isInCache({url}) {
return memCache[url];
}

function getNewUrl(oldUrl) {
const splitted = oldUrl.split("/").slice(1, -1);
return splitted.reduce((oldValue, newValur) => {
return (`${oldValue}/${newValur}`);
}, "");
}

module.exports = (req, res, next) => {
const forbiddenPaths = ["/admin/billing/getChargeableStudents"];
const dontAuth = forbiddenPaths.find((path) => {
return path === `${req.method} ${req.url}`;
});
const isCached = isInCache(req);
if (req.method === "GET" && !dontAuth) {
if (isCached) {
return res.response200((isCached), "Was cached!");
}
res.response200 = (data = {}, message = "ok") => {
memCache[req.originalUrl] = data;
res.json({data, status: "success", message});
};
} else if (req.method === "POST" && isCached) {
Reflect.deleteProperty(memCache, req.url);
return next();
} else if (req.method === "PUT" && isCached) {
Reflect.deleteProperty(memCache, getNewUrl(req.originalUrl));
return next();
}
return next();
};
3 changes: 2 additions & 1 deletion middleware/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const responseHelpers = require("./responseHelpers");
const cacheMiddleware = require("./cacheMiddleware");

module.exports = {responseHelpers};
module.exports = {responseHelpers, cacheMiddleware};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"main": "app.js",
"scripts": {
"start": "node ./app.js",
"test": "NODE_ENV=test ./node_modules/mocha/bin/_mocha --timeout 1000 --exit"
"test": "NODE_ENV=test ./node_modules/mocha/bin/_mocha --timeout 10000 --exit"
},
"keywords": [
"exam",
Expand Down
7 changes: 7 additions & 0 deletions routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const {
EvaluationsController,
CoursesController,
StudentsController,
StatsController,
TechnologiesController
} = require("./controllers");
const afipAPI = require("./services/afip-mock-api");
Expand All @@ -27,6 +28,7 @@ module.exports = (app, router) => {
const evaluationController = EvaluationsController(mongoose);
const technologyController = TechnologiesController(mongoose);
const billingController = BillingController(mongoose);
const statsController = StatsController(mongoose);

const controllers = [
{basePath: "/evaluations", controller: evaluationController},
Expand All @@ -40,6 +42,11 @@ module.exports = (app, router) => {
router.route("/admin/billing/getChargeableStudents")
.get(billingController.getChargeableStudents);

router.route("/admin/billing/getInvoices")
.get(billingController.getInvoices);
router.route("/stats/failuresByStates")
.get(statsController.getFailuresByState);

router.route("/afip")
.post(afipAPI.getInvoice);

Expand Down
Loading