Home Reference Source

src/code/publish.js

/**
 * @desc Exports a router for `/code/publish/:id`, the service to publish the
 * code of a specified id.
 * @since 0.1.0
 */
'use strict';

/**
 * @ignore
 */
const CeallogFunction = require('../models/CeallogFunction'),
	compileRequest = require('../compiler').compileRequest,
	HttpError = require('../classes/HttpError'),
	PublishError = require('../classes/PublishError'),
	settings = require('../settings'),
	util = require('../mongoose-util');

/**
 * @desc An object acting like a map where key represents the type of message
 * and the value is a string with the message itself.
 * @since 0.1.0
 */
const messages = {
	MISSING_PARAMS: 'Required parameters missing.',
	NON_EXISTING_RESOURCE: 'Resource does not exist.'
};

/**
 * @desc Attempts to find the existing published resource by the id provided. If
 * successful, the result is added to `req.publisher.newRecord`.
 * @param {Object} req Express request object
 * @param {Object} res Express response object
 * @param {function} next Function to be called by Express next.
 * @since 0.1.0
 */
const findNew = (req, res, next) => {
	let err;

	/* istanbul ignore else */
	if ('id' in req.params) {
		let id = util.getObjectId(req.params.id);

		req.publisher = req.publisher || {oldRecord: null, newRecord: null};

		CeallogFunction.findOne({_id: id}, (err, result) => {
			let dbResult = util.dbResultCallback(req.publisher)(err, result);

			if (dbResult.type == 'SUCCESS' && result) {
				req.publisher.newRecord = result;
				req.code = result.body;

				next();
			} else {
				try {
					throw new PublishError(
						messages.NON_EXISTING_RESOURCE,
						'NON_EXISTING_RESOURCE'
					);
				} catch (e) {
					err = new HttpError(e, e.errorType, 404);
					err.sendError(res);
				}
			}
		});
	} else {
		try {
			throw new PublishError(messages.MISSING_PARAMS, 'MISSING_PARAMS');
		} catch (e) {
			err = new HttpError(e, e.errorType, 400);
			err.sendError(res);
		}
	}
};

/**
 * @desc Attempts to find the existing published resource by the name of the new
 * record.  If successful, the result is added to `req.publisher.oldRecord`.
 * @param {Object} req Express request object
 * @param {Object} res Express response object
 * @param {function} next Function to be called by Express next.
 * @since 0.1.0
 */
const findOld = (req, res, next) => {
	let publisher = req.publisher;
	let newRecord = publisher.newRecord;
	let name = newRecord ? newRecord.name : null;

	if (name) {
		let query = {
			$and: [{name: {$eq: name}}, {published: {$eq: true}}]
		};

		if (newRecord && newRecord._id) {
			query.$and.push({_id: {$ne: newRecord._id}});
		}


		CeallogFunction.findOne(query, (err, result) => {
			let dbResult = util.dbResultCallback(req.publisher)(err, result);

			if (dbResult.type == 'SUCCESS') {
				req.publisher.oldRecord = result;
			}

			next();
		});
	}
};

/**
 * @desc Compiles the new code before proceeding.
 * @param {Object} req Express request object
 * @param {Object} res Express response object
 * @param {function} next Function to be called by Express next.
 * @since 0.1.0
 */
const handleCompilerResult = (req, res, next) => {
	let err;

	try {
		if (req.compiler.error) {
			throw new HttpError(req.compiler.error, 'COMPILATION_ERROR', 400);
		}
	} catch (e) {
		err = e;
	} finally {
		if (err) {
			let newRecord = req.publisher.newRecord || {};
			let id = newRecord._id;

			err.sendError(res);

			if (id) {
				CeallogFunction.update({_id: id}, {compiled: false, published: false});
			}

			return;
		}
		next();
	}
};

/**
 * @desc Updates the old record `published` field to `false` and the new record
 * `published` field to `true`.
 * @param {Object} req Express request object
 * @param {Object} res Express response object
 * @param {function} next Function to be called by Express next.
 * @since 0.1.0
 */
const update = (req, res, next) => {
	let newRecord = req.publisher.newRecord;
	let oldRecord = req.publisher.oldRecord;

	new Promise((resolve, reject) => {
		if (oldRecord) {
			return CeallogFunction.update(
				{_id: oldRecord._id},
				{published: false},
				(err, _) => {
					if (err) return reject(err);
					
					let name = oldRecord.name;
					
					req.cache.removePublished(name);
					resolve();
				}
			);
		}
		resolve();
	})
		.then(_ => {
			return new Promise((resolve, reject) => {
				CeallogFunction.update(
					{_id: newRecord._id},
					{published: true},
					(err, _) => {
						if (err) return reject(err);

						resolve(newRecord);
					}
				);
			});
		})
		.then(r => {
			let response = {
				body: r.body,
				compiled: r.compiled,
				created_date: r.created_date,
				id: r._id,
				label: r.label,
				name: r.name,
				published: true,
				service: `/${settings.cealloga.api_path}/${r.name}`
			};
			
			req.cache.add(req.compiler.compiled, response);
			
			res.statusCode = 200;
			res.json(response);
		})
		.catch(err => {
			/* istanbul ignore next */
			let dbResult = util.dbResultCallback(req.publisher)(err, null);
			
			/* istanbul ignore next */
			try {
				throw new PublishError(dbResult.type, dbResult.message);
			} catch (e) {
				new HttpError(e).sendError(res);
			}
		});
};

module.exports = [
	findNew,
	compileRequest,
	handleCompilerResult,
	findOld,
	update
];