/**
* api router module
*
* This module allows for programatic download of transcript data.
*
* @module routes/api
*/
// jshint esversion: 8
// async functions
var express = require("express");
var router = express.Router();
var modelhelper = require("../lib/modelhelper");
var cap = require("../lib/capabilities");
var log = require("../lib/log");
const members = require("../models/members");
const registrations = require("../models/registrations");
/**
* GET all members.
*
* Return all members in either JSON or CSV format. JSON is returned by default.
* If ?return=csv is provided in the query, a CSV will be provided.
* If ?requrn=strictcsv, a CSV will be provided containing ONLY those options that are
* actually present in the database (for use with the CSV import capability).
*
* If the calling route provides req._id, then limit responses to just that
* member, by _id.
*
* @private
* @memberof module:routes/api
* @param {Object} req request object
* @param {Object} req.user user object to check against 'api' capability
* @param {String} req.params.id limit to responses to a single member by _id
* @param {String} req.query.return optionally select non-JSON output format
* @param {Object} res response object
* @param {Function} next function call to next middleware
*/
async function routeGETApiMembers(req, res, next) {
log("routeGETApiMembers()");
if (!cap.check(req.user, "api")) {
log("routeGETApiMembers: denied: " + req.user.access);
return res.status(401).json({
message: "You do not have permission to access this API",
});
}
// query by _id if provided
var q = {};
if ("id" in req.params) {
log("routeGETApiMembers: id: " + req.params.id);
q._id = req.params.id;
}
//if no id filtering queries were provided and
//if apiList access is not allowed, deny
if (!("_id" in q) && !cap.check(req.user, "viewOther")) {
log("routeGETApiMembers: list denied: " + req.user.access);
return res.status(401).json({
message: "You do not have permission to access this API",
});
}
// return only database entries when strict is requested
if (req.query.return == "strictcsv") {
q.return = "strict";
}
try {
if (req.query.return == "csv" || req.query.return == "strictcsv") {
log("routeGETApiMembers: return csv");
response = await modelhelper.getMember(q);
res.setHeader("Content-Disposition", "attachment;filename=members.csv");
// response must be an array
if (response.constructor !== Array) {
response = [response];
}
// build the header row and add it at the beginning of the array
header = [];
for (var prop in response[0]) {
if (response[0].hasOwnProperty(prop)) {
header.push(prop);
}
}
response.unshift(header);
return res.csv(response);
} else {
log("routeGETApiMembers: return");
return res.json(await modelhelper.getMember(q));
}
} catch (err) {
log("routeGETApiMembers: error");
return res.status(500).json({
message: err.message,
});
}
}
/**
* GET all registration entries.
*
* Return all registration entries in either JSON or CSV format. JSON is returned by default.
* If ?return=csv is provided in the query, a CSV will be provided.
* If ?requrn=strictcsv, a CSV will be provided containing ONLY those options that are
* actually present in the database (for use with the CSV import capability).
*
* @private
* @memberof module:routes/api
* @param {Object} req request object
* @param {Object} req.user user object to check against 'api' capability
* @param {Date} req.query.earliest filters so that no objects are returned older than this time
* @param {String} req.query.return optionally select non-JSON output format
* @param {Object} res response object
* @param {Function} next function call to next middleware
*/
async function routeGETApiRegistrations(req, res, next) {
log("routeGETApiRegistrations()");
//if api access is not allowed, deny
if (!cap.check(req.user, "api")) {
log("routeGETApiRegistrations: denied: " + req.user.access);
return res.status(401).json({
message: "You do not have permission to access this API",
});
}
// query by _id if provided
var q = {};
if ("id" in req.params) {
log("routeGETApiRegistrations: id: " + req.params.id);
q._id = req.params.id;
// query by member_id if provided
} else if ("memberid" in req.params) {
log("routeGETApiRegistrations: memberid: " + req.params.memberid);
q.member_id = req.params.memberid;
}
// query by earliest if provided
if ("earliest" in req.query) {
log("routeGETApiRegistrations: earliest: " + req.query.earliest);
q.earliest = req.query.earliest;
}
//if no member or id filtering queries were provided and
//if apiList access is not allowed, deny
if (!("_id" in q || "member_id" in q) && !cap.check(req.user, "viewOther")) {
log("routeGETApiRegistrations: list denied: " + req.user.access);
return res.status(401).json({
message: "You do not have permission to access this API",
});
}
// return only database entries when strict is requested
if (req.query.return == "strictcsv") {
q.return = "strict";
}
try {
if (req.query.return == "csv" || req.query.return == "strictcsv") {
log("routeGETApiRegistrations: return csv");
response = await modelhelper.getRegistration(q);
res.setHeader("Content-Disposition", "attachment;filename=registrations.csv");
// response must be an array
if (response.constructor !== Array) {
response = [response];
}
// build the header row and add it at the beginning of the array
header = [];
for (var prop in response[0]) {
if (response[0].hasOwnProperty(prop)) {
header.push(prop);
}
}
response.unshift(header);
return res.csv(response);
} else {
log("routeGETApiRegistrations: return");
return res.json(await modelhelper.getRegistration(q));
}
} catch (err) {
log("routeGETApiRegistrations: error");
return res.status(500).json({
message: err.message,
});
}
}
/**
* POST a new member.
*
* Create a new member in the members collection as specified by req.body,
* and generate a new database identifier. The consutrcted object will be
* returned.
*
* @private
* @memberof module:routes/api
* @param {Object} req request object
* @param {String} req.body.memberID memberID is a 64 bit int, so pass in as a string
* @param {Number} req.body.councilID
* @param {String} req.body.firstName
* @param {String} req.body.lastName
* @param {String} req.body.access
* @param {Object} res response object
* @param {Function} next function call to next middleware
*/
async function routePOSTApiMembers(req, res, next) {
log("routePOSTApiMembers()");
if (!cap.check(req.user, "api") || !cap.check(req.user, "add")) {
log("routePOSTApiMembers: denied: " + req.user.access);
return res.status(401).json({
message: "You do not have permission to access this API",
});
}
if (req.body.access > req.user.access) {
log("routePOSTApiMembers: denied access level: " + req.user.access);
return res.status(401).json({
message: "You do not have permission to use that access level",
});
}
// blank _id is not valid, so remove it.
// the CSV importer does this by accident sometimes
if("_id" in req.body && (req.body._id == undefined || req.body._id == null || req.body._id == "")) {
log("routePOSTApiMembers: remove blank _id");
delete req.body._id;
}
try {
var newMember = new members(req.body);
newMember.save(function (err) {
if (err) {
log("routePOSTApiMembers: err " + err);
return res.status(500).send(err);
} else {
log("routePOSTApiMembers: saved");
return res.status(200).send(newMember.exportObject());
}
});
} catch (err) {
log("routePOSTApiMembers: error");
return res.status(500).json({
message: err.message,
});
}
}
/**
* PUT an existing member.
*
* Update a new member in the members collection as specified by req.params.id
* using the data in req.body. The consutrcted object will be
* returned.
*
* @private
* @memberof module:routes/api
* @param {Object} req request object
* @param {String} req.params.id limit to responses to a single member by _id
* @param {String} req.body.memberID memberID is a 64 bit int, so pass in as a string
* @param {Number} req.body.councilID
* @param {String} req.body.firstName
* @param {String} req.body.lastName
* @param {String} req.body.access
* @param {Object} res response object
* @param {Function} next function call to next middleware
*/
async function routePUTApiMembers(req, res, next) {
log("routePUTApiMembers()");
if (!cap.check(req.user, "api") || !cap.check(req.user, "editOther")) {
log("routePUTApiMembers: denied: " + req.user.access);
return res.status(401).json({
message: "You do not have permission to access this API",
});
}
if (req.body.access > req.user.access) {
log("routePUTApiMembers: denied access level: " + req.user.access);
return res.status(401).json({
message: "You do not have permission to use that access level",
});
}
try {
members.findByIdAndUpdate(req.params.id, req.body, { new: true }, function (
err,
editMember
) {
if (err) {
log("routePUTApiMembers: err " + err);
return res.status(500).send(err);
} else {
log("routePUTApiMembers: saved");
return res.status(200).send(editMember.exportObject());
}
});
} catch (err) {
log("routePUTApiMembers: error");
return res.status(500).json({
message: err.message,
});
}
}
/**
* DELETE an existing member.
*
* Delete member in the members collection as specified by req.params.id.
* The deleted object will be returned.
*
* @private
* @memberof module:routes/api
* @param {Object} req request object
* @param {String} req.params.id database _id
* @param {Object} res response object
* @param {Function} next function call to next middleware
*/
async function routeDELETEApiMembers(req, res, next) {
log("routeDELETEApiMembers()");
if (!cap.check(req.user, "api") || !cap.check(req.user, "delete")) {
log("routeDELETEApiMembers: denied: " + req.user.access);
return res.status(401).json({
message: "You do not have permission to access this API",
});
}
try {
members.findByIdAndRemove(req.params.id, function (err, deleteMember) {
if (err) {
log("routeDELETEApiMembers: err " + err);
return res.status(500).send(err);
} else {
log("routeDELETEApiMembers: deleted");
return res.status(200);
}
});
} catch (err) {
log("routeDELETEApiMembers: error");
return res.status(500).json({
message: err.message,
});
}
}
/**
* POST a new registration.
*
* Create a new registration in the registrations collection as specified by req.body,
* and generate a new database identifier. The consutrcted object will be
* returned.
*
* @private
* @memberof module:routes/api
* @param {Object} req request object
* @param {String} req.body.memberID memberID is a 64 bit int, so pass in as a string
* @param {Number} req.body.councilID
* @param {String} req.body.date in ISO format
* @param {Number} req.body.type
* @param {String} req.body.title
* @param {Number} req.body.credits
* @param {String} req.body.instructor
* @param {String} req.body.physical
* @param {String} req.body.online
* @param {Number} req.body.status
* @param {Object} res response object
* @param {Function} next function call to next middleware
*/
async function routePOSTApiRegistrations(req, res, next) {
log("routePOSTApiRegistrations()");
if (!cap.check(req.user, "api") || !cap.check(req.user, "add")) {
log("routePOSTApiRegistrations: denied: " + req.user.access);
return res.status(401).json({
message: "You do not have permission to access this API",
});
}
// blank _id is not valid, so remove it.
// the CSV importer does this by accident sometimes
if("_id" in req.body && (req.body._id == undefined || req.body._id == null || req.body._id == "")) {
log("routePOSTApiRegistrations: remove blank _id");
delete req.body._id;
}
try {
var newReg = new registrations(req.body);
newReg.save(function (err) {
if (err) {
log("routePOSTApiRegistrations: err " + err);
return res.status(500).send(err);
} else {
log("routePOSTApiRegistrations: saved");
return res.status(200).send(newReg.exportObject());
}
});
} catch (err) {
log("routePOSTApiRegistrations: error");
return res.status(500).json({
message: err.message,
});
}
}
/**
* PUT an existing registration.
*
* Update an existing registration in the registrations collection as specified by req.body. The consutrcted object will be
* The consutrcted object will be returned.
*
* @private
* @memberof module:routes/api
* @param {Object} req request object
* @param {String} req.params.id database _id
* @param {String} req.body.memberID memberID is a 64 bit int, so pass in as a string
* @param {Number} req.body.councilID
* @param {String} req.body.date in ISO format
* @param {Number} req.body.type
* @param {String} req.body.title
* @param {Number} req.body.credits
* @param {String} req.body.instructor
* @param {String} req.body.physical
* @param {String} req.body.online
* @param {Number} req.body.status
* @param {Object} res response object
* @param {Function} next function call to next middleware
*/
async function routePUTApiRegistrations(req, res, next) {
log("routePUTApiRegistrations()");
if (!cap.check(req.user, "api") || !cap.check(req.user, "editOther")) {
log("routePUTApiRegistrations: denied: " + req.user.access);
return res.status(401).json({
message: "You do not have permission to access this API",
});
}
try {
registrations.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true },
function (err, editReg) {
if (err) {
log("routePUTApiRegistrations: err " + err);
return res.status(500).send(err);
} else {
log("routePUTApiRegistrations: saved");
return res.status(200).send(editReg.exportObject());
}
}
);
} catch (err) {
log("routePUTApiRegistrations: error");
return res.status(500).json({
message: err.message,
});
}
}
/**
* DELETE an existing registration.
*
* Delete an existing registration in the registrations collection as specified by req.params.id.
* The deleted object will be returned.
*
* @private
* @memberof module:routes/api
* @param {Object} req request object
* @param {String} req.params.id database _id
* @param {Object} res response object
* @param {Function} next function call to next middleware
*/
async function routeDELETEApiRegistrations(req, res, next) {
log("routeDELETEApiRegistrations()");
if (!cap.check(req.user, "api") || !cap.check(req.user, "delete")) {
log("routeDELETEApiRegistrations: denied: " + req.user.access);
return res.status(401).json({
message: "You do not have permission to access this API",
});
}
try {
registrations.findByIdAndRemove(req.params.id, function (err, deleteReg) {
if (err) {
log("routeDELETEApiRegistrations: err " + err);
return res.status(500).send(err);
} else {
log("routeDELETEApiRegistrations: deleted");
return res.status(200);
}
});
} catch (err) {
log("routeDELETEApiRegistrations: error");
return res.status(500).json({
message: err.message,
});
}
}
// register routes and export router
router.get("/members", routeGETApiMembers);
router.post("/members", routePOSTApiMembers);
router.put("/members", routePOSTApiMembers);
router.get("/members/:memberid/registrations", routeGETApiRegistrations);
router.get("/members/:id", routeGETApiMembers);
router.put("/members/:id", routePUTApiMembers);
router.delete("/members/:id", routeDELETEApiMembers);
router.get("/registrations", routeGETApiRegistrations);
router.post("/registrations", routePOSTApiRegistrations);
router.put("/registrations", routePOSTApiRegistrations);
router.get("/registrations/:id", routeGETApiRegistrations);
router.put("/registrations/:id", routePUTApiRegistrations);
router.delete("/registrations/:id", routeDELETEApiRegistrations);
module.exports = router;