fixing propagation

This commit is contained in:
hannathkadher
2025-03-07 23:01:38 +04:00
parent 226781e53f
commit 1220ee395d
12 changed files with 236 additions and 68 deletions

View File

@ -6,7 +6,6 @@ import {
Unique, Unique,
Index, Index,
JoinColumn, JoinColumn,
OneToOne,
JoinTable, JoinTable,
} from 'typeorm'; } from 'typeorm';
import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { AbstractEntity } from '../../abstract/entities/abstract.entity';
@ -18,7 +17,6 @@ import { PermissionTypeEntity } from '../../permission/entities';
import { SceneDeviceEntity } from '../../scene-device/entities'; import { SceneDeviceEntity } from '../../scene-device/entities';
import { SpaceEntity } from '../../space/entities/space.entity'; import { SpaceEntity } from '../../space/entities/space.entity';
import { SubspaceEntity } from '../../space/entities/subspace/subspace.entity'; import { SubspaceEntity } from '../../space/entities/subspace/subspace.entity';
import { TagEntity } from '../../space/entities/tag.entity';
import { NewTagEntity } from '../../tag'; import { NewTagEntity } from '../../tag';
@Entity({ name: 'device' }) @Entity({ name: 'device' })
@ -79,14 +77,10 @@ export class DeviceEntity extends AbstractEntity<DeviceDto> {
@OneToMany(() => SceneDeviceEntity, (sceneDevice) => sceneDevice.device, {}) @OneToMany(() => SceneDeviceEntity, (sceneDevice) => sceneDevice.device, {})
sceneDevices: SceneDeviceEntity[]; sceneDevices: SceneDeviceEntity[];
@OneToOne(() => TagEntity, (tag) => tag.device, {
nullable: true,
})
tag: TagEntity;
@OneToMany(() => NewTagEntity, (tag) => tag.devices) @OneToMany(() => NewTagEntity, (tag) => tag.devices)
@JoinTable({ name: 'device_tags' }) @JoinTable({ name: 'device_tags' })
public tags: NewTagEntity[]; public tag: NewTagEntity[];
constructor(partial: Partial<DeviceEntity>) { constructor(partial: Partial<DeviceEntity>) {
super(); super();

View File

@ -1546,4 +1546,21 @@ export class DeviceService {
); );
} }
} }
async moveDevicesToSpace(
targetSpace: SpaceEntity,
deviceIds: string[],
): Promise<void> {
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 },
);
}
} }

View File

@ -104,7 +104,7 @@ export class PropogateUpdateSpaceModelHandler
for (const space of spaces) { for (const space of spaces) {
await this.subSpaceService.createSubSpaceFromModel( await this.subSpaceService.createSubSpaceFromModel(
subspaceModels, subspaceModels,
space, [space],
queryRunner, queryRunner,
); );
} }

View File

@ -16,6 +16,7 @@ import { ProductEntity } from '@app/common/modules/product/entities';
import { SpaceRepository } from '@app/common/modules/space'; import { SpaceRepository } from '@app/common/modules/space';
import { SpaceProductAllocationService } from 'src/space/services/space-product-allocation.service'; import { SpaceProductAllocationService } from 'src/space/services/space-product-allocation.service';
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity'; import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
import { ProjectEntity } from '@app/common/modules/project/entities';
@Injectable() @Injectable()
export class SpaceModelProductAllocationService { export class SpaceModelProductAllocationService {
@ -141,7 +142,7 @@ export class SpaceModelProductAllocationService {
async updateProductAllocations( async updateProductAllocations(
dtos: ModifyTagModelDto[], dtos: ModifyTagModelDto[],
projectUuid: string, project: ProjectEntity,
spaceModel: SpaceModelEntity, spaceModel: SpaceModelEntity,
queryRunner: QueryRunner, queryRunner: QueryRunner,
modifySubspaceModels?: ModifySubspaceModelDto[], modifySubspaceModels?: ModifySubspaceModelDto[],
@ -161,7 +162,7 @@ export class SpaceModelProductAllocationService {
const processedTags = await this.tagService.processTags( const processedTags = await this.tagService.processTags(
addTagDtos, addTagDtos,
projectUuid, project.uuid,
queryRunner, queryRunner,
); );
const addTagUuidMap = new Map<string, ModifyTagModelDto>(); const addTagUuidMap = new Map<string, ModifyTagModelDto>();
@ -192,13 +193,19 @@ export class SpaceModelProductAllocationService {
await Promise.all([ await Promise.all([
this.processAddActions( this.processAddActions(
filteredDtos, filteredDtos,
projectUuid, project.uuid,
spaceModel, spaceModel,
queryRunner, queryRunner,
modifySubspaceModels, modifySubspaceModels,
spaces, spaces,
), ),
this.processDeleteActions(filteredDtos, queryRunner, spaceModel), this.processDeleteActions(
filteredDtos,
queryRunner,
spaceModel,
project,
spaces,
),
]); ]);
} catch (error) { } catch (error) {
throw this.handleError(error, 'Error while updating product allocations'); throw this.handleError(error, 'Error while updating product allocations');
@ -304,6 +311,8 @@ export class SpaceModelProductAllocationService {
dtos: ModifyTagModelDto[], dtos: ModifyTagModelDto[],
queryRunner: QueryRunner, queryRunner: QueryRunner,
spaceModel: SpaceModelEntity, spaceModel: SpaceModelEntity,
project: ProjectEntity,
spaces?: SpaceEntity[],
): Promise<SpaceModelProductAllocationEntity[]> { ): Promise<SpaceModelProductAllocationEntity[]> {
try { try {
if (!dtos || dtos.length === 0) { if (!dtos || dtos.length === 0) {
@ -325,7 +334,11 @@ export class SpaceModelProductAllocationService {
uuid: spaceModel.uuid, 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 await queryRunner.manager
.createQueryBuilder() .createQueryBuilder()
.delete() .delete()

View File

@ -189,7 +189,7 @@ export class SpaceModelService {
async update(dto: UpdateSpaceModelDto, param: SpaceModelParam) { async update(dto: UpdateSpaceModelDto, param: SpaceModelParam) {
const queryRunner = this.dataSource.createQueryRunner(); const queryRunner = this.dataSource.createQueryRunner();
await this.validateProject(param.projectUuid); const project = await this.validateProject(param.projectUuid);
const spaceModel = await this.validateSpaceModel( const spaceModel = await this.validateSpaceModel(
param.spaceModelUuid, param.spaceModelUuid,
param.projectUuid, param.projectUuid,
@ -227,7 +227,7 @@ export class SpaceModelService {
if (dto.tags) { if (dto.tags) {
await this.spaceModelProductAllocationService.updateProductAllocations( await this.spaceModelProductAllocationService.updateProductAllocations(
dto.tags, dto.tags,
param.projectUuid, project,
spaceModel, spaceModel,
queryRunner, queryRunner,
dto.subspaceModels, dto.subspaceModels,

View File

@ -14,6 +14,8 @@ import { ProcessTagDto } from 'src/tags/dtos';
import { TagService } from 'src/tags/services'; import { TagService } from 'src/tags/services';
import { SubspaceModelProductAllocationService } from './subspace-model-product-allocation.service'; import { SubspaceModelProductAllocationService } from './subspace-model-product-allocation.service';
import { ISingleSubspaceModel } from 'src/space-model/interfaces'; 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() @Injectable()
export class SubSpaceModelService { export class SubSpaceModelService {
@ -21,6 +23,7 @@ export class SubSpaceModelService {
private readonly subspaceModelRepository: SubspaceModelRepository, private readonly subspaceModelRepository: SubspaceModelRepository,
private readonly tagService: TagService, private readonly tagService: TagService,
private readonly productAllocationService: SubspaceModelProductAllocationService, private readonly productAllocationService: SubspaceModelProductAllocationService,
private readonly subspaceService: SubSpaceService,
) {} ) {}
async createModels( async createModels(
@ -74,6 +77,7 @@ export class SubSpaceModelService {
dtos: ModifySubspaceModelDto[], dtos: ModifySubspaceModelDto[],
spaceModel: SpaceModelEntity, spaceModel: SpaceModelEntity,
queryRunner: QueryRunner, queryRunner: QueryRunner,
spaces?: SpaceEntity[],
): Promise<ISingleSubspaceModel[]> { ): Promise<ISingleSubspaceModel[]> {
if (!dtos.length) return []; if (!dtos.length) return [];
@ -89,6 +93,14 @@ export class SubSpaceModelService {
const savedSubspaces = await queryRunner.manager.save(subspaceEntities); const savedSubspaces = await queryRunner.manager.save(subspaceEntities);
if (spaces) {
await this.subspaceService.createSubSpaceFromModel(
savedSubspaces,
spaces,
queryRunner,
);
}
return savedSubspaces.map((subspace, index) => ({ return savedSubspaces.map((subspace, index) => ({
subspaceModel: subspace, subspaceModel: subspace,
action: ModifyAction.ADD, action: ModifyAction.ADD,
@ -102,6 +114,7 @@ export class SubSpaceModelService {
queryRunner: QueryRunner, queryRunner: QueryRunner,
projectUuid: string, projectUuid: string,
spaceTagUpdateDtos?: ModifyTagModelDto[], spaceTagUpdateDtos?: ModifyTagModelDto[],
spaces?: SpaceEntity[],
) { ) {
try { try {
if (!dtos || dtos.length === 0) { if (!dtos || dtos.length === 0) {
@ -125,6 +138,7 @@ export class SubSpaceModelService {
addDtos, addDtos,
spaceModel, spaceModel,
queryRunner, queryRunner,
spaces,
); );
const combineModels = [...addedModels, ...updatedModels]; const combineModels = [...addedModels, ...updatedModels];

View File

@ -46,6 +46,16 @@ import { SpaceModelProductAllocationService } from './services/space-model-produ
import { SubspaceModelProductAllocationService } from './services/subspace/subspace-model-product-allocation.service'; import { SubspaceModelProductAllocationService } from './services/subspace/subspace-model-product-allocation.service';
import { SpaceProductAllocationService } from 'src/space/services/space-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 { 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 = [ const CommandHandlers = [
PropogateUpdateSpaceModelHandler, PropogateUpdateSpaceModelHandler,
@ -80,6 +90,14 @@ const CommandHandlers = [
SpaceLinkRepository, SpaceLinkRepository,
InviteSpaceRepository, InviteSpaceRepository,
NewTagService, NewTagService,
DeviceService,
DeviceStatusFirebaseService,
DeviceStatusLogRepository,
SceneService,
SceneIconRepository,
SceneDeviceRepository,
SceneRepository,
AutomationRepository,
SpaceModelProductAllocationRepoitory, SpaceModelProductAllocationRepoitory,
SubspaceModelProductAllocationRepoitory, SubspaceModelProductAllocationRepoitory,
NewTagRepository, NewTagRepository,
@ -90,6 +108,7 @@ const CommandHandlers = [
SubspaceProductAllocationService, SubspaceProductAllocationService,
SpaceProductAllocationRepository, SpaceProductAllocationRepository,
SubspaceProductAllocationRepository, SubspaceProductAllocationRepository,
SubSpaceService,
], ],
exports: [CqrsModule, SpaceModelService], exports: [CqrsModule, SpaceModelService],
}) })

View File

@ -13,12 +13,16 @@ import { ModifySubspaceDto } from '../dtos';
import { ProcessTagDto } from 'src/tags/dtos'; import { ProcessTagDto } from 'src/tags/dtos';
import { TagService as NewTagService } from 'src/tags/services'; import { TagService as NewTagService } from 'src/tags/services';
import { SpaceModelProductAllocationEntity } from '@app/common/modules/space-model'; 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() @Injectable()
export class SpaceProductAllocationService { export class SpaceProductAllocationService {
constructor( constructor(
private readonly tagService: NewTagService, private readonly tagService: NewTagService,
private readonly spaceProductAllocationRepository: SpaceProductAllocationRepository, private readonly spaceProductAllocationRepository: SpaceProductAllocationRepository,
private readonly spaceService: ValidationService,
) {} ) {}
async createSpaceProductAllocations( async createSpaceProductAllocations(
@ -220,6 +224,114 @@ export class SpaceProductAllocationService {
} }
} }
async propagateDeleteToInheritedAllocations(
queryRunner: QueryRunner,
allocationsToUpdate: SpaceModelProductAllocationEntity[],
tagUuidsToDelete: string[],
project: ProjectEntity,
spaces?: SpaceEntity[],
): Promise<void> {
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<void> {
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( private async processDeleteActions(
dtos: ModifyTagDto[], dtos: ModifyTagDto[],
queryRunner: QueryRunner, queryRunner: QueryRunner,

View File

@ -19,6 +19,11 @@ import { ValidateSpacesDto } from '../dtos/validation.space.dto';
import { ProjectParam } from '../dtos'; import { ProjectParam } from '../dtos';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import { In } from 'typeorm'; 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() @Injectable()
export class ValidationService { export class ValidationService {
@ -286,4 +291,24 @@ export class ValidationService {
); );
} }
} }
async getOrphanSpace(project: ProjectEntity): Promise<SpaceEntity> {
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;
}
} }

View File

@ -38,6 +38,9 @@ import { ProcessTagDto } from 'src/tags/dtos';
import { SpaceProductAllocationService } from './space-product-allocation.service'; import { SpaceProductAllocationService } from './space-product-allocation.service';
import { SubspaceProductAllocationService } from './subspace/subspace-product-allocation.service'; import { SubspaceProductAllocationService } from './subspace/subspace-product-allocation.service';
import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity'; 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() @Injectable()
export class SpaceService { export class SpaceService {
constructor( constructor(
@ -53,6 +56,8 @@ export class SpaceService {
private commandBus: CommandBus, private commandBus: CommandBus,
private readonly spaceProductAllocationService: SpaceProductAllocationService, private readonly spaceProductAllocationService: SpaceProductAllocationService,
private readonly subspaceProductAllocationService: SubspaceProductAllocationService, private readonly subspaceProductAllocationService: SubspaceProductAllocationService,
private readonly deviceRepository: DeviceRepository,
private readonly deviceSevice: DeviceService,
) {} ) {}
async createSpace( 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( async getSpacesHierarchyForCommunity(
params: CommunitySpaceParam, params: CommunitySpaceParam,

View File

@ -70,29 +70,23 @@ export class SubSpaceService {
async createSubSpaceFromModel( async createSubSpaceFromModel(
subspaceModels: SubspaceModelEntity[], subspaceModels: SubspaceModelEntity[],
space: SpaceEntity, spaces: SpaceEntity[],
queryRunner: QueryRunner, queryRunner: QueryRunner,
): Promise<void> { ): Promise<void> {
if (!subspaceModels?.length) return; if (!subspaceModels?.length || !spaces?.length) return;
const subspaceData = subspaceModels.map((subSpaceModel) => ({ const subspaceData = [];
for (const space of spaces) {
for (const subSpaceModel of subspaceModels) {
subspaceData.push({
subspaceName: subSpaceModel.subspaceName, subspaceName: subSpaceModel.subspaceName,
space, space,
subSpaceModel, subSpaceModel,
})); });
}
const subspaces = await this.createSubspaces(subspaceData, queryRunner); }
await this.createSubspaces(subspaceData, queryRunner);
await Promise.all(
subspaceModels.map((model, index) =>
this.tagService.createTagsFromModel(
queryRunner,
model.tags || [],
null,
subspaces[index],
),
),
);
} }
async createSubspacesFromDto( async createSubspacesFromDto(

View File

@ -118,6 +118,7 @@ export const CommandHandlers = [DisableSpaceHandler];
UserRepository, UserRepository,
SpaceUserService, SpaceUserService,
SpaceSceneService, SpaceSceneService,
DeviceService,
SceneService, SceneService,
SceneIconRepository, SceneIconRepository,
SceneRepository, SceneRepository,