server/features/tenant_administration/middlewares/main/middleware.js

/* eslint-disable security/detect-object-injection */

'use strict';

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

/**
 * Module dependencies, required for this module
 * @ignore
 */
const TwyrBaseMiddleware = require('twyr-base-middleware').TwyrBaseMiddleware;
const TwyrMiddlewareError = require('twyr-middleware-error').TwyrMiddlewareError;

/**
 * @class   Main
 * @extends {TwyrBaseMiddleware}
 * @classdesc The Twyr Web Application Server Tenant Administration Feature Main middleware - manages CRUD for account data.
 *
 *
 */
class Main extends TwyrBaseMiddleware {
	// #region Constructor
	constructor(parent, loader) {
		super(parent, loader);
	}
	// #endregion

	// #region startup/teardown code
	/**
	 * @async
	 * @function
	 * @override
	 * @instance
	 * @memberof ApiService
	 * @name     _setup
	 *
	 * @returns  {null} Nothing.
	 *
	 * @summary  Sets up the broker to manage API exposed by other modules.
	 */
	async _setup() {
		try {
			await super._setup();

			const dbSrvc = this.$dependencies.DatabaseService;
			const self = this; // eslint-disable-line consistent-this

			Object.defineProperty(this, '$TenantModel', {
				'__proto__': null,
				'configurable': true,

				'value': dbSrvc.Model.extend({
					'tableName': 'tenants',
					'idAttribute': 'tenant_id',
					'hasTimestamps': true,

					'location': function() {
						return this.hasOne(self.$TenantLocationModel, 'tenant_id');
					}
				})
			});

			Object.defineProperty(this, '$TenantLocationModel', {
				'__proto__': null,
				'configurable': true,

				'value': dbSrvc.Model.extend({
					'tableName': 'tenant_locations',
					'idAttribute': 'tenant_location_id',
					'hasTimestamps': true,

					'tenant': function() {
						return this.belongsTo(self.$TenantModel, 'tenant_id');
					}
				})
			});

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

	/**
	 * @async
	 * @function
	 * @override
	 * @instance
	 * @memberof ApiService
	 * @name     _teardown
	 *
	 * @returns  {undefined} Nothing.
	 *
	 * @summary  Deletes the broker that manages API.
	 */
	async _teardown() {
		try {
			delete this.$TenantLocationModel;
			delete this.$TenantModel;

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

	// #region Protected methods
	async _registerApis() {
		try {
			const ApiService = this.$dependencies.ApiService;

			await ApiService.add(`${this.name}::getTenant`, this._getTenant.bind(this));
			await ApiService.add(`${this.name}::updateTenant`, this._updateTenant.bind(this));
			await ApiService.add(`${this.name}::deleteTenant`, this._deleteTenant.bind(this));

			await ApiService.add(`${this.name}::getLocation`, this._getLocation.bind(this));
			await ApiService.add(`${this.name}::addLocation`, this._addLocation.bind(this));
			await ApiService.add(`${this.name}::deleteLocation`, this._deleteLocation.bind(this));

			await super._registerApis();
			return null;
		}
		catch(err) {
			throw new TwyrMiddlewareError(`${this.name}::_registerApis`, err);
		}
	}

	async _deregisterApis() {
		try {
			const ApiService = this.$dependencies.ApiService;

			await ApiService.remove(`${this.name}::deleteLocation`, this._deleteLocation.bind(this));
			await ApiService.remove(`${this.name}::addLocation`, this._addLocation.bind(this));
			await ApiService.remove(`${this.name}::getLocation`, this._getLocation.bind(this));

			await ApiService.remove(`${this.name}::deleteTenant`, this._deleteTenant.bind(this));
			await ApiService.remove(`${this.name}::updateTenant`, this._updateTenant.bind(this));
			await ApiService.remove(`${this.name}::getTenant`, this._getTenant.bind(this));

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

	// #region API
	async _getTenant(ctxt) {
		try {
			const TenantRecord = new this.$TenantModel({
				'tenant_id': ctxt.state.tenant.tenant_id
			});

			let tenantData = await TenantRecord.fetch({
				'withRelated': [{
					[ctxt.query.include]: function(qb) {
						qb.where('is_primary', true);
					}
				}]
			});

			tenantData = this.$jsonApiMapper.map(tenantData, 'tenant-administration/tenants', {
				'typeForModel': {
					'location': 'tenant-administration/tenant_locations'
				},

				'enableLinks': false
			});

			return tenantData;
		}
		catch(err) {
			throw new TwyrMiddlewareError(`${this.name}::_getTenant`, err);
		}
	}

	async _updateTenant(ctxt) {
		try {
			const tenant = ctxt.request.body;

			delete tenant.data.relationships;
			delete tenant.included;

			const jsonDeserializedData = await this.$jsonApiDeserializer.deserializeAsync(tenant);
			jsonDeserializedData['tenant_id'] = jsonDeserializedData['id'];

			delete jsonDeserializedData.id;
			delete jsonDeserializedData.created_at;
			delete jsonDeserializedData.updated_at;

			const savedRecord = await this.$TenantModel
				.forge()
				.save(jsonDeserializedData, {
					'method': 'update',
					'patch': true
				});

			return {
				'data': {
					'type': tenant.data.type,
					'id': savedRecord.get('tenant_id')
				}
			};
		}
		catch(err) {
			throw new TwyrMiddlewareError(`${this.name}::_updateTenant`, err);
		}
	}

	async _deleteTenant(ctxt) {
		try {
			if(ctxt.state.tenant['sub_domain'] === 'www') { // eslint-disable-line curly
				throw new Error(`WWW tenant cannot be deleted`);
			}

			await new this.$TenantModel({
				'tenant_id': ctxt.state.tenant.tenant_id
			}).destroy();

			return null;
		}
		catch(err) {
			throw new TwyrMiddlewareError(`${this.name}::_deleteTenant`, err);
		}
	}

	async _getLocation(ctxt) {
		try {
			const TenantLocationRecord = new this.$TenantLocationModel({
				'tenant_location_id': ctxt.params['tenant_location_id']
			});

			let tenantLocationData = await TenantLocationRecord.fetch({
				'withRelated': [ctxt.query.include]
			});

			tenantLocationData = this.$jsonApiMapper.map(tenantLocationData, 'tenant-administration/tenant_locations', {
				'typeForModel': {
					'tenant': 'tenant-administration/tenants'
				},

				'enableLinks': false
			});

			return tenantLocationData;
		}
		catch(err) {
			throw new TwyrMiddlewareError(`${this.name}::_getLocation`, err);
		}
	}

	async _addLocation(ctxt, insert) {
		try {
			const tenantLocation = ctxt.request.body;

			const jsonDeserializedData = await this.$jsonApiDeserializer.deserializeAsync(tenantLocation);
			jsonDeserializedData['tenant_location_id'] = jsonDeserializedData.id;

			delete jsonDeserializedData.id;
			delete jsonDeserializedData.created_at;
			delete jsonDeserializedData.updated_at;

			Object.keys(tenantLocation.data.relationships || {}).forEach((relationshipName) => {
				if(!tenantLocation.data.relationships[relationshipName].data) {
					delete jsonDeserializedData[relationshipName];
					return;
				}

				if(!tenantLocation.data.relationships[relationshipName].data.id) {
					delete jsonDeserializedData[relationshipName];
					return;
				}

				jsonDeserializedData[`${relationshipName}_id`] = tenantLocation.data.relationships[relationshipName].data.id;
			});

			const savedRecord = await this.$TenantLocationModel
				.forge()
				.save(jsonDeserializedData, {
					'method': insert ? 'insert' : 'update',
					'patch': !insert
				});

			return {
				'data': {
					'type': tenantLocation.data.type,
					'id': savedRecord.get('tenant_location_id')
				}
			};
		}
		catch(err) {
			throw new TwyrMiddlewareError(`${this.name}::_addLocation`, err);
		}
	}

	async _deleteLocation(ctxt) {
		try {
			await new this.$TenantLocationModel({
				'tenant_location_id': ctxt.params['tenant_location_id']
			})
			.destroy();

			return null;
		}
		catch(err) {
			throw new TwyrMiddlewareError(`${this.name}::_deleteLocation`, err);
		}
	}
	// #endregion

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

exports.middleware = Main;