Files
backend/src/space/services/space-validation.service.ts
ZaydSkaff 689a38ee0c Revamp/space management (#409)
* task: add getCommunitiesV2

* task: update getOneSpace API to match revamp structure

* refactor: implement modifications to pace management APIs

* refactor: remove space link
2025-06-18 10:34:29 +03:00

277 lines
8.3 KiB
TypeScript

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<void> {
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<SpaceEntity> {
await this.validateCommunityAndProject(communityUuid, projectUuid);
const space = await this.validateSpace(spaceUuid);
return space;
}
async validateSpace(spaceUuid: string): Promise<SpaceEntity> {
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<SpaceEntity> {
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<SpaceModelEntity> {
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<SpaceEntity[]> {
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,
);
}
}
}