From 902ddbab50d61020e5603f4ecf52b64d14cee7ef Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Thu, 27 Feb 2025 13:36:08 +0300 Subject: [PATCH 1/2] update space service --- .../space/repositories/space.repository.ts | 7 + src/space-model/space-model.module.ts | 12 +- src/space/dtos/add.space.dto.ts | 8 +- .../space-product-allocation.service.ts | 334 ++++++++++++++++++ src/space/services/space.service.ts | 79 ++++- src/space/space.module.ts | 12 +- 6 files changed, 439 insertions(+), 13 deletions(-) create mode 100644 src/space/services/space-product-allocation.service.ts diff --git a/libs/common/src/modules/space/repositories/space.repository.ts b/libs/common/src/modules/space/repositories/space.repository.ts index 92ddc19..2462749 100644 --- a/libs/common/src/modules/space/repositories/space.repository.ts +++ b/libs/common/src/modules/space/repositories/space.repository.ts @@ -4,6 +4,7 @@ import { InviteSpaceEntity } from '../entities/invite-space.entity'; import { SpaceLinkEntity } from '../entities/space-link.entity'; import { SpaceEntity } from '../entities/space.entity'; import { TagEntity } from '../entities/tag.entity'; +import { SpaceProductAllocationEntity } from '../entities/space-product-allocation.entity'; @Injectable() export class SpaceRepository extends Repository { @@ -32,3 +33,9 @@ export class InviteSpaceRepository extends Repository { super(InviteSpaceEntity, dataSource.createEntityManager()); } } +@Injectable() +export class SpaceProductAllocationRepository extends Repository { + constructor(private dataSource: DataSource) { + super(SpaceProductAllocationEntity, dataSource.createEntityManager()); + } +} diff --git a/src/space-model/space-model.module.ts b/src/space-model/space-model.module.ts index b73eceb..c9711f2 100644 --- a/src/space-model/space-model.module.ts +++ b/src/space-model/space-model.module.ts @@ -20,10 +20,14 @@ import { CqrsModule } from '@nestjs/cqrs'; import { InviteSpaceRepository, SpaceLinkRepository, + SpaceProductAllocationRepository, SpaceRepository, TagRepository, } from '@app/common/modules/space'; -import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; +import { + SubspaceProductAllocationRepository, + SubspaceRepository, +} from '@app/common/modules/space/repositories/subspace.repository'; import { SpaceLinkService, SpaceService, @@ -40,6 +44,8 @@ import { TagService as NewTagService } from 'src/tags/services/tags.service'; import { NewTagRepository } from '@app/common/modules/tag/repositories/tag-repository'; import { SpaceModelProductAllocationService } from './services/space-model-product-allocation.service'; import { SubspaceModelProductAllocationService } from './services/subspace/subspace-model-product-allocation.service'; +import { SpaceProductAllocationService } from 'src/space/services/space-product-allocation.service'; +import { SubspaceProductAllocationService } from 'src/space/services/subspace/subspace-product-allocation.service'; const CommandHandlers = [ PropogateUpdateSpaceModelHandler, @@ -80,6 +86,10 @@ const CommandHandlers = [ SpaceModelProductAllocationService, SubspaceModelProductAllocationRepoitory, SubspaceModelProductAllocationService, + SpaceProductAllocationService, + SubspaceProductAllocationService, + SpaceProductAllocationRepository, + SubspaceProductAllocationRepository, ], exports: [CqrsModule, SpaceModelService], }) diff --git a/src/space/dtos/add.space.dto.ts b/src/space/dtos/add.space.dto.ts index 15595be..0ffeac1 100644 --- a/src/space/dtos/add.space.dto.ts +++ b/src/space/dtos/add.space.dto.ts @@ -10,7 +10,7 @@ import { ValidateNested, } from 'class-validator'; import { AddSubspaceDto } from './subspace'; -import { CreateTagDto } from './tag'; +import { ProcessTagDto } from 'src/tags/dtos'; export class AddSpaceDto { @ApiProperty({ @@ -79,11 +79,11 @@ export class AddSpaceDto { @ApiProperty({ description: 'List of tags associated with the space model', - type: [CreateTagDto], + type: [ProcessTagDto], }) @ValidateNested({ each: true }) - @Type(() => CreateTagDto) - tags?: CreateTagDto[]; + @Type(() => ProcessTagDto) + tags?: ProcessTagDto[]; } export class AddUserSpaceDto { diff --git a/src/space/services/space-product-allocation.service.ts b/src/space/services/space-product-allocation.service.ts new file mode 100644 index 0000000..f29d36c --- /dev/null +++ b/src/space/services/space-product-allocation.service.ts @@ -0,0 +1,334 @@ +import { ModifyAction } from '@app/common/constants/modify-action.enum'; +import { ProductEntity } from '@app/common/modules/product/entities'; +import { SpaceProductAllocationRepository } from '@app/common/modules/space'; +import { SpaceProductAllocationEntity } from '@app/common/modules/space/entities/space-product-allocation.entity'; +import { SpaceEntity } from '@app/common/modules/space/entities/space.entity'; +import { NewTagEntity } from '@app/common/modules/tag'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; + +import { In, QueryRunner } from 'typeorm'; +import { SubspaceProductAllocationEntity } from '@app/common/modules/space/entities/subspace/subspace-product-allocation.entity'; +import { ModifyTagDto } from '../dtos/tag/modify-tag.dto'; +import { ModifySubspaceDto } from '../dtos'; +import { ProcessTagDto } from 'src/tags/dtos'; +import { TagService as NewTagService } from 'src/tags/services'; + +@Injectable() +export class SpaceProductAllocationService { + constructor( + private readonly tagService: NewTagService, + private readonly spaceProductAllocationRepository: SpaceProductAllocationRepository, + ) {} + + async createSpaceProductAllocations( + space: SpaceEntity, + processedTags: NewTagEntity[], + queryRunner: QueryRunner, + modifySubspaces?: ModifySubspaceDto[], + ): Promise { + try { + if (!processedTags.length) return; + + const productAllocations: SpaceProductAllocationEntity[] = []; + const existingAllocations = new Map< + string, + SpaceProductAllocationEntity + >(); + + for (const tag of processedTags) { + let isTagNeeded = true; + + if (modifySubspaces) { + const relatedSubspaces = await queryRunner.manager.find( + SubspaceProductAllocationEntity, + { + where: { + product: tag.product, + subspace: { space: { uuid: space.uuid } }, + tags: { uuid: tag.uuid }, + }, + relations: ['subspace', 'tags'], + }, + ); + + for (const subspaceWithTag of relatedSubspaces) { + const modifyingSubspace = modifySubspaces.find( + (subspace) => + subspace.action === ModifyAction.UPDATE && + subspace.uuid === subspaceWithTag.subspace.uuid, + ); + + if ( + modifyingSubspace && + modifyingSubspace.tags && + modifyingSubspace.tags.some( + (subspaceTag) => + subspaceTag.action === ModifyAction.DELETE && + subspaceTag.tagUuid === tag.uuid, + ) + ) { + isTagNeeded = true; + break; + } + } + } + + if (isTagNeeded) { + await this.validateTagWithinSpace(queryRunner, tag, space); + + let allocation = existingAllocations.get(tag.product.uuid); + if (!allocation) { + allocation = await this.getAllocationByProduct( + tag.product, + space, + queryRunner, + ); + if (allocation) { + existingAllocations.set(tag.product.uuid, allocation); + } + } + + if (!allocation) { + allocation = this.createNewAllocation(space, tag, queryRunner); + productAllocations.push(allocation); + } else if (!allocation.tags.some((t) => t.uuid === tag.uuid)) { + allocation.tags.push(tag); + await this.saveAllocation(allocation, queryRunner); + } + } + } + + if (productAllocations.length > 0) { + await this.saveAllocations(productAllocations, queryRunner); + } + } catch (error) { + throw this.handleError( + error, + 'Failed to create space product allocations', + ); + } + } + async updateSpaceProductAllocations( + dtos: ModifyTagDto[], + projectUuid: string, + space: SpaceEntity, + queryRunner: QueryRunner, + modifySubspace?: ModifySubspaceDto[], + ): Promise { + try { + await Promise.all([ + this.processAddActions( + dtos, + projectUuid, + space, + queryRunner, + modifySubspace, + ), + + this.processDeleteActions(dtos, queryRunner), + ]); + } catch (error) { + throw this.handleError(error, 'Error while updating product allocations'); + } + } + private async processDeleteActions( + dtos: ModifyTagDto[], + queryRunner: QueryRunner, + ): Promise { + try { + if (!dtos || dtos.length === 0) { + throw new Error('No DTOs provided for deletion.'); + } + + const tagUuidsToDelete = dtos + .filter((dto) => dto.action === ModifyAction.DELETE && dto.tagUuid) + .map((dto) => dto.tagUuid); + + if (tagUuidsToDelete.length === 0) return []; + + const allocationsToUpdate = await queryRunner.manager.find( + SpaceProductAllocationEntity, + { + where: { tags: { uuid: In(tagUuidsToDelete) } }, + relations: ['tags'], + }, + ); + + if (!allocationsToUpdate || allocationsToUpdate.length === 0) return []; + + const deletedAllocations: SpaceProductAllocationEntity[] = []; + const allocationUpdates: SpaceProductAllocationEntity[] = []; + + for (const allocation of allocationsToUpdate) { + const updatedTags = allocation.tags.filter( + (tag) => !tagUuidsToDelete.includes(tag.uuid), + ); + + if (updatedTags.length === allocation.tags.length) { + continue; + } + + if (updatedTags.length === 0) { + deletedAllocations.push(allocation); + } else { + allocation.tags = updatedTags; + allocationUpdates.push(allocation); + } + } + + if (allocationUpdates.length > 0) { + await queryRunner.manager.save( + SpaceProductAllocationEntity, + allocationUpdates, + ); + } + + if (deletedAllocations.length > 0) { + await queryRunner.manager.remove( + SpaceProductAllocationEntity, + deletedAllocations, + ); + } + + await queryRunner.manager + .createQueryBuilder() + .delete() + .from('space_product_tags') + .where( + 'space_product_allocation_id NOT IN ' + + queryRunner.manager + .createQueryBuilder() + .select('uuid') + .from(SpaceProductAllocationEntity, 'allocation') + .getQuery(), + ) + .execute(); + + return deletedAllocations; + } catch (error) { + throw this.handleError(error, `Failed to delete tags in space`); + } + } + private async processAddActions( + dtos: ModifyTagDto[], + projectUuid: string, + space: SpaceEntity, + queryRunner: QueryRunner, + modifySubspace?: ModifySubspaceDto[], + ): Promise { + const addDtos: ProcessTagDto[] = dtos + .filter((dto) => dto.action === ModifyAction.ADD) + .map((dto) => ({ + name: dto.name, + productUuid: dto.productUuid, + uuid: dto.newTagUuid, + })); + + if (addDtos.length > 0) { + const processedTags = await this.tagService.processTags( + addDtos, + projectUuid, + queryRunner, + ); + + await this.createSpaceProductAllocations( + space, + processedTags, + queryRunner, + modifySubspace, + ); + } + } + private async validateTagWithinSpace( + queryRunner: QueryRunner, + tag: NewTagEntity, + space: SpaceEntity, + ) { + const existingAllocationsForProduct = await queryRunner.manager.find( + SpaceProductAllocationEntity, + { + where: { space, product: tag.product }, + relations: ['tags'], + }, + ); + + const existingTagsForProduct = existingAllocationsForProduct.flatMap( + (allocation) => allocation.tags, + ); + + const isDuplicateTag = existingTagsForProduct.some( + (existingTag) => existingTag.uuid === tag.uuid, + ); + + if (isDuplicateTag) { + throw new HttpException( + `Tag ${tag.uuid} is already allocated to product ${tag.product.uuid} within this space (${space.uuid}).`, + HttpStatus.BAD_REQUEST, + ); + } + } + + private async getAllocationByProduct( + product: ProductEntity, + space: SpaceEntity, + queryRunner?: QueryRunner, + ): Promise { + return queryRunner + ? queryRunner.manager.findOne(SpaceProductAllocationEntity, { + where: { space, product: product }, + relations: ['tags'], + }) + : this.spaceProductAllocationRepository.findOne({ + where: { space, product: product }, + relations: ['tags'], + }); + } + + private createNewAllocation( + space: SpaceEntity, + tag: NewTagEntity, + queryRunner?: QueryRunner, + ): SpaceProductAllocationEntity { + return queryRunner + ? queryRunner.manager.create(SpaceProductAllocationEntity, { + space, + product: tag.product, + tags: [tag], + }) + : this.spaceProductAllocationRepository.create({ + space, + product: tag.product, + tags: [tag], + }); + } + + private async saveAllocation( + allocation: SpaceProductAllocationEntity, + queryRunner?: QueryRunner, + ) { + queryRunner + ? await queryRunner.manager.save(SpaceProductAllocationEntity, allocation) + : await this.spaceProductAllocationRepository.save(allocation); + } + + private async saveAllocations( + allocations: SpaceProductAllocationEntity[], + queryRunner?: QueryRunner, + ) { + queryRunner + ? await queryRunner.manager.save( + SpaceProductAllocationEntity, + allocations, + ) + : await this.spaceProductAllocationRepository.save(allocations); + } + + private handleError(error: any, message: string): HttpException { + return new HttpException( + error instanceof HttpException ? error.message : message, + error instanceof HttpException + ? error.getStatus() + : HttpStatus.INTERNAL_SERVER_ERROR, + ); + } +} diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 4027ab1..74523f5 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -12,7 +12,6 @@ import { AddSpaceDto, AddSubspaceDto, CommunitySpaceParam, - CreateTagDto, GetSpaceParam, UpdateSpaceDto, } from '../dtos'; @@ -29,11 +28,15 @@ import { } from '@app/common/constants/orphan-constant'; import { CommandBus } from '@nestjs/cqrs'; import { TagService } from './tag'; +import { TagService as NewTagService } from 'src/tags/services/tags.service'; import { SpaceModelService } from 'src/space-model/services'; import { DisableSpaceCommand } from '../commands'; import { GetSpaceDto } from '../dtos/get.space.dto'; import { removeCircularReferences } from '@app/common/helper/removeCircularReferences'; import { SpaceEntity } from '@app/common/modules/space/entities/space.entity'; +import { ProcessTagDto } from 'src/tags/dtos'; +import { SpaceProductAllocationService } from './space-product-allocation.service'; +import { SubspaceProductAllocationService } from './subspace/subspace-product-allocation.service'; @Injectable() export class SpaceService { constructor( @@ -44,8 +47,11 @@ export class SpaceService { private readonly subSpaceService: SubSpaceService, private readonly validationService: ValidationService, private readonly tagService: TagService, + private readonly newTagService: NewTagService, private readonly spaceModelService: SpaceModelService, private commandBus: CommandBus, + private readonly spaceProductAllocationService: SpaceProductAllocationService, + private readonly subspaceProductAllocationService: SubspaceProductAllocationService, ) {} async createSpace( @@ -93,6 +99,10 @@ export class SpaceService { }); const newSpace = await queryRunner.manager.save(space); + const subspaceTags = + this.subSpaceService.extractTagsFromSubspace(subspaces); + const allTags = [...tags, ...subspaceTags]; + this.validateUniqueTags(allTags); await Promise.all([ spaceModelUuid && @@ -106,10 +116,17 @@ export class SpaceService { ) : Promise.resolve(), subspaces?.length - ? this.createSubspaces(subspaces, newSpace, queryRunner, tags) + ? this.createSubspaces( + subspaces, + newSpace, + queryRunner, + tags, + projectUuid, + ) : Promise.resolve(), + tags?.length - ? this.createTags(tags, queryRunner, newSpace) + ? this.createTags(tags, projectUuid, queryRunner, space) : Promise.resolve(), ]); @@ -121,6 +138,8 @@ export class SpaceService { message: 'Space created successfully', }); } catch (error) { + console.log('error', error); + await queryRunner.rollbackTransaction(); if (error instanceof HttpException) { @@ -131,7 +150,31 @@ export class SpaceService { await queryRunner.release(); } } + private validateUniqueTags(allTags: ProcessTagDto[]) { + const tagUuidSet = new Set(); + const tagNameProductSet = new Set(); + for (const tag of allTags) { + if (tag.uuid) { + if (tagUuidSet.has(tag.uuid)) { + throw new HttpException( + `Duplicate tag UUID found: ${tag.uuid}`, + HttpStatus.BAD_REQUEST, + ); + } + tagUuidSet.add(tag.uuid); + } else { + const tagKey = `${tag.name}-${tag.productUuid}`; + if (tagNameProductSet.has(tagKey)) { + throw new HttpException( + `Duplicate tag found with name "${tag.name}" and product "${tag.productUuid}".`, + HttpStatus.BAD_REQUEST, + ); + } + tagNameProductSet.add(tagKey); + } + } + } async createFromModel( spaceModelUuid: string, queryRunner: QueryRunner, @@ -408,6 +451,8 @@ export class SpaceService { modifiedSubspaces, queryRunner, space, + projectUuid, + updateSpaceDto.tags, ); } @@ -423,7 +468,15 @@ export class SpaceService { space, ); } - + if (updateSpaceDto.tags) { + await this.spaceProductAllocationService.updateSpaceProductAllocations( + updateSpaceDto.tags, + projectUuid, + space, + queryRunner, + updateSpaceDto.subspace, + ); + } await queryRunner.commitTransaction(); return new SuccessResponseDto({ @@ -603,21 +656,33 @@ export class SpaceService { subspaces: AddSubspaceDto[], space: SpaceEntity, queryRunner: QueryRunner, - tags: CreateTagDto[], + tags: ProcessTagDto[], + projectUuid: string, ): Promise { space.subspaces = await this.subSpaceService.createSubspacesFromDto( subspaces, space, queryRunner, tags, + projectUuid, ); } private async createTags( - tags: CreateTagDto[], + tags: ProcessTagDto[], + projectUuid: string, queryRunner: QueryRunner, space: SpaceEntity, ): Promise { - space.tags = await this.tagService.createTags(tags, queryRunner, space); + const processedTags = await this.newTagService.processTags( + tags, + projectUuid, + queryRunner, + ); + await this.spaceProductAllocationService.createSpaceProductAllocations( + space, + processedTags, + queryRunner, + ); } } diff --git a/src/space/space.module.ts b/src/space/space.module.ts index e197f6c..ea0d757 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -23,6 +23,7 @@ import { SpaceLinkRepository, TagRepository, InviteSpaceRepository, + SpaceProductAllocationRepository, } from '@app/common/modules/space/repositories'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { @@ -54,7 +55,10 @@ import { } from '@app/common/modules/space-model'; import { CommunityModule } from 'src/community/community.module'; import { ValidationService } from './services'; -import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; +import { + SubspaceProductAllocationRepository, + SubspaceRepository, +} from '@app/common/modules/space/repositories/subspace.repository'; import { TagService } from './services/tag'; import { SpaceModelService, @@ -76,6 +80,8 @@ import { TagService as NewTagService } from 'src/tags/services/tags.service'; import { NewTagRepository } from '@app/common/modules/tag/repositories/tag-repository'; import { SpaceModelProductAllocationService } from 'src/space-model/services/space-model-product-allocation.service'; import { SubspaceModelProductAllocationService } from 'src/space-model/services/subspace/subspace-model-product-allocation.service'; +import { SpaceProductAllocationService } from './services/space-product-allocation.service'; +import { SubspaceProductAllocationService } from './services/subspace/subspace-product-allocation.service'; export const CommandHandlers = [DisableSpaceHandler]; @@ -141,6 +147,10 @@ export const CommandHandlers = [DisableSpaceHandler]; NewTagRepository, SpaceModelProductAllocationService, SubspaceModelProductAllocationService, + SpaceProductAllocationService, + SubspaceProductAllocationService, + SpaceProductAllocationRepository, + SubspaceProductAllocationRepository, ], exports: [SpaceService], }) From a9e5454b61b040db5e97803686c72b3b4d26a17a Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Thu, 27 Feb 2025 13:36:28 +0300 Subject: [PATCH 2/2] Add space product --- .../space/repositories/subspace.repository.ts | 7 + .../subspace/subspace-model.service.ts | 1 - src/space/dtos/subspace/add.subspace.dto.ts | 8 +- src/space/dtos/tag/modify-tag.dto.ts | 21 +- .../interfaces/single-subspace.interface.ts | 9 + .../subspace-product-allocation.service.ts | 508 ++++++++++++++++++ .../services/subspace/subspace.service.ts | 71 ++- src/space/services/tag/tag.service.ts | 37 +- src/tags/services/tags.service.ts | 2 + 9 files changed, 619 insertions(+), 45 deletions(-) create mode 100644 src/space/interfaces/single-subspace.interface.ts create mode 100644 src/space/services/subspace/subspace-product-allocation.service.ts diff --git a/libs/common/src/modules/space/repositories/subspace.repository.ts b/libs/common/src/modules/space/repositories/subspace.repository.ts index 6ccf171..1c130f1 100644 --- a/libs/common/src/modules/space/repositories/subspace.repository.ts +++ b/libs/common/src/modules/space/repositories/subspace.repository.ts @@ -1,6 +1,7 @@ import { DataSource, Repository } from 'typeorm'; import { Injectable } from '@nestjs/common'; import { SubspaceEntity } from '../entities/subspace/subspace.entity'; +import { SubspaceProductAllocationEntity } from '../entities/subspace/subspace-product-allocation.entity'; @Injectable() export class SubspaceRepository extends Repository { @@ -8,3 +9,9 @@ export class SubspaceRepository extends Repository { super(SubspaceEntity, dataSource.createEntityManager()); } } +@Injectable() +export class SubspaceProductAllocationRepository extends Repository { + constructor(private dataSource: DataSource) { + super(SubspaceProductAllocationEntity, dataSource.createEntityManager()); + } +} diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index 0915f53..57e5652 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -14,7 +14,6 @@ import { ProcessTagDto } from 'src/tags/dtos'; import { TagService } from 'src/tags/services'; import { SubspaceModelProductAllocationService } from './subspace-model-product-allocation.service'; import { ISingleSubspaceModel } from 'src/space-model/interfaces'; -import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity'; @Injectable() export class SubSpaceModelService { diff --git a/src/space/dtos/subspace/add.subspace.dto.ts b/src/space/dtos/subspace/add.subspace.dto.ts index 95ad0fb..79dc395 100644 --- a/src/space/dtos/subspace/add.subspace.dto.ts +++ b/src/space/dtos/subspace/add.subspace.dto.ts @@ -6,8 +6,8 @@ import { IsString, ValidateNested, } from 'class-validator'; -import { CreateTagDto } from '../tag'; import { Type } from 'class-transformer'; +import { ProcessTagDto } from 'src/tags/dtos'; export class AddSubspaceDto { @ApiProperty({ @@ -20,11 +20,11 @@ export class AddSubspaceDto { @ApiProperty({ description: 'List of tags associated with the subspace', - type: [CreateTagDto], + type: [ProcessTagDto], }) @IsArray() @ValidateNested({ each: true }) - @Type(() => CreateTagDto) + @Type(() => ProcessTagDto) @IsOptional() - tags?: CreateTagDto[]; + tags?: ProcessTagDto[]; } diff --git a/src/space/dtos/tag/modify-tag.dto.ts b/src/space/dtos/tag/modify-tag.dto.ts index 6088a2a..a8902c0 100644 --- a/src/space/dtos/tag/modify-tag.dto.ts +++ b/src/space/dtos/tag/modify-tag.dto.ts @@ -1,6 +1,6 @@ import { ModifyAction } from '@app/common/constants/modify-action.enum'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsEnum, IsOptional, IsString } from 'class-validator'; +import { IsEnum, IsOptional, IsString, IsUUID } from 'class-validator'; export class ModifyTagDto { @ApiProperty({ @@ -11,12 +11,21 @@ export class ModifyTagDto { action: ModifyAction; @ApiPropertyOptional({ - description: 'UUID of the tag (required for update/delete)', + description: 'UUID of the new tag', example: '123e4567-e89b-12d3-a456-426614174000', }) @IsOptional() - @IsString() - uuid?: string; + @IsUUID() + newTagUuid: string; + + @ApiPropertyOptional({ + description: + 'UUID of an existing tag (required for update/delete, optional for add)', + example: 'a1b2c3d4-5678-90ef-abcd-1234567890ef', + }) + @IsOptional() + @IsUUID() + tagUuid?: string; @ApiPropertyOptional({ description: 'Name of the tag (required for add/update)', @@ -24,7 +33,7 @@ export class ModifyTagDto { }) @IsOptional() @IsString() - tag?: string; + name?: string; @ApiPropertyOptional({ description: @@ -32,6 +41,6 @@ export class ModifyTagDto { example: 'c789a91e-549a-4753-9006-02f89e8170e0', }) @IsOptional() - @IsString() + @IsUUID() productUuid?: string; } diff --git a/src/space/interfaces/single-subspace.interface.ts b/src/space/interfaces/single-subspace.interface.ts new file mode 100644 index 0000000..013193c --- /dev/null +++ b/src/space/interfaces/single-subspace.interface.ts @@ -0,0 +1,9 @@ +import { ModifyAction } from '@app/common/constants/modify-action.enum'; +import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity'; +import { ModifyTagDto } from '../dtos/tag/modify-tag.dto'; + +export interface ISingleSubspace { + subspace: SubspaceEntity; + action: ModifyAction; + tags: ModifyTagDto[]; +} diff --git a/src/space/services/subspace/subspace-product-allocation.service.ts b/src/space/services/subspace/subspace-product-allocation.service.ts new file mode 100644 index 0000000..6e7e2cb --- /dev/null +++ b/src/space/services/subspace/subspace-product-allocation.service.ts @@ -0,0 +1,508 @@ +import { ModifyAction } from '@app/common/constants/modify-action.enum'; +import { ProductEntity } from '@app/common/modules/product/entities'; +import { SpaceProductAllocationRepository } from '@app/common/modules/space'; +import { SpaceProductAllocationEntity } from '@app/common/modules/space/entities/space-product-allocation.entity'; +import { SpaceEntity } from '@app/common/modules/space/entities/space.entity'; +import { SubspaceProductAllocationEntity } from '@app/common/modules/space/entities/subspace/subspace-product-allocation.entity'; +import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity'; +import { SubspaceProductAllocationRepository } from '@app/common/modules/space/repositories/subspace.repository'; +import { NewTagEntity } from '@app/common/modules/tag'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { ModifyTagDto } from 'src/space/dtos/tag/modify-tag.dto'; +import { ISingleSubspace } from 'src/space/interfaces/single-subspace.interface'; +import { ProcessTagDto } from 'src/tags/dtos'; +import { TagService as NewTagService } from 'src/tags/services'; +import { In, QueryRunner } from 'typeorm'; + +@Injectable() +export class SubspaceProductAllocationService { + constructor( + private readonly tagService: NewTagService, + + private readonly spaceProductAllocationRepository: SpaceProductAllocationRepository, + private readonly subspaceProductAllocationRepository: SubspaceProductAllocationRepository, + ) {} + + async createSubspaceProductAllocations( + subspace: SubspaceEntity, + processedTags: NewTagEntity[], + queryRunner?: QueryRunner, + spaceAllocationsToExclude?: SpaceProductAllocationEntity[], + ): Promise { + try { + if (!processedTags.length) return; + + const allocations: SubspaceProductAllocationEntity[] = []; + + for (const tag of processedTags) { + await this.validateTagWithinSubspace( + queryRunner, + tag, + subspace, + spaceAllocationsToExclude, + ); + + let allocation = await this.getAllocationByProduct( + tag.product, + subspace, + queryRunner, + ); + + if (!allocation) { + allocation = this.createNewSubspaceAllocation( + subspace, + tag, + queryRunner, + ); + allocations.push(allocation); + } else if (!allocation.tags.some((t) => t.uuid === tag.uuid)) { + allocation.tags.push(tag); + await this.saveAllocation(allocation, queryRunner); + } + } + + if (allocations.length > 0) { + await this.saveAllocations(allocations, queryRunner); + } + } catch (error) { + throw this.handleError( + error, + 'Failed to create subspace product allocations', + ); + } + } + async updateSubspaceProductAllocations( + subspaces: ISingleSubspace[], + projectUuid: string, + queryRunner: QueryRunner, + space: SpaceEntity, + spaceTagUpdateDtos?: ModifyTagDto[], + ) { + const spaceAllocationToExclude: SpaceProductAllocationEntity[] = []; + + for (const subspace of subspaces) { + const tagDtos = subspace.tags; + if (tagDtos.length > 0) { + const tagsToAddDto: ProcessTagDto[] = tagDtos + .filter((dto) => dto.action === ModifyAction.ADD) + .map((dto) => ({ + name: dto.name, + productUuid: dto.productUuid, + uuid: dto.newTagUuid, + })); + + const tagsToDeleteDto = tagDtos.filter( + (dto) => dto.action === ModifyAction.DELETE, + ); + + if (tagsToAddDto.length > 0) { + let processedTags = await this.tagService.processTags( + tagsToAddDto, + projectUuid, + queryRunner, + ); + + for (const subspaceDto of subspaces) { + if ( + subspaceDto !== subspace && + subspaceDto.action === ModifyAction.UPDATE && + subspaceDto.tags + ) { + const deletedTags = subspaceDto.tags.filter( + (tagDto) => + tagDto.action === ModifyAction.DELETE && + processedTags.some((tag) => tag.uuid === tagDto.tagUuid), + ); + + for (const deletedTag of deletedTags) { + const allocation = await queryRunner.manager.findOne( + SubspaceProductAllocationEntity, + { + where: { + subspace: { uuid: subspaceDto.subspace.uuid }, + }, + relations: ['tags', 'product', 'subspace'], + }, + ); + + const isCommonTag = allocation.tags.some( + (tag) => tag.uuid === deletedTag.tagUuid, + ); + + if (allocation && isCommonTag) { + const tagEntity = allocation.tags.find( + (tag) => tag.uuid === deletedTag.tagUuid, + ); + + allocation.tags = allocation.tags.filter( + (tag) => tag.uuid !== deletedTag.tagUuid, + ); + + await queryRunner.manager.save(allocation); + + const productAllocationExistInSubspace = + await queryRunner.manager.findOne( + SubspaceProductAllocationEntity, + { + where: { + subspace: { + uuid: subspaceDto.subspace.uuid, + }, + product: { uuid: allocation.product.uuid }, + }, + relations: ['tags'], + }, + ); + + if (productAllocationExistInSubspace) { + productAllocationExistInSubspace.tags.push(tagEntity); + await queryRunner.manager.save( + productAllocationExistInSubspace, + ); + } else { + const newProductAllocation = queryRunner.manager.create( + SubspaceProductAllocationEntity, + { + subspace: subspace.subspace, + product: allocation.product, + tags: [tagEntity], + }, + ); + + await queryRunner.manager.save(newProductAllocation); + } + + processedTags = processedTags.filter( + (tag) => tag.uuid !== deletedTag.tagUuid, + ); + + subspaceDto.tags = subspaceDto.tags.filter( + (tagDto) => tagDto.tagUuid !== deletedTag.tagUuid, + ); + } + } + } + if ( + subspaceDto !== subspace && + subspaceDto.action === ModifyAction.DELETE + ) { + const allocation = await queryRunner.manager.findOne( + SubspaceProductAllocationEntity, + { + where: { + subspace: { uuid: subspaceDto.subspace.uuid }, + }, + relations: ['tags'], + }, + ); + + const repeatedTags = allocation?.tags.filter((tag) => + processedTags.some( + (processedTag) => processedTag.uuid === tag.uuid, + ), + ); + if (repeatedTags.length > 0) { + allocation.tags = allocation.tags.filter( + (tag) => + !repeatedTags.some( + (repeatedTag) => repeatedTag.uuid === tag.uuid, + ), + ); + + await queryRunner.manager.save(allocation); + + const productAllocationExistInSubspace = + await queryRunner.manager.findOne( + SubspaceProductAllocationEntity, + { + where: { + subspace: { uuid: subspaceDto.subspace.uuid }, + product: { uuid: allocation.product.uuid }, + }, + relations: ['tags'], + }, + ); + + if (productAllocationExistInSubspace) { + productAllocationExistInSubspace.tags.push(...repeatedTags); + await queryRunner.manager.save( + productAllocationExistInSubspace, + ); + } else { + const newProductAllocation = queryRunner.manager.create( + SubspaceProductAllocationEntity, + { + subspace: subspace.subspace, + product: allocation.product, + tags: repeatedTags, + }, + ); + + await queryRunner.manager.save(newProductAllocation); + } + + const newAllocation = queryRunner.manager.create( + SubspaceProductAllocationEntity, + { + subspace: subspace.subspace, + product: allocation.product, + tags: repeatedTags, + }, + ); + + await queryRunner.manager.save(newAllocation); + } + } + } + if (spaceTagUpdateDtos) { + const deletedSpaceTags = spaceTagUpdateDtos.filter( + (tagDto) => + tagDto.action === ModifyAction.DELETE && + processedTags.some((tag) => tag.uuid === tagDto.tagUuid), + ); + for (const deletedTag of deletedSpaceTags) { + const allocation = await queryRunner.manager.findOne( + SpaceProductAllocationEntity, + { + where: { + space: { uuid: space.uuid }, + tags: { uuid: deletedTag.tagUuid }, + }, + relations: ['tags', 'subspace'], + }, + ); + + if ( + allocation && + allocation.tags.some((tag) => tag.uuid === deletedTag.tagUuid) + ) { + spaceAllocationToExclude.push(allocation); + } + } + } + + await this.createSubspaceProductAllocations( + subspace.subspace, + processedTags, + queryRunner, + spaceAllocationToExclude, + ); + } + if (tagsToDeleteDto.length > 0) { + await this.processDeleteActions(tagsToDeleteDto, queryRunner); + } + } + } + } + + async processDeleteActions( + dtos: ModifyTagDto[], + queryRunner: QueryRunner, + ): Promise { + try { + if (!dtos || dtos.length === 0) { + throw new Error('No DTOs provided for deletion.'); + } + + const tagUuidsToDelete = dtos + .filter((dto) => dto.action === ModifyAction.DELETE && dto.tagUuid) + .map((dto) => dto.tagUuid); + + if (tagUuidsToDelete.length === 0) return []; + + const allocationsToUpdate = await queryRunner.manager.find( + SubspaceProductAllocationEntity, + { + where: { tags: { uuid: In(tagUuidsToDelete) } }, + relations: ['tags'], + }, + ); + + if (!allocationsToUpdate || allocationsToUpdate.length === 0) return []; + + const deletedAllocations: SubspaceProductAllocationEntity[] = []; + const allocationUpdates: SubspaceProductAllocationEntity[] = []; + + for (const allocation of allocationsToUpdate) { + const updatedTags = allocation.tags.filter( + (tag) => !tagUuidsToDelete.includes(tag.uuid), + ); + + if (updatedTags.length === allocation.tags.length) { + continue; + } + + if (updatedTags.length === 0) { + deletedAllocations.push(allocation); + } else { + allocation.tags = updatedTags; + allocationUpdates.push(allocation); + } + } + + if (allocationUpdates.length > 0) { + await queryRunner.manager.save( + SubspaceProductAllocationEntity, + allocationUpdates, + ); + } + + if (deletedAllocations.length > 0) { + await queryRunner.manager.remove( + SubspaceProductAllocationEntity, + deletedAllocations, + ); + } + + await queryRunner.manager + .createQueryBuilder() + .delete() + .from('subspace_product_tags') + .where( + 'subspace_product_allocation_id NOT IN ' + + queryRunner.manager + .createQueryBuilder() + .select('uuid') + .from(SubspaceProductAllocationEntity, 'allocation') + .getQuery(), + ) + .execute(); + + return deletedAllocations; + } catch (error) { + throw this.handleError(error, `Failed to delete tags in subspace`); + } + } + private async validateTagWithinSubspace( + queryRunner: QueryRunner | undefined, + tag: NewTagEntity, + subspace: SubspaceEntity, + spaceAllocationsToExclude?: SpaceProductAllocationEntity[], + ): Promise { + const existingTagInSpace = await (queryRunner + ? queryRunner.manager.findOne(SpaceProductAllocationEntity, { + where: { + product: tag.product, + space: subspace.space, + tags: { uuid: tag.uuid }, + }, + relations: ['tags'], + }) + : this.spaceProductAllocationRepository.findOne({ + where: { + product: tag.product, + space: subspace.space, + tags: { uuid: tag.uuid }, + }, + relations: ['tags'], + })); + + const isExcluded = spaceAllocationsToExclude?.some( + (excludedAllocation) => + excludedAllocation.product.uuid === tag.product.uuid && + excludedAllocation.tags.some((t) => t.uuid === tag.uuid), + ); + + if (!isExcluded && existingTagInSpace) { + throw new HttpException( + `Tag ${tag.uuid} (Product: ${tag.product.uuid}) is already allocated at the space level (${subspace.space.uuid}). Cannot allocate the same tag in a subspace.`, + HttpStatus.BAD_REQUEST, + ); + } + + const existingTagInSameSpace = await (queryRunner + ? queryRunner.manager.findOne(SubspaceProductAllocationEntity, { + where: { + product: tag.product, + subspace: { space: subspace.space }, + tags: { uuid: tag.uuid }, + }, + relations: ['subspace', 'tags'], + }) + : this.subspaceProductAllocationRepository.findOne({ + where: { + product: tag.product, + subspace: { space: subspace.space }, + tags: { uuid: tag.uuid }, + }, + relations: ['subspace', 'tags'], + })); + + if ( + existingTagInSameSpace && + existingTagInSameSpace.subspace.uuid !== subspace.uuid + ) { + throw new HttpException( + `Tag ${tag.uuid} (Product: ${tag.product.uuid}) is already allocated in another subspace (${existingTagInSameSpace.subspace.uuid}) within the same space (${subspace.space.uuid}).`, + HttpStatus.BAD_REQUEST, + ); + } + } + private createNewSubspaceAllocation( + subspace: SubspaceEntity, + tag: NewTagEntity, + queryRunner?: QueryRunner, + ): SubspaceProductAllocationEntity { + return queryRunner + ? queryRunner.manager.create(SubspaceProductAllocationEntity, { + subspace, + product: tag.product, + tags: [tag], + }) + : this.subspaceProductAllocationRepository.create({ + subspace, + product: tag.product, + tags: [tag], + }); + } + private async getAllocationByProduct( + product: ProductEntity, + subspace: SubspaceEntity, + queryRunner?: QueryRunner, + ): Promise { + return queryRunner + ? queryRunner.manager.findOne(SubspaceProductAllocationEntity, { + where: { subspace, product }, + relations: ['tags'], + }) + : this.subspaceProductAllocationRepository.findOne({ + where: { subspace, product }, + relations: ['tags'], + }); + } + private async saveAllocation( + allocation: SubspaceProductAllocationEntity, + queryRunner?: QueryRunner, + ): Promise { + if (queryRunner) { + await queryRunner.manager.save( + SubspaceProductAllocationEntity, + allocation, + ); + } else { + await this.subspaceProductAllocationRepository.save(allocation); + } + } + + private async saveAllocations( + allocations: SubspaceProductAllocationEntity[], + queryRunner?: QueryRunner, + ): Promise { + if (queryRunner) { + await queryRunner.manager.save( + SubspaceProductAllocationEntity, + allocations, + ); + } else { + await this.subspaceProductAllocationRepository.save(allocations); + } + } + private handleError(error: any, message: string): HttpException { + return new HttpException( + error instanceof HttpException ? error.message : message, + error instanceof HttpException + ? error.getStatus() + : HttpStatus.INTERNAL_SERVER_ERROR, + ); + } +} diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index 3136d5d..9172db9 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -2,7 +2,6 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { AddSubspaceDto, - CreateTagDto, DeleteSubspaceDto, GetSpaceParam, GetSubSpaceParam, @@ -26,6 +25,9 @@ import { SubspaceDeviceService } from './subspace-device.service'; import { ModifyTagDto } from 'src/space/dtos/tag/modify-tag.dto'; import { SpaceEntity } from '@app/common/modules/space/entities/space.entity'; import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity'; +import { ProcessTagDto } from 'src/tags/dtos'; +import { TagService as NewTagService } from 'src/tags/services/tags.service'; +import { SubspaceProductAllocationService } from './subspace-product-allocation.service'; @Injectable() export class SubSpaceService { @@ -33,7 +35,9 @@ export class SubSpaceService { private readonly subspaceRepository: SubspaceRepository, private readonly validationService: ValidationService, private readonly tagService: TagService, + private readonly newTagService: NewTagService, public readonly deviceService: SubspaceDeviceService, + private readonly subspaceProductAllocationService: SubspaceProductAllocationService, ) {} async createSubspaces( @@ -51,6 +55,7 @@ export class SubSpaceService { subspaceNames, subspaceData[0].space, ); + const subspaces = subspaceData.map((data) => queryRunner.manager.create(this.subspaceRepository.target, data), ); @@ -94,7 +99,8 @@ export class SubSpaceService { addSubspaceDtos: AddSubspaceDto[], space: SpaceEntity, queryRunner: QueryRunner, - otherTags?: CreateTagDto[], + otherTags?: ProcessTagDto[], + projectUuid?: string, ): Promise { try { this.checkForDuplicateNames( @@ -107,20 +113,22 @@ export class SubSpaceService { })); 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, + + const allTags = [...(dto.tags || []), ...(otherTags || [])]; + + if (allTags.length) { + const processedTags = await this.newTagService.processTags( + allTags, + projectUuid, queryRunner, - null, + ); + await this.subspaceProductAllocationService.createSubspaceProductAllocations( subspace, - [...(otherTags || []), ...otherDtoTags], + processedTags, + queryRunner, ); } }), @@ -318,14 +326,28 @@ export class SubSpaceService { subspaceDtos: ModifySubspaceDto[], queryRunner: QueryRunner, space?: SpaceEntity, + projectUuid?: string, + spaceTagUpdateDtos?: ModifyTagDto[], ) { + const addedSubspaces = []; + const updatedSubspaces = []; + for (const subspace of subspaceDtos) { switch (subspace.action) { case ModifyAction.ADD: - await this.handleAddAction(subspace, space, queryRunner); + const addedSubspace = await this.handleAddAction( + subspace, + space, + queryRunner, + ); + addedSubspaces.push(addedSubspace); break; case ModifyAction.UPDATE: - await this.handleUpdateAction(subspace, queryRunner); + const updatedSubspace = await this.handleUpdateAction( + subspace, + queryRunner, + ); + updatedSubspaces.push(updatedSubspace); break; case ModifyAction.DELETE: await this.handleDeleteAction(subspace, queryRunner); @@ -337,6 +359,18 @@ export class SubSpaceService { ); } } + + const combinedSubspaces = [...addedSubspaces, ...updatedSubspaces]; + + if (combinedSubspaces.length > 0) { + await this.subspaceProductAllocationService.updateSubspaceProductAllocations( + combinedSubspaces, + projectUuid, + queryRunner, + space, + spaceTagUpdateDtos, + ); + } } async unlinkModels( @@ -382,10 +416,10 @@ export class SubSpaceService { space: SpaceEntity, queryRunner: QueryRunner, ): Promise { - const createTagDtos: CreateTagDto[] = + const createTagDtos: ProcessTagDto[] = subspace.tags?.map((tag) => ({ - tag: tag.tag as string, - uuid: tag.uuid, + tag: tag.name as string, + uuid: tag.tagUuid, productUuid: tag.productUuid as string, })) || []; const subSpace = await this.createSubspacesFromDto( @@ -440,7 +474,7 @@ export class SubSpaceService { ); if (subspace.tags?.length) { - const modifyTagDtos: CreateTagDto[] = subspace.tags.map((tag) => ({ + const modifyTagDtos: ProcessTagDto[] = subspace.tags.map((tag) => ({ uuid: tag.uuid, action: ModifyAction.ADD, tag: tag.tag, @@ -538,4 +572,7 @@ export class SubSpaceService { await this.checkForDuplicateNames(names); await this.checkExistingNamesInSpace(names, space); } + extractTagsFromSubspace(addSubspaceDto: AddSubspaceDto[]): ProcessTagDto[] { + return addSubspaceDto.flatMap((subspace) => subspace.tags || []); + } } diff --git a/src/space/services/tag/tag.service.ts b/src/space/services/tag/tag.service.ts index b546b97..edf6575 100644 --- a/src/space/services/tag/tag.service.ts +++ b/src/space/services/tag/tag.service.ts @@ -177,20 +177,20 @@ export class TagService { subspace?: SubspaceEntity, ): Promise { try { - const existingTag = await this.getTagByUuid(tag.uuid); + const existingTag = await this.getTagByUuid(tag.tagUuid); const contextSpace = space ?? subspace?.space; - if (contextSpace && tag.tag !== existingTag.tag) { + if (contextSpace && tag.name !== existingTag.tag) { await this.checkTagReuse( - tag.tag, + tag.name, existingTag.product.uuid, contextSpace, ); } return await queryRunner.manager.save( - Object.assign(existingTag, { tag: tag.tag }), + Object.assign(existingTag, { tag: tag.name }), ); } catch (error) { throw this.handleUnexpectedError('Failed to update tags', error); @@ -297,9 +297,9 @@ export class TagService { await this.createTags( [ { - tag: tag.tag, + tag: tag.name, productUuid: tag.productUuid, - uuid: tag.uuid, + uuid: tag.tagUuid, }, ], queryRunner, @@ -313,7 +313,7 @@ export class TagService { await this.updateTag(tag, queryRunner, space, subspace); break; case ModifyAction.DELETE: - await this.deleteTags([tag.uuid], queryRunner); + await this.deleteTags([tag.tagUuid], queryRunner); break; default: throw new HttpException( @@ -385,7 +385,9 @@ export class TagService { const filteredTagExists = tagExists.filter( (existingTag) => - !tagsToDelete?.some((deleteTag) => deleteTag.uuid === existingTag.uuid), + !tagsToDelete?.some( + (deleteTag) => deleteTag.tagUuid === existingTag.uuid, + ), ); if (filteredTagExists.length > 0) { @@ -517,14 +519,14 @@ export class TagService { tagsToAdd .filter((tagToAdd) => spaceTagsToDelete.some( - (tagToDelete) => tagToAdd.uuid === tagToDelete.uuid, + (tagToDelete) => tagToAdd.tagUuid === tagToDelete.tagUuid, ), ) - .map((tag) => tag.uuid), + .map((tag) => tag.tagUuid), ); const remainingTags = spaceTags.filter( - (tag) => !commonTagUuids.has(tag.uuid), // Exclude tags in commonTagUuids + (tag) => !commonTagUuids.has(tag.tagUuid), // Exclude tags in commonTagUuids ); return remainingTags; @@ -551,11 +553,11 @@ export class TagService { ); const subspaceTagsToDeleteUuids = new Set( - subspaceTagsToDelete.map((tag) => tag.uuid), + subspaceTagsToDelete.map((tag) => tag.tagUuid), ); const commonTagsInSubspaces = subspaceTagsToAdd.filter((tag) => - subspaceTagsToDeleteUuids.has(tag.uuid), + subspaceTagsToDeleteUuids.has(tag.tagUuid), ); // Find UUIDs of tags that are common between spaceTagsToAdd and subspace tags marked for deletion @@ -567,17 +569,18 @@ export class TagService { subspace.tags?.filter( (tagToDelete) => tagToDelete.action === 'delete' && - tagToAdd.uuid === tagToDelete.uuid, + tagToAdd.tagUuid === tagToDelete.tagUuid, ) || [], ), ) - .map((tag) => tag.uuid), + .map((tag) => tag.tagUuid), ); // Modify subspaceModels by removing tags with UUIDs present in commonTagUuids let modifiedSubspaces = subspaceModels.map((subspace) => ({ ...subspace, - tags: subspace.tags?.filter((tag) => !commonTagUuids.has(tag.uuid)) || [], + tags: + subspace.tags?.filter((tag) => !commonTagUuids.has(tag.tagUuid)) || [], })); modifiedSubspaces = modifiedSubspaces.map((subspace) => ({ @@ -588,7 +591,7 @@ export class TagService { !( tag.action === 'delete' && commonTagsInSubspaces.some( - (commonTag) => commonTag.uuid === tag.uuid, + (commonTag) => commonTag.tagUuid === tag.tagUuid, ) ), ) || [], diff --git a/src/tags/services/tags.service.ts b/src/tags/services/tags.service.ts index 8e8a1b1..964e98c 100644 --- a/src/tags/services/tags.service.ts +++ b/src/tags/services/tags.service.ts @@ -171,6 +171,8 @@ export class TagService { return newTags; } catch (error) { + console.log('error1', error); + throw new HttpException( error instanceof HttpException ? error.message