feat(nestjs): migrate to NestJS

This commit is contained in:
Ahmed Bouhuolia
2025-04-07 11:51:24 +02:00
parent f068218a16
commit 55fcc908ef
3779 changed files with 631 additions and 195332 deletions

View File

@@ -0,0 +1,122 @@
import { Queue } from 'bullmq';
import { InjectQueue } from '@nestjs/bullmq';
import {
IOrganizationBuildEventPayload,
IOrganizationBuiltEventPayload,
OrganizationBuildQueue,
OrganizationBuildQueueJob,
OrganizationBuildQueueJobPayload,
} from '../Organization.types';
import { Injectable } from '@nestjs/common';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
import {
throwIfTenantInitizalized,
throwIfTenantIsBuilding,
} from '../Organization/_utils';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { TenantsManagerService } from '@/modules/TenantDBManager/TenantsManager';
import { events } from '@/common/events/events';
import { transformBuildDto } from '../Organization.utils';
import { BuildOrganizationDto } from '../dtos/Organization.dto';
import { TenantRepository } from '@/modules/System/repositories/Tenant.repository';
@Injectable()
export class BuildOrganizationService {
constructor(
private readonly eventPublisher: EventEmitter2,
private readonly tenantsManager: TenantsManagerService,
private readonly tenancyContext: TenancyContext,
private readonly tenantRepository: TenantRepository,
@InjectQueue(OrganizationBuildQueue)
private readonly organizationBuildQueue: Queue,
) {}
/**
* Builds the database schema and seed data of the given organization id.
* @param {string} organizationId
* @return {Promise<void>}
*/
public async build(buildDTO: BuildOrganizationDto): Promise<void> {
const tenant = await this.tenancyContext.getTenant();
const systemUser = await this.tenancyContext.getSystemUser();
// Throw error if the tenant is already initialized.
throwIfTenantInitizalized(tenant);
await this.tenantsManager.dropDatabaseIfExists();
await this.tenantsManager.createDatabase();
await this.tenantsManager.migrateTenant();
await this.tenantsManager.seedTenant()
// Throws `onOrganizationBuild` event.
await this.eventPublisher.emitAsync(events.organization.build, {
tenantId: tenant.id,
buildDTO,
systemUser,
} as IOrganizationBuildEventPayload);
// Marks the tenant as completed builing.
await this.tenantRepository.markAsBuilt().findById(tenant.id);
await this.tenantRepository.markAsBuildCompleted().findById(tenant.id);
// Flags the tenant database batch.
await this.tenantRepository.flagTenantDBBatch().findById(tenant.id);
// Triggers the organization built event.
await this.eventPublisher.emitAsync(events.organization.built, {
tenantId: tenant.id,
} as IOrganizationBuiltEventPayload);
}
/**
* Execute the tenant database build process.
* @param {BuildOrganizationDto} buildDTO - Organization build dto.
* @returns {Promise<{ nextRunAt: Date; jobId: string }>} - Returns the next run date and job id.
*/
async buildRunJob(
buildDTO: BuildOrganizationDto,
): Promise<{ nextRunAt: Date; jobId: string }> {
const tenant = await this.tenancyContext.getTenant();
const systemUser = await this.tenancyContext.getSystemUser();
// Throw error if the tenant is already initialized.
throwIfTenantInitizalized(tenant);
// Throw error if tenant is currently building.
throwIfTenantIsBuilding(tenant);
// Transforms build DTO object.
const transformedBuildDTO = transformBuildDto(buildDTO);
// Saves the tenant metadata.
await this.tenantRepository.saveMetadata(tenant.id, transformedBuildDTO);
// Run the organization database build job.
const jobMeta = await this.organizationBuildQueue.add(
OrganizationBuildQueueJob,
{
organizationId: tenant.organizationId,
userId: systemUser.id,
buildDto: transformedBuildDTO,
} as OrganizationBuildQueueJobPayload,
);
// Marks the tenant as currently building.
await this.tenantRepository.markAsBuilding(jobMeta.id).findById(tenant.id);
return {
nextRunAt: jobMeta.data.nextRunAt,
jobId: jobMeta.data.id,
};
}
/**
* Unlocks tenant build run job.
*/
public async revertBuildRunJob() {
const tenant = await this.tenancyContext.getTenant();
await this.tenantRepository.markAsBuildCompleted().findById(tenant.id);
}
}

View File

@@ -0,0 +1,35 @@
import { ServiceError } from '@/modules/Items/ServiceError';
import { OrganizationBaseCurrencyLocking } from '../Organization/OrganizationBaseCurrencyLocking.service';
import { TenantModel } from '@/modules/System/models/TenantModel';
import { Injectable } from '@nestjs/common';
import { ERRORS } from '../Organization.constants';
@Injectable()
export class CommandOrganizationValidators {
constructor(
private readonly baseCurrencyMutateLocking: OrganizationBaseCurrencyLocking,
) {}
/**
* Validate mutate base currency ability.
* @param {Tenant} tenant -
* @param {string} newBaseCurrency -
* @param {string} oldBaseCurrency -
*/
async validateMutateBaseCurrency(
tenant: TenantModel,
newBaseCurrency: string,
oldBaseCurrency: string,
) {
if (tenant.isReady && newBaseCurrency !== oldBaseCurrency) {
const isLocked =
await this.baseCurrencyMutateLocking.isBaseCurrencyMutateLocked(
tenant.id,
);
if (isLocked) {
throw new ServiceError(ERRORS.BASE_CURRENCY_MUTATE_LOCKED);
}
}
}
}

View File

@@ -0,0 +1,50 @@
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
import { UpdateOrganizationDto } from '../dtos/Organization.dto';
import { throwIfTenantNotExists } from '../Organization/_utils';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { CommandOrganizationValidators } from './CommandOrganizationValidators.service';
import { TenantRepository } from '@/modules/System/repositories/Tenant.repository';
import { Injectable } from '@nestjs/common';
@Injectable()
export class UpdateOrganizationService {
constructor(
private readonly tenancyContext: TenancyContext,
private readonly eventEmitter: EventEmitter2,
private readonly commandOrganizationValidators: CommandOrganizationValidators,
private readonly tenantRepository: TenantRepository,
) {}
/**
* Updates organization information.
* @param {UpdateOrganizationDto} organizationDTO
*/
public async execute(organizationDTO: UpdateOrganizationDto): Promise<void> {
const tenant = await this.tenancyContext.getTenant(true);
// Throw error if the tenant not exists.
throwIfTenantNotExists(tenant);
// Validate organization transactions before mutate base currency.
if (organizationDTO.baseCurrency) {
await this.commandOrganizationValidators.validateMutateBaseCurrency(
tenant,
organizationDTO.baseCurrency,
tenant.metadata?.baseCurrency,
);
}
await this.tenantRepository.saveMetadata(tenant.id, organizationDTO);
if (organizationDTO.baseCurrency !== tenant.metadata?.baseCurrency) {
// Triggers `onOrganizationBaseCurrencyUpdated` event.
await this.eventEmitter.emitAsync(
events.organization.baseCurrencyUpdated,
{
organizationDTO,
},
);
}
}
}