mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-07-10 07:07:21 +00:00

* task: add getCommunitiesV2 * task: update getOneSpace API to match revamp structure * refactor: implement modifications to pace management APIs * refactor: remove space link
521 lines
16 KiB
TypeScript
521 lines
16 KiB
TypeScript
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
|
import {
|
|
SpaceModelEntity,
|
|
SpaceModelProductAllocationEntity,
|
|
SubspaceModelEntity,
|
|
SubspaceModelProductAllocationEntity,
|
|
SubspaceModelRepository,
|
|
} from '@app/common/modules/space-model';
|
|
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
|
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
|
import { ModifySubspaceModelDto } from 'src/space-model/dtos/subspaces-model-dtos';
|
|
import {
|
|
IRelocatedAllocation,
|
|
ISingleSubspaceModel,
|
|
ISubspaceModelUpdates,
|
|
} from 'src/space-model/interfaces';
|
|
import { ModifyTagDto } from 'src/space/dtos/tag/modify-tag.dto';
|
|
import { SubSpaceService } from 'src/space/services/subspace';
|
|
import { ProcessTagDto } from 'src/tags/dtos';
|
|
import { TagService } from 'src/tags/services';
|
|
import { In, Not, QueryFailedError, QueryRunner } from 'typeorm';
|
|
import { CreateSubspaceModelDto } from '../../dtos';
|
|
import { SubspaceModelProductAllocationService } from './subspace-model-product-allocation.service';
|
|
|
|
@Injectable()
|
|
export class SubSpaceModelService {
|
|
constructor(
|
|
private readonly subspaceModelRepository: SubspaceModelRepository,
|
|
private readonly tagService: TagService,
|
|
private readonly productAllocationService: SubspaceModelProductAllocationService,
|
|
private readonly subspaceService: SubSpaceService,
|
|
) {}
|
|
|
|
async createModels(
|
|
spaceModel: SpaceModelEntity,
|
|
dtos: CreateSubspaceModelDto[],
|
|
queryRunner: QueryRunner,
|
|
) {
|
|
try {
|
|
this.validateNamesInDTO(dtos.map((dto) => dto.subspaceName));
|
|
|
|
const subspaceEntities: SubspaceModelEntity[] = dtos.map((dto) =>
|
|
queryRunner.manager.create(this.subspaceModelRepository.target, {
|
|
subspaceName: dto.subspaceName,
|
|
spaceModel,
|
|
}),
|
|
);
|
|
|
|
const savedSubspaces = await queryRunner.manager.save(subspaceEntities);
|
|
|
|
for (const [index, dto] of dtos.entries()) {
|
|
const subspaceModel = savedSubspaces[index];
|
|
|
|
const processedTags = await this.tagService.upsertTags(
|
|
dto.tags.map((tag) => ({
|
|
tagName: tag.name,
|
|
productUuid: tag.productUuid,
|
|
tagUuid: tag.uuid,
|
|
})),
|
|
spaceModel.project.uuid,
|
|
queryRunner,
|
|
);
|
|
|
|
await this.productAllocationService.createProductAllocations(
|
|
subspaceModel,
|
|
spaceModel,
|
|
processedTags,
|
|
queryRunner,
|
|
);
|
|
}
|
|
|
|
return savedSubspaces;
|
|
} catch (error) {
|
|
throw new HttpException(
|
|
error instanceof HttpException
|
|
? error.message
|
|
: 'An unexpected error occurred while creating subspace models',
|
|
error instanceof HttpException
|
|
? error.getStatus()
|
|
: HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
|
|
async handleAddAction(
|
|
dtos: ModifySubspaceModelDto[],
|
|
spaceModel: SpaceModelEntity,
|
|
queryRunner: QueryRunner,
|
|
spaces?: SpaceEntity[],
|
|
): Promise<ISingleSubspaceModel[]> {
|
|
if (!dtos.length) return [];
|
|
|
|
const subspaceNames = dtos.map((dto) => dto.subspaceName);
|
|
await this.checkDuplicateNamesBatch(subspaceNames, spaceModel.uuid);
|
|
|
|
const subspaceEntities = dtos.map((dto) =>
|
|
queryRunner.manager.create(SubspaceModelEntity, {
|
|
subspaceName: dto.subspaceName,
|
|
spaceModel,
|
|
}),
|
|
);
|
|
|
|
const savedSubspaces = await queryRunner.manager.save(subspaceEntities);
|
|
|
|
if (spaces) {
|
|
await this.subspaceService.createSubSpaceFromModel(
|
|
savedSubspaces,
|
|
spaces,
|
|
queryRunner,
|
|
);
|
|
}
|
|
|
|
return savedSubspaces.map((subspace, index) => ({
|
|
subspaceModel: subspace,
|
|
action: ModifyAction.ADD,
|
|
tags: dtos[index].tags ? dtos[index].tags : [],
|
|
}));
|
|
}
|
|
|
|
async modifySubspaceModels(
|
|
dtos: ModifySubspaceModelDto[],
|
|
spaceModel: SpaceModelEntity,
|
|
queryRunner: QueryRunner,
|
|
projectUuid: string,
|
|
spaceTagUpdateDtos?: ModifyTagDto[],
|
|
spaces?: SpaceEntity[],
|
|
): Promise<ISubspaceModelUpdates> {
|
|
try {
|
|
if (!dtos || dtos.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const addDtos = dtos.filter((dto) => dto.action === ModifyAction.ADD);
|
|
const combinedDtos = dtos.filter(
|
|
(dto) => dto.action !== ModifyAction.ADD,
|
|
);
|
|
const deleteDtos = dtos.filter(
|
|
(dto) => dto.action === ModifyAction.DELETE,
|
|
);
|
|
|
|
const updatedSubspaces = await this.updateSubspaceModel(
|
|
combinedDtos,
|
|
spaceModel,
|
|
queryRunner,
|
|
);
|
|
const createdSubspaces = await this.handleAddAction(
|
|
addDtos,
|
|
spaceModel,
|
|
queryRunner,
|
|
spaces,
|
|
);
|
|
const combineModels = [...createdSubspaces, ...updatedSubspaces];
|
|
|
|
const updatedAllocations =
|
|
await this.productAllocationService.updateAllocations(
|
|
combineModels,
|
|
projectUuid,
|
|
queryRunner,
|
|
spaceModel,
|
|
// spaceTagUpdateDtos,
|
|
);
|
|
|
|
const deletedSubspaces = await this.deleteSubspaceModels(
|
|
deleteDtos,
|
|
queryRunner,
|
|
spaceModel,
|
|
spaceTagUpdateDtos,
|
|
);
|
|
return {
|
|
subspaceModels: [
|
|
createdSubspaces ?? [],
|
|
updatedSubspaces ?? [],
|
|
deletedSubspaces ?? [],
|
|
]
|
|
.filter((arr) => arr.length > 0)
|
|
.flat(),
|
|
updatedAllocations: updatedAllocations,
|
|
};
|
|
} catch (error) {
|
|
console.error('Error in modifySubspaceModels:', error);
|
|
throw new HttpException(
|
|
{
|
|
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
|
message: 'An error occurred while modifying subspace models',
|
|
error: error.message,
|
|
},
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
|
|
async deleteSubspaceModels(
|
|
deleteDtos: ModifySubspaceModelDto[],
|
|
queryRunner: QueryRunner,
|
|
spaceModel: SpaceModelEntity,
|
|
spaceTagUpdateDtos?: ModifyTagDto[],
|
|
): Promise<ISingleSubspaceModel[]> {
|
|
try {
|
|
if (!deleteDtos || deleteDtos.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
const deleteResults = [];
|
|
const subspaceUuids = deleteDtos.map((dto) => dto.uuid).filter(Boolean);
|
|
|
|
if (subspaceUuids.length === 0) {
|
|
throw new Error('Invalid subspace UUIDs provided.');
|
|
}
|
|
|
|
const subspaces = await this.getSubspacesByUuids(
|
|
queryRunner,
|
|
subspaceUuids,
|
|
);
|
|
|
|
if (!subspaces.length) return deleteResults;
|
|
|
|
await queryRunner.manager.update(
|
|
SubspaceModelEntity,
|
|
{ uuid: In(subspaceUuids) },
|
|
{ disabled: true },
|
|
);
|
|
|
|
const allocationsToRemove = subspaces.flatMap((subspace) =>
|
|
(subspace.productAllocations || []).map((allocation) => ({
|
|
...allocation,
|
|
subspaceModel: subspace,
|
|
})),
|
|
);
|
|
|
|
const relocatedAllocationsMap = new Map<string, IRelocatedAllocation[]>();
|
|
if (allocationsToRemove.length > 0) {
|
|
const spaceAllocationsMap = new Map<
|
|
string,
|
|
SpaceModelProductAllocationEntity
|
|
>();
|
|
|
|
for (const allocation of allocationsToRemove) {
|
|
// const product = allocation.product;
|
|
// const tags = allocation.tags;
|
|
// const subspaceUuid = allocation.subspaceModel.uuid;
|
|
// const spaceAllocationKey = `${spaceModel.uuid}-${product.uuid}`;
|
|
// if (!spaceAllocationsMap.has(spaceAllocationKey)) {
|
|
// const spaceAllocation = await queryRunner.manager.findOne(
|
|
// SpaceModelProductAllocationEntity,
|
|
// {
|
|
// where: {
|
|
// spaceModel: { uuid: spaceModel.uuid },
|
|
// product: { uuid: product.uuid },
|
|
// },
|
|
// relations: ['tags'],
|
|
// },
|
|
// );
|
|
// if (spaceAllocation) {
|
|
// spaceAllocationsMap.set(spaceAllocationKey, spaceAllocation);
|
|
// }
|
|
// }
|
|
// const movedToAlreadyExistingSpaceAllocations: IRelocatedAllocation[] =
|
|
// [];
|
|
// const spaceAllocation = spaceAllocationsMap.get(spaceAllocationKey);
|
|
// if (spaceAllocation) {
|
|
// const existingTagUuids = new Set(
|
|
// spaceAllocation.tags.map((tag) => tag.uuid),
|
|
// );
|
|
// const newTags = tags.filter(
|
|
// (tag) => !existingTagUuids.has(tag.uuid),
|
|
// );
|
|
// if (newTags.length > 0) {
|
|
// movedToAlreadyExistingSpaceAllocations.push({
|
|
// tags: newTags,
|
|
// allocation: spaceAllocation,
|
|
// });
|
|
// spaceAllocation.tags.push(...newTags);
|
|
// await queryRunner.manager.save(spaceAllocation);
|
|
// }
|
|
// } else {
|
|
// let tagsToAdd = [...tags];
|
|
// if (spaceTagUpdateDtos && spaceTagUpdateDtos.length > 0) {
|
|
// const spaceTagDtosToAdd = spaceTagUpdateDtos.filter(
|
|
// (dto) => dto.action === ModifyAction.ADD,
|
|
// );
|
|
// tagsToAdd = tagsToAdd.filter(
|
|
// (tag) =>
|
|
// !spaceTagDtosToAdd.some(
|
|
// (addDto) =>
|
|
// (addDto.name && addDto.name === tag.name) ||
|
|
// (addDto.newTagUuid && addDto.newTagUuid === tag.uuid),
|
|
// ),
|
|
// );
|
|
// }
|
|
// if (tagsToAdd.length > 0) {
|
|
// const newSpaceAllocation = queryRunner.manager.create(
|
|
// SpaceModelProductAllocationEntity,
|
|
// {
|
|
// spaceModel: spaceModel,
|
|
// product: product,
|
|
// tags: tags,
|
|
// },
|
|
// );
|
|
// movedToAlreadyExistingSpaceAllocations.push({
|
|
// allocation: newSpaceAllocation,
|
|
// tags: tags,
|
|
// });
|
|
// await queryRunner.manager.save(newSpaceAllocation);
|
|
// }
|
|
// }
|
|
// if (movedToAlreadyExistingSpaceAllocations.length > 0) {
|
|
// if (!relocatedAllocationsMap.has(subspaceUuid)) {
|
|
// relocatedAllocationsMap.set(subspaceUuid, []);
|
|
// }
|
|
// relocatedAllocationsMap
|
|
// .get(subspaceUuid)
|
|
// .push(...movedToAlreadyExistingSpaceAllocations);
|
|
// }
|
|
}
|
|
|
|
await queryRunner.manager.remove(
|
|
SubspaceModelProductAllocationEntity,
|
|
allocationsToRemove,
|
|
);
|
|
}
|
|
|
|
await queryRunner.manager
|
|
.createQueryBuilder()
|
|
.delete()
|
|
.from('subspace_model_product_tags')
|
|
.where(
|
|
'subspace_model_product_allocation_uuid NOT IN (' +
|
|
queryRunner.manager
|
|
.createQueryBuilder()
|
|
.select('allocation.uuid')
|
|
.from(SubspaceModelProductAllocationEntity, 'allocation')
|
|
.getQuery() +
|
|
')',
|
|
)
|
|
.execute();
|
|
|
|
deleteResults.push(...subspaceUuids.map((uuid) => ({ uuid })));
|
|
|
|
return subspaces.map((subspace) => ({
|
|
subspaceModel: subspace,
|
|
action: ModifyAction.DELETE,
|
|
relocatedAllocations: relocatedAllocationsMap.get(subspace.uuid) || [],
|
|
}));
|
|
} catch (error) {
|
|
if (error instanceof QueryFailedError) {
|
|
throw new HttpException(
|
|
`Database query failed: ${error.message}`,
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
} else if (error instanceof TypeError) {
|
|
throw new HttpException(
|
|
`Invalid data encountered: ${error.message}`,
|
|
HttpStatus.BAD_REQUEST,
|
|
);
|
|
} else {
|
|
throw new HttpException(
|
|
`Unexpected error during subspace deletion: ${error.message}`,
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
async clearModels(subspaceUuids: string[], queryRunner: QueryRunner) {
|
|
try {
|
|
const subspaces = await this.getSubspacesByUuids(
|
|
queryRunner,
|
|
subspaceUuids,
|
|
);
|
|
if (!subspaces.length) return;
|
|
await queryRunner.manager.update(
|
|
SubspaceModelEntity,
|
|
{ uuid: In(subspaceUuids) },
|
|
{ disabled: true },
|
|
);
|
|
|
|
await this.productAllocationService.clearAllAllocations(
|
|
subspaceUuids,
|
|
queryRunner,
|
|
);
|
|
} catch (error) {
|
|
throw new HttpException(
|
|
error instanceof HttpException
|
|
? error.message
|
|
: 'An unexpected error occurred while clearing subspace models',
|
|
error instanceof HttpException
|
|
? error.getStatus()
|
|
: HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
|
|
async updateSubspaceModel(
|
|
dtos: ModifySubspaceModelDto[],
|
|
spaceModel: SpaceModelEntity,
|
|
queryRunner: QueryRunner,
|
|
): Promise<ISingleSubspaceModel[]> {
|
|
if (!dtos.length) return [];
|
|
|
|
const updatedSubspaces: {
|
|
subspaceModel: SubspaceModelEntity;
|
|
tags: ModifyTagDto[];
|
|
action: ModifyAction.UPDATE;
|
|
}[] = [];
|
|
|
|
for (const dto of dtos) {
|
|
if (!dto.subspaceName) continue;
|
|
|
|
const existingSubspace = await queryRunner.manager.findOne(
|
|
this.subspaceModelRepository.target,
|
|
{ where: { uuid: dto.uuid } },
|
|
);
|
|
|
|
if (existingSubspace) {
|
|
if (existingSubspace.subspaceName !== dto.subspaceName) {
|
|
await this.checkDuplicateNames(dto.subspaceName, spaceModel.uuid);
|
|
existingSubspace.subspaceName = dto.subspaceName;
|
|
await queryRunner.manager.save(existingSubspace);
|
|
}
|
|
|
|
updatedSubspaces.push({
|
|
subspaceModel: existingSubspace,
|
|
tags: dto.tags ?? [],
|
|
action: ModifyAction.UPDATE,
|
|
});
|
|
}
|
|
}
|
|
|
|
return updatedSubspaces;
|
|
}
|
|
|
|
private async checkDuplicateNames(
|
|
subspaceName: string,
|
|
spaceModelUuid: string,
|
|
excludeUuid?: string,
|
|
): Promise<void> {
|
|
const duplicateSubspace = await this.subspaceModelRepository.findOne({
|
|
where: {
|
|
subspaceName,
|
|
spaceModel: {
|
|
uuid: spaceModelUuid,
|
|
},
|
|
disabled: false,
|
|
...(excludeUuid && { uuid: Not(excludeUuid) }),
|
|
},
|
|
});
|
|
|
|
if (duplicateSubspace) {
|
|
throw new HttpException(
|
|
`A subspace with the name '${subspaceName}' already exists within the same space model. ${spaceModelUuid}`,
|
|
HttpStatus.CONFLICT,
|
|
);
|
|
}
|
|
}
|
|
|
|
async checkDuplicateNamesBatch(
|
|
subspaceNames: string[],
|
|
spaceModelUuid: string,
|
|
): Promise<void> {
|
|
if (!subspaceNames.length) return;
|
|
|
|
const existingSubspaces = await this.subspaceModelRepository.find({
|
|
where: {
|
|
subspaceName: In(subspaceNames),
|
|
spaceModel: { uuid: spaceModelUuid },
|
|
disabled: false,
|
|
},
|
|
select: ['subspaceName'],
|
|
});
|
|
|
|
if (existingSubspaces.length > 0) {
|
|
const duplicateNames = existingSubspaces.map(
|
|
(subspace) => subspace.subspaceName,
|
|
);
|
|
throw new HttpException(
|
|
`Duplicate subspace names found: ${duplicateNames.join(', ')}`,
|
|
HttpStatus.CONFLICT,
|
|
);
|
|
}
|
|
}
|
|
|
|
private async validateNamesInDTO(names: string[]) {
|
|
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 model names found: ${[...duplicateNames].join(', ')}`,
|
|
HttpStatus.CONFLICT,
|
|
);
|
|
}
|
|
}
|
|
|
|
extractTagsFromSubspaceModels(
|
|
subspaceModels: CreateSubspaceModelDto[],
|
|
): ProcessTagDto[] {
|
|
return subspaceModels.flatMap((subspace) => subspace.tags || []);
|
|
}
|
|
|
|
private async getSubspacesByUuids(
|
|
queryRunner: QueryRunner,
|
|
subspaceUuids: string[],
|
|
): Promise<SubspaceModelEntity[]> {
|
|
return await queryRunner.manager.find(SubspaceModelEntity, {
|
|
where: { uuid: In(subspaceUuids) },
|
|
relations: [
|
|
'productAllocations',
|
|
'productAllocations.product',
|
|
'productAllocations.tags',
|
|
'spaceModel',
|
|
],
|
|
});
|
|
}
|
|
}
|