import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { DeviceRepository } from '@app/common/modules/device/repositories'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { SpaceModelEntity, SpaceModelRepository, } from '@app/common/modules/space-model'; import { SpaceEntity } from '@app/common/modules/space/entities/space.entity'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { BadRequestException, forwardRef, HttpException, HttpStatus, Inject, Injectable, } from '@nestjs/common'; import { In, QueryRunner } from 'typeorm'; import { CommunityService } from '../../community/services'; import { ProjectService } from '../../project/services'; import { ProjectParam } from '../dtos'; import { ValidateSpacesDto } from '../dtos/validation.space.dto'; @Injectable() export class ValidationService { constructor( private readonly projectService: ProjectService, @Inject(forwardRef(() => CommunityService)) private readonly communityService: CommunityService, private readonly spaceRepository: SpaceRepository, private readonly projectRepository: ProjectRepository, private readonly communityRepository: CommunityRepository, private readonly spaceModelRepository: SpaceModelRepository, private readonly deviceRepository: DeviceRepository, ) {} async validateSpacesWithDevicesOrSubspaces( validateSpacesDto: ValidateSpacesDto, projectParam: ProjectParam, ) { const { spacesUuids } = validateSpacesDto; const { projectUuid } = projectParam; await this.communityService.validateProject(projectUuid); const spaces = await this.spaceRepository.find({ where: { uuid: In(spacesUuids), disabled: false }, relations: ['devices', 'subspaces', 'productAllocations', 'spaceModel'], }); const hasInvalidSpaces = spaces.some( (space) => space.devices.length > 0 || space.subspaces.length > 0 || space.productAllocations.length > 0 || space.spaceModel, ); if (hasInvalidSpaces) { throw new BadRequestException( 'Selected spaces already have linked space model / sub-spaces and devices', ); } return new SuccessResponseDto({ statusCode: HttpStatus.OK, message: 'Validation completed successfully. No spaces have linked devices or sub-spaces.', }); } async validateCommunityAndProject( communityUuid: string, projectUuid: string, queryRunner?: QueryRunner, ) { const project = await this.projectService.findOne(projectUuid, queryRunner); const community = await this.communityService.getCommunityById( { communityUuid, projectUuid, }, queryRunner, ); return { community: community.data, project: project }; } async checkCommunityAndProjectSpaceExistence( communityUuid: string, projectUuid: string, spaceUuid?: string, ): Promise { const [projectExists, communityExists, spaceExists] = await Promise.all([ this.projectRepository.exists({ where: { uuid: projectUuid } }), this.communityRepository.exists({ where: { uuid: communityUuid, project: { uuid: projectUuid } }, }), spaceUuid ? this.spaceRepository.exists({ where: { uuid: spaceUuid }, }) : Promise.resolve(true), ]); if (!projectExists) throw new HttpException(`Project not found`, HttpStatus.NOT_FOUND); if (!communityExists) throw new HttpException(`Community not found`, HttpStatus.NOT_FOUND); if (spaceUuid && !spaceExists) throw new HttpException(`Space not found`, HttpStatus.NOT_FOUND); } async validateSpaceWithinCommunityAndProject( communityUuid: string, projectUuid: string, spaceUuid?: string, ): Promise { await this.validateCommunityAndProject(communityUuid, projectUuid); const space = await this.validateSpace(spaceUuid); return space; } async validateSpace(spaceUuid: string): Promise { const space = await this.spaceRepository.findOne({ where: { uuid: spaceUuid, disabled: false }, relations: [ 'parent', 'children', 'subspaces', 'productAllocations', 'productAllocations.product', 'subspaces.productAllocations', 'subspaces.productAllocations.product', 'subspaces.devices', 'spaceModel', ], }); if (!space) { throw new HttpException( `Space with UUID ${spaceUuid} not found`, HttpStatus.NOT_FOUND, ); } // const devices = await this.deviceRepository.find({ // where: { spaceDevice: { uuid: spaceUuid } }, // select: ['uuid', 'deviceTuyaUuid', 'isActive', 'createdAt', 'updatedAt'], // relations: ['productDevice', 'subspace'], // }); // space.devices = devices; return space; } async fetchSpaceDevices(spaceUuid: string): Promise { const space = await this.spaceRepository.findOne({ where: { uuid: spaceUuid, disabled: false }, relations: [ 'devices', 'devices.productDevice', 'devices.tag', 'devices.subspace', ], }); if (!space) { throw new HttpException( `Space with UUID ${spaceUuid} not found`, HttpStatus.NOT_FOUND, ); } return space; } async validateSpaceModel( spaceModelUuid: string, queryRunner?: QueryRunner, ): Promise { const queryBuilder = ( queryRunner.manager.getRepository(SpaceModelEntity) || this.spaceModelRepository ) .createQueryBuilder('spaceModel') .leftJoinAndSelect( 'spaceModel.subspaceModels', 'subspaceModels', 'subspaceModels.disabled = :disabled', { disabled: false }, ) .leftJoinAndSelect( 'subspaceModels.productAllocations', 'subspaceProductAllocations', ) .leftJoinAndSelect( 'subspaceProductAllocations.tag', 'subspaceAllocationTag', ) .leftJoinAndSelect( 'subspaceProductAllocations.product', 'subspaceAllocationProduct', ) .leftJoinAndSelect('spaceModel.productAllocations', 'productAllocations') .leftJoinAndSelect( 'productAllocations.product', 'productAllocationProduct', ) .leftJoinAndSelect('productAllocations.tag', 'productAllocationTag') .andWhere('spaceModel.disabled = :disabled', { disabled: false }) .where('spaceModel.uuid = :uuid', { uuid: spaceModelUuid }); const spaceModel = await queryBuilder.getOne(); if (!spaceModel) { throw new HttpException( `Space model with UUID ${spaceModelUuid} not found`, HttpStatus.NOT_FOUND, ); } return spaceModel; } private async fetchAncestors(space: SpaceEntity): Promise { const ancestors: SpaceEntity[] = []; let currentSpace = space; while (currentSpace && currentSpace.parent) { // Fetch the parent space const parent = await this.spaceRepository.findOne({ where: { uuid: currentSpace.parent.uuid }, relations: ['parent'], // To continue fetching upwards }); if (parent) { ancestors.push(parent); currentSpace = parent; } else { currentSpace = null; } } // Return the ancestors in reverse order to have the root at the start return ancestors.reverse(); } async getParentHierarchy( space: SpaceEntity, ): Promise<{ uuid: string; spaceName: string }[]> { try { const targetSpace = await this.spaceRepository.findOne({ where: { uuid: space.uuid }, relations: ['parent'], }); if (!targetSpace) { throw new HttpException('Space not found', HttpStatus.NOT_FOUND); } const ancestors = await this.fetchAncestors(targetSpace); return ancestors.map((parentSpace) => ({ uuid: parentSpace.uuid, spaceName: parentSpace.spaceName, })); } catch (error) { console.error('Error fetching parent hierarchy:', error.message); throw new HttpException( 'Error fetching parent hierarchy', HttpStatus.INTERNAL_SERVER_ERROR, ); } } }