import { SpaceRepository } from '@app/common/modules/space/repositories'; import { BadRequestException, HttpException, HttpStatus, Injectable, } from '@nestjs/common'; import { CommunityService } from '../../community/services'; import { ProjectService } from '../../project/services'; import { SpaceModelEntity, SpaceModelRepository, } from '@app/common/modules/space-model'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { DeviceRepository } from '@app/common/modules/device/repositories'; import { SpaceEntity } from '@app/common/modules/space/entities/space.entity'; import { ValidateSpacesDto } from '../dtos/validation.space.dto'; import { ProjectParam } from '../dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { In } from 'typeorm'; @Injectable() export class ValidationService { constructor( private readonly projectService: ProjectService, 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'], }); const hasInvalidSpaces = spaces.some( (space) => space.devices.length > 0 || space.subspaces.length > 0, ); 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, ) { const project = await this.projectService.findOne(projectUuid); const community = await this.communityService.getCommunityById({ communityUuid, projectUuid, }); 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', 'tags', 'subspaces.tags', 'subspaces.devices', ], }); 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', 'tag', '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): Promise { const spaceModel = await this.spaceModelRepository.findOne({ where: { uuid: spaceModelUuid }, relations: [ 'subspaceModels', 'subspaceModels.productAllocations', 'subspaceModels.productAllocations.tags', 'productAllocations.product', 'productAllocations.tags', ], }); if (!spaceModel) { throw new HttpException( `Space model with UUID ${spaceModelUuid} not found`, HttpStatus.NOT_FOUND, ); } return spaceModel; } async getFullSpaceHierarchy( space: SpaceEntity, ): Promise<{ uuid: string; spaceName: string }[]> { try { // Fetch only the relevant spaces, starting with the target space const targetSpace = await this.spaceRepository.findOne({ where: { uuid: space.uuid }, relations: ['parent', 'children'], }); // Fetch only the ancestors of the target space const ancestors = await this.fetchAncestors(targetSpace); // Optionally, fetch descendants if required const descendants = await this.fetchDescendants(targetSpace); const fullHierarchy = [...ancestors, targetSpace, ...descendants].map( (space) => ({ uuid: space.uuid, spaceName: space.spaceName, }), ); return fullHierarchy; } catch (error) { console.error('Error fetching space hierarchy:', error.message); throw new HttpException( 'Error fetching space hierarchy', HttpStatus.INTERNAL_SERVER_ERROR, ); } } 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(); } private async fetchDescendants(space: SpaceEntity): Promise { const descendants: SpaceEntity[] = []; // Fetch the immediate children of the current space const children = await this.spaceRepository.find({ where: { parent: { uuid: space.uuid } }, relations: ['children'], // To continue fetching downwards }); for (const child of children) { // Add the child to the descendants list descendants.push(child); // Recursively fetch the child's descendants const childDescendants = await this.fetchDescendants(child); descendants.push(...childDescendants); } return descendants; } 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, ); } } }