From c1aa5232c75f635842f98fcf4c2324613ddf258c Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 24 Jan 2025 20:56:50 +0400 Subject: [PATCH 1/3] fixed delete --- .../tag-model-dtos/create-tag-model.dto.ts | 12 +- .../services/space-model.service.ts | 14 +- .../subspace/subspace-model.service.ts | 4 +- src/space-model/services/tag-model.service.ts | 124 +++++++++++++++++- 4 files changed, 143 insertions(+), 11 deletions(-) diff --git a/src/space-model/dtos/tag-model-dtos/create-tag-model.dto.ts b/src/space-model/dtos/tag-model-dtos/create-tag-model.dto.ts index 5f4ec66..c271eef 100644 --- a/src/space-model/dtos/tag-model-dtos/create-tag-model.dto.ts +++ b/src/space-model/dtos/tag-model-dtos/create-tag-model.dto.ts @@ -1,5 +1,5 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class CreateTagModelDto { @ApiProperty({ @@ -10,6 +10,14 @@ export class CreateTagModelDto { @IsString() tag: string; + @ApiPropertyOptional({ + description: 'UUID of the tag model (required for update/delete)', + example: '123e4567-e89b-12d3-a456-426614174000', + }) + @IsOptional() + @IsString() + uuid?: string; + @ApiProperty({ description: 'ID of the product associated with the tag', example: '123e4567-e89b-12d3-a456-426614174000', diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 5ef531c..98d6c78 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -89,6 +89,8 @@ export class SpaceModelService { statusCode: HttpStatus.CREATED, }); } catch (error) { + console.log(JSON.stringify(createSpaceModelDto)); + await queryRunner.rollbackTransaction(); const errorMessage = @@ -177,7 +179,6 @@ export class SpaceModelService { async update(dto: UpdateSpaceModelDto, param: SpaceModelParam) { const queryRunner = this.dataSource.createQueryRunner(); - await this.validateProject(param.projectUuid); const spaceModel = await this.validateSpaceModel(param.spaceModelUuid); await queryRunner.connect(); @@ -201,6 +202,12 @@ export class SpaceModelService { ); } + const spaceTagsAfterMove = this.tagModelService.getSubspaceTagsToBeAdded( + dto.tags, + dto.subspaceModels, + ); + console.log(spaceTagsAfterMove); + if (dto.subspaceModels) { modifiedSubspaceModels = await this.subSpaceModelService.modifySubSpaceModels( @@ -212,9 +219,10 @@ export class SpaceModelService { if (dto.tags) { modifiedTagsModelPayload = await this.tagModelService.modifyTags( - dto.tags, + spaceTagsAfterMove, queryRunner, spaceModel, + null, ); } @@ -235,8 +243,8 @@ export class SpaceModelService { message: 'SpaceModel updated successfully', }); } catch (error) { + console.log(JSON.stringify(dto)); await queryRunner.rollbackTransaction(); - if (error instanceof HttpException) { throw error; } diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index f309856..dd3fe91 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -50,8 +50,7 @@ export class SubSpaceModelService { const otherDtoTags = subSpaceModelDtos .filter((_, i) => i !== index) .flatMap((otherDto) => otherDto.tags || []); - - if (dto.tags?.length) { + if (dto.tags && dto.tags.length > 0) { subspace.tags = await this.tagModelService.createTags( dto.tags, queryRunner, @@ -174,6 +173,7 @@ export class SubSpaceModelService { const createTagDtos: CreateTagModelDto[] = subspace.tags?.map((tag) => ({ tag: tag.tag, + uuid: tag.uuid, productUuid: tag.productUuid, })) || []; diff --git a/src/space-model/services/tag-model.service.ts b/src/space-model/services/tag-model.service.ts index 14f2cf2..abb0d3a 100644 --- a/src/space-model/services/tag-model.service.ts +++ b/src/space-model/services/tag-model.service.ts @@ -6,7 +6,11 @@ import { } from '@app/common/modules/space-model/entities'; import { SubspaceModelEntity } from '@app/common/modules/space-model/entities'; import { TagModelRepository } from '@app/common/modules/space-model'; -import { CreateTagModelDto, ModifyTagModelDto } from '../dtos'; +import { + CreateTagModelDto, + ModifySubspaceModelDto, + ModifyTagModelDto, +} from '../dtos'; import { ProductService } from 'src/product/services'; import { ModifyAction } from '@app/common/constants/modify-action.enum'; import { ModifiedTagsModelPayload } from '../interfaces'; @@ -39,11 +43,55 @@ export class TagModelService { ); } + const tagEntitiesToCreate = tags.filter((tagDto) => tagDto.uuid === null); + const tagEntitiesToUpdate = tags.filter((tagDto) => tagDto.uuid !== null); + + try { + const createdTags = await this.bulkSaveTags( + tagEntitiesToCreate, + queryRunner, + spaceModel, + subspaceModel, + ); + + // Update existing tags + const updatedTags = await this.moveTags( + tagEntitiesToUpdate, + queryRunner, + spaceModel, + subspaceModel, + ); + + // Combine created and updated tags + return [...createdTags, ...updatedTags]; + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + `Failed to create tag models due to an unexpected error.: ${error}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async bulkSaveTags( + tags: CreateTagModelDto[], + queryRunner: QueryRunner, + spaceModel?: SpaceModelEntity, + subspaceModel?: SubspaceModelEntity, + ): Promise { + if (!tags.length) { + return []; + } + const tagEntities = await Promise.all( - tags.map(async (tagDto) => + tags.map((tagDto) => this.prepareTagEntity(tagDto, queryRunner, spaceModel, subspaceModel), ), ); + try { return await queryRunner.manager.save(tagEntities); } catch (error) { @@ -52,12 +100,46 @@ export class TagModelService { } throw new HttpException( - 'Failed to save tag models due to an unexpected error.', + `Failed to save tag models due to an unexpected error: ${error.message}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } } + async moveTags( + tags: CreateTagModelDto[], + queryRunner: QueryRunner, + spaceModel?: SpaceModelEntity, + subspaceModel?: SubspaceModelEntity, + ): Promise { + if (!tags.length) { + return []; + } + + return await Promise.all( + tags.map(async (tagDto) => { + const tag = await this.getTagByUuid(tagDto.uuid); + if (!tag) { + throw new HttpException( + `Tag with UUID ${tagDto.uuid} not found.`, + HttpStatus.NOT_FOUND, + ); + } + + if (subspaceModel && subspaceModel.spaceModel) { + await queryRunner.manager.update( + this.tagModelRepository.target, + { uuid: tag.uuid }, + { subspaceModel }, + ); + tag.subspaceModel = subspaceModel; + } + + return tag; + }), + ); + } + async updateTag( tag: ModifyTagModelDto, queryRunner: QueryRunner, @@ -153,6 +235,7 @@ export class TagModelService { if (tag.action === ModifyAction.ADD) { const createTagDto: CreateTagModelDto = { tag: tag.tag as string, + uuid: tag.uuid, productUuid: tag.productUuid as string, }; @@ -268,7 +351,6 @@ export class TagModelService { HttpStatus.BAD_REQUEST, ); } - return queryRunner.manager.create(TagModel, { tag: tagDto.tag, product: product.data, @@ -333,4 +415,38 @@ export class TagModelService { return existingTag; } + + getSubspaceTagsToBeAdded( + spaceTags: ModifyTagModelDto[], + subspaceModels: ModifySubspaceModelDto[], + ): ModifyTagModelDto[] { + if (!subspaceModels || subspaceModels.length === 0) { + return spaceTags; + } + + const spaceTagsToDelete = spaceTags.filter( + (tag) => tag.action === 'delete', + ); + + const tagsToAdd = subspaceModels.flatMap( + (subspace) => subspace.tags?.filter((tag) => tag.action === 'add') || [], + ); + + const commonTagUuids = new Set( + tagsToAdd + .filter((tagToAdd) => + spaceTagsToDelete.some( + (tagToDelete) => tagToAdd.uuid === tagToDelete.uuid, + ), + ) + .map((tag) => tag.uuid), + ); + + const remainingTags = spaceTags.filter( + (tag) => + !tag.uuid || commonTagUuids.size === 0 || commonTagUuids.has(tag.uuid), + ); + + return remainingTags; + } } From be60e02b9950e8ac4cdbb162a6e879055ddcc687 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 27 Jan 2025 12:54:38 +0400 Subject: [PATCH 2/3] fixed moving tags --- .../space-model/entities/tag-model.entity.ts | 10 +- .../services/space-model.service.ts | 11 +- src/space-model/services/tag-model.service.ts | 161 +++++++++++++++--- 3 files changed, 140 insertions(+), 42 deletions(-) diff --git a/libs/common/src/modules/space-model/entities/tag-model.entity.ts b/libs/common/src/modules/space-model/entities/tag-model.entity.ts index 3f7805c..e2a70ee 100644 --- a/libs/common/src/modules/space-model/entities/tag-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/tag-model.entity.ts @@ -1,11 +1,4 @@ -import { - Column, - Entity, - JoinColumn, - ManyToOne, - OneToMany, - Unique, -} from 'typeorm'; +import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { TagModelDto } from '../dtos/tag-model.dto'; import { SpaceModelEntity } from './space-model.entity'; @@ -14,7 +7,6 @@ import { ProductEntity } from '../../product/entities'; import { TagEntity } from '../../space/entities/tag.entity'; @Entity({ name: 'tag_model' }) -@Unique(['tag', 'product', 'spaceModel', 'subspaceModel']) export class TagModel extends AbstractEntity { @Column({ type: 'varchar', length: 255 }) tag: string; diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 98d6c78..05906ca 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -89,8 +89,6 @@ export class SpaceModelService { statusCode: HttpStatus.CREATED, }); } catch (error) { - console.log(JSON.stringify(createSpaceModelDto)); - await queryRunner.rollbackTransaction(); const errorMessage = @@ -206,12 +204,16 @@ export class SpaceModelService { dto.tags, dto.subspaceModels, ); - console.log(spaceTagsAfterMove); + + const modifiedSubspaces = this.tagModelService.getModifiedSubspaces( + dto.tags, + dto.subspaceModels, + ); if (dto.subspaceModels) { modifiedSubspaceModels = await this.subSpaceModelService.modifySubSpaceModels( - dto.subspaceModels, + modifiedSubspaces, spaceModel, queryRunner, ); @@ -243,7 +245,6 @@ export class SpaceModelService { message: 'SpaceModel updated successfully', }); } catch (error) { - console.log(JSON.stringify(dto)); await queryRunner.rollbackTransaction(); if (error instanceof HttpException) { throw error; diff --git a/src/space-model/services/tag-model.service.ts b/src/space-model/services/tag-model.service.ts index abb0d3a..0fb1942 100644 --- a/src/space-model/services/tag-model.service.ts +++ b/src/space-model/services/tag-model.service.ts @@ -28,6 +28,7 @@ export class TagModelService { spaceModel?: SpaceModelEntity, subspaceModel?: SubspaceModelEntity, additionalTags?: CreateTagModelDto[], + tagsToDelete?: ModifyTagModelDto[], ): Promise { if (!tags.length) { throw new HttpException('Tags cannot be empty.', HttpStatus.BAD_REQUEST); @@ -46,12 +47,14 @@ export class TagModelService { const tagEntitiesToCreate = tags.filter((tagDto) => tagDto.uuid === null); const tagEntitiesToUpdate = tags.filter((tagDto) => tagDto.uuid !== null); + try { const createdTags = await this.bulkSaveTags( tagEntitiesToCreate, queryRunner, spaceModel, subspaceModel, + tagsToDelete, ); // Update existing tags @@ -81,6 +84,7 @@ export class TagModelService { queryRunner: QueryRunner, spaceModel?: SpaceModelEntity, subspaceModel?: SubspaceModelEntity, + tagsToDelete?: ModifyTagModelDto[], ): Promise { if (!tags.length) { return []; @@ -88,7 +92,13 @@ export class TagModelService { const tagEntities = await Promise.all( tags.map((tagDto) => - this.prepareTagEntity(tagDto, queryRunner, spaceModel, subspaceModel), + this.prepareTagEntity( + tagDto, + queryRunner, + spaceModel, + subspaceModel, + tagsToDelete, + ), ), ); @@ -116,28 +126,53 @@ export class TagModelService { return []; } - return await Promise.all( - tags.map(async (tagDto) => { - const tag = await this.getTagByUuid(tagDto.uuid); - if (!tag) { - throw new HttpException( - `Tag with UUID ${tagDto.uuid} not found.`, - HttpStatus.NOT_FOUND, - ); - } + try { + return await Promise.all( + tags.map(async (tagDto) => { + try { + const tag = await this.getTagByUuid(tagDto.uuid); + if (!tag) { + throw new HttpException( + `Tag with UUID ${tagDto.uuid} not found.`, + HttpStatus.NOT_FOUND, + ); + } + + if (subspaceModel && subspaceModel.spaceModel) { + await queryRunner.manager.update( + this.tagModelRepository.target, + { uuid: tag.uuid }, + { subspaceModel, spaceModel: null }, + ); + tag.subspaceModel = subspaceModel; + } - if (subspaceModel && subspaceModel.spaceModel) { - await queryRunner.manager.update( - this.tagModelRepository.target, - { uuid: tag.uuid }, - { subspaceModel }, - ); - tag.subspaceModel = subspaceModel; - } + if (subspaceModel === null && spaceModel) { + await queryRunner.manager.update( + this.tagModelRepository.target, + { uuid: tag.uuid }, + { subspaceModel: null, spaceModel: spaceModel }, + ); + tag.subspaceModel = null; + tag.spaceModel = spaceModel; + } - return tag; - }), - ); + return tag; + } catch (error) { + console.error( + `Error moving tag with UUID ${tagDto.uuid}: ${error.message}`, + ); + throw error; // Re-throw the error to propagate it to the parent Promise.all + } + }), + ); + } catch (error) { + console.error(`Error in moveTags: ${error.message}`); + throw new HttpException( + `Failed to move tags due to an unexpected error: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } } async updateTag( @@ -231,6 +266,10 @@ export class TagModelService { deleted: [], }; try { + const tagsToDelete = tags.filter( + (tag) => tag.action === ModifyAction.DELETE, + ); + for (const tag of tags) { if (tag.action === ModifyAction.ADD) { const createTagDto: CreateTagModelDto = { @@ -239,11 +278,14 @@ export class TagModelService { productUuid: tag.productUuid as string, }; + const newModel = await this.createTags( [createTagDto], queryRunner, spaceModel, subspaceModel, + null, + tagsToDelete, ); modifiedTagModels.added.push(...newModel); } else if (tag.action === ModifyAction.UPDATE) { @@ -285,9 +327,11 @@ export class TagModelService { tag: string, productUuid: string, spaceModel: SpaceModelEntity, + tagsToDelete?: ModifyTagModelDto[], ): Promise { try { - const tagExists = await this.tagModelRepository.exists({ + // Query to find existing tags + const tagExists = await this.tagModelRepository.find({ where: [ { tag, @@ -304,16 +348,29 @@ export class TagModelService { ], }); - if (tagExists) { + + // Remove tags that are marked for deletion + const filteredTagExists = tagExists.filter( + (existingTag) => + !tagsToDelete?.some( + (deleteTag) => deleteTag.uuid === existingTag.uuid, + ), + ); + + + // If any tags remain, throw an exception + if (filteredTagExists.length > 0) { throw new HttpException( `Tag ${tag} can't be reused`, HttpStatus.CONFLICT, ); } + } catch (error) { if (error instanceof HttpException) { throw error; } + console.error(`Error while checking tag reuse: ${error.message}`); throw new HttpException( `An error occurred while checking tag reuse: ${error.message}`, HttpStatus.INTERNAL_SERVER_ERROR, @@ -326,6 +383,7 @@ export class TagModelService { queryRunner: QueryRunner, spaceModel?: SpaceModelEntity, subspaceModel?: SubspaceModelEntity, + tagsToDelete?: ModifyTagModelDto[], ): Promise { try { const product = await this.productService.findOne(tagDto.productUuid); @@ -338,7 +396,12 @@ export class TagModelService { } if (spaceModel) { - await this.checkTagReuse(tagDto.tag, tagDto.productUuid, spaceModel); + await this.checkTagReuse( + tagDto.tag, + tagDto.productUuid, + spaceModel, + tagsToDelete, + ); } else if (subspaceModel && subspaceModel.spaceModel) { await this.checkTagReuse( tagDto.tag, @@ -351,11 +414,14 @@ export class TagModelService { HttpStatus.BAD_REQUEST, ); } + const tagSpace = + spaceModel !== null ? spaceModel : subspaceModel.spaceModel; + return queryRunner.manager.create(TagModel, { tag: tagDto.tag, product: product.data, - spaceModel, - subspaceModel, + spaceModel: tagSpace, + subspaceModel: subspaceModel, }); } catch (error) { if (error instanceof HttpException) { @@ -421,16 +487,20 @@ export class TagModelService { subspaceModels: ModifySubspaceModelDto[], ): ModifyTagModelDto[] { if (!subspaceModels || subspaceModels.length === 0) { + return spaceTags; } + const spaceTagsToDelete = spaceTags.filter( (tag) => tag.action === 'delete', ); + const tagsToAdd = subspaceModels.flatMap( (subspace) => subspace.tags?.filter((tag) => tag.action === 'add') || [], ); + const commonTagUuids = new Set( tagsToAdd @@ -443,10 +513,45 @@ export class TagModelService { ); const remainingTags = spaceTags.filter( - (tag) => - !tag.uuid || commonTagUuids.size === 0 || commonTagUuids.has(tag.uuid), + (tag) => !commonTagUuids.has(tag.uuid), // Exclude tags in commonTagUuids ); return remainingTags; } + + getModifiedSubspaces( + spaceTags: ModifyTagModelDto[], + subspaceModels: ModifySubspaceModelDto[], + ): ModifySubspaceModelDto[] { + if (!subspaceModels || subspaceModels.length === 0) { + return []; + } + + // Extract tags marked for addition in spaceTags + const spaceTagsToAdd = spaceTags.filter((tag) => tag.action === 'add'); + + // Find UUIDs of tags that are common between spaceTagsToAdd and subspace tags marked for deletion + const commonTagUuids = new Set( + spaceTagsToAdd + .flatMap((tagToAdd) => + subspaceModels.flatMap( + (subspace) => + subspace.tags?.filter( + (tagToDelete) => + tagToDelete.action === 'delete' && + tagToAdd.uuid === tagToDelete.uuid, + ) || [], + ), + ) + .map((tag) => tag.uuid), + ); + + // Modify subspaceModels by removing tags with UUIDs present in commonTagUuids + const modifiedSubspaces = subspaceModels.map((subspace) => ({ + ...subspace, + tags: subspace.tags?.filter((tag) => !commonTagUuids.has(tag.uuid)) || [], + })); + + return modifiedSubspaces; + } } From 96bf8ed7f8fa3041a5e30fe2b64940608e53af35 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 27 Jan 2025 12:54:46 +0400 Subject: [PATCH 3/3] prettier --- src/space-model/services/tag-model.service.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/space-model/services/tag-model.service.ts b/src/space-model/services/tag-model.service.ts index 0fb1942..0f77b3d 100644 --- a/src/space-model/services/tag-model.service.ts +++ b/src/space-model/services/tag-model.service.ts @@ -47,7 +47,6 @@ export class TagModelService { const tagEntitiesToCreate = tags.filter((tagDto) => tagDto.uuid === null); const tagEntitiesToUpdate = tags.filter((tagDto) => tagDto.uuid !== null); - try { const createdTags = await this.bulkSaveTags( tagEntitiesToCreate, @@ -137,7 +136,7 @@ export class TagModelService { HttpStatus.NOT_FOUND, ); } - + if (subspaceModel && subspaceModel.spaceModel) { await queryRunner.manager.update( this.tagModelRepository.target, @@ -278,7 +277,6 @@ export class TagModelService { productUuid: tag.productUuid as string, }; - const newModel = await this.createTags( [createTagDto], queryRunner, @@ -348,7 +346,6 @@ export class TagModelService { ], }); - // Remove tags that are marked for deletion const filteredTagExists = tagExists.filter( (existingTag) => @@ -357,7 +354,6 @@ export class TagModelService { ), ); - // If any tags remain, throw an exception if (filteredTagExists.length > 0) { throw new HttpException( @@ -365,7 +361,6 @@ export class TagModelService { HttpStatus.CONFLICT, ); } - } catch (error) { if (error instanceof HttpException) { throw error; @@ -487,20 +482,16 @@ export class TagModelService { subspaceModels: ModifySubspaceModelDto[], ): ModifyTagModelDto[] { if (!subspaceModels || subspaceModels.length === 0) { - return spaceTags; } - const spaceTagsToDelete = spaceTags.filter( (tag) => tag.action === 'delete', ); - const tagsToAdd = subspaceModels.flatMap( (subspace) => subspace.tags?.filter((tag) => tag.action === 'add') || [], ); - const commonTagUuids = new Set( tagsToAdd