@absolunet/ioc2.1.0

View on GitHub

http/controllers/Controller.js

//--------------------------------------------------------
//-- Node IoC - HTTP - Controller - Controller
//--------------------------------------------------------

import __ from '@absolunet/private-registry';


/**
 * Abstract controller class that defines all the shortcuts to take action on the response object.
 *
 * @memberof http.controllers
 * @abstract
 * @hideconstructor
 */
class Controller {

	/**
	 * Prepare request handling.
	 *
	 * @param {container.Container} app - The current container instance.
	 * @param {request} request - The current request instance.
	 * @param {response} response - The current response instance.
	 */
	prepareHandling(app, request, response) {
		__(this).set('app', app);
		__(this).set('request', request);
		__(this).set('response', response);
		__(this).set('isStreaming', false);
	}

	/**
	 * Send HTML response.
	 *
	 * @param {string} view - The view name.
	 * @param {*} data - The view-model data.
	 * @returns {response} The response instance.
	 */
	view(view, data = {}) {
		return this.response
			.type('html')
			.send(this.app.make('view').make(view, data));
	}

	/**
	 * Send JSON response.
	 *
	 * @param {string|number|boolean|object} object - The JSON value to send.
	 * @returns {response} The response instance.
	 */
	json(object) {
		return this.response.json(object);
	}

	/**
	 * Dump parameters in the current response.
	 *
	 * @param {...*} parameters - The dumped values.
	 */
	dump(...parameters) {
		this.app.make('dumper')
			.setResponse(this.response)
			.dump(...parameters);
	}

	/**
	 * Send a stream response.
	 * Ends the request when the handler resolves.
	 *
	 * @param {Function} handler - The stream handler.
	 * @returns {Promise} The async process promise.
	 */
	async stream(handler) {
		__(this).set('isStreaming', true);
		this.response.writeHead(200, {
			'Content-Type': 'text/plain',
			'Transfer-Encoding': 'chunked'
		});

		await handler();

		return this.response.end();
	}

	/**
	 * Write line in the stream response.
	 *
	 * @param {string} line - The line to stream into the response.
	 * @returns {http.controllers.Controller} The current controller instance.
	 */
	writeStreamLine(line) {
		if (this.isStreaming) {
			this.response.write(`${line}\n`);
		}

		return this;
	}

	/**
	 * Send a stream response from command output.
	 *
	 * @param {string|Array<string>} command - The command string.
	 * @returns {Promise} The async process promise.
	 */
	streamCommand(command) {
		const { writeStreamLineHandler: handler } = this;

		return this.stream(async () => {
			await this.runCommand(command, handler);
		});
	}

	/**
	 * Run command.
	 * Handle output if an handler is provided.
	 *
	 * @param {string|Array<string>} command - The command string.
	 * @param {Function|null} [handler] - The handler that will received printed data after command will have run.
	 * @returns {Promise} The async process promise.
	 */
	async runCommand(command, handler = null) {
		const commandRegistrar = this.app.make('command.registrar');
		const interceptor      = this.app.make('terminal.interceptor');

		if (handler) {
			interceptor.add(handler).mute();
		}

		await commandRegistrar.resolve(command, true);

		if (handler) {
			interceptor.unmute().remove(handler);
		}
	}

	/**
	 * Validate the current request body against the given schema.
	 *
	 * @param {Function} validationClosure - The validation callback that returns the validation schema.
	 */
	validate(validationClosure) {
		const validator = this.app.make('validator');
		validator.assert(this.request.body, validator.object().keys(validationClosure(validator)));
	}

	/**
	 * Redirect to the given URL with the appropriate redirection code.
	 *
	 * @param {string} to - The URL to redirect to.
	 * @param {boolean} [permanent=false] - Indicates that the redirection response should flag a permanent redirection.
	 * @returns {response} The current response instance.
	 */
	redirect(to, permanent = false) {
		return this.response.redirect(permanent ? 301 : 302, to);
	}

	/**
	 * Redirect to the given URL permanently.
	 *
	 * @param {string} to - The URL to redirect to.
	 * @returns {response} The current response instance.
	 */
	permanentRedirect(to) {
		return this.redirect(to, true);
	}

	/**
	 * Set response status.
	 *
	 * @param {number} status - The HTTP status code.
	 * @returns {http.controllers.Controller} The current controller instance.
	 */
	status(status) {
		this.response.status(status);

		return this;
	}

	/**
	 * Set response status and throw an HTTP error that reflects the given status.
	 *
	 * @param {number} status - The HTTP status code.
	 * @throws {http.exceptions.HttpError} The corresponding HTTP error based on the HTTP status code.
	 */
	throwWithStatus(status) {
		this.status(status);

		throw this.app.make('http.error.mapper').getErrorInstanceFromHttpStatus(status);
	}

	/**
	 * Set 200 OK status.
	 *
	 * @returns {http.controllers.Controller} The current controller instance.
	 */
	ok() {
		return this.status(200);
	}

	/**
	 * Set 201 Created status.
	 *
	 * @returns {http.controllers.Controller} The current controller instance.
	 */
	created() {
		return this.status(201);
	}

	/**
	 * Set 202 Accepted status.
	 *
	 * @returns {http.controllers.Controller} The current controller instance.
	 */
	accepted() {
		return this.status(202);
	}

	/**
	 * Set 204 No Content status.
	 *
	 * @returns {http.controllers.Controller} The current controller instance.
	 */
	noContent() {
		this.status(204);
		this.response.end();

		return this;
	}

	/**
	 * Set 400 Bad Request status.
	 *
	 * @returns {http.controllers.Controller} The current controller instance.
	 */
	badRequest() {
		return this.throwWithStatus(400);
	}

	/**
	 * Set 401 Unauthorized status.
	 *
	 * @returns {http.controllers.Controller} The current controller instance.
	 */
	unauthorized() {
		return this.throwWithStatus(401);
	}

	/**
	 * Set 403 Forbidden status.
	 *
	 * @returns {http.controllers.Controller} The current controller instance.
	 */
	forbidden() {
		return this.throwWithStatus(403);
	}

	/**
	 * Set 404 Not Found status.
	 *
	 * @returns {http.controllers.Controller} The current controller instance.
	 */
	notFound() {
		return this.throwWithStatus(404);
	}

	/**
	 * Set 405 Method Not Allowed status.
	 *
	 * @returns {http.controllers.Controller} The current controller instance.
	 */
	methodNotAllowed() {
		return this.throwWithStatus(405);
	}

	/**
	 * Set 408 Timeout status.
	 *
	 * @returns {http.controllers.Controller} The current controller instance.
	 */
	timeout() {
		return this.throwWithStatus(408);
	}

	/**
	 * Set 418 I'm A Teapot status.
	 *
	 * @returns {http.controllers.Controller} The current controller instance.
	 */
	teapot() {
		return this.status(418);
	}

	/**
	 * Command streaming interceptor handler.
	 *
	 * @type {Function}
	 */
	get writeStreamLineHandler() {
		return (content) => {
			this.writeStreamLine(`${content.trim().replace(/\n$/u, '')}`);
		};
	}

	/**
	 * Indicates if the response is currently streaming.
	 *
	 * @type {boolean}
	 */
	get isStreaming() {
		return __(this).get('isStreaming');
	}

	/**
	 * Container.
	 *
	 * @type {container.Container}
	 */
	get app() {
		return __(this).get('app');
	}

	/**
	 * Express request.
	 *
	 * @type {request}
	 */
	get request() {
		return __(this).get('request');
	}

	/**
	 * Express response.
	 *
	 * @type {response}
	 */
	get response() {
		return __(this).get('response');
	}

}


export default Controller;