mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-08-26 09:19:40 +00:00
832 lines
25 KiB
TypeScript
832 lines
25 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 { 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 { CreateProductAllocationDto } from '../dtos/create-product-allocation.dto';
|
|
import { GetSpaceDto } from '../dtos/get.space.dto';
|
|
import { OrderSpacesDto } from '../dtos/order.spaces.dto';
|
|
import { SpaceWithParentsDto } from '../dtos/space.parents.dto';
|
|
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 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,
|
|
queryRunner?: QueryRunner,
|
|
recursiveCallParentEntity?: SpaceEntity,
|
|
): Promise<BaseResponseDto> {
|
|
const isRecursiveCall = !!queryRunner;
|
|
|
|
const {
|
|
parentUuid,
|
|
spaceModelUuid,
|
|
subspaces,
|
|
productAllocations,
|
|
children,
|
|
} = addSpaceDto;
|
|
const { communityUuid, projectUuid } = params;
|
|
|
|
if (!queryRunner) {
|
|
queryRunner = this.dataSource.createQueryRunner();
|
|
await queryRunner.connect();
|
|
await queryRunner.startTransaction();
|
|
}
|
|
|
|
const { community } =
|
|
await this.validationService.validateCommunityAndProject(
|
|
communityUuid,
|
|
projectUuid,
|
|
queryRunner,
|
|
);
|
|
|
|
this.validateSpaceCreationCriteria({
|
|
spaceModelUuid,
|
|
subspaces,
|
|
productAllocations,
|
|
});
|
|
|
|
const parent =
|
|
parentUuid && !isRecursiveCall
|
|
? await this.validationService.validateSpace(parentUuid)
|
|
: null;
|
|
|
|
const spaceModel = spaceModelUuid
|
|
? await this.validationService.validateSpaceModel(spaceModelUuid)
|
|
: null;
|
|
try {
|
|
const space = queryRunner.manager.create(SpaceEntity, {
|
|
// todo: find a better way to handle this instead of naming every key
|
|
spaceName: addSpaceDto.spaceName,
|
|
icon: addSpaceDto.icon,
|
|
x: addSpaceDto.x,
|
|
y: addSpaceDto.y,
|
|
spaceModel,
|
|
parent: isRecursiveCall
|
|
? recursiveCallParentEntity
|
|
: parentUuid
|
|
? parent
|
|
: null,
|
|
community,
|
|
});
|
|
|
|
const newSpace = await queryRunner.manager.save(space);
|
|
this.checkDuplicateTags([
|
|
...(productAllocations || []),
|
|
...(subspaces?.flatMap(
|
|
(subspace) => subspace.productAllocations || [],
|
|
) || []),
|
|
]);
|
|
|
|
if (spaceModelUuid) {
|
|
await this.spaceModelService.linkToSpace(
|
|
newSpace,
|
|
spaceModel,
|
|
queryRunner,
|
|
);
|
|
}
|
|
|
|
await Promise.all([
|
|
subspaces?.length
|
|
? this.subSpaceService.createSubspacesFromDto(
|
|
subspaces,
|
|
space,
|
|
queryRunner,
|
|
projectUuid,
|
|
)
|
|
: Promise.resolve(),
|
|
productAllocations?.length
|
|
? this.createAllocations(
|
|
productAllocations,
|
|
projectUuid,
|
|
queryRunner,
|
|
newSpace,
|
|
)
|
|
: Promise.resolve(),
|
|
]);
|
|
|
|
if (children?.length) {
|
|
await Promise.all(
|
|
children.map((child) =>
|
|
this.createSpace(
|
|
{ ...child, parentUuid: newSpace.uuid },
|
|
{ communityUuid, projectUuid },
|
|
queryRunner,
|
|
newSpace,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (!isRecursiveCall) {
|
|
await queryRunner.commitTransaction();
|
|
}
|
|
|
|
return new SuccessResponseDto({
|
|
statusCode: HttpStatus.CREATED,
|
|
data: JSON.parse(JSON.stringify(newSpace, removeCircularReferences())),
|
|
message: 'Space created successfully',
|
|
});
|
|
} catch (error) {
|
|
!isRecursiveCall ? await queryRunner.rollbackTransaction() : null;
|
|
|
|
if (error instanceof HttpException) {
|
|
throw error;
|
|
}
|
|
throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
|
|
} finally {
|
|
!isRecursiveCall ? await queryRunner.release() : null;
|
|
}
|
|
}
|
|
private checkDuplicateTags(allocations: CreateProductAllocationDto[]) {
|
|
const tagUuidSet = new Set<string>();
|
|
const tagNameProductSet = new Set<string>();
|
|
|
|
for (const allocation of allocations) {
|
|
if (allocation.tagUuid) {
|
|
if (tagUuidSet.has(allocation.tagUuid)) {
|
|
throw new HttpException(
|
|
`Duplicate tag UUID found: ${allocation.tagUuid}`,
|
|
HttpStatus.BAD_REQUEST,
|
|
);
|
|
}
|
|
tagUuidSet.add(allocation.tagUuid);
|
|
} else {
|
|
const tagKey = `${allocation.tagName}-${allocation.productUuid}`;
|
|
if (tagNameProductSet.has(tagKey)) {
|
|
throw new HttpException(
|
|
`Duplicate tag found with name "${allocation.tagName}" and product "${allocation.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.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();
|
|
if (!space) {
|
|
throw new HttpException(
|
|
`Space with ID ${spaceUuid} not found`,
|
|
HttpStatus.NOT_FOUND,
|
|
);
|
|
}
|
|
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 fetching the space',
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
async updateSpacesOrder(
|
|
parentSpaceUuid: string,
|
|
{ spacesUuids }: OrderSpacesDto,
|
|
) {
|
|
const parentSpace = await this.spaceRepository.findOne({
|
|
where: { uuid: parentSpaceUuid, disabled: false },
|
|
relations: ['children'],
|
|
});
|
|
if (!parentSpace) {
|
|
throw new HttpException(
|
|
`Parent space with ID ${parentSpaceUuid} not found`,
|
|
HttpStatus.NOT_FOUND,
|
|
);
|
|
}
|
|
// ensure that all sent spaces belong to the parent space
|
|
const missingSpaces = spacesUuids.filter(
|
|
(uuid) => !parentSpace.children.some((child) => child.uuid === uuid),
|
|
);
|
|
if (missingSpaces.length > 0) {
|
|
throw new HttpException(
|
|
`Some spaces with IDs ${missingSpaces.join(
|
|
', ',
|
|
)} do not belong to the parent space with ID ${parentSpaceUuid}`,
|
|
HttpStatus.BAD_REQUEST,
|
|
);
|
|
}
|
|
try {
|
|
await this.spaceRepository.update(
|
|
{ uuid: In(spacesUuids), parent: { uuid: parentSpaceUuid } },
|
|
{
|
|
order: () =>
|
|
'CASE ' +
|
|
spacesUuids
|
|
.map((s, index) => `WHEN uuid = '${s}' THEN ${index + 1}`)
|
|
.join(' ') +
|
|
' END',
|
|
},
|
|
);
|
|
return;
|
|
} catch (error) {
|
|
console.error('Error updating spaces order:', error);
|
|
throw new HttpException(
|
|
'An error occurred while updating spaces order',
|
|
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();
|
|
}
|
|
}
|
|
|
|
private 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 hasAllocations = updateSpaceDto.productAllocations?.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 || hasAllocations) {
|
|
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 (hasAllocations && space.productAllocations && space.spaceModel) {
|
|
await this.spaceProductAllocationService.unlinkModels(
|
|
space,
|
|
queryRunner,
|
|
);
|
|
}
|
|
|
|
if (updateSpaceDto.subspaces) {
|
|
await this.subSpaceService.updateSubspaceInSpace(
|
|
updateSpaceDto.subspaces,
|
|
queryRunner,
|
|
space,
|
|
projectUuid,
|
|
);
|
|
}
|
|
|
|
if (updateSpaceDto.productAllocations) {
|
|
await queryRunner.manager.delete(SpaceProductAllocationEntity, {
|
|
space: { uuid: space.uuid },
|
|
tag: {
|
|
uuid: Not(
|
|
In(
|
|
updateSpaceDto.productAllocations
|
|
.filter((tag) => tag.tagUuid)
|
|
.map((tag) => tag.tagUuid),
|
|
),
|
|
),
|
|
},
|
|
});
|
|
await this.createAllocations(
|
|
updateSpaceDto.productAllocations,
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
});
|
|
rootSpaces.forEach(this.sortSpaceChildren.bind(this));
|
|
return rootSpaces;
|
|
}
|
|
|
|
private sortSpaceChildren(space: SpaceEntity) {
|
|
if (space.children && space.children.length > 0) {
|
|
space.children.sort((a, b) => {
|
|
const aOrder = a.order ?? Infinity;
|
|
const bOrder = b.order ?? Infinity;
|
|
return aOrder - bOrder;
|
|
});
|
|
space.children.forEach(this.sortSpaceChildren.bind(this)); // Recursively sort children of children
|
|
}
|
|
}
|
|
|
|
private validateSpaceCreationCriteria({
|
|
spaceModelUuid,
|
|
productAllocations,
|
|
subspaces,
|
|
}: Pick<
|
|
AddSpaceDto,
|
|
'spaceModelUuid' | 'productAllocations' | 'subspaces'
|
|
>): void {
|
|
const hasProductsOrSubspaces =
|
|
(productAllocations && productAllocations.length > 0) ||
|
|
(subspaces && subspaces.length > 0);
|
|
|
|
if (spaceModelUuid && hasProductsOrSubspaces) {
|
|
throw new HttpException(
|
|
'For space creation choose either space model or products and subspace',
|
|
HttpStatus.CONFLICT,
|
|
);
|
|
}
|
|
}
|
|
|
|
private async createAllocations(
|
|
productAllocations: CreateProductAllocationDto[],
|
|
projectUuid: string,
|
|
queryRunner: QueryRunner,
|
|
space: SpaceEntity,
|
|
): Promise<void> {
|
|
const allocationsData = await this.tagService.upsertTags(
|
|
productAllocations,
|
|
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 = productAllocations.map(
|
|
({ tagUuid, tagName, productUuid }) => {
|
|
const inputTag = tagUuid
|
|
? createdTagsByUUID.get(tagUuid)
|
|
: createdTagsByName.get(tagName);
|
|
return {
|
|
tag: inputTag?.uuid,
|
|
product: productUuid,
|
|
};
|
|
},
|
|
);
|
|
|
|
await this.spaceProductAllocationService.createProductAllocations(
|
|
space,
|
|
productTagMapping,
|
|
queryRunner,
|
|
);
|
|
}
|
|
}
|