updated space update

This commit is contained in:
hannathkadher
2024-12-25 10:52:48 +04:00
parent 2e46176fd5
commit a74a0db26e
13 changed files with 331 additions and 168 deletions

View File

@ -58,4 +58,9 @@ export class SpaceModelEntity extends AbstractEntity<SpaceModelDto> {
@OneToMany(() => TagModel, (tag) => tag.spaceModel) @OneToMany(() => TagModel, (tag) => tag.spaceModel)
tags: TagModel[]; tags: TagModel[];
constructor(partial: Partial<SpaceModelEntity>) {
super();
Object.assign(this, partial);
}
} }

View File

@ -30,10 +30,10 @@ export class SubspaceModelEntity extends AbstractEntity<SubSpaceModelDto> {
) )
public spaceModel: SpaceModelEntity; public spaceModel: SpaceModelEntity;
@OneToMany(() => SubspaceEntity, (space) => space.subSpaceModel, { @OneToMany(() => SubspaceEntity, (subspace) => subspace.subSpaceModel, {
cascade: true, cascade: true,
}) })
public spaces: SubspaceEntity[]; public subspaceModel: SubspaceEntity[];
@Column({ @Column({
nullable: false, nullable: false,

View File

@ -103,8 +103,9 @@ export class SpaceEntity extends AbstractEntity<SpaceDto> {
@OneToMany(() => SceneEntity, (scene) => scene.space) @OneToMany(() => SceneEntity, (scene) => scene.space)
scenes: SceneEntity[]; scenes: SceneEntity[];
@ManyToOne(() => SpaceModelEntity, { nullable: true }) @ManyToOne(() => SpaceModelEntity, (spaceModel) => spaceModel.spaces, {
@JoinColumn({ name: 'space_model_uuid' }) nullable: true,
})
spaceModel?: SpaceModelEntity; spaceModel?: SpaceModelEntity;
@OneToMany( @OneToMany(

View File

@ -37,8 +37,9 @@ export class SubspaceEntity extends AbstractEntity<SubspaceDto> {
}) })
devices: DeviceEntity[]; devices: DeviceEntity[];
@ManyToOne(() => SubspaceModelEntity, { nullable: true }) @ManyToOne(() => SubspaceModelEntity, (subspace) => subspace.subspaceModel, {
@JoinColumn({ name: 'subspace_model_uuid' }) nullable: true,
})
subSpaceModel?: SubspaceModelEntity; subSpaceModel?: SubspaceModelEntity;
@OneToMany(() => TagEntity, (tag) => tag.subspace) @OneToMany(() => TagEntity, (tag) => tag.subspace)

View File

@ -106,8 +106,7 @@ export class SpaceModelService {
pageable.where = { pageable.where = {
project: { uuid: param.projectUuid }, project: { uuid: param.projectUuid },
}; };
pageable.include = pageable.include = 'subspaceModels,tags,subspaceModels.tags';
'subspaceModels,spaceProductModels,subspaceModels.productModels,subspaceModels.productModels.itemModels,spaceProductModels.items';
const customModel = TypeORMCustomModel(this.spaceModelRepository); const customModel = TypeORMCustomModel(this.spaceModelRepository);
@ -252,9 +251,15 @@ export class SpaceModelService {
const spaceModel = await this.spaceModelRepository.findOne({ const spaceModel = await this.spaceModelRepository.findOne({
where: { where: {
uuid, uuid,
disabled: true, disabled: false,
}, },
relations: ['subspaceModels', 'tags'], relations: [
'subspaceModels',
'tags',
'tags.product',
'subspaceModels.tags',
'subspaceModels.tags.product',
],
}); });
if (!spaceModel) { if (!spaceModel) {
throw new HttpException('space model not found', HttpStatus.NOT_FOUND); throw new HttpException('space model not found', HttpStatus.NOT_FOUND);

View File

@ -37,6 +37,6 @@ const CommandHandlers = [PropogateSubspaceHandler];
TagModelService, TagModelService,
TagModelRepository, TagModelRepository,
], ],
exports: [CqrsModule], exports: [CqrsModule, SpaceModelService],
}) })
export class SpaceModelModule {} export class SpaceModelModule {}

View File

@ -74,7 +74,6 @@ export class AddSpaceDto {
type: [AddSubspaceDto], type: [AddSubspaceDto],
}) })
@IsOptional() @IsOptional()
@IsArray()
@ValidateNested({ each: true }) @ValidateNested({ each: true })
@Type(() => AddSubspaceDto) @Type(() => AddSubspaceDto)
subspaces?: AddSubspaceDto[]; subspaces?: AddSubspaceDto[];
@ -83,7 +82,6 @@ export class AddSpaceDto {
description: 'List of tags associated with the space model', description: 'List of tags associated with the space model',
type: [CreateTagDto], type: [CreateTagDto],
}) })
@IsArray()
@ValidateNested({ each: true }) @ValidateNested({ each: true })
@Type(() => CreateTagDto) @Type(() => CreateTagDto)
tags?: CreateTagDto[]; tags?: CreateTagDto[];

View File

@ -46,7 +46,7 @@ export class UpdateSpaceDto {
@IsArray() @IsArray()
@ValidateNested({ each: true }) @ValidateNested({ each: true })
@Type(() => ModifySubspaceDto) @Type(() => ModifySubspaceDto)
subspaceModels?: ModifySubspaceDto[]; subspace?: ModifySubspaceDto[];
@ApiPropertyOptional({ @ApiPropertyOptional({
description: description:

View File

@ -43,7 +43,7 @@ export class ValidationService {
async validateSpace(spaceUuid: string): Promise<SpaceEntity> { async validateSpace(spaceUuid: string): Promise<SpaceEntity> {
const space = await this.spaceRepository.findOne({ const space = await this.spaceRepository.findOne({
where: { uuid: spaceUuid }, where: { uuid: spaceUuid, disabled: false },
relations: ['subspaces', 'tags'], relations: ['subspaces', 'tags'],
}); });
@ -62,11 +62,10 @@ export class ValidationService {
where: { uuid: spaceModelUuid }, where: { uuid: spaceModelUuid },
relations: [ relations: [
'subspaceModels', 'subspaceModels',
'subspaceModels.productModels.product', 'subspaceModels.tags',
'subspaceModels.productModels', 'tags',
'spaceProductModels', 'subspaceModels.tags.product',
'spaceProductModels.product', 'tags.product',
'spaceProductModels.items',
], ],
}); });

View File

@ -7,7 +7,9 @@ import {
} from '@nestjs/common'; } from '@nestjs/common';
import { import {
AddSpaceDto, AddSpaceDto,
AddSubspaceDto,
CommunitySpaceParam, CommunitySpaceParam,
CreateTagDto,
GetSpaceParam, GetSpaceParam,
UpdateSpaceDto, UpdateSpaceDto,
} from '../dtos'; } from '../dtos';
@ -17,10 +19,11 @@ import { SpaceEntity } from '@app/common/modules/space/entities';
import { generateRandomString } from '@app/common/helper/randomString'; import { generateRandomString } from '@app/common/helper/randomString';
import { SpaceLinkService } from './space-link'; import { SpaceLinkService } from './space-link';
import { SubSpaceService } from './subspace'; import { SubSpaceService } from './subspace';
import { DataSource, Not } from 'typeorm'; import { DataSource, Not, QueryRunner } from 'typeorm';
import { ValidationService } from './space-validation.service'; import { ValidationService } from './space-validation.service';
import { ORPHAN_SPACE_NAME } from '@app/common/constants/orphan-constant'; import { ORPHAN_SPACE_NAME } from '@app/common/constants/orphan-constant';
import { TagService } from './tag'; import { TagService } from './tag';
import { SpaceModelService } from 'src/space-model/services';
@Injectable() @Injectable()
export class SpaceService { export class SpaceService {
@ -31,6 +34,7 @@ export class SpaceService {
private readonly subSpaceService: SubSpaceService, private readonly subSpaceService: SubSpaceService,
private readonly validationService: ValidationService, private readonly validationService: ValidationService,
private readonly tagService: TagService, private readonly tagService: TagService,
private readonly spaceModelService: SpaceModelService,
) {} ) {}
async createSpace( async createSpace(
@ -78,31 +82,23 @@ export class SpaceService {
const newSpace = await queryRunner.manager.save(space); const newSpace = await queryRunner.manager.save(space);
if (direction && parent) { await Promise.all([
await this.spaceLinkService.saveSpaceLink( spaceModelUuid &&
parent.uuid, this.createFromModel(spaceModelUuid, queryRunner, newSpace),
newSpace.uuid, direction && parent
direction, ? this.spaceLinkService.saveSpaceLink(
); parent.uuid,
} newSpace.uuid,
direction,
if (subspaces?.length) { )
await this.subSpaceService.createSubspacesFromDto( : Promise.resolve(),
subspaces, subspaces?.length
newSpace, ? this.createSubspaces(subspaces, newSpace, queryRunner, tags)
queryRunner, : Promise.resolve(),
tags, tags?.length
); ? this.createTags(tags, queryRunner, newSpace)
} : Promise.resolve(),
]);
if (tags?.length) {
newSpace.tags = await this.tagService.createTags(
tags,
queryRunner,
newSpace,
null,
);
}
await queryRunner.commitTransaction(); await queryRunner.commitTransaction();
@ -123,6 +119,41 @@ export class SpaceService {
} }
} }
async createFromModel(
spaceModelUuid: string,
queryRunner: QueryRunner,
space: SpaceEntity,
) {
try {
const spaceModel =
await this.spaceModelService.validateSpaceModel(spaceModelUuid);
space.spaceModel = spaceModel;
await queryRunner.manager.save(SpaceEntity, space);
await this.subSpaceService.createSubSpaceFromModel(
spaceModel.subspaceModels,
space,
queryRunner,
);
await this.tagService.createTagsFromModel(
queryRunner,
spaceModel.tags,
space,
null,
);
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(
'An error occurred while creating the space from space model',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async getSpacesHierarchyForCommunity( async getSpacesHierarchyForCommunity(
params: CommunitySpaceParam, params: CommunitySpaceParam,
): Promise<BaseResponseDto> { ): Promise<BaseResponseDto> {
@ -274,29 +305,32 @@ export class SpaceService {
if (space.spaceName === ORPHAN_SPACE_NAME) { if (space.spaceName === ORPHAN_SPACE_NAME) {
throw new HttpException( throw new HttpException(
`space ${ORPHAN_SPACE_NAME} cannot be updated`, `Space "${ORPHAN_SPACE_NAME}" cannot be updated`,
HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST,
); );
} }
if (updateSpaceDto.spaceName) space.spaceName = updateSpaceDto.spaceName;
if (updateSpaceDto.x) space.x = updateSpaceDto.x; this.updateSpaceProperties(space, updateSpaceDto);
if (updateSpaceDto.y) space.y = updateSpaceDto.y;
if (updateSpaceDto.icon) space.icon = updateSpaceDto.icon;
if (updateSpaceDto.icon) space.icon = updateSpaceDto.icon;
await queryRunner.manager.save(space); await queryRunner.manager.save(space);
if (updateSpaceDto.subspaceModels) { const hasSubspace = updateSpaceDto.subspace?.length > 0;
const hasTags = updateSpaceDto.tags?.length > 0;
if (hasSubspace || hasTags) {
await this.tagService.unlinkModels(space.tags, queryRunner);
await this.subSpaceService.unlinkModels(space.subspaces, queryRunner);
}
if (hasSubspace) {
await this.subSpaceService.modifySubSpace( await this.subSpaceService.modifySubSpace(
updateSpaceDto.subspaceModels, updateSpaceDto.subspace,
space, space,
queryRunner, queryRunner,
); );
} }
if (updateSpaceDto.tags) { if (hasTags) {
await this.tagService.modifyTags( await this.tagService.modifyTags(
updateSpaceDto.tags, updateSpaceDto.tags,
queryRunner, queryRunner,
@ -317,6 +351,7 @@ export class SpaceService {
if (error instanceof HttpException) { if (error instanceof HttpException) {
throw error; throw error;
} }
throw new HttpException( throw new HttpException(
'An error occurred while updating the space', 'An error occurred while updating the space',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
@ -326,6 +361,18 @@ export class SpaceService {
} }
} }
private updateSpaceProperties(
space: SpaceEntity,
updateSpaceDto: UpdateSpaceDto,
): void {
const { spaceName, x, y, icon } = updateSpaceDto;
if (spaceName) space.spaceName = spaceName;
if (x) space.x = x;
if (y) space.y = y;
if (icon) space.icon = icon;
}
async getSpacesHierarchyForSpace( async getSpacesHierarchyForSpace(
params: GetSpaceParam, params: GetSpaceParam,
): Promise<BaseResponseDto> { ): Promise<BaseResponseDto> {
@ -339,7 +386,7 @@ export class SpaceService {
try { try {
// Get all spaces that are children of the provided space, including the parent-child relations // Get all spaces that are children of the provided space, including the parent-child relations
const spaces = await this.spaceRepository.find({ const spaces = await this.spaceRepository.find({
where: { parent: { uuid: spaceUuid } }, where: { parent: { uuid: spaceUuid }, disabled: false },
relations: ['parent', 'children'], // Include parent and children relations relations: ['parent', 'children'], // Include parent and children relations
}); });
@ -426,4 +473,26 @@ export class SpaceService {
); );
} }
} }
private async createSubspaces(
subspaces: AddSubspaceDto[],
space: SpaceEntity,
queryRunner: QueryRunner,
tags: CreateTagDto[],
): Promise<void> {
space.subspaces = await this.subSpaceService.createSubspacesFromDto(
subspaces,
space,
queryRunner,
tags,
);
}
private async createTags(
tags: CreateTagDto[],
queryRunner: QueryRunner,
space: SpaceEntity,
): Promise<void> {
space.tags = await this.tagService.createTags(tags, queryRunner, space);
}
} }

View File

@ -19,11 +19,9 @@ import { In, QueryRunner } from 'typeorm';
import { import {
SpaceEntity, SpaceEntity,
SubspaceEntity, SubspaceEntity,
TagEntity,
} from '@app/common/modules/space/entities'; } from '@app/common/modules/space/entities';
import { import { SubspaceModelEntity } from '@app/common/modules/space-model';
SpaceModelEntity,
SubspaceModelEntity,
} from '@app/common/modules/space-model';
import { ValidationService } from '../space-validation.service'; import { ValidationService } from '../space-validation.service';
import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
import { TagService } from '../tag'; import { TagService } from '../tag';
@ -49,7 +47,6 @@ export class SubSpaceService {
const subspaces = subspaceData.map((data) => const subspaces = subspaceData.map((data) =>
queryRunner.manager.create(this.subspaceRepository.target, data), queryRunner.manager.create(this.subspaceRepository.target, data),
); );
return await queryRunner.manager.save(subspaces); return await queryRunner.manager.save(subspaces);
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException(
@ -60,21 +57,30 @@ export class SubSpaceService {
} }
async createSubSpaceFromModel( async createSubSpaceFromModel(
spaceModel: SpaceModelEntity, subspaceModels: SubspaceModelEntity[],
space: SpaceEntity, space: SpaceEntity,
queryRunner: QueryRunner, queryRunner: QueryRunner,
): Promise<void> { ): Promise<void> {
const subSpaceModels = spaceModel.subspaceModels; if (!subspaceModels?.length) return;
if (!subSpaceModels?.length) return; const subspaceData = subspaceModels.map((subSpaceModel) => ({
const subspaceData = subSpaceModels.map((subSpaceModel) => ({
subspaceName: subSpaceModel.subspaceName, subspaceName: subSpaceModel.subspaceName,
space, space,
subSpaceModel, subSpaceModel,
})); }));
await this.createSubspaces(subspaceData, queryRunner); const subspaces = await this.createSubspaces(subspaceData, queryRunner);
await Promise.all(
subspaceModels.map((model, index) =>
this.tagService.createTagsFromModel(
queryRunner,
model.tags || [],
null,
subspaces[index],
),
),
);
} }
async createSubspacesFromDto( async createSubspacesFromDto(
@ -317,6 +323,31 @@ export class SubSpaceService {
} }
} }
async unlinkModels(
subspaces: SubspaceEntity[],
queryRunner: QueryRunner,
): Promise<void> {
if (!subspaces || subspaces.length === 0) {
return;
}
try {
const allTags = subspaces.flatMap((subSpace) => {
subSpace.subSpaceModel = null;
return subSpace.tags || [];
});
await this.tagService.unlinkModels(allTags, queryRunner);
await queryRunner.manager.save(subspaces);
} catch (error) {
if (error instanceof HttpException) throw error;
throw new HttpException(
`Failed to unlink subspace models: ${error.message}`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async getOne(params: GetSubSpaceParam): Promise<BaseResponseDto> { async getOne(params: GetSubSpaceParam): Promise<BaseResponseDto> {
await this.validationService.validateSpaceWithinCommunityAndProject( await this.validationService.validateSpaceWithinCommunityAndProject(
params.communityUuid, params.communityUuid,

View File

@ -5,6 +5,7 @@ import {
TagEntity, TagEntity,
TagRepository, TagRepository,
} from '@app/common/modules/space'; } from '@app/common/modules/space';
import { TagModel } from '@app/common/modules/space-model';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { ProductService } from 'src/product/services'; import { ProductService } from 'src/product/services';
import { CreateTagDto } from 'src/space/dtos'; import { CreateTagDto } from 'src/space/dtos';
@ -25,39 +26,43 @@ export class TagService {
subspace?: SubspaceEntity, subspace?: SubspaceEntity,
additionalTags?: CreateTagDto[], additionalTags?: CreateTagDto[],
): Promise<TagEntity[]> { ): Promise<TagEntity[]> {
if (!tags.length) { this.validateTagsInput(tags);
throw new HttpException('Tags cannot be empty.', HttpStatus.BAD_REQUEST);
}
const combinedTags = additionalTags ? [...tags, ...additionalTags] : tags; const combinedTags = this.combineTags(tags, additionalTags);
const duplicateTags = this.findDuplicateTags(combinedTags); this.ensureNoDuplicateTags(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 { try {
const tagEntities = await Promise.all(
tags.map(async (tagDto) =>
this.prepareTagEntity(tagDto, queryRunner, space, subspace),
),
);
return await queryRunner.manager.save(tagEntities); return await queryRunner.manager.save(tagEntities);
} catch (error) { } catch (error) {
if (error instanceof HttpException) { throw this.handleUnexpectedError('Failed to save tags', error);
throw error;
}
throw new HttpException(
'Failed to save tag models due to an unexpected error.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
} }
} }
async createTagsFromModel(
queryRunner: QueryRunner,
tagModels: TagModel[],
space?: SpaceEntity,
subspace?: SubspaceEntity,
): Promise<void> {
if (!tagModels?.length) return;
const tags = tagModels.map((model) =>
queryRunner.manager.create(this.tagRepository.target, {
tag: model.tag,
space: space || undefined,
subspace: subspace || undefined,
product: model.product,
}),
);
await queryRunner.manager.save(tags);
}
async updateTag( async updateTag(
tag: ModifyTagDto, tag: ModifyTagDto,
queryRunner: QueryRunner, queryRunner: QueryRunner,
@ -67,48 +72,94 @@ export class TagService {
try { try {
const existingTag = await this.getTagByUuid(tag.uuid); const existingTag = await this.getTagByUuid(tag.uuid);
if (space) { const contextSpace = space ?? subspace?.space;
await this.checkTagReuse(tag.tag, existingTag.product.uuid, space);
} else { if (contextSpace) {
await this.checkTagReuse( await this.checkTagReuse(
tag.tag, tag.tag,
existingTag.product.uuid, existingTag.product.uuid,
subspace.space, contextSpace,
); );
} }
if (tag.tag) { return await queryRunner.manager.save(
existingTag.tag = tag.tag; Object.assign(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,
); );
} catch (error) {
throw this.handleUnexpectedError('Failed to update tags', error);
} }
} }
async deleteTags(tagUuids: string[], queryRunner: QueryRunner) { async deleteTags(tagUuids: string[], queryRunner: QueryRunner) {
try { if (!tagUuids?.length) return;
const deletePromises = tagUuids.map((id) =>
queryRunner.manager.softDelete(this.tagRepository.target, id),
);
await Promise.all(deletePromises); try {
await Promise.all(
tagUuids.map((id) =>
queryRunner.manager.update(
this.tagRepository.target,
{ uuid: id },
{ disabled: true },
),
),
);
return { message: 'Tags deleted successfully', tagUuids }; return { message: 'Tags deleted successfully', tagUuids };
} catch (error) { } catch (error) {
if (error instanceof HttpException) { throw this.handleUnexpectedError('Failed to update tags', error);
throw error; }
} }
throw new HttpException(
error.message || 'Failed to delete tags', async modifyTags(
HttpStatus.INTERNAL_SERVER_ERROR, tags: ModifyTagDto[],
queryRunner: QueryRunner,
space?: SpaceEntity,
subspace?: SubspaceEntity,
): Promise<void> {
if (!tags?.length) return;
try {
await Promise.all(
tags.map(async (tag) => {
switch (tag.action) {
case ModifyAction.ADD:
await this.createTags(
[{ tag: tag.tag, productUuid: tag.productUuid }],
queryRunner,
space,
subspace,
);
break;
case ModifyAction.UPDATE:
await this.updateTag(tag, queryRunner, space, subspace);
break;
case ModifyAction.DELETE:
await this.deleteTags([tag.uuid], queryRunner);
break;
default:
throw new HttpException(
`Invalid action "${tag.action}" provided.`,
HttpStatus.BAD_REQUEST,
);
}
}),
); );
} catch (error) {
throw this.handleUnexpectedError('Failed to modify tags', error);
}
}
async unlinkModels(tags: TagEntity[], queryRunner: QueryRunner) {
if (!tags?.length) return;
try {
tags.forEach((tag) => {
tag.model = null;
});
await queryRunner.manager.save(tags);
} catch (error) {
throw this.handleUnexpectedError('Failed to unlink tag models', error);
} }
} }
@ -128,48 +179,6 @@ export class TagService {
return duplicates; return duplicates;
} }
async modifyTags(
tags: ModifyTagDto[],
queryRunner: QueryRunner,
space?: SpaceEntity,
subspace?: SubspaceEntity,
): Promise<void> {
try {
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( private async checkTagReuse(
tag: string, tag: string,
productUuid: string, productUuid: string,
@ -214,11 +223,11 @@ export class TagService {
); );
} }
if (space) { await this.checkTagReuse(
await this.checkTagReuse(tagDto.tag, tagDto.productUuid, space); tagDto.tag,
} else { tagDto.productUuid,
await this.checkTagReuse(tagDto.tag, tagDto.productUuid, subspace.space); space ?? subspace.space,
} );
return queryRunner.manager.create(TagEntity, { return queryRunner.manager.create(TagEntity, {
tag: tagDto.tag, tag: tagDto.tag,
@ -233,6 +242,7 @@ export class TagService {
where: { uuid }, where: { uuid },
relations: ['product'], relations: ['product'],
}); });
if (!tag) { if (!tag) {
throw new HttpException( throw new HttpException(
`Tag with ID ${uuid} not found.`, `Tag with ID ${uuid} not found.`,
@ -241,4 +251,39 @@ export class TagService {
} }
return tag; return tag;
} }
private handleUnexpectedError(
message: string,
error: unknown,
): HttpException {
if (error instanceof HttpException) throw error;
return new HttpException(
`${message}: ${(error as Error)?.message || 'Unknown error'}`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
private combineTags(
primaryTags: CreateTagDto[],
additionalTags?: CreateTagDto[],
): CreateTagDto[] {
return additionalTags ? [...primaryTags, ...additionalTags] : primaryTags;
}
private ensureNoDuplicateTags(tags: CreateTagDto[]): void {
const duplicates = this.findDuplicateTags(tags);
if (duplicates.length > 0) {
throw new HttpException(
`Duplicate tags found: ${duplicates.join(', ')}`,
HttpStatus.BAD_REQUEST,
);
}
}
private validateTagsInput(tags: CreateTagDto[]): void {
if (!tags?.length) {
return;
}
}
} }

View File

@ -43,12 +43,18 @@ import { SceneDeviceRepository } from '@app/common/modules/scene-device/reposito
import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { ProjectRepository } from '@app/common/modules/project/repositiories';
import { import {
SpaceModelRepository, SpaceModelRepository,
SubspaceModelRepository,
TagModelRepository, TagModelRepository,
} from '@app/common/modules/space-model'; } from '@app/common/modules/space-model';
import { CommunityModule } from 'src/community/community.module'; import { CommunityModule } from 'src/community/community.module';
import { ValidationService } from './services'; import { ValidationService } from './services';
import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
import { TagService } from './services/tag'; import { TagService } from './services/tag';
import {
SpaceModelService,
SubSpaceModelService,
TagModelService,
} from 'src/space-model/services';
@Module({ @Module({
imports: [ConfigModule, SpaceRepositoryModule, CommunityModule], imports: [ConfigModule, SpaceRepositoryModule, CommunityModule],
@ -88,9 +94,12 @@ import { TagService } from './services/tag';
DeviceStatusFirebaseService, DeviceStatusFirebaseService,
DeviceStatusLogRepository, DeviceStatusLogRepository,
SceneDeviceRepository, SceneDeviceRepository,
SpaceModelService,
SubSpaceModelService,
TagModelService,
ProjectRepository, ProjectRepository,
SpaceModelRepository, SpaceModelRepository,
SubspaceModelRepository,
], ],
exports: [SpaceService], exports: [SpaceService],
}) })