Merge pull request #272 from SyncrowIOT/SP-1079-BE-Implement-API-for-linking-space-model-to-space

This commit is contained in:
hannathkadher
2025-03-02 00:17:50 +04:00
committed by GitHub
5 changed files with 248 additions and 11 deletions

View File

@ -292,7 +292,7 @@ export class ControllerRoute {
public static readonly CREATE_SPACE_MODEL_DESCRIPTION =
'This endpoint allows you to create a new space model within a specified project. A space model defines the structure of spaces, including subspaces, products, and product items, and is uniquely identifiable within the project.';
public static readonly GET_SPACE_MODEL_SUMMARY = 'Get a New Space Model';
public static readonly GET_SPACE_MODEL_SUMMARY = 'Get a Space Model';
public static readonly GET_SPACE_MODEL_DESCRIPTION =
'Fetch a space model details';

View File

@ -14,6 +14,7 @@ import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { SpaceModelService } from '../services';
import {
CreateSpaceModelDto,
LinkSpacesToModelDto,
SpaceModelParam,
UpdateSpaceModelDto,
} from '../dtos';
@ -107,4 +108,20 @@ export class SpaceModelController {
async delete(@Param() param: SpaceModelParam): Promise<BaseResponseDto> {
return await this.spaceModelService.deleteSpaceModel(param);
}
@ApiBearerAuth()
@UseGuards(PermissionsGuard)
@Permissions('SPACE_MODEL_LINK')
@ApiOperation({
summary: ControllerRoute.SPACE_MODEL.ACTIONS.DELETE_SPACE_MODEL_SUMMARY,
description:
ControllerRoute.SPACE_MODEL.ACTIONS.DELETE_SPACE_MODEL_DESCRIPTION,
})
@Post(':spaceModelUuid/spaces/link')
async link(
@Param() params: SpaceModelParam,
@Body() dto: LinkSpacesToModelDto,
): Promise<BaseResponseDto> {
return await this.spaceModelService.linkSpaceModel(params, dto);
}
}

View File

@ -5,11 +5,15 @@ import {
SubspaceModelProductAllocationEntity,
} from '@app/common/modules/space-model';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateSpaceModelDto, UpdateSpaceModelDto } from '../dtos';
import {
CreateSpaceModelDto,
LinkSpacesToModelDto,
UpdateSpaceModelDto,
} from '../dtos';
import { ProjectParam } from 'src/community/dtos';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import { SubSpaceModelService } from './subspace/subspace-model.service';
import { DataSource, QueryRunner, SelectQueryBuilder } from 'typeorm';
import { DataSource, In, QueryRunner, SelectQueryBuilder } from 'typeorm';
import {
TypeORMCustomModel,
TypeORMCustomModelFindAllQuery,
@ -21,7 +25,20 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { CommandBus } from '@nestjs/cqrs';
import { ProcessTagDto } from 'src/tags/dtos';
import { SpaceModelProductAllocationService } from './space-model-product-allocation.service';
import { SpaceRepository } from '@app/common/modules/space';
import {
SpaceProductAllocationRepository,
SpaceRepository,
} from '@app/common/modules/space';
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
import {
SubspaceProductAllocationRepository,
SubspaceRepository,
} from '@app/common/modules/space/repositories/subspace.repository';
import {
ORPHAN_COMMUNITY_NAME,
ORPHAN_SPACE_NAME,
} from '@app/common/constants/orphan-constant';
import { DeviceRepository } from '@app/common/modules/device/repositories';
@Injectable()
export class SpaceModelService {
@ -33,6 +50,10 @@ export class SpaceModelService {
private commandBus: CommandBus,
private readonly spaceModelProductAllocationService: SpaceModelProductAllocationService,
private readonly spaceRepository: SpaceRepository,
private readonly spaceProductAllocationRepository: SpaceProductAllocationRepository,
private readonly subspaceRepository: SubspaceRepository,
private readonly subspaceProductAllocationRepository: SubspaceProductAllocationRepository,
private readonly deviceRepository: DeviceRepository,
) {}
async createSpaceModel(
@ -286,6 +307,205 @@ export class SpaceModelService {
}
}
async linkSpaceModel(
params: SpaceModelParam,
dto: LinkSpacesToModelDto,
): Promise<BaseResponseDto> {
const project = await this.validateProject(params.projectUuid);
try {
const spaceModel = await this.spaceModelRepository.findOne({
where: { uuid: params.spaceModelUuid },
relations: [
'productAllocations',
'subspaceModels',
'subspaceModels.productAllocations',
],
});
if (!spaceModel) {
throw new HttpException(
`Space Model with UUID ${params.spaceModelUuid} not found`,
HttpStatus.NOT_FOUND,
);
}
if (!spaceModel.productAllocations.length) {
throw new HttpException(
`Space Model ${params.spaceModelUuid} has no product allocations`,
HttpStatus.BAD_REQUEST,
);
}
const spaces = await this.spaceRepository.find({
where: { uuid: In(dto.spaceUuids), disabled: false },
relations: [
'spaceModel',
'devices',
'subspaces',
'productAllocations',
'subspaces.productAllocations',
'community',
],
});
if (!spaces.length) {
throw new HttpException(
`No spaces found for the given UUIDs`,
HttpStatus.NOT_FOUND,
);
}
await Promise.all(
spaces.map(async (space) => {
const hasDependencies =
space.devices.length > 0 ||
space.subspaces.length > 0 ||
space.productAllocations.length > 0;
if (!hasDependencies && !space.spaceModel) {
await this.linkToSpace(space, spaceModel);
} else if (dto.overwrite) {
await this.overwriteSpace(space, project);
await this.linkToSpace(space, spaceModel);
}
}),
);
return new SuccessResponseDto({
message: 'Spaces linked successfully',
data: dto.spaceUuids,
});
} catch (error) {
throw new HttpException(
`Failed to link space model: ${error.message}`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async linkToSpace(
space: SpaceEntity,
spaceModel: SpaceModelEntity,
): Promise<void> {
try {
space.spaceModel = spaceModel;
await this.spaceRepository.save(space);
const spaceProductAllocations = spaceModel.productAllocations.map(
(modelAllocation) =>
this.spaceProductAllocationRepository.create({
space,
inheritedFromModel: modelAllocation,
product: modelAllocation.product,
tags: modelAllocation.tags,
}),
);
await this.spaceProductAllocationRepository.save(spaceProductAllocations);
if (!spaceModel.subspaceModels.length) {
throw new HttpException(
`Space Model ${spaceModel.uuid} has no subspaces`,
HttpStatus.BAD_REQUEST,
);
}
await Promise.all(
spaceModel.subspaceModels.map(async (subspaceModel) => {
const subspace = this.subspaceRepository.create({
subspaceName: subspaceModel.subspaceName,
subSpaceModel: subspaceModel,
space: space,
});
await this.subspaceRepository.save(subspace);
const subspaceAllocations = subspaceModel.productAllocations.map(
(modelAllocation) =>
this.subspaceProductAllocationRepository.create({
subspace,
inheritedFromModel: modelAllocation,
product: modelAllocation.product,
tags: modelAllocation.tags,
}),
);
if (subspaceAllocations.length) {
await this.subspaceProductAllocationRepository.save(
subspaceAllocations,
);
}
}),
);
} catch (error) {
throw new HttpException(
`Failed to link space ${space.uuid} to space model ${spaceModel.uuid}: ${error.message}`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async overwriteSpace(
space: SpaceEntity,
project: ProjectEntity,
): Promise<void> {
try {
if (space.productAllocations.length) {
await this.spaceProductAllocationRepository.delete({
uuid: In(
space.productAllocations.map((allocation) => allocation.uuid),
),
});
}
await Promise.all(
space.subspaces.map(async (subspace) => {
await this.subspaceRepository.update(
{ uuid: subspace.uuid },
{ disabled: true },
);
if (subspace.productAllocations.length) {
await this.subspaceProductAllocationRepository.delete({
uuid: In(
subspace.productAllocations.map(
(allocation) => allocation.uuid,
),
),
});
}
}),
);
if (space.devices.length > 0) {
const orphanSpace = await this.spaceRepository.findOne({
where: {
community: {
name: `${ORPHAN_COMMUNITY_NAME}-${project.name}`,
},
spaceName: ORPHAN_SPACE_NAME,
},
});
if (!orphanSpace) {
throw new HttpException(
`Orphan space not found in community ${project.name}`,
HttpStatus.NOT_FOUND,
);
}
await this.deviceRepository.update(
{ uuid: In(space.devices.map((device) => device.uuid)) },
{ spaceDevice: orphanSpace },
);
}
} catch (error) {
throw new Error(
`Failed to overwrite space ${space.uuid}: ${error.message}`,
);
}
}
async validateName(modelName: string, projectUuid: string): Promise<void> {
const isModelExist = await this.spaceModelRepository.findOne({
where: { modelName, project: { uuid: projectUuid }, disabled: false },

View File

@ -66,12 +66,12 @@ export class DisableSpaceHandler
}
const tagUuids = space.tags?.map((tag) => tag.uuid) || [];
const subspaceDtos =
/* const subspaceDtos =
space.subspaces?.map((subspace) => ({
subspaceUuid: subspace.uuid,
})) || [];
})) || []; */
const deletionTasks = [
this.subSpaceService.deleteSubspaces(subspaceDtos, queryRunner),
// this.subSpaceService.deleteSubspaces(subspaceDtos, queryRunner),
this.userService.deleteUserSpace(space.uuid),
this.tagService.deleteTags(tagUuids, queryRunner),
this.deviceService.deleteDevice(

View File

@ -282,11 +282,11 @@ export class SubSpaceService {
}
}
async deleteSubspaces(
/* async deleteSubspaces(
deleteDtos: DeleteSubspaceDto[],
queryRunner: QueryRunner,
) {
const deleteResults: { uuid: string }[] = [];
/* const deleteResults: { uuid: string }[] = [];
for (const dto of deleteDtos) {
const subspace = await this.findOne(dto.subspaceUuid);
@ -319,8 +319,8 @@ export class SubSpaceService {
deleteResults.push({ uuid: dto.subspaceUuid });
}
return deleteResults;
}
return deleteResults;
} */
async modifySubSpace(
subspaceDtos: ModifySubspaceDto[],