update in tag service

This commit is contained in:
hannathkadher
2024-12-24 11:33:58 +04:00
parent 1e47fffc0a
commit cb2778dce5
22 changed files with 705 additions and 117 deletions

View File

@ -13,7 +13,7 @@ export class CreateSubspaceModelDto {
subspaceName: string;
@ApiProperty({
description: 'List of tags associated with the subspace',
description: 'List of tag models associated with the subspace',
type: [CreateTagModelDto],
})
@IsArray()

View File

@ -3,7 +3,7 @@ import { IsNotEmpty, IsString } from 'class-validator';
export class CreateTagModelDto {
@ApiProperty({
description: 'Tag associated with the space or subspace',
description: 'Tag models associated with the space or subspace models',
example: 'Temperature Control',
})
@IsNotEmpty()

View File

@ -11,7 +11,7 @@ export class ModifyTagModelDto {
action: ModifyAction;
@ApiPropertyOptional({
description: 'UUID of the tag (required for update/delete)',
description: 'UUID of the tag model (required for update/delete)',
example: '123e4567-e89b-12d3-a456-426614174000',
})
@IsOptional()
@ -19,7 +19,7 @@ export class ModifyTagModelDto {
uuid?: string;
@ApiPropertyOptional({
description: 'Name of the tag (required for add/update)',
description: 'Name of the tag model (required for add/update)',
example: 'Temperature Sensor',
})
@IsOptional()

View File

@ -152,6 +152,14 @@ export class SpaceModelService {
queryRunner,
);
}
if (dto.tags) {
await this.tagModelService.modifyTags(
dto.tags,
queryRunner,
spaceModel,
);
}
await queryRunner.commitTransaction();
return new SuccessResponseDto({

View File

@ -4,16 +4,9 @@ import {
SubspaceModelRepository,
} from '@app/common/modules/space-model';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import {
CreateSubspaceModelDto,
UpdateSubspaceModelDto,
CreateTagModelDto,
} from '../../dtos';
import { CreateSubspaceModelDto, CreateTagModelDto } from '../../dtos';
import { QueryRunner } from 'typeorm';
import {
IDeletedSubsaceModelInterface,
IUpdateSubspaceModelInterface,
} from 'src/space-model/interfaces';
import { IDeletedSubsaceModelInterface } from 'src/space-model/interfaces';
import {
DeleteSubspaceModelDto,
ModifySubspaceModelDto,
@ -63,33 +56,6 @@ export class SubSpaceModelService {
return savedSubspaces;
}
async updateSubspaceModels(
updateDtos: UpdateSubspaceModelDto[],
queryRunner: QueryRunner,
): Promise<IUpdateSubspaceModelInterface[]> {
const updateResults: IUpdateSubspaceModelInterface[] = [];
for (const dto of updateDtos) {
await this.findOne(dto.subspaceUuid);
const updateResult: IUpdateSubspaceModelInterface = {
uuid: dto.subspaceUuid,
};
if (dto.subspaceName) {
await this.updateSubspaceName(
dto.subspaceUuid,
dto.subspaceName,
queryRunner,
);
updateResult.subspaceName = dto.subspaceName;
}
updateResults.push(updateResult);
}
return updateResults;
}
async deleteSubspaceModels(
deleteDtos: DeleteSubspaceModelDto[],
queryRunner: QueryRunner,
@ -167,22 +133,23 @@ export class SubSpaceModelService {
}
private async handleUpdateAction(
subspace: ModifySubspaceModelDto,
modifyDto: ModifySubspaceModelDto,
queryRunner: QueryRunner,
): Promise<void> {
const existingSubspace = await this.findOne(subspace.uuid);
const subspace = await this.findOne(modifyDto.uuid);
if (subspace.subspaceName) {
existingSubspace.subspaceName = subspace.subspaceName;
await queryRunner.manager.save(existingSubspace);
}
await this.updateSubspaceName(
queryRunner,
subspace,
modifyDto.subspaceName,
);
if (subspace.tags?.length) {
if (modifyDto.tags?.length) {
await this.tagModelService.modifyTags(
subspace.tags,
modifyDto.tags,
queryRunner,
null,
existingSubspace,
subspace,
);
}
}
@ -258,14 +225,13 @@ export class SubSpaceModelService {
}
private async updateSubspaceName(
uuid: string,
subspaceName: string,
queryRunner: QueryRunner,
subSpaceModel: SubspaceModelEntity,
subspaceName?: string,
): Promise<void> {
await queryRunner.manager.update(
this.subspaceModelRepository.target,
{ uuid },
{ subspaceName },
);
if (subspaceName) {
subSpaceModel.subspaceName = subspaceName;
await queryRunner.manager.save(subSpaceModel);
}
}
}

View File

@ -169,7 +169,7 @@ export class TagModelService {
}
throw new HttpException(
`An error occurred while modifying tags: ${error.message}`,
`An error occurred while modifying tag models: ${error.message}`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
@ -236,7 +236,7 @@ export class TagModelService {
});
if (!tag) {
throw new HttpException(
`Tag with ID ${uuid} not found.`,
`Tag model with ID ${uuid} not found.`,
HttpStatus.NOT_FOUND,
);
}

View File

@ -11,6 +11,7 @@ import {
ValidateNested,
} from 'class-validator';
import { AddSubspaceDto } from './subspace';
import { CreateTagDto } from './tag';
export class AddSpaceDto {
@ApiProperty({
@ -77,6 +78,15 @@ export class AddSpaceDto {
@ValidateNested({ each: true })
@Type(() => AddSubspaceDto)
subspaces?: AddSubspaceDto[];
@ApiProperty({
description: 'List of tags associated with the space model',
type: [CreateTagDto],
})
@IsArray()
@ValidateNested({ each: true })
@Type(() => CreateTagDto)
tags?: CreateTagDto[];
}
export class AddUserSpaceDto {

View File

@ -5,3 +5,4 @@ export * from './user-space.param';
export * from './subspace';
export * from './project.param.dto';
export * from './update.space.dto';
export * from './tag';

View File

@ -1,5 +1,7 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';
import { IsArray, IsNotEmpty, IsString, ValidateNested } from 'class-validator';
import { CreateTagDto } from '../tag';
import { Type } from 'class-transformer';
export class AddSubspaceDto {
@ApiProperty({
@ -9,4 +11,13 @@ export class AddSubspaceDto {
@IsNotEmpty()
@IsString()
subspaceName: string;
@ApiProperty({
description: 'List of tags associated with the subspace',
type: [CreateTagDto],
})
@IsArray()
@ValidateNested({ each: true })
@Type(() => CreateTagDto)
tags?: CreateTagDto[];
}

View File

@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';
export class DeleteSubspaceDto {
@ApiProperty({
description: 'Uuid of the subspace model need to be deleted',
example: '982fc3a3-64dc-4afb-a5b5-65ee8fef0424',
})
@IsNotEmpty()
@IsString()
subspaceUuid: string;
}

View File

@ -1,3 +1,6 @@
export * from './add.subspace.dto';
export * from './get.subspace.param';
export * from './add.subspace-device.param';
export * from './update.subspace.dto';
export * from './delete.subspace.dto';
export * from './modify.subspace.dto';

View File

@ -0,0 +1,47 @@
import { ModifyAction } from '@app/common/constants/modify-action.enum';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import {
IsEnum,
IsOptional,
IsString,
IsArray,
ValidateNested,
} from 'class-validator';
import { ModifyTagDto } from '../tag/modify-tag.dto';
export class ModifySubspaceDto {
@ApiProperty({
description: 'Action to perform: add, update, or delete',
example: ModifyAction.ADD,
})
@IsEnum(ModifyAction)
action: ModifyAction;
@ApiPropertyOptional({
description: 'UUID of the subspace (required for update/delete)',
example: '123e4567-e89b-12d3-a456-426614174000',
})
@IsOptional()
@IsString()
uuid?: string;
@ApiPropertyOptional({
description: 'Name of the subspace (required for add/update)',
example: 'Living Room',
})
@IsOptional()
@IsString()
subspaceName?: string;
@ApiPropertyOptional({
description:
'List of tag modifications (add/update/delete) for the subspace',
type: [ModifyTagDto],
})
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => ModifyTagDto)
tags?: ModifyTagDto[];
}

View File

@ -0,0 +1,16 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';
export class UpdateSubspaceDto {
@ApiProperty({
description: 'Name of the subspace',
example: 'Living Room',
})
@IsNotEmpty()
@IsString()
subspaceName?: string;
@IsNotEmpty()
@IsString()
subspaceUuid: string;
}

View File

@ -0,0 +1,20 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';
export class CreateTagDto {
@ApiProperty({
description: 'Tag associated with the space or subspace',
example: 'Temperature Control',
})
@IsNotEmpty()
@IsString()
tag: string;
@ApiProperty({
description: 'ID of the product associated with the tag',
example: '123e4567-e89b-12d3-a456-426614174000',
})
@IsNotEmpty()
@IsString()
productUuid: string;
}

View File

@ -0,0 +1 @@
export * from './create-tag-dto';

View File

@ -0,0 +1,37 @@
import { ModifyAction } from '@app/common/constants/modify-action.enum';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsEnum, IsOptional, IsString } from 'class-validator';
export class ModifyTagDto {
@ApiProperty({
description: 'Action to perform: add, update, or delete',
example: ModifyAction.ADD,
})
@IsEnum(ModifyAction)
action: ModifyAction;
@ApiPropertyOptional({
description: 'UUID of the tag (required for update/delete)',
example: '123e4567-e89b-12d3-a456-426614174000',
})
@IsOptional()
@IsString()
uuid?: string;
@ApiPropertyOptional({
description: 'Name of the tag (required for add/update)',
example: 'Temperature Sensor',
})
@IsOptional()
@IsString()
tag?: string;
@ApiPropertyOptional({
description:
'UUID of the product associated with the tag (required for add)',
example: 'c789a91e-549a-4753-9006-02f89e8170e0',
})
@IsOptional()
@IsString()
productUuid?: string;
}

View File

@ -1,4 +1,59 @@
import { PartialType } from '@nestjs/swagger';
import { AddSpaceDto } from './add.space.dto';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import {
IsArray,
IsNumber,
IsOptional,
IsString,
ValidateNested,
} from 'class-validator';
import { ModifySubspaceDto } from './subspace';
import { Type } from 'class-transformer';
import { ModifyTagDto } from './tag/modify-tag.dto';
export class UpdateSpaceDto extends PartialType(AddSpaceDto) {}
export class UpdateSpaceDto {
@ApiProperty({
description: 'Updated name of the space ',
example: 'New Space Name',
})
@IsOptional()
@IsString()
spaceName?: string;
@ApiProperty({
description: 'Icon identifier for the space',
example: 'assets/location',
required: false,
})
@IsString()
@IsOptional()
public icon?: string;
@ApiProperty({ description: 'X position on canvas', example: 120 })
@IsNumber()
x: number;
@ApiProperty({ description: 'Y position on canvas', example: 200 })
@IsNumber()
y: number;
@ApiPropertyOptional({
description: 'List of subspace modifications (add/update/delete)',
type: [ModifySubspaceDto],
})
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => ModifySubspaceDto)
subspaceModels?: ModifySubspaceDto[];
@ApiPropertyOptional({
description:
'List of tag modifications (add/update/delete) for the space model',
type: [ModifyTagDto],
})
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => ModifyTagDto)
tags?: ModifyTagDto[];
}

View File

@ -16,11 +16,11 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { SpaceEntity } from '@app/common/modules/space/entities';
import { generateRandomString } from '@app/common/helper/randomString';
import { SpaceLinkService } from './space-link';
import { CreateSubspaceModelDto } from 'src/space-model/dtos';
import { SubSpaceService } from './subspace';
import { DataSource, Not } from 'typeorm';
import { ValidationService } from './space-validation.service';
import { ORPHAN_SPACE_NAME } from '@app/common/constants/orphan-constant';
import { TagService } from './tag';
@Injectable()
export class SpaceService {
@ -30,13 +30,15 @@ export class SpaceService {
private readonly spaceLinkService: SpaceLinkService,
private readonly subSpaceService: SubSpaceService,
private readonly validationService: ValidationService,
private readonly tagService: TagService,
) {}
async createSpace(
addSpaceDto: AddSpaceDto,
params: CommunitySpaceParam,
): Promise<BaseResponseDto> {
const { parentUuid, direction, spaceModelUuid, subspaces } = addSpaceDto;
const { parentUuid, direction, spaceModelUuid, subspaces, tags } =
addSpaceDto;
const { communityUuid, projectUuid } = params;
if (addSpaceDto.spaceName === ORPHAN_SPACE_NAME) {
@ -67,14 +69,14 @@ export class SpaceService {
: null;
try {
const newSpace = queryRunner.manager.create(SpaceEntity, {
const space = queryRunner.manager.create(SpaceEntity, {
...addSpaceDto,
spaceModel,
parent: parentUuid ? parent : null,
community,
});
await queryRunner.manager.save(newSpace);
const newSpace = await queryRunner.manager.save(space);
if (direction && parent) {
await this.spaceLinkService.saveSpaceLink(
@ -90,11 +92,14 @@ export class SpaceService {
newSpace,
queryRunner,
);
} else if (spaceModel && spaceModel.subspaceModels.length) {
await this.subSpaceService.createSubSpaceFromModel(
spaceModel,
newSpace,
}
if (tags?.length) {
newSpace.tags = await this.tagService.createTags(
tags,
queryRunner,
newSpace,
null,
);
}
@ -242,19 +247,32 @@ export class SpaceService {
HttpStatus.BAD_REQUEST,
);
}
if (updateSpaceDto.spaceName) space.spaceName = updateSpaceDto.spaceName;
// If a parentId is provided, check if the parent exists
const { parentUuid } = updateSpaceDto;
const parent = parentUuid
? await this.validationService.validateSpace(parentUuid)
: null;
if (updateSpaceDto.x) space.x = updateSpaceDto.x;
// Update other space properties from updateSpaceDto
Object.assign(space, updateSpaceDto, { parent });
if (updateSpaceDto.y) space.y = updateSpaceDto.y;
if (updateSpaceDto.icon) space.icon = updateSpaceDto.icon;
if (updateSpaceDto.icon) space.icon = updateSpaceDto.icon;
// Save the updated space
await queryRunner.manager.save(space);
if (updateSpaceDto.subspaceModels) {
await this.subSpaceService.modifySubSpace(
updateSpaceDto.subspaceModels,
space,
queryRunner,
);
}
if (updateSpaceDto.tags) {
await this.tagService.modifyTags(
updateSpaceDto.tags,
queryRunner,
space,
);
}
await queryRunner.commitTransaction();
return new SuccessResponseDto({

View File

@ -1,6 +1,13 @@
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { AddSubspaceDto, GetSpaceParam, GetSubSpaceParam } from '../../dtos';
import {
AddSubspaceDto,
CreateTagDto,
DeleteSubspaceDto,
GetSpaceParam,
GetSubSpaceParam,
ModifySubspaceDto,
} from '../../dtos';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import {
TypeORMCustomModel,
@ -19,12 +26,15 @@ import {
} from '@app/common/modules/space-model';
import { ValidationService } from '../space-validation.service';
import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
import { TagService } from '../tag';
import { ModifyAction } from '@app/common/constants/modify-action.enum';
@Injectable()
export class SubSpaceService {
constructor(
private readonly subspaceRepository: SubspaceRepository,
private readonly validationService: ValidationService,
private readonly tagService: TagService,
) {}
async createSubspaces(
@ -71,6 +81,7 @@ export class SubSpaceService {
addSubspaceDtos: AddSubspaceDto[],
space: SpaceEntity,
queryRunner: QueryRunner,
otherTags?: CreateTagDto[],
): Promise<SubspaceEntity[]> {
try {
const subspaceData = addSubspaceDtos.map((dto) => ({
@ -80,6 +91,20 @@ export class SubSpaceService {
const subspaces = await this.createSubspaces(subspaceData, queryRunner);
await Promise.all(
addSubspaceDtos.map(async (dto, index) => {
const subspace = subspaces[index];
if (dto.tags?.length) {
subspace.tags = await this.tagService.createTags(
dto.tags,
queryRunner,
null,
subspace,
otherTags,
);
}
}),
);
return subspaces;
} catch (error) {
throw new Error(
@ -144,43 +169,6 @@ export class SubSpaceService {
}
}
async findOne(params: GetSubSpaceParam): Promise<BaseResponseDto> {
const { communityUuid, subSpaceUuid, spaceUuid, projectUuid } = params;
await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid,
projectUuid,
spaceUuid,
);
try {
const subSpace = await this.subspaceRepository.findOne({
where: {
uuid: subSpaceUuid,
},
});
// If space is not found, throw a NotFoundException
if (!subSpace) {
throw new HttpException(
`Sub Space with UUID ${subSpaceUuid} not found`,
HttpStatus.NOT_FOUND,
);
}
return new SuccessResponseDto({
message: `Subspace with ID ${subSpaceUuid} successfully fetched`,
data: subSpace,
});
} catch (error) {
if (error instanceof HttpException) {
throw error; // If it's an HttpException, rethrow it
} else {
throw new HttpException(
'An error occurred while deleting the subspace',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}
async updateSubSpace(
params: GetSubSpaceParam,
updateSubSpaceDto: AddSubspaceDto,
@ -256,4 +244,153 @@ export class SubSpaceService {
);
}
}
async deleteSubspaces(
deleteDtos: DeleteSubspaceDto[],
queryRunner: QueryRunner,
) {
const deleteResults: { uuid: string }[] = [];
for (const dto of deleteDtos) {
const subspaceModel = await this.findOne(dto.subspaceUuid);
await queryRunner.manager.update(
this.subspaceRepository.target,
{ uuid: dto.subspaceUuid },
{ disabled: true },
);
if (subspaceModel.tags?.length) {
const modifyTagDtos = subspaceModel.tags.map((tag) => ({
uuid: tag.uuid,
action: ModifyAction.DELETE,
}));
await this.tagService.modifyTags(
modifyTagDtos,
queryRunner,
null,
subspaceModel,
);
}
deleteResults.push({ uuid: dto.subspaceUuid });
}
return deleteResults;
}
async modifySubSpace(
subspaceDtos: ModifySubspaceDto[],
space: SpaceEntity,
queryRunner: QueryRunner,
): Promise<void> {
for (const subspace of subspaceDtos) {
switch (subspace.action) {
case ModifyAction.ADD:
await this.handleAddAction(subspace, space, queryRunner);
break;
case ModifyAction.UPDATE:
await this.handleUpdateAction(subspace, queryRunner);
break;
case ModifyAction.DELETE:
await this.handleDeleteAction(subspace, queryRunner);
break;
default:
throw new HttpException(
`Invalid action "${subspace.action}".`,
HttpStatus.BAD_REQUEST,
);
}
}
}
private async handleAddAction(
subspace: ModifySubspaceDto,
space: SpaceEntity,
queryRunner: QueryRunner,
): Promise<void> {
const createTagDtos: CreateTagDto[] =
subspace.tags?.map((tag) => ({
tag: tag.tag as string,
productUuid: tag.productUuid as string,
})) || [];
await this.createSubspacesFromDto(
[{ subspaceName: subspace.subspaceName, tags: createTagDtos }],
space,
queryRunner,
);
}
private async handleUpdateAction(
modifyDto: ModifySubspaceDto,
queryRunner: QueryRunner,
): Promise<void> {
const subspace = await this.findOne(modifyDto.uuid);
await this.updateSubspaceName(
queryRunner,
subspace,
modifyDto.subspaceName,
);
if (modifyDto.tags?.length) {
await this.tagService.modifyTags(
modifyDto.tags,
queryRunner,
null,
subspace,
);
}
}
private async handleDeleteAction(
subspace: ModifySubspaceDto,
queryRunner: QueryRunner,
): Promise<void> {
const subspaceModel = await this.findOne(subspace.uuid);
await queryRunner.manager.update(
this.subspaceRepository.target,
{ uuid: subspace.uuid },
{ disabled: true },
);
if (subspaceModel.tags?.length) {
const modifyTagDtos = subspaceModel.tags.map((tag) => ({
uuid: tag.uuid,
action: ModifyAction.DELETE,
}));
await this.tagService.modifyTags(
modifyTagDtos,
queryRunner,
null,
subspaceModel,
);
}
}
private async findOne(subspaceUuid: string): Promise<SubspaceEntity> {
const subspace = await this.subspaceRepository.findOne({
where: { uuid: subspaceUuid },
relations: ['tags'],
});
if (!subspace) {
throw new HttpException(
`SubspaceModel with UUID ${subspaceUuid} not found.`,
HttpStatus.NOT_FOUND,
);
}
return subspace;
}
private async updateSubspaceName(
queryRunner: QueryRunner,
subSpace: SubspaceEntity,
subspaceName?: string,
): Promise<void> {
if (subspaceName) {
subSpace.subspaceName = subspaceName;
await queryRunner.manager.save(subSpace);
}
}
}

View File

@ -0,0 +1 @@
export * from './tag.service';

View File

@ -0,0 +1,237 @@
import { ModifyAction } from '@app/common/constants/modify-action.enum';
import {
SpaceEntity,
SubspaceEntity,
TagEntity,
TagRepository,
} from '@app/common/modules/space';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { ProductService } from 'src/product/services';
import { CreateTagDto } from 'src/space/dtos';
import { ModifyTagDto } from 'src/space/dtos/tag/modify-tag.dto';
import { QueryRunner } from 'typeorm';
@Injectable()
export class TagService {
constructor(
private readonly tagRepository: TagRepository,
private readonly productService: ProductService,
) {}
async createTags(
tags: CreateTagDto[],
queryRunner: QueryRunner,
space?: SpaceEntity,
subspace?: SubspaceEntity,
additionalTags?: CreateTagDto[],
): Promise<TagEntity[]> {
if (!tags.length) {
throw new HttpException('Tags cannot be empty.', HttpStatus.BAD_REQUEST);
}
const combinedTags = additionalTags ? [...tags, ...additionalTags] : tags;
const duplicateTags = this.findDuplicateTags(combinedTags);
if (duplicateTags.length > 0) {
throw new HttpException(
`Duplicate tags found for the same product: ${duplicateTags.join(', ')}`,
HttpStatus.BAD_REQUEST,
);
}
const tagEntities = await Promise.all(
tags.map(async (tagDto) =>
this.prepareTagEntity(tagDto, queryRunner, space, subspace),
),
);
try {
return await queryRunner.manager.save(tagEntities);
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(
'Failed to save tag models due to an unexpected error.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async updateTag(
tag: ModifyTagDto,
queryRunner: QueryRunner,
space?: SpaceEntity,
subspace?: SubspaceEntity,
): Promise<TagEntity> {
try {
const existingTag = await this.getTagByUuid(tag.uuid);
if (space) {
await this.checkTagReuse(tag.tag, existingTag.product.uuid, space);
} else {
await this.checkTagReuse(
tag.tag,
existingTag.product.uuid,
subspace.space,
);
}
if (tag.tag) {
existingTag.tag = tag.tag;
}
return await queryRunner.manager.save(existingTag);
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(
error.message || 'Failed to update tags',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async deleteTags(tagUuids: string[], queryRunner: QueryRunner) {
try {
const deletePromises = tagUuids.map((id) =>
queryRunner.manager.softDelete(this.tagRepository.target, id),
);
await Promise.all(deletePromises);
return { message: 'Tags deleted successfully', tagUuids };
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(
error.message || 'Failed to delete tags',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
private findDuplicateTags(tags: CreateTagDto[]): string[] {
const seen = new Map<string, boolean>();
const duplicates: string[] = [];
tags.forEach((tagDto) => {
const key = `${tagDto.productUuid}-${tagDto.tag}`;
if (seen.has(key)) {
duplicates.push(`${tagDto.tag} for Product: ${tagDto.productUuid}`);
} else {
seen.set(key, true);
}
});
return duplicates;
}
async modifyTags(
tags: ModifyTagDto[],
queryRunner: QueryRunner,
space?: SpaceEntity,
subspace?: SubspaceEntity,
): Promise<void> {
try {
console.log(tags);
for (const tag of tags) {
if (tag.action === ModifyAction.ADD) {
const createTagDto: CreateTagDto = {
tag: tag.tag as string,
productUuid: tag.productUuid as string,
};
await this.createTags([createTagDto], queryRunner, space, subspace);
} else if (tag.action === ModifyAction.UPDATE) {
await this.updateTag(tag, queryRunner, space, subspace);
} else if (tag.action === ModifyAction.DELETE) {
await queryRunner.manager.update(
this.tagRepository.target,
{ uuid: tag.uuid },
{ disabled: true },
);
} else {
throw new HttpException(
`Invalid action "${tag.action}" provided.`,
HttpStatus.BAD_REQUEST,
);
}
}
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(
`An error occurred while modifying tags: ${error.message}`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
private async checkTagReuse(
tag: string,
productUuid: string,
space: SpaceEntity,
): Promise<void> {
const isTagInSpace = await this.tagRepository.exists({
where: { tag, space, product: { uuid: productUuid } },
});
const isTagInSubspace = await this.tagRepository.exists({
where: {
tag,
subspace: { space },
product: { uuid: productUuid },
},
});
if (isTagInSpace || isTagInSubspace) {
throw new HttpException(`Tag can't be reused`, HttpStatus.CONFLICT);
}
}
private async prepareTagEntity(
tagDto: CreateTagDto,
queryRunner: QueryRunner,
space?: SpaceEntity,
subspace?: SubspaceEntity,
): Promise<TagEntity> {
const product = await this.productService.findOne(tagDto.productUuid);
if (!product) {
throw new HttpException(
`Product with UUID ${tagDto.productUuid} not found.`,
HttpStatus.NOT_FOUND,
);
}
if (space) {
await this.checkTagReuse(tagDto.tag, tagDto.productUuid, space);
} else {
await this.checkTagReuse(tagDto.tag, tagDto.productUuid, subspace.space);
}
return queryRunner.manager.create(TagEntity, {
tag: tagDto.tag,
product: product.data,
space,
subspace,
});
}
private async getTagByUuid(uuid: string): Promise<TagEntity> {
const tag = await this.tagRepository.findOne({
where: { uuid },
relations: ['product'],
});
if (!tag) {
throw new HttpException(
`Tag with ID ${uuid} not found.`,
HttpStatus.NOT_FOUND,
);
}
return tag;
}
}

View File

@ -21,6 +21,7 @@ import {
import {
SpaceRepository,
SpaceLinkRepository,
TagRepository,
} from '@app/common/modules/space/repositories';
import { CommunityRepository } from '@app/common/modules/community/repositories';
import {
@ -40,10 +41,14 @@ import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status
import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories';
import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories';
import { ProjectRepository } from '@app/common/modules/project/repositiories';
import { SpaceModelRepository } from '@app/common/modules/space-model';
import {
SpaceModelRepository,
TagModelRepository,
} 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 { TagService } from './services/tag';
@Module({
imports: [ConfigModule, SpaceRepositoryModule, CommunityModule],
@ -57,8 +62,11 @@ import { SubspaceRepository } from '@app/common/modules/space/repositories/subsp
],
providers: [
ValidationService,
TagModelRepository,
TagRepository,
SpaceService,
TuyaService,
TagService,
ProductRepository,
SubSpaceService,
SpaceDeviceService,