From 1220ee395d8f80a14c1f8d996e5e9aa7e10e0414 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 7 Mar 2025 23:01:38 +0400 Subject: [PATCH] fixing propagation --- .../modules/device/entities/device.entity.ts | 8 +- src/device/services/device.service.ts | 17 +++ .../handlers/propate-subspace-handler.ts | 2 +- .../space-model-product-allocation.service.ts | 31 ++++- .../services/space-model.service.ts | 4 +- .../subspace/subspace-model.service.ts | 14 +++ src/space-model/space-model.module.ts | 19 +++ .../space-product-allocation.service.ts | 112 ++++++++++++++++++ .../services/space-validation.service.ts | 25 ++++ src/space/services/space.service.ts | 39 +----- .../services/subspace/subspace.service.ts | 32 ++--- src/space/space.module.ts | 1 + 12 files changed, 236 insertions(+), 68 deletions(-) diff --git a/libs/common/src/modules/device/entities/device.entity.ts b/libs/common/src/modules/device/entities/device.entity.ts index 0ab8c7d..787b100 100644 --- a/libs/common/src/modules/device/entities/device.entity.ts +++ b/libs/common/src/modules/device/entities/device.entity.ts @@ -6,7 +6,6 @@ import { Unique, Index, JoinColumn, - OneToOne, JoinTable, } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; @@ -18,7 +17,6 @@ import { PermissionTypeEntity } from '../../permission/entities'; import { SceneDeviceEntity } from '../../scene-device/entities'; import { SpaceEntity } from '../../space/entities/space.entity'; import { SubspaceEntity } from '../../space/entities/subspace/subspace.entity'; -import { TagEntity } from '../../space/entities/tag.entity'; import { NewTagEntity } from '../../tag'; @Entity({ name: 'device' }) @@ -79,14 +77,10 @@ export class DeviceEntity extends AbstractEntity { @OneToMany(() => SceneDeviceEntity, (sceneDevice) => sceneDevice.device, {}) sceneDevices: SceneDeviceEntity[]; - @OneToOne(() => TagEntity, (tag) => tag.device, { - nullable: true, - }) - tag: TagEntity; @OneToMany(() => NewTagEntity, (tag) => tag.devices) @JoinTable({ name: 'device_tags' }) - public tags: NewTagEntity[]; + public tag: NewTagEntity[]; constructor(partial: Partial) { super(); diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index c6d6f12..333aee5 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -1546,4 +1546,21 @@ export class DeviceService { ); } } + + async moveDevicesToSpace( + targetSpace: SpaceEntity, + deviceIds: string[], + ): Promise { + if (!deviceIds || deviceIds.length === 0) { + throw new HttpException( + 'No device IDs provided for transfer', + HttpStatus.BAD_REQUEST, + ); + } + + await this.deviceRepository.update( + { uuid: In(deviceIds) }, + { spaceDevice: targetSpace }, + ); + } } diff --git a/src/space-model/handlers/propate-subspace-handler.ts b/src/space-model/handlers/propate-subspace-handler.ts index 1bd244d..36936ff 100644 --- a/src/space-model/handlers/propate-subspace-handler.ts +++ b/src/space-model/handlers/propate-subspace-handler.ts @@ -104,7 +104,7 @@ export class PropogateUpdateSpaceModelHandler for (const space of spaces) { await this.subSpaceService.createSubSpaceFromModel( subspaceModels, - space, + [space], queryRunner, ); } diff --git a/src/space-model/services/space-model-product-allocation.service.ts b/src/space-model/services/space-model-product-allocation.service.ts index d895500..33f26f0 100644 --- a/src/space-model/services/space-model-product-allocation.service.ts +++ b/src/space-model/services/space-model-product-allocation.service.ts @@ -16,6 +16,7 @@ import { ProductEntity } from '@app/common/modules/product/entities'; import { SpaceRepository } from '@app/common/modules/space'; import { SpaceProductAllocationService } from 'src/space/services/space-product-allocation.service'; import { SpaceEntity } from '@app/common/modules/space/entities/space.entity'; +import { ProjectEntity } from '@app/common/modules/project/entities'; @Injectable() export class SpaceModelProductAllocationService { @@ -141,7 +142,7 @@ export class SpaceModelProductAllocationService { async updateProductAllocations( dtos: ModifyTagModelDto[], - projectUuid: string, + project: ProjectEntity, spaceModel: SpaceModelEntity, queryRunner: QueryRunner, modifySubspaceModels?: ModifySubspaceModelDto[], @@ -161,7 +162,7 @@ export class SpaceModelProductAllocationService { const processedTags = await this.tagService.processTags( addTagDtos, - projectUuid, + project.uuid, queryRunner, ); const addTagUuidMap = new Map(); @@ -192,13 +193,19 @@ export class SpaceModelProductAllocationService { await Promise.all([ this.processAddActions( filteredDtos, - projectUuid, + project.uuid, spaceModel, queryRunner, modifySubspaceModels, spaces, ), - this.processDeleteActions(filteredDtos, queryRunner, spaceModel), + this.processDeleteActions( + filteredDtos, + queryRunner, + spaceModel, + project, + spaces, + ), ]); } catch (error) { throw this.handleError(error, 'Error while updating product allocations'); @@ -304,6 +311,8 @@ export class SpaceModelProductAllocationService { dtos: ModifyTagModelDto[], queryRunner: QueryRunner, spaceModel: SpaceModelEntity, + project: ProjectEntity, + spaces?: SpaceEntity[], ): Promise { try { if (!dtos || dtos.length === 0) { @@ -325,7 +334,11 @@ export class SpaceModelProductAllocationService { uuid: spaceModel.uuid, }, }, - relations: ['tags'], + relations: [ + 'tags', + 'inheritedSpaceAllocations', + 'inheritedSpaceAllocations.tags', + ], }, ); @@ -365,6 +378,14 @@ export class SpaceModelProductAllocationService { ); } + await this.spaceProductAllocationService.propagateDeleteToInheritedAllocations( + queryRunner, + allocationsToUpdate, + tagUuidsToDelete, + project, + spaces, + ); + await queryRunner.manager .createQueryBuilder() .delete() diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 2950a64..ba7d5f5 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -189,7 +189,7 @@ export class SpaceModelService { async update(dto: UpdateSpaceModelDto, param: SpaceModelParam) { const queryRunner = this.dataSource.createQueryRunner(); - await this.validateProject(param.projectUuid); + const project = await this.validateProject(param.projectUuid); const spaceModel = await this.validateSpaceModel( param.spaceModelUuid, param.projectUuid, @@ -227,7 +227,7 @@ export class SpaceModelService { if (dto.tags) { await this.spaceModelProductAllocationService.updateProductAllocations( dto.tags, - param.projectUuid, + project, spaceModel, queryRunner, dto.subspaceModels, diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index 90e1ba1..776e171 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -14,6 +14,8 @@ import { ProcessTagDto } from 'src/tags/dtos'; import { TagService } from 'src/tags/services'; import { SubspaceModelProductAllocationService } from './subspace-model-product-allocation.service'; import { ISingleSubspaceModel } from 'src/space-model/interfaces'; +import { SpaceEntity } from '@app/common/modules/space/entities/space.entity'; +import { SubSpaceService } from 'src/space/services/subspace'; @Injectable() export class SubSpaceModelService { @@ -21,6 +23,7 @@ export class SubSpaceModelService { private readonly subspaceModelRepository: SubspaceModelRepository, private readonly tagService: TagService, private readonly productAllocationService: SubspaceModelProductAllocationService, + private readonly subspaceService: SubSpaceService, ) {} async createModels( @@ -74,6 +77,7 @@ export class SubSpaceModelService { dtos: ModifySubspaceModelDto[], spaceModel: SpaceModelEntity, queryRunner: QueryRunner, + spaces?: SpaceEntity[], ): Promise { if (!dtos.length) return []; @@ -89,6 +93,14 @@ export class SubSpaceModelService { const savedSubspaces = await queryRunner.manager.save(subspaceEntities); + if (spaces) { + await this.subspaceService.createSubSpaceFromModel( + savedSubspaces, + spaces, + queryRunner, + ); + } + return savedSubspaces.map((subspace, index) => ({ subspaceModel: subspace, action: ModifyAction.ADD, @@ -102,6 +114,7 @@ export class SubSpaceModelService { queryRunner: QueryRunner, projectUuid: string, spaceTagUpdateDtos?: ModifyTagModelDto[], + spaces?: SpaceEntity[], ) { try { if (!dtos || dtos.length === 0) { @@ -125,6 +138,7 @@ export class SubSpaceModelService { addDtos, spaceModel, queryRunner, + spaces, ); const combineModels = [...addedModels, ...updatedModels]; diff --git a/src/space-model/space-model.module.ts b/src/space-model/space-model.module.ts index c9711f2..d49f590 100644 --- a/src/space-model/space-model.module.ts +++ b/src/space-model/space-model.module.ts @@ -46,6 +46,16 @@ import { SpaceModelProductAllocationService } from './services/space-model-produ import { SubspaceModelProductAllocationService } from './services/subspace/subspace-model-product-allocation.service'; import { SpaceProductAllocationService } from 'src/space/services/space-product-allocation.service'; import { SubspaceProductAllocationService } from 'src/space/services/subspace/subspace-product-allocation.service'; +import { DeviceService } from 'src/device/services'; +import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; +import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service'; +import { SceneService } from 'src/scene/services'; +import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories'; +import { + SceneIconRepository, + SceneRepository, +} from '@app/common/modules/scene/repositories'; +import { AutomationRepository } from '@app/common/modules/automation/repositories'; const CommandHandlers = [ PropogateUpdateSpaceModelHandler, @@ -80,6 +90,14 @@ const CommandHandlers = [ SpaceLinkRepository, InviteSpaceRepository, NewTagService, + DeviceService, + DeviceStatusFirebaseService, + DeviceStatusLogRepository, + SceneService, + SceneIconRepository, + SceneDeviceRepository, + SceneRepository, + AutomationRepository, SpaceModelProductAllocationRepoitory, SubspaceModelProductAllocationRepoitory, NewTagRepository, @@ -90,6 +108,7 @@ const CommandHandlers = [ SubspaceProductAllocationService, SpaceProductAllocationRepository, SubspaceProductAllocationRepository, + SubSpaceService, ], exports: [CqrsModule, SpaceModelService], }) diff --git a/src/space/services/space-product-allocation.service.ts b/src/space/services/space-product-allocation.service.ts index 8b8acb1..a737f3b 100644 --- a/src/space/services/space-product-allocation.service.ts +++ b/src/space/services/space-product-allocation.service.ts @@ -13,12 +13,16 @@ import { ModifySubspaceDto } from '../dtos'; import { ProcessTagDto } from 'src/tags/dtos'; import { TagService as NewTagService } from 'src/tags/services'; import { SpaceModelProductAllocationEntity } from '@app/common/modules/space-model'; +import { DeviceEntity } from '@app/common/modules/device/entities'; +import { ProjectEntity } from '@app/common/modules/project/entities'; +import { ValidationService } from './space-validation.service'; @Injectable() export class SpaceProductAllocationService { constructor( private readonly tagService: NewTagService, private readonly spaceProductAllocationRepository: SpaceProductAllocationRepository, + private readonly spaceService: ValidationService, ) {} async createSpaceProductAllocations( @@ -220,6 +224,114 @@ export class SpaceProductAllocationService { } } + async propagateDeleteToInheritedAllocations( + queryRunner: QueryRunner, + allocationsToUpdate: SpaceModelProductAllocationEntity[], + tagUuidsToDelete: string[], + project: ProjectEntity, + spaces?: SpaceEntity[], + ): Promise { + try { + const inheritedAllocationUpdates: SpaceProductAllocationEntity[] = []; + const inheritedAllocationsToDelete: SpaceProductAllocationEntity[] = []; + + for (const allocation of allocationsToUpdate) { + for (const inheritedAllocation of allocation.inheritedSpaceAllocations) { + const updatedInheritedTags = inheritedAllocation.tags.filter( + (tag) => !tagUuidsToDelete.includes(tag.uuid), + ); + + if (updatedInheritedTags.length === inheritedAllocation.tags.length) { + continue; + } + + if (updatedInheritedTags.length === 0) { + inheritedAllocationsToDelete.push(inheritedAllocation); + } else { + inheritedAllocation.tags = updatedInheritedTags; + inheritedAllocationUpdates.push(inheritedAllocation); + } + } + } + + if (inheritedAllocationUpdates.length > 0) { + await queryRunner.manager.save( + SpaceProductAllocationEntity, + inheritedAllocationUpdates, + ); + } + + if (inheritedAllocationsToDelete.length > 0) { + await queryRunner.manager.remove( + SpaceProductAllocationEntity, + inheritedAllocationsToDelete, + ); + } + + if (spaces && spaces.length > 0) { + await this.moveDevicesToOrphanSpace( + queryRunner, + spaces, + tagUuidsToDelete, + project, + ); + } + + await queryRunner.manager + .createQueryBuilder() + .delete() + .from('space_product_tags') + .where( + 'space_product_allocation_uuid NOT IN (' + + queryRunner.manager + .createQueryBuilder() + .select('allocation.uuid') + .from(SpaceProductAllocationEntity, 'allocation') + .getQuery() + + ')', + ) + .execute(); + } catch (error) { + throw this.handleError( + error, + `Failed to propagate tag deletion to inherited allocations`, + ); + } + } + + async moveDevicesToOrphanSpace( + queryRunner: QueryRunner, + spaces: SpaceEntity[], + tagUuidsToDelete: string[], + project: ProjectEntity, + ): Promise { + try { + const orphanSpace = await this.spaceService.getOrphanSpace(project); + + const devicesToMove = await queryRunner.manager + .createQueryBuilder(DeviceEntity, 'device') + .leftJoinAndSelect('device.tag', 'tag') + .where('device.spaceDevice IN (:...spaceUuids)', { + spaceUuids: spaces.map((space) => space.uuid), + }) + .andWhere('tag.uuid IN (:...tagUuidsToDelete)', { tagUuidsToDelete }) + .getMany(); + + if (devicesToMove.length === 0) return; + + await queryRunner.manager + .createQueryBuilder() + .update(DeviceEntity) + .set({ spaceDevice: orphanSpace }) + .where('uuid IN (:...deviceUuids)', { + deviceUuids: devicesToMove.map((device) => device.uuid), + }) + .execute(); + } catch (error) { + throw this.handleError(error, `Failed to move devices to orphan space`); + } + } + private async processDeleteActions( dtos: ModifyTagDto[], queryRunner: QueryRunner, diff --git a/src/space/services/space-validation.service.ts b/src/space/services/space-validation.service.ts index be173b0..08828b4 100644 --- a/src/space/services/space-validation.service.ts +++ b/src/space/services/space-validation.service.ts @@ -19,6 +19,11 @@ import { ValidateSpacesDto } from '../dtos/validation.space.dto'; import { ProjectParam } from '../dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { In } from 'typeorm'; +import { + ORPHAN_COMMUNITY_NAME, + ORPHAN_SPACE_NAME, +} from '@app/common/constants/orphan-constant'; +import { ProjectEntity } from '@app/common/modules/project/entities'; @Injectable() export class ValidationService { @@ -286,4 +291,24 @@ export class ValidationService { ); } } + + async getOrphanSpace(project: ProjectEntity): Promise { + 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, + ); + } + + return orphanSpace; + } } diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 844c42f..7a8aa50 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -38,6 +38,9 @@ import { ProcessTagDto } from 'src/tags/dtos'; import { SpaceProductAllocationService } from './space-product-allocation.service'; import { SubspaceProductAllocationService } from './subspace/subspace-product-allocation.service'; import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity'; +import { ProjectEntity } from '@app/common/modules/project/entities'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { DeviceService } from 'src/device/services'; @Injectable() export class SpaceService { constructor( @@ -53,6 +56,8 @@ export class SpaceService { private commandBus: CommandBus, private readonly spaceProductAllocationService: SpaceProductAllocationService, private readonly subspaceProductAllocationService: SubspaceProductAllocationService, + private readonly deviceRepository: DeviceRepository, + private readonly deviceSevice: DeviceService, ) {} async createSpace( @@ -186,40 +191,6 @@ export class SpaceService { } } } - async createFromModel( - spaceModelUuid: string, - queryRunner: QueryRunner, - space: SpaceEntity, - ) { - try { - const spaceModel = - await this.spaceModelService.validateSpaceModel(spaceModelUuid); - - space.spaceModel = spaceModel; - await queryRunner.manager.save(SpaceEntity, space); - - await this.subSpaceService.createSubSpaceFromModel( - spaceModel.subspaceModels, - space, - queryRunner, - ); - - await this.tagService.createTagsFromModel( - queryRunner, - spaceModel.tags, - space, - null, - ); - } catch (error) { - if (error instanceof HttpException) { - throw error; - } - throw new HttpException( - 'An error occurred while creating the space from space model', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } async getSpacesHierarchyForCommunity( params: CommunitySpaceParam, diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index 500ea69..023dafb 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -70,29 +70,23 @@ export class SubSpaceService { async createSubSpaceFromModel( subspaceModels: SubspaceModelEntity[], - space: SpaceEntity, + spaces: SpaceEntity[], queryRunner: QueryRunner, ): Promise { - if (!subspaceModels?.length) return; + if (!subspaceModels?.length || !spaces?.length) return; - const subspaceData = subspaceModels.map((subSpaceModel) => ({ - subspaceName: subSpaceModel.subspaceName, - space, - subSpaceModel, - })); + const subspaceData = []; - const subspaces = await this.createSubspaces(subspaceData, queryRunner); - - await Promise.all( - subspaceModels.map((model, index) => - this.tagService.createTagsFromModel( - queryRunner, - model.tags || [], - null, - subspaces[index], - ), - ), - ); + for (const space of spaces) { + for (const subSpaceModel of subspaceModels) { + subspaceData.push({ + subspaceName: subSpaceModel.subspaceName, + space, + subSpaceModel, + }); + } + } + await this.createSubspaces(subspaceData, queryRunner); } async createSubspacesFromDto( diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 17cf774..3808a0e 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -118,6 +118,7 @@ export const CommandHandlers = [DisableSpaceHandler]; UserRepository, SpaceUserService, SpaceSceneService, + DeviceService, SceneService, SceneIconRepository, SceneRepository,