server/services/database_service/service.js

'use strict';

/**
 * Module dependencies, required for ALL Twyr' modules
 * @ignore
 */

/**
 * Module dependencies, required for this module
 * @ignore
 */
const TwyrBaseService = require('twyr-base-service').TwyrBaseService;
const TwyrSrvcError = require('twyr-service-error').TwyrServiceError;

/**
 * @class   DatabaseService
 * @extends {TwyrBaseService}
 * @classdesc The Twyr Web Application Server Database Service.
 *
 * @description
 * Allows the rest of the Twyr Modules to store / retrieve data in the PostgreSQL database.
 *
 */
class DatabaseService extends TwyrBaseService {
	// #region Constructor
	constructor(parent, loader) {
		super(parent, loader);
	}
	// #endregion

	// #region startup/teardown code
	/**
	 * @async
	 * @function
	 * @override
	 * @instance
	 * @memberof DatabaseService
	 * @name     _setup
	 *
	 * @returns  {Promise} Promise that resolves / rejects based on whether the connection request went through.
	 *
	 * @summary  Sets up the connection to the configured PostgreSQL Server.
	 */
	async _setup() {
		try {
			await super._setup();

			this.$config.debug = (this.$config.debug === true);
			if(this.$config.connection) { // eslint-disable-line curly
				this.$config.connection.port = Number(this.$config.connection.port);
			}

			if(this.$config.pool) {
				this.$config.pool.min = Number(this.$config.pool.min);
				this.$config.pool.max = Number(this.$config.pool.max);

				this.$config.pool['afterCreate'] = async function(rawConnection, done) {
					try {
						const pgError = require('pg-error');

						rawConnection.connection.parseE = pgError.parse;
						rawConnection.connection.parseN = pgError.parse;

						rawConnection.connection.on('PgError', function(err) {
							switch (err.severity) {
								case 'ERROR':
								case 'FATAL':
								case 'PANIC':
									this.emit('error', err);
									break;

								default:
									this.emit('notice', err);
									break;
							}
						});

						const promises = require('bluebird');
						promises.promisifyAll(rawConnection.connection);
						done();
					}
					catch(err) {
						done(err);
					}
				};
			}

			const knex = require('knex');
			const knexInstance = knex(this.$config);
			knexInstance.on('query', this._databaseQuery.bind(this));
			knexInstance.on('query-response', this._databaseQuery.bind(this));
			knexInstance.on('query-error', this._databaseQueryError.bind(this));

			const bookshelf = require('bookshelf');
			this.$database = bookshelf(knexInstance);

			const jsonApiParams = require('bookshelf-jsonapi-params');
			this.$database.plugin(jsonApiParams, {
				'pagination': {
					'limit': 25
				}
			});

			return null;
		}
		catch(err) {
			throw new TwyrSrvcError(`${this.name}::_setup error`, err);
		}
	}

	/**
	 * @async
	 * @function
	 * @override
	 * @instance
	 * @memberof DatabaseService
	 * @name     _teardown
	 *
	 * @returns  {null} Nothing.
	 *
	 * @summary  Logs off from the connection to the configured PostgreSQL Server.
	 */
	async _teardown() {
		try {
			if(!this.$database)
				return null;

			await this.$database.knex.destroy();
			delete this.$database;

			await super._teardown();
			return null;
		}
		catch(err) {
			throw new TwyrSrvcError(`${this.name}::_teardown error`, err);
		}
	}
	// #endregion

	// #region Private Methods
	_databaseQuery(queryData) {
		this.$dependencies.LoggerService.debug(`${this.name}::_databaseQuery: ${JSON.stringify(queryData, null, '\t')}`);
	}

	_databaseQueryError(error, query) {
		const queryLog = { 'sql': query.sql, 'bindings': query.bindings, 'options': query.options };
		this.$dependencies.LoggerService.error(`${this.name}::_databaseQueryError:\nQuery: ${JSON.stringify(queryLog, null, '\t')}\nError: ${JSON.stringify(error, null, '\t')}`);
	}
	// #endregion

	// #region Properties
	/**
	 * @override
	 */
	get Interface() {
		return this.$database;
	}

	/**
	 * @override
	 */
	get dependencies() {
		return ['ConfigurationService', 'LoggerService'].concat(super.dependencies);
	}

	/**
	 * @override
	 */
	get basePath() {
		return __dirname;
	}
	// #endregion
}

exports.service = DatabaseService;