diff --git a/config/default.js b/config/default.js index c6906ef..267502e 100644 --- a/config/default.js +++ b/config/default.js @@ -21,10 +21,20 @@ module.exports = { }, directory: path.join(__dirname, '../content-types'), messages: { + content: { + title: 'Revisions for \'%id\'', + }, + format: { + id: 'Content ID must be in UUID format', + revision: 'Revision must be a number', + }, missing: { type: 'Content Type \'%type\' not found', id: 'Content with ID \'%id\' in Content Type \'%type\' not found', - revision: 'Revision for ID \'%id\' in Content Type \'%type\' not found', + revision: 'Revision \'%revision\' for ID \'%id\' in Content Type \'%type\' not found', + }, + revisions: { + title: 'Revision \'%revision\' for \'%id\'', }, }, }, diff --git a/lib/routes/content.js b/lib/routes/content.js index 25e995c..41b66e3 100644 --- a/lib/routes/content.js +++ b/lib/routes/content.js @@ -9,12 +9,52 @@ const content = require('punchcard-content-types'); const multipart = require('connect-multiparty'); const uuid = require('uuid'); const _ = require('lodash'); +const isUUID = require('validator/lib/isUUID'); const utils = require('../utils'); const database = require('../database'); const multipartMiddleware = multipart(); +const check = { + id: (req) => { + if (!isUUID(req.params.id)) { + _.set(req.session, '404', { + message: config.content.messages.format.id, + safe: `/${config.content.base}`, + }); + + return false; + } + + return true; + }, + revision: (req) => { + if (!Number.isInteger(parseInt(req.params.revision, 10))) { + _.set(req.session, '404', { + message: config.content.messages.format.revision.replace('%revision', req.params.revision), + safe: `/${config.content.base}`, + }); + + return false; + } + + return true; + }, + type: (req, type) => { + if (type === false) { + _.set(req.session, '404', { + message: config.content.messages.missing.type.replace('%type', req.params.type), + safe: `/${config.content.base}`, + }); + + return false; + } + + return true; + }, +}; + /* * Content Route Resolution * @@ -52,12 +92,8 @@ const routes = application => { app.get(`/${config.content.base}/:type`, (req, res, next) => { const type = utils.singleItem('id', req.params.type.toLowerCase(), types); - if (type === false) { - _.set(req.session, '404', { - message: config.content.messages.missing.type.replace('%type', req.params.type), - safe: `/${config.content.base}`, - }); - + // check type exists + if (!check.type(req, type)) { return next(); } @@ -96,12 +132,8 @@ const routes = application => { values = utils.config(values); - if (type === false) { - _.set(req.session, '404', { - message: config.content.messages.missing.type.replace('%type', req.params.type), - safe: `/${config.content.base}`, - }); - + // check type exists + if (!check.type(req, type)) { return next(); } @@ -120,59 +152,137 @@ const routes = application => { }); /* - * @name Individual Content Type Edit Page + * @name Individual Piece of Content * * @param {object} req - HTTP Request * @param {object} res - HTTP Response * @param {object} next - Express callback */ - app.get(`/${config.content.base}/:type/:id/:revision/${config.content.actions.edit}`, (req, res, next) => { + app.get(`/${config.content.base}/:type/:id`, (req, res, next) => { const type = utils.singleItem('id', req.params.type.toLowerCase(), types); - const errors = _.get(req.session, `form.content.add[${req.params.type.toLowerCase()}].errors`, {}); - const idSess = _.get(req.session, `form.content.add[${req.params.type.toLowerCase()}].id`, {}); - const revisionSess = _.get(req.session, `form.content.add[${req.params.type.toLowerCase()}].revision`, {}); - let values = _.get(req.session, `form.content.add[${req.params.type.toLowerCase()}].content`, {}); - const data = {}; - let revisionSearch; - _.unset(req.session, 'form.content.add'); + // check type exists + if (!check.type(req, type)) { + return next(); + } - values = utils.config(values); + // id in url is not UUID + if (!check.id(req)) { + return next(); + } - // no content type in url, so 404 - if (type === false) { - _.set(req.session, '404', { - message: config.content.messages.missing.type.replace('%type', req.params.type), - safe: `/${config.content.base}`, + type.primary = type.attributes[0].inputs[Object.keys(type.attributes[0].inputs)[0]].name; + + return database + .select('*') + .from(`content-type--${type.id}`) + .where('id', req.params.id) + .orderBy('revision', 'DESC').then(rows => { + if (rows.length < 1) { + _.set(req.session, '404', { + message: config.content.messages.missing.id.replace('%type', req.params.type).replace('%id', req.params.id), + safe: `/${config.content.base}/${req.params.type}`, + }); + + return next(); + } + + res.render('content/content', { + title: config.content.messages.content.title.replace('%id', req.params.id), + content: rows, + type, + config: config.content, + }); + + return true; }); + }); + + /* + * @name Specific revision of an Individual Piece of Content + * + * @param {object} req - HTTP Request + * @param {object} res - HTTP Response + * @param {object} next - Express callback + */ + app.get(`/${config.content.base}/:type/:id/:revision`, (req, res, next) => { + const type = utils.singleItem('id', req.params.type.toLowerCase(), types); + + // check type exists + if (!check.type(req, type)) { + return next(); + } + + // id in url is not UUID + if (!check.id(req)) { + return next(); + } + // revision in url is not a number + if (!check.revision(req)) { return next(); } - // no id in url, so 404 - if (!req.params.id) { - _.set(req.session, '404', { - message: config.content.messages.missing.id.replace('%type', req.params.type).replace('%id', req.params.id), - safe: `/${config.content.base}/${req.params.type}`, + type.primary = type.attributes[0].inputs[Object.keys(type.attributes[0].inputs)[0]].name; + + return database + .select('*') + .from(`content-type--${type.id}`) + .where('revision', req.params.revision) + .orderBy('revision', 'DESC').then(rows => { + if (rows.length < 1) { + _.set(req.session, '404', { + message: config.content.messages.missing.revision.replace('%revision', req.params.revision).replace('%type', req.params.type).replace('%id', req.params.id), + safe: `/${config.content.base}/${req.params.type}`, + }); + + return next(); + } + + res.render('content/content', { + title: config.content.messages.revisions.title.replace('%revision', req.params.revision).replace('%id', req.params.id), + content: rows, + type, + config: config.content, + }); + + return true; }); + }); + /* + * @name Individual Content Type Edit Page + * + * @param {object} req - HTTP Request + * @param {object} res - HTTP Response + * @param {object} next - Express callback + */ + app.get(`/${config.content.base}/:type/:id/:revision/${config.content.actions.edit}`, (req, res, next) => { + const type = utils.singleItem('id', req.params.type.toLowerCase(), types); + const errors = _.get(req.session, `form.content.add[${req.params.type.toLowerCase()}].errors`, {}); + const idSess = _.get(req.session, `form.content.add[${req.params.type.toLowerCase()}].id`, {}); + const revisionSess = _.get(req.session, `form.content.add[${req.params.type.toLowerCase()}].revision`, {}); + let values = _.get(req.session, `form.content.add[${req.params.type.toLowerCase()}].content`, {}); + const data = {}; + + // check type exists + if (!check.type(req, type)) { return next(); } - // no revision in url, so create search to find latest revision - if (!req.params.revision || !Number.isInteger(req.params.revision)) { - revisionSearch = database(`content-type--${type.id}`) - .select('revision') - .where('id', req.params.id) - .orderBy('revision', 'DESC') - .limit(1); + // id in url is not UUID + if (!check.id(req)) { + return next(); } - else { - revisionSearch = database(`content-type--${type.id}`) - .select('revision') - .where('revision', req.params.revision); + + // revision in url is not a number + if (!check.revision(req)) { + return next(); } + _.unset(req.session, 'form.content.add'); + + values = utils.config(values); // something went wrong on save: if (Object.keys(values).length > 0) { @@ -201,25 +311,13 @@ const routes = application => { // eslint mad if no return, then mad at this else if it is there else { // eslint-disable-line no-else-return // Search for the revision - return revisionSearch.then(rows => { - if (rows.length < 1) { - _.set(req.session, '404', { - message: config.content.messages.missing.revision.replace('%type', req.params.type).replace('%id', req.params.id), - safe: `/${config.content.base}/${req.params.type}`, - }); - - return next(); - } - const revision = rows[0].revision; - - return database(`content-type--${type.id}`).where({ - id: req.params.id, - revision, - }); + return database(`content-type--${type.id}`).where({ + id: req.params.id, + revision: req.params.revision, }).then(rows => { if (rows.length < 1) { _.set(req.session, '404', { - message: config.content.messages.missing.id.replace('%type', req.params.type).replace('%id', req.params.id).replace('%revision', req.params.revision), + message: config.content.messages.missing.revision.replace('%type', req.params.type).replace('%id', req.params.id).replace('%revision', req.params.revision), safe: `/${config.content.base}/${req.params.type}`, }); @@ -282,12 +380,8 @@ const routes = application => { app.post(`/${config.content.base}/:type/${config.content.actions.save}`, multipartMiddleware, (req, res, next) => { const type = utils.singleItem('id', req.params.type.toLowerCase(), types); - if (type === false) { - _.set(req.session, '404', { - message: config.content.messages.missing.type.replace('%type', req.params.type), - safe: `/${config.content.base}`, - }); - + // check type exists + if (!check.type(req, type)) { return next(); } diff --git a/views/content/content.html b/views/content/content.html new file mode 100644 index 0000000..57a4b5d --- /dev/null +++ b/views/content/content.html @@ -0,0 +1,41 @@ +{% extends "_donut.html" %} + +{% block pageHead %} + {{ super() }} +{% endblock %} + +{% block header %} + {{ super() }} +{% endblock %} + +{% block main %} +

{{title}}

+ +

Revisions

+ +{% if content %} + + + + + + + + + {% for c in content %} + {% if c.value %} + + + + + + + {% endif %} + {% endfor %} +
revision{{config.base}}{{config.actions.edit}}date
{{c.revision}}{{c.value[type.primary]}}{{config.actions.edit}}{{c.created}}
+{% endif %} +{% endblock %} + +{% block footer %} +{{ super() }} +{% endblock %} diff --git a/views/content/revision.html b/views/content/revision.html new file mode 100644 index 0000000..57a4b5d --- /dev/null +++ b/views/content/revision.html @@ -0,0 +1,41 @@ +{% extends "_donut.html" %} + +{% block pageHead %} + {{ super() }} +{% endblock %} + +{% block header %} + {{ super() }} +{% endblock %} + +{% block main %} +

{{title}}

+ +

Revisions

+ +{% if content %} + + + + + + + + + {% for c in content %} + {% if c.value %} + + + + + + + {% endif %} + {% endfor %} +
revision{{config.base}}{{config.actions.edit}}date
{{c.revision}}{{c.value[type.primary]}}{{config.actions.edit}}{{c.created}}
+{% endif %} +{% endblock %} + +{% block footer %} +{{ super() }} +{% endblock %}