app/controllers/survey-controller.js

  1. /**
  2. * @module survey-controller
  3. */
  4. const utils = require('../lib/utils');
  5. const TError = require('../lib/custom-error').TranslatedError;
  6. const communicator = require('../lib/communicator');
  7. const surveyModel = require('../models/survey-model');
  8. const userModel = require('../models/user-model');
  9. const config = require('../models/config-model').server;
  10. const express = require('express');
  11. const router = express.Router();
  12. const routerUtils = require('../lib/router-utils');
  13. // var debug = require( 'debug' )( 'survey-controller' );
  14. module.exports = (app) => {
  15. app.use(`${app.get('base path')}/`, router);
  16. };
  17. router.param('enketo_id', routerUtils.enketoId);
  18. router.param('encrypted_enketo_id_single', routerUtils.encryptedEnketoIdSingle);
  19. router.param('encrypted_enketo_id_view', routerUtils.encryptedEnketoIdView);
  20. router.param('mod', (req, rex, next, mod) => {
  21. if (mod === 'i') {
  22. req.iframe = true;
  23. next();
  24. } else {
  25. req.iframe = false;
  26. next('route');
  27. }
  28. });
  29. router
  30. // .get( '*', loggedInCheck )
  31. .get(`${config['offline path']}/:enketo_id`, offlineWebform)
  32. .get(`${config['offline path']}/`, redirect)
  33. .get('/connection', (req, res) => {
  34. res.status = 200;
  35. res.send(`connected ${Math.random()}`);
  36. })
  37. .get('/preview', preview)
  38. .get('/preview/:mod', preview)
  39. .get('/preview/:enketo_id', preview)
  40. .get('/preview/:mod/:enketo_id', preview)
  41. .get('/:enketo_id', webform)
  42. .get('/:mod/:enketo_id', webform)
  43. .get('/single/:enketo_id', single)
  44. .get('/single/:encrypted_enketo_id_single', single)
  45. .get('/single/:mod/:enketo_id', single)
  46. .get('/single/:mod/:encrypted_enketo_id_single', single)
  47. .get('/view/:encrypted_enketo_id_view', view)
  48. .get('/view/:mod/:encrypted_enketo_id_view', view)
  49. .get('/edit/:enketo_id', edit)
  50. .get('/edit/:mod/:enketo_id', edit)
  51. .get('/xform/:enketo_id', xform)
  52. .get('/xform/:encrypted_enketo_id_single', xform)
  53. .get('/xform/:encrypted_enketo_id_view', xform)
  54. .get(/.*\/::[A-z0-9]{4,8}/, redirect);
  55. // TODO: I suspect this check is no longer used and can be removed
  56. // function loggedInCheck( req, res, next ) {
  57. // req.logout = !!userModel.getCredentials( req );
  58. // next();
  59. // }
  60. /**
  61. * @param {module:api-controller~ExpressRequest} req - HTTP request
  62. * @param {module:api-controller~ExpressResponse} res - HTTP response
  63. * @param {Function} next - Express callback
  64. */
  65. function offlineWebform(req, res, next) {
  66. if (!req.app.get('offline enabled')) {
  67. const error = new Error(
  68. 'Offline functionality has not been enabled for this application.'
  69. );
  70. error.status = 405;
  71. next(error);
  72. } else {
  73. req.offlinePath = config['offline path'];
  74. webform(req, res, next);
  75. }
  76. }
  77. /**
  78. * @param {module:api-controller~ExpressRequest} req - HTTP request
  79. * @param {module:api-controller~ExpressResponse} res - HTTP response
  80. * @param {Function} next - Express callback
  81. */
  82. function webform(req, res, next) {
  83. const options = {
  84. offlinePath: req.offlinePath,
  85. iframe: req.iframe,
  86. print: req.query.print === 'true',
  87. desktop: req.query.desktop === 'true',
  88. };
  89. _renderWebform(req, res, next, options);
  90. }
  91. /**
  92. * @param {module:api-controller~ExpressRequest} req - HTTP request
  93. * @param {module:api-controller~ExpressResponse} res - HTTP response
  94. * @param {Function} next - Express callback
  95. */
  96. function single(req, res, next) {
  97. const options = {
  98. type: 'single',
  99. iframe: req.iframe,
  100. };
  101. if (req.encryptedEnketoId && req.cookies[req.encryptedEnketoId]) {
  102. res.redirect(
  103. `${req.baseUrl}/thanks?taken=${req.cookies[req.encryptedEnketoId]}`
  104. );
  105. } else {
  106. _renderWebform(req, res, next, options);
  107. }
  108. }
  109. /**
  110. * @param {module:api-controller~ExpressRequest} req - HTTP request
  111. * @param {module:api-controller~ExpressResponse} res - HTTP response
  112. * @param {Function} next - Express callback
  113. */
  114. function view(req, res, next) {
  115. const options = {
  116. type: 'view',
  117. iframe: req.iframe,
  118. print: req.query.print === 'true',
  119. };
  120. _renderWebform(req, res, next, options);
  121. }
  122. /**
  123. * @param {module:api-controller~ExpressRequest} req - HTTP request
  124. * @param {module:api-controller~ExpressResponse} res - HTTP response
  125. * @param {Function} next - Express callback
  126. */
  127. function preview(req, res, next) {
  128. const options = {
  129. type: 'preview',
  130. iframe: req.iframe || !!req.query.iframe,
  131. notification: utils.pickRandomItemFromArray(config.notifications),
  132. };
  133. _renderWebform(req, res, next, options);
  134. }
  135. /**
  136. * This serves a page that redirects old pre-2.0.0 urls into new urls.
  137. * The reason this on the client-side is to cache the redirect itself which is important
  138. * in case people have bookmarked an offline-capable old-style url and go into the field without Internet.
  139. *
  140. * @param {module:api-controller~ExpressRequest} req - HTTP request
  141. * @param {module:api-controller~ExpressResponse} res - HTTP response
  142. */
  143. function redirect(req, res) {
  144. res.render('surveys/webform-redirect');
  145. }
  146. /**
  147. * @param {module:api-controller~ExpressRequest} req - HTTP request
  148. * @param {module:api-controller~ExpressResponse} res - HTTP response
  149. * @param {Function} next - Express callback
  150. */
  151. function edit(req, res, next) {
  152. const options = {
  153. type: 'edit',
  154. iframe: req.iframe,
  155. };
  156. if (req.query.instance_id) {
  157. _renderWebform(req, res, next, options);
  158. } else {
  159. const error = new TError('error.invalidediturl');
  160. error.status = 400;
  161. next(error);
  162. }
  163. }
  164. /**
  165. * @param {module:api-controller~ExpressRequest} req - HTTP request
  166. * @param {module:api-controller~ExpressResponse} res - HTTP response
  167. * @param {Function} next - Express callback
  168. * @param { object } options - Options passed to render
  169. */
  170. function _renderWebform(req, res, next, options) {
  171. const deviceId =
  172. req.signedCookies.__enketo_meta_deviceid ||
  173. `${req.hostname}:${utils.randomString(16)}`;
  174. const cookieOptions = {
  175. signed: true,
  176. maxAge: 10 * 365 * 24 * 60 * 60 * 1000,
  177. };
  178. res.cookie('__enketo_meta_deviceid', deviceId, cookieOptions).render(
  179. 'surveys/webform',
  180. options
  181. );
  182. }
  183. /**
  184. * Debugging view that shows underlying XForm
  185. *
  186. * @param {module:api-controller~ExpressRequest} req - HTTP request
  187. * @param {module:api-controller~ExpressResponse} res - HTTP response
  188. * @param {Function} next - Express callback
  189. */
  190. function xform(req, res, next) {
  191. return surveyModel
  192. .get(req.enketoId)
  193. .then((survey) => {
  194. survey.credentials = userModel.getCredentials(req);
  195. return survey;
  196. })
  197. .then(communicator.getXFormInfo)
  198. .then(communicator.getXForm)
  199. .then((survey) => {
  200. res.set('Content-Type', 'text/xml').send(survey.xform);
  201. })
  202. .catch(next);
  203. }