mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-11-26 08:24:55 +00:00
739 lines
23 KiB
TypeScript
739 lines
23 KiB
TypeScript
import {
|
|
ORPHAN_COMMUNITY_NAME,
|
|
ORPHAN_SPACE_NAME,
|
|
} from '@app/common/constants/orphan-constant';
|
|
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
|
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
|
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 { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity';
|
|
import {
|
|
InviteSpaceRepository,
|
|
SpaceRepository,
|
|
} from '@app/common/modules/space/repositories';
|
|
import {
|
|
BadRequestException,
|
|
HttpException,
|
|
HttpStatus,
|
|
Injectable,
|
|
} from '@nestjs/common';
|
|
import { CommandBus } from '@nestjs/cqrs';
|
|
import { DeviceService } from 'src/device/services';
|
|
import { SpaceModelService } from 'src/space-model/services';
|
|
import { ProcessTagDto } from 'src/tags/dtos';
|
|
import { TagService } from 'src/tags/services/tags.service';
|
|
import { DataSource, In, Not, QueryRunner } from 'typeorm';
|
|
import { DisableSpaceCommand } from '../commands';
|
|
import {
|
|
AddSpaceDto,
|
|
CommunitySpaceParam,
|
|
GetSpaceParam,
|
|
UpdateSpaceDto,
|
|
} from '../dtos';
|
|
import { GetSpaceDto } from '../dtos/get.space.dto';
|
|
import { SpaceWithParentsDto } from '../dtos/space.parents.dto';
|
|
import { SpaceLinkService } from './space-link';
|
|
import { SpaceProductAllocationService } from './space-product-allocation.service';
|
|
import { ValidationService } from './space-validation.service';
|
|
import { SubSpaceService } from './subspace';
|
|
@Injectable()
|
|
export class SpaceService {
|
|
constructor(
|
|
private readonly dataSource: DataSource,
|
|
private readonly spaceRepository: SpaceRepository,
|
|
private readonly inviteSpaceRepository: InviteSpaceRepository,
|
|
private readonly spaceLinkService: SpaceLinkService,
|
|
private readonly subSpaceService: SubSpaceService,
|
|
private readonly validationService: ValidationService,
|
|
private readonly tagService: TagService,
|
|
private readonly spaceModelService: SpaceModelService,
|
|
private readonly deviceService: DeviceService,
|
|
private commandBus: CommandBus,
|
|
private readonly spaceProductAllocationService: SpaceProductAllocationService,
|
|
) {}
|
|
|
|
async createSpace(
|
|
addSpaceDto: AddSpaceDto,
|
|
params: CommunitySpaceParam,
|
|
): Promise<BaseResponseDto> {
|
|
const { parentUuid, direction, spaceModelUuid, subspaces, tags } =
|
|
addSpaceDto;
|
|
const { communityUuid, projectUuid } = params;
|
|
|
|
const queryRunner = this.dataSource.createQueryRunner();
|
|
|
|
await queryRunner.connect();
|
|
await queryRunner.startTransaction();
|
|
|
|
const { community } =
|
|
await this.validationService.validateCommunityAndProject(
|
|
communityUuid,
|
|
projectUuid,
|
|
);
|
|
|
|
this.validateSpaceCreationCriteria({ spaceModelUuid, subspaces, tags });
|
|
|
|
const parent = parentUuid
|
|
? await this.validationService.validateSpace(parentUuid)
|
|
: null;
|
|
|
|
const spaceModel = spaceModelUuid
|
|
? await this.validationService.validateSpaceModel(spaceModelUuid)
|
|
: null;
|
|
|
|
try {
|
|
const space = queryRunner.manager.create(SpaceEntity, {
|
|
...addSpaceDto,
|
|
spaceModel,
|
|
parent: parentUuid ? parent : null,
|
|
community,
|
|
});
|
|
|
|
const newSpace = await queryRunner.manager.save(space);
|
|
|
|
const subspaceTags =
|
|
subspaces?.flatMap((subspace) => subspace.tags || []) || [];
|
|
|
|
this.checkDuplicateTags([...tags, ...subspaceTags]);
|
|
|
|
if (spaceModelUuid) {
|
|
// no need to check for existing dependencies here as validateSpaceCreationCriteria
|
|
// ensures no tags or subspaces are present along with spaceModelUuid
|
|
await this.spaceModelService.linkToSpace(
|
|
newSpace,
|
|
spaceModel,
|
|
queryRunner,
|
|
);
|
|
}
|
|
|
|
await Promise.all([
|
|
// todo: remove this logic as we are not using space links anymore
|
|
direction && parent
|
|
? this.spaceLinkService.saveSpaceLink(
|
|
parent.uuid,
|
|
newSpace.uuid,
|
|
direction,
|
|
queryRunner,
|
|
)
|
|
: Promise.resolve(),
|
|
subspaces?.length
|
|
? this.subSpaceService.createSubspacesFromDto(
|
|
subspaces,
|
|
space,
|
|
queryRunner,
|
|
projectUuid,
|
|
)
|
|
: Promise.resolve(),
|
|
tags?.length
|
|
? this.createAllocations(tags, projectUuid, queryRunner, newSpace)
|
|
: Promise.resolve(),
|
|
]);
|
|
|
|
await queryRunner.commitTransaction();
|
|
|
|
return new SuccessResponseDto({
|
|
statusCode: HttpStatus.CREATED,
|
|
data: JSON.parse(JSON.stringify(newSpace, removeCircularReferences())),
|
|
message: 'Space created successfully',
|
|
});
|
|
} catch (error) {
|
|
await queryRunner.rollbackTransaction();
|
|
|
|
if (error instanceof HttpException) {
|
|
throw error;
|
|
}
|
|
throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
|
|
} finally {
|
|
await queryRunner.release();
|
|
}
|
|
}
|
|
private checkDuplicateTags(allTags: ProcessTagDto[]) {
|
|
const tagUuidSet = new Set<string>();
|
|
const tagNameProductSet = new Set<string>();
|
|
|
|
for (const tag of allTags) {
|
|
if (tag.uuid) {
|
|
if (tagUuidSet.has(tag.uuid)) {
|
|
throw new HttpException(
|
|
`Duplicate tag UUID found: ${tag.uuid}`,
|
|
HttpStatus.BAD_REQUEST,
|
|
);
|
|
}
|
|
tagUuidSet.add(tag.uuid);
|
|
} else {
|
|
const tagKey = `${tag.name}-${tag.productUuid}`;
|
|
if (tagNameProductSet.has(tagKey)) {
|
|
throw new HttpException(
|
|
`Duplicate tag found with name "${tag.name}" and product "${tag.productUuid}".`,
|
|
HttpStatus.BAD_REQUEST,
|
|
);
|
|
}
|
|
tagNameProductSet.add(tagKey);
|
|
}
|
|
}
|
|
}
|
|
|
|
async getSpacesHierarchyForCommunity(
|
|
params: CommunitySpaceParam,
|
|
getSpaceDto?: GetSpaceDto & { search?: string },
|
|
): Promise<BaseResponseDto> {
|
|
const { communityUuid, projectUuid } = params;
|
|
const { onlyWithDevices, search } = getSpaceDto;
|
|
await this.validationService.validateCommunityAndProject(
|
|
communityUuid,
|
|
projectUuid,
|
|
);
|
|
try {
|
|
const queryBuilder = this.spaceRepository
|
|
.createQueryBuilder('space')
|
|
.leftJoinAndSelect('space.parent', 'parent')
|
|
.leftJoinAndSelect(
|
|
'space.children',
|
|
'children',
|
|
'children.disabled = :disabled',
|
|
{ disabled: false },
|
|
)
|
|
.leftJoinAndSelect(
|
|
'space.incomingConnections',
|
|
'incomingConnections',
|
|
'incomingConnections.disabled = :incomingConnectionDisabled',
|
|
{ incomingConnectionDisabled: false },
|
|
)
|
|
.leftJoinAndSelect('space.productAllocations', 'productAllocations')
|
|
.leftJoinAndSelect('productAllocations.tag', 'tag')
|
|
.leftJoinAndSelect('productAllocations.product', 'product')
|
|
.leftJoinAndSelect(
|
|
'space.subspaces',
|
|
'subspaces',
|
|
'subspaces.disabled = :subspaceDisabled',
|
|
{ subspaceDisabled: false },
|
|
)
|
|
.leftJoinAndSelect(
|
|
'subspaces.productAllocations',
|
|
'subspaceProductAllocations',
|
|
)
|
|
.leftJoinAndSelect('subspaceProductAllocations.tag', 'subspaceTag')
|
|
.leftJoinAndSelect(
|
|
'subspaceProductAllocations.product',
|
|
'subspaceProduct',
|
|
)
|
|
.leftJoinAndSelect('space.spaceModel', 'spaceModel')
|
|
.where('space.community_id = :communityUuid', { communityUuid })
|
|
.andWhere('space.spaceName != :orphanSpaceName', {
|
|
orphanSpaceName: ORPHAN_SPACE_NAME,
|
|
})
|
|
.andWhere('space.disabled = :disabled', { disabled: false });
|
|
|
|
if (search) {
|
|
queryBuilder.andWhere(
|
|
'(space.spaceName ILIKE :search OR parent.spaceName ILIKE :search)',
|
|
{ search: `%${search}%` },
|
|
);
|
|
}
|
|
|
|
if (onlyWithDevices) {
|
|
queryBuilder.innerJoin('space.devices', 'devices');
|
|
}
|
|
|
|
let spaces = await queryBuilder.getMany();
|
|
|
|
if (onlyWithDevices) {
|
|
spaces = await Promise.all(
|
|
spaces.map(async (space) => {
|
|
const spaceHierarchy =
|
|
await this.deviceService.getParentHierarchy(space);
|
|
const parentHierarchy = spaceHierarchy
|
|
.slice(0, 3)
|
|
.map((space) => space.spaceName)
|
|
.join(' - ');
|
|
|
|
return {
|
|
...space,
|
|
lastThreeParents: parentHierarchy,
|
|
} as SpaceWithParentsDto;
|
|
}),
|
|
);
|
|
}
|
|
const spaceHierarchy = this.buildSpaceHierarchy(spaces);
|
|
|
|
return new SuccessResponseDto({
|
|
message: `Spaces in community ${communityUuid} successfully fetched in hierarchy`,
|
|
data: onlyWithDevices ? spaces : spaceHierarchy,
|
|
statusCode: HttpStatus.OK,
|
|
});
|
|
} catch (error) {
|
|
throw new HttpException(
|
|
`An error occurred while fetching the spaces ${error}`,
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
|
|
async findOne(params: GetSpaceParam): Promise<BaseResponseDto> {
|
|
const { communityUuid, spaceUuid, projectUuid } = params;
|
|
try {
|
|
await this.validationService.validateCommunityAndProject(
|
|
communityUuid,
|
|
projectUuid,
|
|
);
|
|
|
|
const queryBuilder = this.spaceRepository
|
|
.createQueryBuilder('space')
|
|
.leftJoinAndSelect('space.productAllocations', 'productAllocations')
|
|
.leftJoinAndSelect('productAllocations.tag', 'spaceTag')
|
|
.leftJoinAndSelect('productAllocations.product', 'spaceProduct')
|
|
.leftJoinAndSelect(
|
|
'space.subspaces',
|
|
'subspaces',
|
|
'subspaces.disabled = :subspaceDisabled',
|
|
{ subspaceDisabled: false },
|
|
)
|
|
.leftJoinAndSelect(
|
|
'subspaces.productAllocations',
|
|
'subspaceProductAllocations',
|
|
)
|
|
.leftJoinAndSelect('subspaceProductAllocations.tag', 'subspaceTag')
|
|
.leftJoinAndSelect(
|
|
'subspaceProductAllocations.product',
|
|
'subspaceProduct',
|
|
)
|
|
.where('space.community_id = :communityUuid', { communityUuid })
|
|
.andWhere('space.spaceName != :orphanSpaceName', {
|
|
orphanSpaceName: ORPHAN_SPACE_NAME,
|
|
})
|
|
.andWhere('space.uuid = :spaceUuid', { spaceUuid })
|
|
.andWhere('space.disabled = :disabled', { disabled: false });
|
|
|
|
const space = await queryBuilder.getOne();
|
|
|
|
return new SuccessResponseDto({
|
|
message: `Space with ID ${spaceUuid} successfully fetched`,
|
|
data: space,
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof HttpException) {
|
|
throw error; // If it's an HttpException, rethrow it
|
|
} else {
|
|
throw new HttpException(
|
|
'An error occurred while deleting the community',
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
async delete(params: GetSpaceParam): Promise<BaseResponseDto> {
|
|
const queryRunner = this.dataSource.createQueryRunner();
|
|
await queryRunner.connect();
|
|
await queryRunner.startTransaction();
|
|
|
|
try {
|
|
const { communityUuid, spaceUuid, projectUuid } = params;
|
|
|
|
const { project } =
|
|
await this.validationService.validateCommunityAndProject(
|
|
communityUuid,
|
|
projectUuid,
|
|
);
|
|
|
|
const space = await this.validationService.validateSpace(spaceUuid);
|
|
|
|
if (space.spaceName === ORPHAN_SPACE_NAME) {
|
|
throw new HttpException(
|
|
`space ${ORPHAN_SPACE_NAME} cannot be deleted`,
|
|
HttpStatus.BAD_REQUEST,
|
|
);
|
|
}
|
|
|
|
const orphanSpace = await this.spaceRepository.findOne({
|
|
where: {
|
|
community: {
|
|
name: `${ORPHAN_COMMUNITY_NAME}-${project.name}`,
|
|
},
|
|
spaceName: ORPHAN_SPACE_NAME,
|
|
},
|
|
});
|
|
|
|
await this.spaceProductAllocationService.clearAllAllocations(
|
|
spaceUuid,
|
|
queryRunner,
|
|
);
|
|
|
|
const subspaces = await queryRunner.manager.find(SubspaceEntity, {
|
|
where: { space: { uuid: spaceUuid } },
|
|
});
|
|
|
|
const subspaceUuids = subspaces.map((subspace) => subspace.uuid);
|
|
|
|
if (subspaceUuids.length > 0) {
|
|
await this.subSpaceService.clearSubspaces(subspaceUuids, queryRunner);
|
|
}
|
|
|
|
await this.disableSpace(space, orphanSpace);
|
|
|
|
await queryRunner.commitTransaction();
|
|
|
|
return new SuccessResponseDto({
|
|
message: `Space with ID ${spaceUuid} successfully deleted`,
|
|
statusCode: HttpStatus.OK,
|
|
});
|
|
} catch (error) {
|
|
await queryRunner.rollbackTransaction();
|
|
console.error('Error deleting space:', error);
|
|
|
|
if (error instanceof HttpException) {
|
|
throw error;
|
|
}
|
|
throw new HttpException(
|
|
`An error occurred while deleting the space: ${error.message}`,
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
} finally {
|
|
await queryRunner.release();
|
|
}
|
|
}
|
|
|
|
async disableSpace(space: SpaceEntity, orphanSpace: SpaceEntity) {
|
|
await this.commandBus.execute(
|
|
new DisableSpaceCommand({ spaceUuid: space.uuid, orphanSpace }),
|
|
);
|
|
}
|
|
|
|
async updateSpace(
|
|
params: GetSpaceParam,
|
|
updateSpaceDto: UpdateSpaceDto,
|
|
): Promise<BaseResponseDto> {
|
|
const { communityUuid, spaceUuid, projectUuid } = params;
|
|
|
|
const queryRunner = this.dataSource.createQueryRunner();
|
|
const hasSubspace = updateSpaceDto.subspaces?.length > 0;
|
|
const hasTags = updateSpaceDto.tags?.length > 0;
|
|
|
|
try {
|
|
await queryRunner.connect();
|
|
await queryRunner.startTransaction();
|
|
const project = await this.spaceModelService.validateProject(
|
|
params.projectUuid,
|
|
);
|
|
|
|
const space =
|
|
await this.validationService.validateSpaceWithinCommunityAndProject(
|
|
communityUuid,
|
|
projectUuid,
|
|
spaceUuid,
|
|
);
|
|
|
|
if (space.spaceModel && !updateSpaceDto.spaceModelUuid) {
|
|
await queryRunner.manager.update(SpaceEntity, space.uuid, {
|
|
spaceModel: null,
|
|
});
|
|
await this.unlinkSpaceFromModel(space, queryRunner);
|
|
}
|
|
|
|
await this.updateSpaceProperties(space, updateSpaceDto, queryRunner);
|
|
|
|
if (hasSubspace || hasTags) {
|
|
await queryRunner.manager.update(SpaceEntity, space.uuid, {
|
|
spaceModel: null,
|
|
});
|
|
}
|
|
|
|
if (updateSpaceDto.spaceModelUuid) {
|
|
const spaceModel = await this.validationService.validateSpaceModel(
|
|
updateSpaceDto.spaceModelUuid,
|
|
);
|
|
|
|
const hasDependencies =
|
|
space.devices?.length > 0 ||
|
|
space.subspaces?.length > 0 ||
|
|
space.productAllocations?.length > 0;
|
|
|
|
if (!hasDependencies && !space.spaceModel) {
|
|
await this.spaceModelService.linkToSpace(
|
|
space,
|
|
spaceModel,
|
|
queryRunner,
|
|
);
|
|
} else if (hasDependencies) {
|
|
// check for uuids that didn't change,
|
|
// get their device ids and check if they has a tag in device entity,
|
|
// if so move them ot the orphan space
|
|
|
|
await this.spaceModelService.removeSpaceOldSubspacesAndAllocations(
|
|
space,
|
|
project,
|
|
queryRunner,
|
|
);
|
|
|
|
await this.spaceModelService.linkToSpace(
|
|
space,
|
|
spaceModel,
|
|
queryRunner,
|
|
);
|
|
}
|
|
}
|
|
if (hasSubspace) {
|
|
await this.subSpaceService.unlinkModels(space.subspaces, queryRunner);
|
|
}
|
|
|
|
if (hasTags && space.productAllocations && space.spaceModel) {
|
|
await this.spaceProductAllocationService.unlinkModels(
|
|
space,
|
|
queryRunner,
|
|
);
|
|
}
|
|
|
|
if (updateSpaceDto.subspaces) {
|
|
await this.subSpaceService.updateSubspaceInSpace(
|
|
updateSpaceDto.subspaces,
|
|
queryRunner,
|
|
space,
|
|
projectUuid,
|
|
);
|
|
}
|
|
|
|
if (updateSpaceDto.tags) {
|
|
await queryRunner.manager.delete(SpaceProductAllocationEntity, {
|
|
space: { uuid: space.uuid },
|
|
tag: {
|
|
uuid: Not(
|
|
In(
|
|
updateSpaceDto.tags
|
|
.filter((tag) => tag.tagUuid)
|
|
.map((tag) => tag.tagUuid),
|
|
),
|
|
),
|
|
},
|
|
});
|
|
await this.createAllocations(
|
|
updateSpaceDto.tags.map((tag) => ({
|
|
name: tag.name,
|
|
uuid: tag.tagUuid,
|
|
productUuid: tag.productUuid,
|
|
})),
|
|
projectUuid,
|
|
queryRunner,
|
|
space,
|
|
);
|
|
}
|
|
|
|
if (space.devices?.length) {
|
|
await this.deviceService.addDevicesToOrphanSpace(
|
|
space,
|
|
project,
|
|
queryRunner,
|
|
);
|
|
}
|
|
|
|
await queryRunner.commitTransaction();
|
|
|
|
return new SuccessResponseDto({
|
|
message: `Space with ID ${spaceUuid} successfully updated`,
|
|
statusCode: HttpStatus.OK,
|
|
});
|
|
} catch (error) {
|
|
await queryRunner.rollbackTransaction();
|
|
|
|
if (error instanceof HttpException) {
|
|
throw error;
|
|
}
|
|
|
|
throw new HttpException(
|
|
`An error occurred while updating the space: error ${error}`,
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
} finally {
|
|
await queryRunner.release();
|
|
}
|
|
}
|
|
|
|
async unlinkSpaceFromModel(
|
|
space: SpaceEntity,
|
|
queryRunner: QueryRunner,
|
|
): Promise<void> {
|
|
try {
|
|
if (space.subspaces || space.productAllocations) {
|
|
if (space.productAllocations) {
|
|
await this.spaceProductAllocationService.unlinkModels(
|
|
space,
|
|
queryRunner,
|
|
);
|
|
}
|
|
|
|
if (space.subspaces) {
|
|
await this.subSpaceService.unlinkModels(space.subspaces, queryRunner);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
throw new HttpException(
|
|
`Failed to unlink space model: ${error.message}`,
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
|
|
private async updateSpaceProperties(
|
|
space: SpaceEntity,
|
|
updateSpaceDto: UpdateSpaceDto,
|
|
queryRunner: QueryRunner,
|
|
): Promise<void> {
|
|
const { spaceName, x, y, icon } = updateSpaceDto;
|
|
|
|
const updateFields: Partial<SpaceEntity> = {};
|
|
|
|
if (spaceName) updateFields.spaceName = spaceName;
|
|
if (x !== undefined) updateFields.x = x;
|
|
if (y !== undefined) updateFields.y = y;
|
|
if (icon) updateFields.icon = icon;
|
|
|
|
if (Object.keys(updateFields).length > 0) {
|
|
await queryRunner.manager.update(SpaceEntity, space.uuid, updateFields);
|
|
}
|
|
}
|
|
|
|
async getSpacesHierarchyForSpace(
|
|
params: GetSpaceParam,
|
|
): Promise<BaseResponseDto> {
|
|
const { spaceUuid, communityUuid, projectUuid } = params;
|
|
await this.validationService.checkCommunityAndProjectSpaceExistence(
|
|
communityUuid,
|
|
projectUuid,
|
|
spaceUuid,
|
|
);
|
|
|
|
try {
|
|
// Get all spaces that are children of the provided space, including the parent-child relations
|
|
const spaces = await this.spaceRepository.find({
|
|
where: { parent: { uuid: spaceUuid }, disabled: false },
|
|
relations: ['parent', 'children'],
|
|
});
|
|
|
|
// Organize spaces into a hierarchical structure
|
|
const spaceHierarchy = this.buildSpaceHierarchy(spaces);
|
|
|
|
return new SuccessResponseDto({
|
|
message: `Spaces under space ${spaceUuid} successfully fetched in hierarchy`,
|
|
data: spaceHierarchy,
|
|
statusCode: HttpStatus.OK,
|
|
});
|
|
} catch (error) {
|
|
throw new HttpException(
|
|
`An error occurred while fetching the spaces under the space ${error}`,
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
|
|
async generateSpaceInvitationCode(params: GetSpaceParam): Promise<any> {
|
|
const { communityUuid, spaceUuid, projectUuid } = params;
|
|
try {
|
|
const invitationCode = generateRandomString(6);
|
|
|
|
const space =
|
|
await this.validationService.validateSpaceWithinCommunityAndProject(
|
|
communityUuid,
|
|
projectUuid,
|
|
spaceUuid,
|
|
);
|
|
await this.inviteSpaceRepository.save({
|
|
space: { uuid: spaceUuid },
|
|
invitationCode,
|
|
});
|
|
|
|
return new SuccessResponseDto({
|
|
message: `Invitation code has been successfuly added to the space`,
|
|
data: {
|
|
invitationCode,
|
|
spaceName: space.spaceName,
|
|
spaceUuid: space.uuid,
|
|
},
|
|
});
|
|
} catch (err) {
|
|
if (err instanceof BadRequestException) {
|
|
throw err;
|
|
} else {
|
|
throw new HttpException('Space not found', HttpStatus.NOT_FOUND);
|
|
}
|
|
}
|
|
}
|
|
|
|
private buildSpaceHierarchy(spaces: SpaceEntity[]): SpaceEntity[] {
|
|
const map = new Map<string, SpaceEntity>();
|
|
|
|
// Step 1: Create a map of spaces by UUID
|
|
spaces.forEach((space: any) => {
|
|
map.set(space.uuid, { ...space, children: [] }); // Ensure children are reset
|
|
});
|
|
|
|
// Step 2: Organize the hierarchy
|
|
const rootSpaces: SpaceEntity[] = [];
|
|
spaces.forEach((space) => {
|
|
if (space.parent && space.parent.uuid) {
|
|
const parent = map.get(space.parent.uuid);
|
|
if (parent) {
|
|
const child = map.get(space.uuid);
|
|
if (child && !parent.children.some((c) => c.uuid === child.uuid)) {
|
|
parent.children.push(child);
|
|
}
|
|
}
|
|
} else {
|
|
rootSpaces.push(map.get(space.uuid)!); // Push only root spaces
|
|
}
|
|
});
|
|
|
|
return rootSpaces;
|
|
}
|
|
|
|
private validateSpaceCreationCriteria({
|
|
spaceModelUuid,
|
|
tags,
|
|
subspaces,
|
|
}: Pick<AddSpaceDto, 'spaceModelUuid' | 'tags' | 'subspaces'>): void {
|
|
const hasTagsOrSubspaces =
|
|
(tags && tags.length > 0) || (subspaces && subspaces.length > 0);
|
|
|
|
if (spaceModelUuid && hasTagsOrSubspaces) {
|
|
throw new HttpException(
|
|
'For space creation choose either space model or products and subspace',
|
|
HttpStatus.CONFLICT,
|
|
);
|
|
}
|
|
}
|
|
|
|
private async createAllocations(
|
|
tags: ProcessTagDto[],
|
|
projectUuid: string,
|
|
queryRunner: QueryRunner,
|
|
space: SpaceEntity,
|
|
): Promise<void> {
|
|
const allocationsData = await this.tagService.processTags(
|
|
tags,
|
|
projectUuid,
|
|
queryRunner,
|
|
);
|
|
|
|
// Create a mapping of created tags by UUID and name for quick lookup
|
|
const createdTagsByUUID = new Map(allocationsData.map((t) => [t.uuid, t]));
|
|
const createdTagsByName = new Map(allocationsData.map((t) => [t.name, t]));
|
|
|
|
// Create the product-tag mapping based on the processed tags
|
|
const productTagMapping = tags.map(({ uuid, name, productUuid }) => {
|
|
const inputTag = uuid
|
|
? createdTagsByUUID.get(uuid)
|
|
: createdTagsByName.get(name);
|
|
return {
|
|
tag: inputTag?.uuid,
|
|
product: productUuid,
|
|
};
|
|
});
|
|
|
|
await this.spaceProductAllocationService.createProductAllocations(
|
|
space,
|
|
productTagMapping,
|
|
queryRunner,
|
|
);
|
|
}
|
|
}
|