Source: routes/api.js

/**
 * 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;