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] 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