diff --git a/libs/common/src/modules/scene-device/entities/scene-device.entity.ts b/libs/common/src/modules/scene-device/entities/scene-device.entity.ts index 9814fdf..a5f1fb5 100644 --- a/libs/common/src/modules/scene-device/entities/scene-device.entity.ts +++ b/libs/common/src/modules/scene-device/entities/scene-device.entity.ts @@ -44,6 +44,12 @@ export class SceneDeviceEntity extends AbstractEntity { @JoinColumn({ name: 'scene_uuid' }) scene: SceneEntity; + @Column({ + nullable: false, + default: false, + }) + public disabled: boolean; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/scene/entities/scene.entity.ts b/libs/common/src/modules/scene/entities/scene.entity.ts index 5daa690..86b1beb 100644 --- a/libs/common/src/modules/scene/entities/scene.entity.ts +++ b/libs/common/src/modules/scene/entities/scene.entity.ts @@ -59,6 +59,12 @@ export class SceneEntity extends AbstractEntity { @JoinColumn({ name: 'space_uuid' }) space: SpaceEntity; + @Column({ + nullable: false, + default: false, + }) + public disabled: boolean; + @ManyToOne(() => SceneIconEntity, (icon) => icon.scenesIconEntity, { nullable: false, }) diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index add62c9..4846cd3 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -41,7 +41,7 @@ import { import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { DeviceRepository } from '@app/common/modules/device/repositories'; import { PermissionType } from '@app/common/constants/permission-type.enum'; -import { In } from 'typeorm'; +import { In, QueryRunner } from 'typeorm'; import { ProductType } from '@app/common/constants/product-type.enum'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service'; @@ -59,6 +59,7 @@ import { DeviceSceneParamDto } from '../dtos/device.param.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { DeleteSceneFromSceneDeviceDto } from '../dtos/delete.device.dto'; +import { DeviceEntity } from '@app/common/modules/device/entities'; @Injectable() export class DeviceService { @@ -83,6 +84,7 @@ export class DeviceService { secretKey, }); } + async getDeviceByDeviceUuid( deviceUuid: string, withProductDevice: boolean = true, @@ -98,6 +100,29 @@ export class DeviceService { relations, }); } + async deleteDevice( + devices: DeviceEntity[], + orphanSpace: SpaceEntity, + queryRunner: QueryRunner, + ): Promise { + try { + const deviceIds = devices.map((device) => device.uuid); + + if (deviceIds.length > 0) { + await queryRunner.manager + .createQueryBuilder() + .update(DeviceEntity) + .set({ spaceDevice: orphanSpace }) + .whereInIds(deviceIds) + .execute(); + } + } catch (error) { + throw new HttpException( + `Failed to update devices to orphan space: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } async getDeviceByDeviceTuyaUuid(deviceTuyaUuid: string) { return await this.deviceRepository.findOne({ diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index 691fbe1..498d157 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -495,12 +495,16 @@ export class SceneService { const space = await this.getSpaceByUuid(scene.space.uuid); await this.delete(scene.sceneTuyaUuid, space.spaceTuyaUuid); - await this.sceneDeviceRepository.delete({ - scene: { uuid: sceneUuid }, - }); - await this.sceneRepository.delete({ - uuid: sceneUuid, - }); + await this.sceneDeviceRepository.update( + { uuid: sceneUuid }, + { disabled: true }, + ); + await this.sceneRepository.update( + { + uuid: sceneUuid, + }, + { disabled: true }, + ); return new SuccessResponseDto({ message: `Scene with ID ${sceneUuid} deleted successfully`, }); diff --git a/src/space/controllers/space.controller.ts b/src/space/controllers/space.controller.ts index 18d556d..9ada727 100644 --- a/src/space/controllers/space.controller.ts +++ b/src/space/controllers/space.controller.ts @@ -67,8 +67,8 @@ export class SpaceController { description: ControllerRoute.SPACE.ACTIONS.DELETE_SPACE_DESCRIPTION, }) @Delete('/:spaceUuid') - async deleteSpace(@Param() params: GetSpaceParam): Promise { - return this.spaceService.delete(params); + async deleteSpace(@Param() params: GetSpaceParam){ + return await this.spaceService.deleteSpace(params); } @ApiBearerAuth() diff --git a/src/space/services/space-link/space-link.service.ts b/src/space/services/space-link/space-link.service.ts index 55c40ab..8ad797e 100644 --- a/src/space/services/space-link/space-link.service.ts +++ b/src/space/services/space-link/space-link.service.ts @@ -92,4 +92,35 @@ export class SpaceLinkService { ); } } + async deleteSpaceLink( + space: SpaceEntity, + queryRunner: QueryRunner, + ): Promise { + try { + const spaceLinks = await queryRunner.manager.find(SpaceLinkEntity, { + where: [ + { startSpace: space, disabled: false }, + { endSpace: space, disabled: false }, + ], + }); + + if (spaceLinks.length === 0) { + return; + } + + const linkIds = spaceLinks.map((link) => link.uuid); + + await queryRunner.manager + .createQueryBuilder() + .update(SpaceLinkEntity) + .set({ disabled: true }) + .whereInIds(linkIds) + .execute(); + } catch (error) { + throw new HttpException( + `Failed to disable space links for the given space: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } diff --git a/src/space/services/space-scene.service.ts b/src/space/services/space-scene.service.ts index 4e77158..6c6b528 100644 --- a/src/space/services/space-scene.service.ts +++ b/src/space/services/space-scene.service.ts @@ -5,6 +5,9 @@ import { SceneService } from '../../scene/services'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { GetSceneDto } from '../../scene/dtos'; import { ValidationService } from './space-validation.service'; +import { SpaceEntity } from '@app/common/modules/space'; +import { QueryRunner } from 'typeorm'; +import { SceneEntity } from '@app/common/modules/scene/entities'; @Injectable() export class SpaceSceneService { @@ -48,4 +51,32 @@ export class SpaceSceneService { } } } + + async deleteScenes( + space: SpaceEntity, + queryRunner: QueryRunner, + ): Promise { + try { + const scenes = await queryRunner.manager.find(SceneEntity, { + where: { space }, + }); + + if (scenes.length === 0) { + return; + } + + const sceneUuids = scenes.map((scene) => scene.uuid); + + await Promise.all( + sceneUuids.map((uuid) => + this.sceneSevice.deleteScene({ sceneUuid: uuid }), + ), + ); + } catch (error) { + throw new HttpException( + `Failed to delete scenes: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } diff --git a/src/space/services/space-validation.service.ts b/src/space/services/space-validation.service.ts index e8bfd32..0fc7673 100644 --- a/src/space/services/space-validation.service.ts +++ b/src/space/services/space-validation.service.ts @@ -1,4 +1,3 @@ -import { CommunityEntity } from '@app/common/modules/community/entities'; import { SpaceEntity } from '@app/common/modules/space/entities'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; @@ -21,16 +20,17 @@ export class ValidationService { async validateCommunityAndProject( communityUuid: string, projectUuid: string, - ): Promise { - await this.projectService.findOne(projectUuid); + ) { + const project = await this.projectService.findOne(projectUuid); const community = await this.communityService.getCommunityById({ communityUuid, projectUuid, }); - return community.data; + return { community: community.data, project: project }; } + async validateSpaceWithinCommunityAndProject( communityUuid: string, projectUuid: string, diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index db41d6b..685ae99 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -24,6 +24,9 @@ import { ValidationService } from './space-validation.service'; import { ORPHAN_SPACE_NAME } from '@app/common/constants/orphan-constant'; import { TagService } from './tag'; import { SpaceModelService } from 'src/space-model/services'; +import { UserSpaceService } from 'src/users/services'; +import { DeviceService } from 'src/device/services'; +import { SpaceSceneService } from './space-scene.service'; @Injectable() export class SpaceService { @@ -35,6 +38,9 @@ export class SpaceService { private readonly validationService: ValidationService, private readonly tagService: TagService, private readonly spaceModelService: SpaceModelService, + private readonly userService: UserSpaceService, + private readonly deviceService: DeviceService, + private readonly sceneService: SpaceSceneService, ) {} async createSpace( @@ -363,33 +369,70 @@ export class SpaceService { } async deleteSpace(params: GetSpaceParam) { + const queryRunner = this.dataSource.createQueryRunner(); + const { spaceUuid } = params; + try { - const { communityUuid, spaceUuid, projectUuid } = params; - const space = - await this.validationService.validateSpaceWithinCommunityAndProject( - communityUuid, - projectUuid, - spaceUuid, - ); + await queryRunner.connect(); + await queryRunner.startTransaction(); + const spaces = await this.spaceRepository.find({ where: { parent: { uuid: spaceUuid }, disabled: false }, - relations: ['parent', 'children'], // Include parent and children relations + relations: [ + 'parent', + 'children', + 'subspaces', + 'tags', + 'subspaces.tags', + 'devices', + ], // Include parent and children relations }); - console.log(spaces); - // space.disabled = true; - await this.spaceRepository.update({ uuid: space.uuid }, space); + //this.disableSpace(space, orphanSpace); + return spaces; } catch (error) { if (error instanceof HttpException) { throw error; } throw new HttpException( - 'An error occurred while deleting the space', + `An error occurred while deleting the space ${error}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } } + async disableSpace(space: SpaceEntity, orphanSpace: SpaceEntity) { + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); + try { + const deleteSubspaceDtos = space.subspaces?.map((subspace) => ({ + subspaceUuid: subspace.uuid, + })); + const deleteSpaceTagsDtos = space.tags?.map((tag) => tag.uuid); + + await this.userService.deleteUserSpace(space.uuid); + await this.subSpaceService.deleteSubspaces( + deleteSubspaceDtos, + queryRunner, + ); + + await this.tagService.deleteTags(deleteSpaceTagsDtos, queryRunner); + + await this.deviceService.deleteDevice( + space.devices, + orphanSpace, + queryRunner, + ); + await this.spaceLinkService.deleteSpaceLink(space, queryRunner); + await this.sceneService.deleteScenes(space, queryRunner); + } catch (error) { + await queryRunner.rollbackTransaction(); + } finally { + await queryRunner.release(); + } + } + private updateSpaceProperties( space: SpaceEntity, updateSpaceDto: UpdateSpaceDto, diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index 01c3bd5..e826071 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -19,7 +19,6 @@ import { In, QueryRunner } from 'typeorm'; import { SpaceEntity, SubspaceEntity, - TagEntity, } from '@app/common/modules/space/entities'; import { SubspaceModelEntity } from '@app/common/modules/space-model'; import { ValidationService } from '../space-validation.service'; diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 7f707e8..c3b29d3 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -28,7 +28,10 @@ import { UserRepository, UserSpaceRepository, } from '@app/common/modules/user/repositories'; -import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { + DeviceRepository, + DeviceUserPermissionRepository, +} from '@app/common/modules/device/repositories'; import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; import { ProductRepository } from '@app/common/modules/product/repositories'; import { SceneService } from '../scene/services'; @@ -55,6 +58,9 @@ import { SubSpaceModelService, TagModelService, } from 'src/space-model/services'; +import { UserSpaceService } from 'src/users/services'; +import { UserDevicePermissionService } from 'src/user-device-permission/services'; +import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, CommunityModule], @@ -100,6 +106,10 @@ import { ProjectRepository, SpaceModelRepository, SubspaceModelRepository, + UserSpaceService, + UserDevicePermissionService, + DeviceUserPermissionRepository, + PermissionTypeRepository, ], exports: [SpaceService], }) diff --git a/src/users/services/user-space.service.ts b/src/users/services/user-space.service.ts index 1c7ac6e..0ee9af5 100644 --- a/src/users/services/user-space.service.ts +++ b/src/users/services/user-space.service.ts @@ -150,4 +150,20 @@ export class UserSpaceService { await Promise.all(permissionPromises); } + + async deleteUserSpace(spaceUuid: string) { + try { + await this.userSpaceRepository + .createQueryBuilder() + .delete() + .where('spaceUuid = :spaceUuid', { spaceUuid }) + .execute(); + } catch (error) { + console.error(`Error deleting user-space associations: ${error.message}`); + throw new HttpException( + `Failed to delete user-space associations: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } }