diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 753bb41..5d06090 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -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'; diff --git a/src/space-model/controllers/space-model.controller.ts b/src/space-model/controllers/space-model.controller.ts index 808cf3c..8ac607c 100644 --- a/src/space-model/controllers/space-model.controller.ts +++ b/src/space-model/controllers/space-model.controller.ts @@ -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 { 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 { + return await this.spaceModelService.linkSpaceModel(params, dto); + } } diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index a402a5b..9a49df0 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -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 { + 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 { + 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 { + 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 { const isModelExist = await this.spaceModelRepository.findOne({ where: { modelName, project: { uuid: projectUuid }, disabled: false }, diff --git a/src/space/handlers/disable-space.handler.ts b/src/space/handlers/disable-space.handler.ts index 3621586..81a90f9 100644 --- a/src/space/handlers/disable-space.handler.ts +++ b/src/space/handlers/disable-space.handler.ts @@ -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( diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index 9172db9..efa5f3e 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -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[],