mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-07-16 02:36:19 +00:00
543 lines
15 KiB
TypeScript
543 lines
15 KiB
TypeScript
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
|
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
|
import {
|
|
AddSubspaceDto,
|
|
CreateTagDto,
|
|
DeleteSubspaceDto,
|
|
GetSpaceParam,
|
|
GetSubSpaceParam,
|
|
ModifySubspaceDto,
|
|
} from '../../dtos';
|
|
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
|
import {
|
|
TypeORMCustomModel,
|
|
TypeORMCustomModelFindAllQuery,
|
|
} from '@app/common/models/typeOrmCustom.model';
|
|
import { PageResponse } from '@app/common/dto/pagination.response.dto';
|
|
import { SubspaceDto } from '@app/common/modules/space/dtos';
|
|
import { In, QueryRunner } from 'typeorm';
|
|
import {
|
|
SpaceEntity,
|
|
SubspaceEntity,
|
|
} from '@app/common/modules/space/entities';
|
|
import { SubspaceModelEntity } from '@app/common/modules/space-model';
|
|
import { ValidationService } from '../space-validation.service';
|
|
import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
|
|
import { TagService } from '../tag';
|
|
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
|
import { SubspaceDeviceService } from './subspace-device.service';
|
|
import { ModifyTagDto } from 'src/space/dtos/tag/modify-tag.dto';
|
|
|
|
@Injectable()
|
|
export class SubSpaceService {
|
|
constructor(
|
|
private readonly subspaceRepository: SubspaceRepository,
|
|
private readonly validationService: ValidationService,
|
|
private readonly tagService: TagService,
|
|
public readonly deviceService: SubspaceDeviceService,
|
|
) {}
|
|
|
|
async createSubspaces(
|
|
subspaceData: Array<{
|
|
subspaceName: string;
|
|
space: SpaceEntity;
|
|
subSpaceModel?: SubspaceModelEntity;
|
|
}>,
|
|
queryRunner: QueryRunner,
|
|
): Promise<SubspaceEntity[]> {
|
|
try {
|
|
const subspaceNames = subspaceData.map((data) => data.subspaceName);
|
|
|
|
await this.checkExistingNamesInSpace(
|
|
subspaceNames,
|
|
subspaceData[0].space,
|
|
);
|
|
const subspaces = subspaceData.map((data) =>
|
|
queryRunner.manager.create(this.subspaceRepository.target, data),
|
|
);
|
|
return await queryRunner.manager.save(subspaces);
|
|
} catch (error) {
|
|
throw new HttpException(
|
|
`An unexpected error occurred while creating subspaces. ${error}`,
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
|
|
async createSubSpaceFromModel(
|
|
subspaceModels: SubspaceModelEntity[],
|
|
space: SpaceEntity,
|
|
queryRunner: QueryRunner,
|
|
): Promise<void> {
|
|
if (!subspaceModels?.length) return;
|
|
|
|
const subspaceData = subspaceModels.map((subSpaceModel) => ({
|
|
subspaceName: subSpaceModel.subspaceName,
|
|
space,
|
|
subSpaceModel,
|
|
}));
|
|
|
|
const subspaces = await this.createSubspaces(subspaceData, queryRunner);
|
|
|
|
await Promise.all(
|
|
subspaceModels.map((model, index) =>
|
|
this.tagService.createTagsFromModel(
|
|
queryRunner,
|
|
model.tags || [],
|
|
null,
|
|
subspaces[index],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
async createSubspacesFromDto(
|
|
addSubspaceDtos: AddSubspaceDto[],
|
|
space: SpaceEntity,
|
|
queryRunner: QueryRunner,
|
|
otherTags?: CreateTagDto[],
|
|
): Promise<SubspaceEntity[]> {
|
|
try {
|
|
this.checkForDuplicateNames(
|
|
addSubspaceDtos.map(({ subspaceName }) => subspaceName),
|
|
);
|
|
|
|
const subspaceData = addSubspaceDtos.map((dto) => ({
|
|
subspaceName: dto.subspaceName,
|
|
space,
|
|
}));
|
|
|
|
const subspaces = await this.createSubspaces(subspaceData, queryRunner);
|
|
|
|
await Promise.all(
|
|
addSubspaceDtos.map(async (dto, index) => {
|
|
const otherDtoTags = addSubspaceDtos
|
|
.filter((_, i) => i !== index)
|
|
.flatMap((otherDto) => otherDto.tags || []);
|
|
const subspace = subspaces[index];
|
|
if (dto.tags?.length) {
|
|
subspace.tags = await this.tagService.createTags(
|
|
dto.tags,
|
|
queryRunner,
|
|
null,
|
|
subspace,
|
|
[...(otherTags || []), ...otherDtoTags],
|
|
);
|
|
}
|
|
}),
|
|
);
|
|
return subspaces;
|
|
} catch (error) {
|
|
if (error instanceof HttpException) {
|
|
throw error;
|
|
}
|
|
|
|
throw new HttpException(
|
|
'Failed to save subspaces due to an unexpected error.',
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
|
|
async createSubspace(
|
|
addSubspaceDto: AddSubspaceDto,
|
|
params: GetSpaceParam,
|
|
): Promise<BaseResponseDto> {
|
|
const space =
|
|
await this.validationService.validateSpaceWithinCommunityAndProject(
|
|
params.communityUuid,
|
|
params.projectUuid,
|
|
params.spaceUuid,
|
|
);
|
|
|
|
try {
|
|
await this.checkExistingNamesInSpace(
|
|
[addSubspaceDto.subspaceName],
|
|
space,
|
|
);
|
|
const newSubspace = this.subspaceRepository.create({
|
|
...addSubspaceDto,
|
|
space,
|
|
});
|
|
|
|
await this.subspaceRepository.save(newSubspace);
|
|
|
|
return new SuccessResponseDto({
|
|
statusCode: HttpStatus.CREATED,
|
|
data: newSubspace,
|
|
message: 'Subspace created successfully',
|
|
});
|
|
} catch (error) {
|
|
throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
|
|
async list(
|
|
params: GetSpaceParam,
|
|
pageable: Partial<TypeORMCustomModelFindAllQuery>,
|
|
): Promise<BaseResponseDto> {
|
|
const { communityUuid, spaceUuid, projectUuid } = params;
|
|
await this.validationService.checkCommunityAndProjectSpaceExistence(
|
|
communityUuid,
|
|
projectUuid,
|
|
spaceUuid,
|
|
);
|
|
|
|
try {
|
|
pageable.modelName = 'subspace';
|
|
pageable.where = { space: { uuid: spaceUuid } };
|
|
const customModel = TypeORMCustomModel(this.subspaceRepository);
|
|
|
|
const { baseResponseDto, paginationResponseDto } =
|
|
await customModel.findAll(pageable);
|
|
return new PageResponse<SubspaceDto>(
|
|
baseResponseDto,
|
|
paginationResponseDto,
|
|
);
|
|
} catch (error) {
|
|
throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
|
|
async updateSubSpace(
|
|
params: GetSubSpaceParam,
|
|
updateSubSpaceDto: AddSubspaceDto,
|
|
): Promise<BaseResponseDto> {
|
|
const { spaceUuid, communityUuid, subSpaceUuid, projectUuid } = params;
|
|
await this.validationService.checkCommunityAndProjectSpaceExistence(
|
|
communityUuid,
|
|
projectUuid,
|
|
spaceUuid,
|
|
);
|
|
const subSpace = await this.subspaceRepository.findOne({
|
|
where: { uuid: subSpaceUuid },
|
|
});
|
|
|
|
if (!subSpace) {
|
|
throw new HttpException(
|
|
`SubSpace with ID ${subSpaceUuid} not found`,
|
|
HttpStatus.NOT_FOUND,
|
|
);
|
|
}
|
|
|
|
try {
|
|
Object.assign(subSpace, updateSubSpaceDto);
|
|
|
|
await this.subspaceRepository.save(subSpace);
|
|
|
|
return new SuccessResponseDto({
|
|
message: `Subspace with ID ${subSpaceUuid} successfully updated`,
|
|
data: subSpace,
|
|
statusCode: HttpStatus.OK,
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof HttpException) {
|
|
throw error;
|
|
}
|
|
throw new HttpException(
|
|
'An error occurred while updating the subspace',
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
|
|
async delete(params: GetSubSpaceParam): Promise<BaseResponseDto> {
|
|
const { spaceUuid, communityUuid, subSpaceUuid, projectUuid } = params;
|
|
await this.validationService.checkCommunityAndProjectSpaceExistence(
|
|
communityUuid,
|
|
projectUuid,
|
|
spaceUuid,
|
|
);
|
|
|
|
const subspace = await this.subspaceRepository.findOne({
|
|
where: { uuid: subSpaceUuid },
|
|
});
|
|
|
|
if (!subspace) {
|
|
throw new HttpException(
|
|
`Subspace with ID ${subSpaceUuid} not found`,
|
|
HttpStatus.NOT_FOUND,
|
|
);
|
|
}
|
|
|
|
try {
|
|
await this.subspaceRepository.remove(subspace);
|
|
|
|
return new SuccessResponseDto({
|
|
message: `Subspace with ID ${subSpaceUuid} successfully deleted`,
|
|
statusCode: HttpStatus.OK,
|
|
});
|
|
} catch (error) {
|
|
throw new HttpException(
|
|
'An error occurred while deleting the subspace',
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
|
|
async deleteSubspaces(
|
|
deleteDtos: DeleteSubspaceDto[],
|
|
queryRunner: QueryRunner,
|
|
) {
|
|
const deleteResults: { uuid: string }[] = [];
|
|
|
|
for (const dto of deleteDtos) {
|
|
const subspace = await this.findOne(dto.subspaceUuid);
|
|
|
|
await queryRunner.manager.update(
|
|
this.subspaceRepository.target,
|
|
{ uuid: dto.subspaceUuid },
|
|
{ disabled: true },
|
|
);
|
|
|
|
if (subspace.tags?.length) {
|
|
const modifyTagDtos = subspace.tags.map((tag) => ({
|
|
uuid: tag.uuid,
|
|
action: ModifyAction.DELETE,
|
|
}));
|
|
await this.tagService.modifyTags(
|
|
modifyTagDtos,
|
|
queryRunner,
|
|
null,
|
|
subspace,
|
|
);
|
|
}
|
|
|
|
if (subspace.devices)
|
|
await this.deviceService.deleteSubspaceDevices(
|
|
subspace.devices,
|
|
queryRunner,
|
|
);
|
|
|
|
deleteResults.push({ uuid: dto.subspaceUuid });
|
|
}
|
|
|
|
return deleteResults;
|
|
}
|
|
|
|
async modifySubSpace(
|
|
subspaceDtos: ModifySubspaceDto[],
|
|
queryRunner: QueryRunner,
|
|
space?: SpaceEntity,
|
|
) {
|
|
for (const subspace of subspaceDtos) {
|
|
switch (subspace.action) {
|
|
case ModifyAction.ADD:
|
|
await this.handleAddAction(subspace, space, queryRunner);
|
|
break;
|
|
case ModifyAction.UPDATE:
|
|
await this.handleUpdateAction(subspace, queryRunner);
|
|
break;
|
|
case ModifyAction.DELETE:
|
|
await this.handleDeleteAction(subspace, queryRunner);
|
|
break;
|
|
default:
|
|
throw new HttpException(
|
|
`Invalid action "${subspace.action}".`,
|
|
HttpStatus.BAD_REQUEST,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
async unlinkModels(
|
|
subspaces: SubspaceEntity[],
|
|
queryRunner: QueryRunner,
|
|
): Promise<void> {
|
|
if (!subspaces || subspaces.length === 0) {
|
|
return;
|
|
}
|
|
try {
|
|
const allTags = subspaces.flatMap((subSpace) => {
|
|
subSpace.subSpaceModel = null;
|
|
return subSpace.tags || [];
|
|
});
|
|
|
|
await this.tagService.unlinkModels(allTags, queryRunner);
|
|
|
|
await queryRunner.manager.save(subspaces);
|
|
} catch (error) {
|
|
if (error instanceof HttpException) throw error;
|
|
throw new HttpException(
|
|
`Failed to unlink subspace models: ${error.message}`,
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
|
|
async getOne(params: GetSubSpaceParam): Promise<BaseResponseDto> {
|
|
await this.validationService.checkCommunityAndProjectSpaceExistence(
|
|
params.communityUuid,
|
|
params.projectUuid,
|
|
params.spaceUuid,
|
|
);
|
|
const subspace = await this.findOne(params.subSpaceUuid);
|
|
return new SuccessResponseDto({
|
|
message: `Successfully retrieved subspace`,
|
|
data: subspace,
|
|
});
|
|
}
|
|
|
|
private async handleAddAction(
|
|
subspace: ModifySubspaceDto,
|
|
space: SpaceEntity,
|
|
queryRunner: QueryRunner,
|
|
): Promise<SubspaceEntity> {
|
|
const createTagDtos: CreateTagDto[] =
|
|
subspace.tags?.map((tag) => ({
|
|
tag: tag.tag as string,
|
|
uuid: tag.uuid,
|
|
productUuid: tag.productUuid as string,
|
|
})) || [];
|
|
const subSpace = await this.createSubspacesFromDto(
|
|
[{ subspaceName: subspace.subspaceName, tags: createTagDtos }],
|
|
space,
|
|
queryRunner,
|
|
);
|
|
return subSpace[0];
|
|
}
|
|
|
|
private async handleUpdateAction(
|
|
modifyDto: ModifySubspaceDto,
|
|
queryRunner: QueryRunner,
|
|
): Promise<void> {
|
|
const subspace = await this.findOne(modifyDto.uuid);
|
|
await this.update(
|
|
queryRunner,
|
|
subspace,
|
|
modifyDto.subspaceName,
|
|
modifyDto.tags,
|
|
);
|
|
}
|
|
|
|
async update(
|
|
queryRunner: QueryRunner,
|
|
subspace: SubspaceEntity,
|
|
subspaceName?: string,
|
|
modifyTagDto?: ModifyTagDto[],
|
|
) {
|
|
await this.updateSubspaceName(queryRunner, subspace, subspaceName);
|
|
|
|
if (modifyTagDto?.length) {
|
|
await this.tagService.modifyTags(
|
|
modifyTagDto,
|
|
queryRunner,
|
|
null,
|
|
subspace,
|
|
);
|
|
}
|
|
}
|
|
|
|
async handleDeleteAction(
|
|
modifyDto: ModifySubspaceDto,
|
|
queryRunner: QueryRunner,
|
|
): Promise<void> {
|
|
const subspace = await this.findOne(modifyDto.uuid);
|
|
|
|
await queryRunner.manager.update(
|
|
this.subspaceRepository.target,
|
|
{ uuid: subspace.uuid },
|
|
{ disabled: true },
|
|
);
|
|
|
|
if (subspace.tags?.length) {
|
|
const modifyTagDtos: CreateTagDto[] = subspace.tags.map((tag) => ({
|
|
uuid: tag.uuid,
|
|
action: ModifyAction.ADD,
|
|
tag: tag.tag,
|
|
productUuid: tag.product.uuid,
|
|
}));
|
|
await this.tagService.moveTags(
|
|
modifyTagDtos,
|
|
queryRunner,
|
|
subspace.space,
|
|
null,
|
|
);
|
|
}
|
|
|
|
if (subspace.devices.length > 0) {
|
|
await this.deviceService.deleteSubspaceDevices(
|
|
subspace.devices,
|
|
queryRunner,
|
|
);
|
|
}
|
|
}
|
|
|
|
private async findOne(subspaceUuid: string): Promise<SubspaceEntity> {
|
|
const subspace = await this.subspaceRepository.findOne({
|
|
where: { uuid: subspaceUuid },
|
|
relations: ['tags', 'space', 'devices', 'tags.product', 'tags.device'],
|
|
});
|
|
if (!subspace) {
|
|
throw new HttpException(
|
|
`SubspaceModel with UUID ${subspaceUuid} not found.`,
|
|
HttpStatus.NOT_FOUND,
|
|
);
|
|
}
|
|
return subspace;
|
|
}
|
|
|
|
async updateSubspaceName(
|
|
queryRunner: QueryRunner,
|
|
subSpace: SubspaceEntity,
|
|
subspaceName?: string,
|
|
): Promise<void> {
|
|
if (subspaceName) {
|
|
subSpace.subspaceName = subspaceName;
|
|
await queryRunner.manager.save(subSpace);
|
|
}
|
|
}
|
|
|
|
private async checkForDuplicateNames(names: string[]): Promise<void> {
|
|
const seenNames = new Set<string>();
|
|
const duplicateNames = new Set<string>();
|
|
|
|
for (const name of names) {
|
|
if (!seenNames.add(name)) {
|
|
duplicateNames.add(name);
|
|
}
|
|
}
|
|
|
|
if (duplicateNames.size > 0) {
|
|
throw new HttpException(
|
|
`Duplicate subspace names found: ${[...duplicateNames].join(', ')}`,
|
|
HttpStatus.CONFLICT,
|
|
);
|
|
}
|
|
}
|
|
|
|
private async checkExistingNamesInSpace(
|
|
names: string[],
|
|
space: SpaceEntity,
|
|
): Promise<void> {
|
|
const existingNames = await this.subspaceRepository.find({
|
|
select: ['subspaceName'],
|
|
where: {
|
|
subspaceName: In(names),
|
|
space: {
|
|
uuid: space.uuid,
|
|
},
|
|
disabled: false,
|
|
},
|
|
});
|
|
|
|
if (existingNames.length > 0) {
|
|
const existingNamesList = existingNames
|
|
.map((e) => e.subspaceName)
|
|
.join(', ');
|
|
throw new HttpException(
|
|
`Subspace names already exist in the space: ${existingNamesList}`,
|
|
HttpStatus.BAD_REQUEST,
|
|
);
|
|
}
|
|
}
|
|
|
|
private async validateName(
|
|
names: string[],
|
|
space: SpaceEntity,
|
|
): Promise<void> {
|
|
await this.checkForDuplicateNames(names);
|
|
await this.checkExistingNamesInSpace(names, space);
|
|
}
|
|
}
|