mirror of
https://github.com/SyncrowIOT/backend.git
synced 2026-10-24 16:27:54 +00:00
Merge pull request #477 from SyncrowIOT/task/implement-duplicate-space-apo
SP-1822: implement duplicate space API
This commit is contained in:
@@ -17,6 +17,7 @@ import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { Permissions } from 'src/decorators/permissions.decorator';
|
||||
import { PermissionsGuard } from 'src/guards/permissions.guard';
|
||||
import { AddSpaceDto, CommunitySpaceParam, UpdateSpaceDto } from '../dtos';
|
||||
import { DuplicateSpaceDto } from '../dtos/duplicate-space.dto';
|
||||
import { GetSpaceDto } from '../dtos/get.space.dto';
|
||||
import { GetSpaceParam } from '../dtos/get.space.param';
|
||||
import { OrderSpacesDto } from '../dtos/order.spaces.dto';
|
||||
@@ -48,6 +49,26 @@ export class SpaceController {
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(PermissionsGuard)
|
||||
@Permissions('SPACE_ADD')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.SPACE.ACTIONS.DUPLICATE_SPACE_SUMMARY,
|
||||
description: ControllerRoute.SPACE.ACTIONS.DUPLICATE_SPACE_DESCRIPTION,
|
||||
})
|
||||
@Post(':spaceUuid/duplicate')
|
||||
async duplicateSpace(
|
||||
@Param('spaceUuid', ParseUUIDPipe) spaceUuid: string,
|
||||
@Body() dto: DuplicateSpaceDto,
|
||||
@Param() communitySpaceParam: CommunitySpaceParam,
|
||||
): Promise<BaseResponseDto> {
|
||||
return await this.spaceService.duplicateSpace(
|
||||
spaceUuid,
|
||||
communitySpaceParam,
|
||||
dto,
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(PermissionsGuard)
|
||||
@Permissions('SPACE_VIEW')
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ORPHAN_SPACE_NAME } from '@app/common/constants/orphan-constant';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString, NotEquals } from 'class-validator';
|
||||
|
||||
export class DuplicateSpaceDto {
|
||||
@ApiProperty({
|
||||
description: 'Name of the space (e.g., Floor 1, Unit 101)',
|
||||
example: 'Unit 101',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@NotEquals(ORPHAN_SPACE_NAME, {
|
||||
message() {
|
||||
return `Space name cannot be "${ORPHAN_SPACE_NAME}". Please choose a different name.`;
|
||||
},
|
||||
})
|
||||
spaceName: string;
|
||||
}
|
||||
@@ -133,7 +133,7 @@ export class ValidationService {
|
||||
'subspaces.productAllocations',
|
||||
'subspaces.productAllocations.product',
|
||||
'subspaces.devices',
|
||||
'spaceModel',
|
||||
// 'spaceModel',
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { generateRandomString } from '@app/common/helper/randomString';
|
||||
import { removeCircularReferences } from '@app/common/helper/removeCircularReferences';
|
||||
import { SpaceProductAllocationEntity } from '@app/common/modules/space/entities/space-product-allocation.entity';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||
import { SubspaceProductAllocationEntity } from '@app/common/modules/space/entities/subspace/subspace-product-allocation.entity';
|
||||
import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity';
|
||||
import {
|
||||
InviteSpaceRepository,
|
||||
@@ -24,6 +25,7 @@ import { DeviceService } from 'src/device/services';
|
||||
import { SpaceModelService } from 'src/space-model/services';
|
||||
import { TagService } from 'src/tags/services/tags.service';
|
||||
import { DataSource, In, Not, QueryRunner } from 'typeorm';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
import { DisableSpaceCommand } from '../commands';
|
||||
import {
|
||||
AddSpaceDto,
|
||||
@@ -32,6 +34,7 @@ import {
|
||||
UpdateSpaceDto,
|
||||
} from '../dtos';
|
||||
import { CreateProductAllocationDto } from '../dtos/create-product-allocation.dto';
|
||||
import { DuplicateSpaceDto } from '../dtos/duplicate-space.dto';
|
||||
import { GetSpaceDto } from '../dtos/get.space.dto';
|
||||
import { OrderSpacesDto } from '../dtos/order.spaces.dto';
|
||||
import { SpaceWithParentsDto } from '../dtos/space.parents.dto';
|
||||
@@ -181,6 +184,155 @@ export class SpaceService {
|
||||
!isRecursiveCall ? await queryRunner.release() : null;
|
||||
}
|
||||
}
|
||||
|
||||
async duplicateSpace(
|
||||
spaceUuid: string,
|
||||
{ communityUuid, projectUuid }: CommunitySpaceParam,
|
||||
dto: DuplicateSpaceDto,
|
||||
queryRunner?: QueryRunner,
|
||||
): Promise<BaseResponseDto> {
|
||||
if (!queryRunner) {
|
||||
queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
}
|
||||
|
||||
try {
|
||||
await this.validationService.validateCommunityAndProject(
|
||||
communityUuid,
|
||||
projectUuid,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
const result = await this.handleSpaceDuplication(
|
||||
spaceUuid,
|
||||
dto.spaceName,
|
||||
queryRunner,
|
||||
);
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
return new SuccessResponseDto({
|
||||
message: `Space with ID ${spaceUuid} successfully duplicated`,
|
||||
data: result,
|
||||
});
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
private async handleSpaceDuplication(
|
||||
spaceUuid: string,
|
||||
newSpaceName: string | null,
|
||||
queryRunner: QueryRunner,
|
||||
parent?: SpaceEntity,
|
||||
) {
|
||||
const space = await this.spaceRepository.findOne({
|
||||
where: { uuid: spaceUuid },
|
||||
relations: [
|
||||
'children',
|
||||
'productAllocations',
|
||||
'subspaces',
|
||||
'subspaces.productAllocations',
|
||||
],
|
||||
});
|
||||
const clonedSpace = structuredClone(space);
|
||||
console.log(`creating duplicate for space ${clonedSpace.spaceName}`);
|
||||
const newSpace = queryRunner.manager.create(SpaceEntity, {
|
||||
...clonedSpace,
|
||||
spaceName: newSpaceName || clonedSpace.spaceName,
|
||||
parent,
|
||||
children: undefined,
|
||||
subspaces: undefined,
|
||||
productAllocations: undefined,
|
||||
uuid: uuidV4(),
|
||||
});
|
||||
|
||||
console.log(`handling space ${newSpace.spaceName} allocations`);
|
||||
if (clonedSpace.productAllocations?.length) {
|
||||
newSpace.productAllocations = this.copySpaceAllocations(
|
||||
newSpace,
|
||||
clonedSpace.productAllocations,
|
||||
queryRunner,
|
||||
);
|
||||
}
|
||||
console.log(`handling space ${newSpace.spaceName} subspaces`);
|
||||
if (clonedSpace.subspaces?.length) {
|
||||
newSpace.subspaces = this.copySpaceSubspaces(
|
||||
newSpace,
|
||||
clonedSpace.subspaces,
|
||||
queryRunner,
|
||||
);
|
||||
}
|
||||
const savedSpace = await queryRunner.manager.save(newSpace);
|
||||
|
||||
console.log(`handling space ${newSpace.spaceName} children`);
|
||||
if (clonedSpace.children?.length) {
|
||||
for (const child of clonedSpace.children) {
|
||||
if (child.disabled) continue;
|
||||
await this.handleSpaceDuplication(
|
||||
child.uuid,
|
||||
child.spaceName,
|
||||
queryRunner,
|
||||
savedSpace,
|
||||
);
|
||||
}
|
||||
}
|
||||
return savedSpace;
|
||||
}
|
||||
private copySpaceSubspaces(
|
||||
newSpace: SpaceEntity,
|
||||
subspaces: SubspaceEntity[],
|
||||
queryRunner: QueryRunner,
|
||||
) {
|
||||
const newSubspaces = [];
|
||||
for (const sub of subspaces) {
|
||||
if (sub.disabled) continue;
|
||||
const clonedSub = structuredClone(sub);
|
||||
delete clonedSub.uuid;
|
||||
const newSubspace = queryRunner.manager.create(SubspaceEntity, {
|
||||
...clonedSub,
|
||||
space: newSpace,
|
||||
productAllocations: [],
|
||||
uuid: uuidV4(),
|
||||
});
|
||||
if (sub.productAllocations?.length) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
for (const { uuid, ...allocation } of sub.productAllocations) {
|
||||
newSubspace.productAllocations.push(
|
||||
queryRunner.manager.create(SubspaceProductAllocationEntity, {
|
||||
...allocation,
|
||||
subspace: newSubspace,
|
||||
uuid: uuidV4(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
newSubspaces.push(newSubspace);
|
||||
}
|
||||
return newSubspaces;
|
||||
}
|
||||
private copySpaceAllocations(
|
||||
newSpace: SpaceEntity,
|
||||
allocations: SpaceProductAllocationEntity[],
|
||||
queryRunner: QueryRunner,
|
||||
) {
|
||||
const newAllocations = [];
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
for (const { uuid, ...allocation } of allocations) {
|
||||
newAllocations.push(
|
||||
queryRunner.manager.create(SpaceProductAllocationEntity, {
|
||||
...allocation,
|
||||
space: newSpace,
|
||||
uuid: uuidV4(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
return newAllocations;
|
||||
}
|
||||
|
||||
private checkDuplicateTags(allocations: CreateProductAllocationDto[]) {
|
||||
const tagUuidSet = new Set<string>();
|
||||
const tagNameProductSet = new Set<string>();
|
||||
|
||||
Reference in New Issue
Block a user