import { Container, Inject, Service } from 'typedi'; import { ITenantManager, ITenant, ITenantDBManager } from '@/interfaces'; import { EventDispatcherInterface, EventDispatcher, } from 'decorators/eventDispatcher'; import { TenantAlreadyInitialized, TenantAlreadySeeded, TenantDatabaseNotBuilt, } from '@/exceptions'; import TenantDBManager from '@/services/Tenancy/TenantDBManager'; import events from '@/subscribers/events'; import { Tenant } from '@/system/models'; import { SeedMigration } from '@/lib/Seeder/SeedMigration'; import i18n from '../../loaders/i18n'; const ERRORS = { TENANT_ALREADY_CREATED: 'TENANT_ALREADY_CREATED', TENANT_NOT_EXISTS: 'TENANT_NOT_EXISTS', }; // Tenants manager service. @Service() export default class TenantsManagerService implements ITenantManager { static instances: { [key: number]: ITenantManager } = {}; @EventDispatcher() private eventDispatcher: EventDispatcherInterface; @Inject('repositories') private sysRepositories: any; private tenantDBManager: ITenantDBManager; /** * Constructor method. */ constructor() { this.tenantDBManager = new TenantDBManager(); } /** * Creates a new teant with unique organization id. * @param {ITenant} tenant * @return {Promise} */ public async createTenant(): Promise { const { tenantRepository } = this.sysRepositories; const tenant = await tenantRepository.createWithUniqueOrgId(); return tenant; } /** * Creates a new tenant database. * @param {ITenant} tenant - * @return {Promise} */ public async createDatabase(tenant: ITenant): Promise { this.throwErrorIfTenantAlreadyInitialized(tenant); await this.tenantDBManager.createDatabase(tenant); this.eventDispatcher.dispatch(events.tenantManager.databaseCreated); } /** * Drops the database if the given tenant. * @param {number} tenantId */ async dropDatabaseIfExists(tenant: ITenant) { // Drop the database if exists. await this.tenantDBManager.dropDatabaseIfExists(tenant); } /** * Detarmines the tenant has database. * @param {ITenant} tenant * @returns {Promise} */ public async hasDatabase(tenant: ITenant): Promise { return this.tenantDBManager.databaseExists(tenant); } /** * Migrates the tenant database. * @param {ITenant} tenant * @return {Promise} */ public async migrateTenant(tenant: ITenant): Promise { // Throw error if the tenant already initialized. this.throwErrorIfTenantAlreadyInitialized(tenant); // Migrate the database tenant. await this.tenantDBManager.migrate(tenant); // Mark the tenant as initialized. await Tenant.markAsInitialized(tenant.id); // Triggers `onTenantMigrated` event. this.eventDispatcher.dispatch(events.tenantManager.tenantMigrated, { tenantId: tenant.id, }); } /** * Seeds the tenant database. * @param {ITenant} tenant * @return {Promise} */ public async seedTenant(tenant: ITenant, tenancyContext): Promise { // Throw error if the tenant is not built yet. this.throwErrorIfTenantNotBuilt(tenant); // Throw error if the tenant is not seeded yet. this.throwErrorIfTenantAlreadySeeded(tenant); // Seeds the organization database data. await new SeedMigration(tenancyContext.knex, tenancyContext).latest(); // Mark the tenant as seeded in specific date. await Tenant.markAsSeeded(tenant.id); // Triggers `onTenantSeeded` event. this.eventDispatcher.dispatch(events.tenantManager.tenantSeeded, { tenantId: tenant.id, }); } /** * Initialize knex instance or retrieve the instance of cache map. * @param {ITenant} tenant * @returns {Knex} */ public setupKnexInstance(tenant: ITenant) { return this.tenantDBManager.setupKnexInstance(tenant); } /** * Retrieve tenant knex instance or throw error in case was not initialized. * @param {number} tenantId * @returns {Knex} */ public getKnexInstance(tenantId: number) { return this.tenantDBManager.getKnexInstance(tenantId); } /** * Throws error if the tenant already seeded. * @throws {TenantAlreadySeeded} */ private throwErrorIfTenantAlreadySeeded(tenant: ITenant) { if (tenant.seededAt) { throw new TenantAlreadySeeded(); } } /** * Throws error if the tenant database is not built yut. * @param {ITenant} tenant */ private throwErrorIfTenantNotBuilt(tenant: ITenant) { if (!tenant.initializedAt) { throw new TenantDatabaseNotBuilt(); } } /** * Throws error if the tenant already migrated. * @throws {TenantAlreadyInitialized} */ private throwErrorIfTenantAlreadyInitialized(tenant: ITenant) { if (tenant.initializedAt) { throw new TenantAlreadyInitialized(); } } /** * Initialize seed migration contxt. * @param {ITenant} tenant * @returns */ public getSeedMigrationContext(tenant: ITenant) { // Initialize the knex instance. const knex = this.setupKnexInstance(tenant); const i18nInstance = i18n(); i18nInstance.setLocale(tenant.metadata.language); return { knex, i18n: i18nInstance, tenant, }; } }