added space delete

This commit is contained in:
hannathkadher
2024-12-24 14:51:06 +04:00
parent cb2778dce5
commit 2e46176fd5
9 changed files with 200 additions and 44 deletions

View File

@ -199,6 +199,12 @@ export class SpaceModelService {
); );
} }
if (spaceModel.tags?.length) {
const deleteSpaceTagsDtos = spaceModel.tags.map((tag) => tag.uuid);
await this.tagModelService.deleteTags(deleteSpaceTagsDtos, queryRunner);
}
await queryRunner.manager.update( await queryRunner.manager.update(
this.spaceModelRepository.target, this.spaceModelRepository.target,
{ uuid: param.spaceModelUuid }, { uuid: param.spaceModelUuid },
@ -248,6 +254,7 @@ export class SpaceModelService {
uuid, uuid,
disabled: true, disabled: true,
}, },
relations: ['subspaceModels', 'tags'],
}); });
if (!spaceModel) { if (!spaceModel) {
throw new HttpException('space model not found', HttpStatus.NOT_FOUND); throw new HttpException('space model not found', HttpStatus.NOT_FOUND);

View File

@ -5,7 +5,7 @@ import {
} from '@app/common/modules/space-model'; } from '@app/common/modules/space-model';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateSubspaceModelDto, CreateTagModelDto } from '../../dtos'; import { CreateSubspaceModelDto, CreateTagModelDto } from '../../dtos';
import { QueryRunner } from 'typeorm'; import { In, QueryRunner } from 'typeorm';
import { IDeletedSubsaceModelInterface } from 'src/space-model/interfaces'; import { IDeletedSubsaceModelInterface } from 'src/space-model/interfaces';
import { import {
DeleteSubspaceModelDto, DeleteSubspaceModelDto,
@ -27,7 +27,7 @@ export class SubSpaceModelService {
queryRunner: QueryRunner, queryRunner: QueryRunner,
otherTags?: CreateTagModelDto[], otherTags?: CreateTagModelDto[],
): Promise<SubspaceModelEntity[]> { ): Promise<SubspaceModelEntity[]> {
this.validateInputDtos(subSpaceModelDtos); this.validateInputDtos(subSpaceModelDtos, spaceModel);
const subspaces = subSpaceModelDtos.map((subspaceDto) => const subspaces = subSpaceModelDtos.map((subspaceDto) =>
queryRunner.manager.create(this.subspaceModelRepository.target, { queryRunner.manager.create(this.subspaceModelRepository.target, {
@ -41,13 +41,18 @@ export class SubSpaceModelService {
await Promise.all( await Promise.all(
subSpaceModelDtos.map(async (dto, index) => { subSpaceModelDtos.map(async (dto, index) => {
const subspace = savedSubspaces[index]; const subspace = savedSubspaces[index];
const otherDtoTags = subSpaceModelDtos
.filter((_, i) => i !== index)
.flatMap((otherDto) => otherDto.tags || []);
if (dto.tags?.length) { if (dto.tags?.length) {
subspace.tags = await this.tagModelService.createTags( subspace.tags = await this.tagModelService.createTags(
dto.tags, dto.tags,
queryRunner, queryRunner,
null, null,
subspace, subspace,
otherTags, [...(otherTags || []), ...otherDtoTags],
); );
} }
}), }),
@ -194,34 +199,61 @@ export class SubSpaceModelService {
return subspace; return subspace;
} }
private validateInputDtos(subSpaceModelDtos: CreateSubspaceModelDto[]): void { private validateInputDtos(
subSpaceModelDtos: CreateSubspaceModelDto[],
spaceModel: SpaceModelEntity,
): void {
if (subSpaceModelDtos.length === 0) { if (subSpaceModelDtos.length === 0) {
throw new HttpException( throw new HttpException(
'Subspace models cannot be empty.', 'Subspace models cannot be empty.',
HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST,
); );
} }
this.validateName(subSpaceModelDtos.map((dto) => dto.subspaceName)); this.validateName(
subSpaceModelDtos.map((dto) => dto.subspaceName),
spaceModel,
);
} }
private validateName(names: string[]): void { private async validateName(
names: string[],
spaceModel: SpaceModelEntity,
): Promise<void> {
const seenNames = new Set<string>(); const seenNames = new Set<string>();
const duplicateNames = new Set<string>(); const duplicateNames = new Set<string>();
for (const name of names) { for (const name of names) {
if (seenNames.has(name)) { if (!seenNames.add(name)) {
duplicateNames.add(name); duplicateNames.add(name);
} else {
seenNames.add(name);
} }
} }
if (duplicateNames.size > 0) { if (duplicateNames.size > 0) {
throw new HttpException( throw new HttpException(
`Duplicate subspace names found: ${[...duplicateNames].join(', ')}`, `Duplicate subspace model names found: ${[...duplicateNames].join(', ')}`,
HttpStatus.CONFLICT, HttpStatus.CONFLICT,
); );
} }
const existingNames = await this.subspaceModelRepository.find({
select: ['subspaceName'],
where: {
subspaceName: In([...seenNames]),
spaceModel: {
uuid: spaceModel.uuid,
},
},
});
if (existingNames.length > 0) {
const existingNamesList = existingNames
.map((e) => e.subspaceName)
.join(', ');
throw new HttpException(
`Subspace model names already exist in the space: ${existingNamesList}`,
HttpStatus.BAD_REQUEST,
);
}
} }
private async updateSubspaceName( private async updateSubspaceName(

View File

@ -180,18 +180,24 @@ export class TagModelService {
productUuid: string, productUuid: string,
spaceModel: SpaceModelEntity, spaceModel: SpaceModelEntity,
): Promise<void> { ): Promise<void> {
const isTagInSpaceModel = await this.tagModelRepository.exists({ const tagExists = await this.tagModelRepository.exists({
where: { tag, spaceModel, product: { uuid: productUuid } }, where: [
}); {
const isTagInSubspaceModel = await this.tagModelRepository.exists({ tag,
where: { spaceModel: { uuid: spaceModel.uuid },
tag, product: { uuid: productUuid },
subspaceModel: { spaceModel }, disabled: false,
product: { uuid: productUuid }, },
}, {
tag,
subspaceModel: { spaceModel: { uuid: spaceModel.uuid } },
product: { uuid: productUuid },
disabled: false,
},
],
}); });
if (isTagInSpaceModel || isTagInSubspaceModel) { if (tagExists) {
throw new HttpException(`Tag can't be reused`, HttpStatus.CONFLICT); throw new HttpException(`Tag can't be reused`, HttpStatus.CONFLICT);
} }
} }

View File

@ -65,7 +65,7 @@ export class SubSpaceController {
}) })
@Get(':subSpaceUuid') @Get(':subSpaceUuid')
async findOne(@Param() params: GetSubSpaceParam): Promise<BaseResponseDto> { async findOne(@Param() params: GetSubSpaceParam): Promise<BaseResponseDto> {
return this.subSpaceService.findOne(params); return this.subSpaceService.getOne(params);
} }
@ApiBearerAuth() @ApiBearerAuth()

View File

@ -30,11 +30,13 @@ export class UpdateSpaceDto {
@ApiProperty({ description: 'X position on canvas', example: 120 }) @ApiProperty({ description: 'X position on canvas', example: 120 })
@IsNumber() @IsNumber()
x: number; @IsOptional()
x?: number;
@ApiProperty({ description: 'Y position on canvas', example: 200 }) @ApiProperty({ description: 'Y position on canvas', example: 200 })
@IsNumber() @IsNumber()
y: number; @IsOptional()
y?: number;
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'List of subspace modifications (add/update/delete)', description: 'List of subspace modifications (add/update/delete)',

View File

@ -44,6 +44,7 @@ export class ValidationService {
async validateSpace(spaceUuid: string): Promise<SpaceEntity> { async validateSpace(spaceUuid: string): Promise<SpaceEntity> {
const space = await this.spaceRepository.findOne({ const space = await this.spaceRepository.findOne({
where: { uuid: spaceUuid }, where: { uuid: spaceUuid },
relations: ['subspaces', 'tags'],
}); });
if (!space) { if (!space) {

View File

@ -58,7 +58,7 @@ export class SpaceService {
projectUuid, projectUuid,
); );
this.validateSpaceCreation(spaceModelUuid); this.validateSpaceCreation(addSpaceDto, spaceModelUuid);
const parent = parentUuid const parent = parentUuid
? await this.validationService.validateSpace(parentUuid) ? await this.validationService.validateSpace(parentUuid)
@ -91,6 +91,7 @@ export class SpaceService {
subspaces, subspaces,
newSpace, newSpace,
queryRunner, queryRunner,
tags,
); );
} }
@ -189,6 +190,10 @@ export class SpaceService {
} }
async delete(params: GetSpaceParam): Promise<BaseResponseDto> { async delete(params: GetSpaceParam): Promise<BaseResponseDto> {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try { try {
const { communityUuid, spaceUuid, projectUuid } = params; const { communityUuid, spaceUuid, projectUuid } = params;
@ -205,14 +210,38 @@ export class SpaceService {
HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST,
); );
} }
// Delete the space
await this.spaceRepository.remove(space); if (space.tags?.length) {
const deleteSpaceTagsDtos = space.tags.map((tag) => tag.uuid);
await this.tagService.deleteTags(deleteSpaceTagsDtos, queryRunner);
}
if (space.subspaces?.length) {
const deleteSubspaceDtos = space.subspaces.map((subspace) => ({
subspaceUuid: subspace.uuid,
}));
await this.subSpaceService.deleteSubspaces(
deleteSubspaceDtos,
queryRunner,
);
}
await queryRunner.manager.update(
this.spaceRepository.target,
{ uuid: params.spaceUuid },
{ disabled: true },
);
await queryRunner.commitTransaction();
return new SuccessResponseDto({ return new SuccessResponseDto({
message: `Space with ID ${spaceUuid} successfully deleted`, message: `Space with ID ${spaceUuid} successfully deleted`,
statusCode: HttpStatus.OK, statusCode: HttpStatus.OK,
}); });
} catch (error) { } catch (error) {
await queryRunner.rollbackTransaction();
if (error instanceof HttpException) { if (error instanceof HttpException) {
throw error; throw error;
} }
@ -220,6 +249,8 @@ export class SpaceService {
'An error occurred while deleting the space', 'An error occurred while deleting the space',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
); );
} finally {
await queryRunner.release();
} }
} }
@ -384,8 +415,11 @@ export class SpaceService {
return rootSpaces; return rootSpaces;
} }
private validateSpaceCreation(spaceModelUuid?: string) { private validateSpaceCreation(
if (spaceModelUuid) { addSpaceDto: AddSpaceDto,
spaceModelUuid?: string,
) {
if (spaceModelUuid && (addSpaceDto.tags || addSpaceDto.subspaces)) {
throw new HttpException( throw new HttpException(
'For space creation choose either space model or products and subspace', 'For space creation choose either space model or products and subspace',
HttpStatus.CONFLICT, HttpStatus.CONFLICT,

View File

@ -15,7 +15,7 @@ import {
} from '@app/common/models/typeOrmCustom.model'; } from '@app/common/models/typeOrmCustom.model';
import { PageResponse } from '@app/common/dto/pagination.response.dto'; import { PageResponse } from '@app/common/dto/pagination.response.dto';
import { SubspaceDto } from '@app/common/modules/space/dtos'; import { SubspaceDto } from '@app/common/modules/space/dtos';
import { QueryRunner } from 'typeorm'; import { In, QueryRunner } from 'typeorm';
import { import {
SpaceEntity, SpaceEntity,
SubspaceEntity, SubspaceEntity,
@ -84,6 +84,11 @@ export class SubSpaceService {
otherTags?: CreateTagDto[], otherTags?: CreateTagDto[],
): Promise<SubspaceEntity[]> { ): Promise<SubspaceEntity[]> {
try { try {
await this.validateName(
addSubspaceDtos.map((dto) => dto.subspaceName),
space,
);
const subspaceData = addSubspaceDtos.map((dto) => ({ const subspaceData = addSubspaceDtos.map((dto) => ({
subspaceName: dto.subspaceName, subspaceName: dto.subspaceName,
space, space,
@ -93,6 +98,9 @@ export class SubSpaceService {
await Promise.all( await Promise.all(
addSubspaceDtos.map(async (dto, index) => { addSubspaceDtos.map(async (dto, index) => {
const otherDtoTags = addSubspaceDtos
.filter((_, i) => i !== index)
.flatMap((otherDto) => otherDto.tags || []);
const subspace = subspaces[index]; const subspace = subspaces[index];
if (dto.tags?.length) { if (dto.tags?.length) {
subspace.tags = await this.tagService.createTags( subspace.tags = await this.tagService.createTags(
@ -100,15 +108,20 @@ export class SubSpaceService {
queryRunner, queryRunner,
null, null,
subspace, subspace,
otherTags, [...(otherTags || []), ...otherDtoTags],
); );
} }
}), }),
); );
return subspaces; return subspaces;
} catch (error) { } catch (error) {
throw new Error( if (error instanceof HttpException) {
`Transaction failed: Unable to create subspaces and products. ${error.message}`, throw error;
}
throw new HttpException(
'Failed to save subspaces due to an unexpected error.',
HttpStatus.INTERNAL_SERVER_ERROR,
); );
} }
} }
@ -304,6 +317,19 @@ export class SubSpaceService {
} }
} }
async getOne(params: GetSubSpaceParam): Promise<BaseResponseDto> {
await this.validationService.validateSpaceWithinCommunityAndProject(
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( private async handleAddAction(
subspace: ModifySubspaceDto, subspace: ModifySubspaceDto,
space: SpaceEntity, space: SpaceEntity,
@ -372,7 +398,7 @@ export class SubSpaceService {
private async findOne(subspaceUuid: string): Promise<SubspaceEntity> { private async findOne(subspaceUuid: string): Promise<SubspaceEntity> {
const subspace = await this.subspaceRepository.findOne({ const subspace = await this.subspaceRepository.findOne({
where: { uuid: subspaceUuid }, where: { uuid: subspaceUuid },
relations: ['tags'], relations: ['tags', 'space'],
}); });
if (!subspace) { if (!subspace) {
throw new HttpException( throw new HttpException(
@ -393,4 +419,45 @@ export class SubSpaceService {
await queryRunner.manager.save(subSpace); await queryRunner.manager.save(subSpace);
} }
} }
private async validateName(
names: string[],
space: SpaceEntity,
): 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,
);
}
const existingNames = await this.subspaceRepository.find({
select: ['subspaceName'],
where: {
subspaceName: In([...seenNames]),
space: {
uuid: space.uuid,
},
},
});
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,
);
}
}
} }

View File

@ -135,7 +135,6 @@ export class TagService {
subspace?: SubspaceEntity, subspace?: SubspaceEntity,
): Promise<void> { ): Promise<void> {
try { try {
console.log(tags);
for (const tag of tags) { for (const tag of tags) {
if (tag.action === ModifyAction.ADD) { if (tag.action === ModifyAction.ADD) {
const createTagDto: CreateTagDto = { const createTagDto: CreateTagDto = {
@ -176,18 +175,26 @@ export class TagService {
productUuid: string, productUuid: string,
space: SpaceEntity, space: SpaceEntity,
): Promise<void> { ): Promise<void> {
const isTagInSpace = await this.tagRepository.exists({ const { uuid: spaceUuid } = space;
where: { tag, space, product: { uuid: productUuid } },
}); const tagExists = await this.tagRepository.exists({
const isTagInSubspace = await this.tagRepository.exists({ where: [
where: { {
tag, tag,
subspace: { space }, space: { uuid: spaceUuid },
product: { uuid: productUuid }, product: { uuid: productUuid },
}, disabled: false,
},
{
tag,
subspace: { space: { uuid: spaceUuid } },
product: { uuid: productUuid },
disabled: false,
},
],
}); });
if (isTagInSpace || isTagInSubspace) { if (tagExists) {
throw new HttpException(`Tag can't be reused`, HttpStatus.CONFLICT); throw new HttpException(`Tag can't be reused`, HttpStatus.CONFLICT);
} }
} }