app/controllers/survey-controller.js

/**
 * @module survey-controller
 */

const utils = require('../lib/utils');
const TError = require('../lib/custom-error').TranslatedError;
const communicator = require('../lib/communicator');
const surveyModel = require('../models/survey-model');
const userModel = require('../models/user-model');
const config = require('../models/config-model').server;
const express = require('express');

const router = express.Router();
const routerUtils = require('../lib/router-utils');
// var debug = require( 'debug' )( 'survey-controller' );

module.exports = (app) => {
    app.use(`${app.get('base path')}/`, router);
};

router.param('enketo_id', routerUtils.enketoId);
router.param('encrypted_enketo_id_single', routerUtils.encryptedEnketoIdSingle);
router.param('encrypted_enketo_id_view', routerUtils.encryptedEnketoIdView);

router.param('mod', (req, rex, next, mod) => {
    if (mod === 'i') {
        req.iframe = true;
        next();
    } else {
        req.iframe = false;
        next('route');
    }
});

router
    // .get( '*', loggedInCheck )
    .get(`${config['offline path']}/:enketo_id`, offlineWebform)
    .get(`${config['offline path']}/`, redirect)
    .get('/connection', (req, res) => {
        res.status = 200;
        res.send(`connected ${Math.random()}`);
    })
    .get('/preview', preview)
    .get('/preview/:mod', preview)
    .get('/preview/:enketo_id', preview)
    .get('/preview/:mod/:enketo_id', preview)
    .get('/:enketo_id', webform)
    .get('/:mod/:enketo_id', webform)
    .get('/single/:enketo_id', single)
    .get('/single/:encrypted_enketo_id_single', single)
    .get('/single/:mod/:enketo_id', single)
    .get('/single/:mod/:encrypted_enketo_id_single', single)
    .get('/view/:encrypted_enketo_id_view', view)
    .get('/view/:mod/:encrypted_enketo_id_view', view)
    .get('/edit/:enketo_id', edit)
    .get('/edit/:mod/:enketo_id', edit)
    .get('/xform/:enketo_id', xform)
    .get('/xform/:encrypted_enketo_id_single', xform)
    .get('/xform/:encrypted_enketo_id_view', xform)
    .get(/.*\/::[A-z0-9]{4,8}/, redirect);

// TODO: I suspect this check is no longer used and can be removed
// function loggedInCheck( req, res, next ) {
//    req.logout = !!userModel.getCredentials( req );
//    next();
// }

/**
 * @param {module:api-controller~ExpressRequest} req - HTTP request
 * @param {module:api-controller~ExpressResponse} res - HTTP response
 * @param {Function} next - Express callback
 */
function offlineWebform(req, res, next) {
    if (!req.app.get('offline enabled')) {
        const error = new Error(
            'Offline functionality has not been enabled for this application.'
        );
        error.status = 405;
        next(error);
    } else {
        req.offlinePath = config['offline path'];
        webform(req, res, next);
    }
}

/**
 * @param {module:api-controller~ExpressRequest} req - HTTP request
 * @param {module:api-controller~ExpressResponse} res - HTTP response
 * @param {Function} next - Express callback
 */
function webform(req, res, next) {
    const options = {
        offlinePath: req.offlinePath,
        iframe: req.iframe,
        print: req.query.print === 'true',
        desktop: req.query.desktop === 'true',
    };

    _renderWebform(req, res, next, options);
}

/**
 * @param {module:api-controller~ExpressRequest} req - HTTP request
 * @param {module:api-controller~ExpressResponse} res - HTTP response
 * @param {Function} next - Express callback
 */
function single(req, res, next) {
    const options = {
        type: 'single',
        iframe: req.iframe,
    };
    if (req.encryptedEnketoId && req.cookies[req.encryptedEnketoId]) {
        res.redirect(
            `${req.baseUrl}/thanks?taken=${req.cookies[req.encryptedEnketoId]}`
        );
    } else {
        _renderWebform(req, res, next, options);
    }
}

/**
 * @param {module:api-controller~ExpressRequest} req - HTTP request
 * @param {module:api-controller~ExpressResponse} res - HTTP response
 * @param {Function} next - Express callback
 */
function view(req, res, next) {
    const options = {
        type: 'view',
        iframe: req.iframe,
        print: req.query.print === 'true',
    };

    _renderWebform(req, res, next, options);
}

/**
 * @param {module:api-controller~ExpressRequest} req - HTTP request
 * @param {module:api-controller~ExpressResponse} res - HTTP response
 * @param {Function} next - Express callback
 */
function preview(req, res, next) {
    const options = {
        type: 'preview',
        iframe: req.iframe || !!req.query.iframe,
        notification: utils.pickRandomItemFromArray(config.notifications),
    };

    _renderWebform(req, res, next, options);
}

/**
 * This serves a page that redirects old pre-2.0.0 urls into new urls.
 * The reason this on the client-side is to cache the redirect itself which is important
 * in case people have bookmarked an offline-capable old-style url and go into the field without Internet.
 *
 * @param {module:api-controller~ExpressRequest} req - HTTP request
 * @param {module:api-controller~ExpressResponse} res - HTTP response
 */
function redirect(req, res) {
    res.render('surveys/webform-redirect');
}

/**
 * @param {module:api-controller~ExpressRequest} req - HTTP request
 * @param {module:api-controller~ExpressResponse} res - HTTP response
 * @param {Function} next - Express callback
 */
function edit(req, res, next) {
    const options = {
        type: 'edit',
        iframe: req.iframe,
    };

    if (req.query.instance_id) {
        _renderWebform(req, res, next, options);
    } else {
        const error = new TError('error.invalidediturl');
        error.status = 400;
        next(error);
    }
}

/**
 * @param {module:api-controller~ExpressRequest} req - HTTP request
 * @param {module:api-controller~ExpressResponse} res - HTTP response
 * @param {Function} next - Express callback
 * @param { object } options - Options passed to render
 */
function _renderWebform(req, res, next, options) {
    const deviceId =
        req.signedCookies.__enketo_meta_deviceid ||
        `${req.hostname}:${utils.randomString(16)}`;
    const cookieOptions = {
        signed: true,
        maxAge: 10 * 365 * 24 * 60 * 60 * 1000,
    };

    res.cookie('__enketo_meta_deviceid', deviceId, cookieOptions).render(
        'surveys/webform',
        options
    );
}

/**
 * Debugging view that shows underlying XForm
 *
 * @param {module:api-controller~ExpressRequest} req - HTTP request
 * @param {module:api-controller~ExpressResponse} res - HTTP response
 * @param {Function} next - Express callback
 */
function xform(req, res, next) {
    return surveyModel
        .get(req.enketoId)
        .then((survey) => {
            survey.credentials = userModel.getCredentials(req);

            return survey;
        })
        .then(communicator.getXFormInfo)
        .then(communicator.getXForm)
        .then((survey) => {
            res.set('Content-Type', 'text/xml').send(survey.xform);
        })
        .catch(next);
}