controllers/citizensController.js

/**
 *  Controller handles the logic for any citizen related routes.
 * @module Controllers/CitizenController
 */

const CitizenModel = require('../models/citizenModel.js')
const StepModel = require('../models/stepModel.js')

// the N3 file service wrapper
const FileService = require('../services/n3FileService.js')
const SolidService = require('../services/solidService.js')
const CitizenService = require('../services/citizenService.js')

// error handler
const Boom = require('@hapi/boom')

/**
 * GET
 *
 * placeholder route that would get all of the citizens and their info
 *
 * /citizens
 */
exports.index = (req, res, next) => {
  const citizen = new CitizenModel(req, null)

  citizen.getAll(req)
    .then(result => {
      res.send(result)
      // todo add error handling
    })
    .catch(e => {
      next(e)
    })
}

/**
 * placeholder route that would handle deletion of citizen
 * currently this just removes all info on our 1 user
 *
 * DELETE
 *
 * /citizens
 */
exports.delete = (req, res, next) => {
  try {
    // create instance of citizen model
    const citizen = new CitizenModel(req, null)

    citizen.deleteAllInfo()
      .then(() => res.send('Deleted all user info'))
  } catch (e) {
    next(e)
  }
}

/**
 * GET and POST, depends on which type of data user (solid or stored here)
 *
 * Show info for a specific step (based on route, eg /citizens/step/providePhoneNumberManually)
 *
 * /citizens/step/:step
 *
 * @param req.params.step {String} - Name of the step used
 * @async
 */
exports.showStep = async (req, res, next) => {
  try {
    // create instance of model
    const step = new StepModel(req)

    // get our stepname from the parameters
    const stepName = req.params.step

    // get the input pattern for all steps
    const inputPatterns = await step.getAllQueryPatternsAsString()

    // check if an input pattern exists for the provided step, handle error if it doesn't
    if (!inputPatterns.includes(stepName + ' ')) { return next(Boom.notFound('Route does not exist.')) }

    // create models after validation to save resources
    let solidService = null
    if (req.method === 'POST') {
      solidService = new SolidService()
    }
    const citizen = new CitizenModel(req, solidService)
    const fileService = new FileService()

    if (solidService !== null && solidService.validated !== null) {
      return next(Boom.notFound('Incorrect data structure, problem occured on line ' + solidService.validated.context.line))
    }

    // add the info for the example user
    const results = await citizen.getInfoByStep(stepName)
    // convert turtle result to json-ld
    if (Object.prototype.hasOwnProperty.call(req.headers, 'accept') && req.headers.accept === 'text/plain') {
      res.send(results)
    } else {
      res.send(fileService.stringToJSONLD(results))
    }
  } catch (e) {
    next(e)
  }
}

/**
 * DELETE
 *
 * delete info for a specific step (based on route, eg /citizens/step/providePhoneNumberManually)
 *
 * /citizens/step/:step
 * @param req.params.step {String} - Name of the step used
 * @async
 */
exports.undoStep = async (req, res, next) => {
  try {
    const citizen = new CitizenModel(req, null)
    const step = new StepModel(req)

    // get our stepname from the parameters
    const stepName = req.params.step

    // get the input pattern for all steps
    const inputPatterns = await step.getAllInputPatternsAsString()

    // check if an input pattern exists for the provided step, handle error if it doesn't
    if (!inputPatterns.includes(stepName + ' ')) { return next(Boom.notFound('Route does not exist.')) }

    // get the input pattern required to edit if it turns out the user already has data stored for this step
    const inputPattern = await step.getInputPatternByStep(stepName)

    // delete the info
    res.send(await citizen.deleteQuadsByInputPatterns(inputPattern))
  } catch (e) {
    next(e)
  }
}

/**
 * DELETE

 * Delete info for one or more steps based on JSON request.

 * /citizens/deleteInfo

 * @param req.body.input {...String} - Names of the steps to delete
 * @async
 */
exports.deleteInfo = async (req, res, next) => {
  try {
    const citizen = new CitizenModel(req, null)
    const step = new StepModel(req)

    // get the input pattern for all steps
    const inputPatterns = step.getAllInputPatternsAsString()

    const inputPatternPromises = []
    // loop the provided steps and check if they exist, delete the ones that exist
    for (const stepName in req.body.input) {
      // check if an input pattern exists for the provided step, handle error if it doesn't
      if (!inputPatterns.includes(stepName)) { return next(Boom.badRequest('The provided step, ' + stepName + ', does not exist.')) }

      // get the input pattern required to edit if it turns out the user already has data stored for this step
      inputPatternPromises.push(step.getInputPatternByStep(stepName))
    }

    const patternsToDelete = await Promise.all(inputPatternPromises)

    // delete the info
    res.send(citizen.deleteQuadsByInputPatterns(patternsToDelete))
  } catch (e) {
    next(e)
  }
}

/**
 *
 * Puts the provided step.
 *
 * Overwrites if existing step has multiple values,
 * otherwise edits already presesent value with provided value.
 *
 * !! It's up to the sender of the request to verify that multiple values make sense for a specific step,
 *
 * Route: /citizens/step/:step
 * @param req.params.step {String} - Name of the step used
 * @param req.body.input.value {String|String[]} - Value(s) of the corresponding step
 * @async
 */
exports.putStep = async (req, res, next) => {
  try {
    // handle error if data was not supplied in correct format
    if (!Object.prototype.hasOwnProperty.call(req.body, 'input') || !Object.prototype.hasOwnProperty.call(req.body.input, 'value')) { return next(Boom.badRequest('Invalid format')) }

    // Reformat input data so we can use the same function for both this route as well as putMultipleSteps
    const stepName = req.params.step
    const stepValue = req.body.input.value
    const input = { [stepName]: stepValue }

    const result = await CitizenService.addSteps(req, input, CitizenService.preparePut)

    res.send(result)
  } catch (error) {
    next(error)
  }
}
/**
 * Puts the provided steps.
 *
 * Overwrites existing steps if a list of values is supplied or if multiple values are already present,
 * otherwise edits steps that are already presesent with a new value
 *
 * !! It's up to the sender of the request to verify that multiple values make sense for a specific step,
 *
 * Route: /citizens/addInfo
 * @param req.body.input {Object} - Key value pairs with stepnames as keys and their values
 * @async
 */
exports.putMultipleSteps = async (req, res, next) => {
  try {
    // handle error if data was not supplied in correct format
    if (!Object.prototype.hasOwnProperty.call(req.body, 'input')) { return next(Boom.badRequest('Invalid format')) }

    const response = await CitizenService.addSteps(req, req.body.input, CitizenService.preparePut)

    res.send(response)
  } catch (e) {
    next(e)
  }
}

/**
 * Posts the provided step.
 *
 * Appends new values if step already exists and if a list of values is supplied,
 * otherwise edits the step with a new value
 *
 * !! It's up to the sender of the request to verify that multiple values make sense for a specific step
 *
 * Route: /citizens/step/:step
 * @param req.params.step {String} - Name of the step used
 * @param req.body.input.value {String|String[]} - Value(s) of the corresponding step
 * @async
 */
exports.postStep = async (req, res, next) => {
  try {
    // handle error if data was not supplied in correct format
    if (!Object.prototype.hasOwnProperty.call(req.body, 'input') || !Object.prototype.hasOwnProperty.call(req.body.input, 'value')) { return next(Boom.badRequest('Invalid format')) }

    // Reformat input data so we can use the same function for both this route as well as postMultipleSteps

    const stepName = req.params.step
    const stepValue = req.body.input.value
    const input = { [stepName]: stepValue }

    const result = await CitizenService.addSteps(req, input, CitizenService.preparePost)

    res.send(result)
  } catch (error) {
    next(error)
  }
}
/**
 * Posts the provided steps.
 *
 * Appends new values to existing steps if a list of values is supplied,
 * otherwise edits steps that are already presesent with a new value

 * !! It's up to the sender of the request to verify that multiple values make sense for a specific step
 *
 * Route: /citizens/addInfo
 * @param req.body.input {Object} - Key value pairs with stepnames as keys and their values
 * @async
 */
exports.postMultipleSteps = async (req, res, next) => {
  try {
    // handle error if data was not supplied in correct format
    if (!Object.prototype.hasOwnProperty.call(req.body, 'input')) { return next(Boom.badRequest('Invalid format')) }

    // return next(Boom.badRequest('Reworking implementation'))
    // create instance of model

    const response = await CitizenService.addSteps(req, req.body.input, CitizenService.preparePost)

    res.send(response)
  } catch (e) {
    next(e)
  }
}