mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-11-27 14:44:55 +00:00
Refactor/space management (#404)
* refactor: reducing used queries on get communities (#385) * refactor: fix create space logic (#394) * Remove unique constraint on subspace and product in SubspaceProductAllocationEntity; update product relation to nullable in NewTagEntity * refactor: fix create space logic * device model updated to include the fixes and final columns * updated space models to include suggested fixes, update final logic and column names * task: removing old references of the old tag-product relation * task: remove old use of tags * task: remove old tag & tag model usage * refactor: delete space * task: remove unused functions * fix lint rule
This commit is contained in:
@ -1,16 +1,19 @@
|
||||
import { ORPHAN_SPACE_NAME } from '@app/common/constants/orphan-constant';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
ArrayUnique,
|
||||
IsBoolean,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
NotEquals,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import { AddSubspaceDto } from './subspace';
|
||||
import { ProcessTagDto } from 'src/tags/dtos';
|
||||
import { AddSubspaceDto } from './subspace';
|
||||
|
||||
export class AddSpaceDto {
|
||||
@ApiProperty({
|
||||
@ -19,6 +22,11 @@ export class AddSpaceDto {
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@NotEquals(ORPHAN_SPACE_NAME, {
|
||||
message() {
|
||||
return `Space name cannot be "${ORPHAN_SPACE_NAME}". Please choose a different name.`;
|
||||
},
|
||||
})
|
||||
spaceName: string;
|
||||
|
||||
@ApiProperty({
|
||||
@ -74,6 +82,20 @@ export class AddSpaceDto {
|
||||
})
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@ArrayUnique((subspace) => subspace.subspaceName, {
|
||||
message(validationArguments) {
|
||||
const subspaces = validationArguments.value;
|
||||
const nameCounts = subspaces.reduce((acc, curr) => {
|
||||
acc[curr.subspaceName] = (acc[curr.subspaceName] || 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
// Find duplicates
|
||||
const duplicates = Object.keys(nameCounts).filter(
|
||||
(name) => nameCounts[name] > 1,
|
||||
);
|
||||
return `Duplicate subspace names found: ${duplicates.join(', ')}`;
|
||||
},
|
||||
})
|
||||
@Type(() => AddSubspaceDto)
|
||||
subspaces?: AddSubspaceDto[];
|
||||
|
||||
|
||||
29
src/space/dtos/create-allocations.dto.ts
Normal file
29
src/space/dtos/create-allocations.dto.ts
Normal file
@ -0,0 +1,29 @@
|
||||
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 { QueryRunner } from 'typeorm';
|
||||
|
||||
export enum AllocationsOwnerType {
|
||||
SPACE = 'space',
|
||||
SUBSPACE = 'subspace',
|
||||
}
|
||||
export class BaseCreateAllocationsDto {
|
||||
tags: ProcessTagDto[];
|
||||
projectUuid: string;
|
||||
queryRunner: QueryRunner;
|
||||
type: AllocationsOwnerType;
|
||||
}
|
||||
|
||||
export class CreateSpaceAllocationsDto extends BaseCreateAllocationsDto {
|
||||
space: SpaceEntity;
|
||||
type: AllocationsOwnerType.SPACE;
|
||||
}
|
||||
|
||||
export class CreateSubspaceAllocationsDto extends BaseCreateAllocationsDto {
|
||||
subspace: SubspaceEntity;
|
||||
type: AllocationsOwnerType.SUBSPACE;
|
||||
}
|
||||
|
||||
export type CreateAllocationsDto =
|
||||
| CreateSpaceAllocationsDto
|
||||
| CreateSubspaceAllocationsDto;
|
||||
@ -1,47 +1,14 @@
|
||||
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;
|
||||
import { ApiPropertyOptional, PartialType } from '@nestjs/swagger';
|
||||
import { IsOptional, IsUUID } from 'class-validator';
|
||||
import { AddSubspaceDto } from './add.subspace.dto';
|
||||
|
||||
export class ModifySubspaceDto extends PartialType(AddSubspaceDto) {
|
||||
@ApiPropertyOptional({
|
||||
description: 'UUID of the subspace (required for update/delete)',
|
||||
description:
|
||||
'UUID of the subspace (will present if updating an existing subspace)',
|
||||
example: '123e4567-e89b-12d3-a456-426614174000',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsUUID()
|
||||
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[];
|
||||
}
|
||||
|
||||
@ -1,26 +1,9 @@
|
||||
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsEnum, IsOptional, IsString, IsUUID } from 'class-validator';
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsOptional, IsString, IsUUID } 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 new tag',
|
||||
example: '123e4567-e89b-12d3-a456-426614174000',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
newTagUuid: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
'UUID of an existing tag (required for update/delete, optional for add)',
|
||||
description: 'UUID of an existing tag',
|
||||
example: 'a1b2c3d4-5678-90ef-abcd-1234567890ef',
|
||||
})
|
||||
@IsOptional()
|
||||
@ -28,7 +11,7 @@ export class ModifyTagDto {
|
||||
tagUuid?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Name of the tag (required for add/update)',
|
||||
description: 'Name of the tag',
|
||||
example: 'Temperature Sensor',
|
||||
})
|
||||
@IsOptional()
|
||||
@ -36,8 +19,7 @@ export class ModifyTagDto {
|
||||
name?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
'UUID of the product associated with the tag (required for add)',
|
||||
description: 'UUID of the product associated with the tag',
|
||||
example: 'c789a91e-549a-4753-9006-02f89e8170e0',
|
||||
})
|
||||
@IsOptional()
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
import { ORPHAN_SPACE_NAME } from '@app/common/constants/orphan-constant';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
IsArray,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
NotEquals,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import { ModifySubspaceDto } from './subspace';
|
||||
import { Type } from 'class-transformer';
|
||||
import { ModifyTagDto } from './tag/modify-tag.dto';
|
||||
|
||||
export class UpdateSpaceDto {
|
||||
@ -17,6 +19,11 @@ export class UpdateSpaceDto {
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@NotEquals(ORPHAN_SPACE_NAME, {
|
||||
message() {
|
||||
return `Space name cannot be "${ORPHAN_SPACE_NAME}". Please choose a different name.`;
|
||||
},
|
||||
})
|
||||
spaceName?: string;
|
||||
|
||||
@ApiProperty({
|
||||
@ -46,7 +53,7 @@ export class UpdateSpaceDto {
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => ModifySubspaceDto)
|
||||
subspace?: ModifySubspaceDto[];
|
||||
subspaces?: ModifySubspaceDto[];
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
@ -58,6 +65,7 @@ export class UpdateSpaceDto {
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => ModifyTagDto)
|
||||
tags?: ModifyTagDto[];
|
||||
|
||||
@ApiProperty({
|
||||
description: 'UUID of the Space',
|
||||
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||
import { HttpException, HttpStatus } from '@nestjs/common';
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||
import { DeviceService } from 'src/device/services';
|
||||
@ -5,12 +6,10 @@ import { UserSpaceService } from 'src/users/services';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { DisableSpaceCommand } from '../commands';
|
||||
import {
|
||||
SubSpaceService,
|
||||
SpaceLinkService,
|
||||
SpaceSceneService,
|
||||
SubSpaceService,
|
||||
} from '../services';
|
||||
import { TagService } from '../services/tag';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||
|
||||
@CommandHandler(DisableSpaceCommand)
|
||||
export class DisableSpaceHandler
|
||||
@ -19,7 +18,6 @@ export class DisableSpaceHandler
|
||||
constructor(
|
||||
private readonly subSpaceService: SubSpaceService,
|
||||
private readonly userService: UserSpaceService,
|
||||
private readonly tagService: TagService,
|
||||
private readonly deviceService: DeviceService,
|
||||
private readonly spaceLinkService: SpaceLinkService,
|
||||
private readonly sceneService: SpaceSceneService,
|
||||
@ -46,6 +44,9 @@ export class DisableSpaceHandler
|
||||
'scenes',
|
||||
'children',
|
||||
'userSpaces',
|
||||
'productAllocations',
|
||||
'productAllocations.tag',
|
||||
'productAllocations.product',
|
||||
],
|
||||
});
|
||||
|
||||
@ -64,15 +65,15 @@ export class DisableSpaceHandler
|
||||
}
|
||||
}
|
||||
|
||||
const tagUuids = space.productAllocations?.map((tag) => tag.uuid) || [];
|
||||
const tagUuids =
|
||||
space.productAllocations?.map(({ tag }) => tag.uuid) || [];
|
||||
/* const subspaceDtos =
|
||||
space.subspaces?.map((subspace) => ({
|
||||
subspaceUuid: subspace.uuid,
|
||||
})) || []; */
|
||||
const deletionTasks = [
|
||||
// this.subSpaceService.deleteSubspaces(subspaceDtos, queryRunner),
|
||||
this.userService.deleteUserSpace(space.uuid),
|
||||
this.tagService.deleteTags(tagUuids, queryRunner),
|
||||
|
||||
this.deviceService.deleteDevice(
|
||||
space.devices,
|
||||
orphanSpace,
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
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[];
|
||||
}
|
||||
6
src/space/interfaces/update-subspace-allocation.dto.ts
Normal file
6
src/space/interfaces/update-subspace-allocation.dto.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { ProcessTagDto } from 'src/tags/dtos';
|
||||
|
||||
export interface UpdateSpaceAllocationDto {
|
||||
uuid: string;
|
||||
tags: ProcessTagDto[];
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { TagService } from 'src/tags/services';
|
||||
import {
|
||||
AllocationsOwnerType,
|
||||
CreateAllocationsDto,
|
||||
} from '../../dtos/create-allocations.dto';
|
||||
import { SpaceProductAllocationService } from '../space-product-allocation.service';
|
||||
import { SubspaceProductAllocationService } from '../subspace/subspace-product-allocation.service';
|
||||
|
||||
@Injectable()
|
||||
export class ProductAllocationService {
|
||||
constructor(
|
||||
private readonly tagService: TagService,
|
||||
private readonly spaceProductAllocationService: SpaceProductAllocationService,
|
||||
private readonly subSpaceProductAllocationService: SubspaceProductAllocationService,
|
||||
) {}
|
||||
|
||||
async createAllocations(dto: CreateAllocationsDto): Promise<void> {
|
||||
const { projectUuid, queryRunner, tags, type } = dto;
|
||||
|
||||
const allocationsData = await this.tagService.processTags(
|
||||
tags,
|
||||
projectUuid,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
// Create a mapping of created tags by UUID and name for quick lookup
|
||||
const createdTagsByUUID = new Map(allocationsData.map((t) => [t.uuid, t]));
|
||||
const createdTagsByName = new Map(allocationsData.map((t) => [t.name, t]));
|
||||
|
||||
// Create the product-tag mapping based on the processed tags
|
||||
const productTagMapping = tags.map(({ uuid, name, productUuid }) => {
|
||||
const inputTag = uuid
|
||||
? createdTagsByUUID.get(uuid)
|
||||
: createdTagsByName.get(name);
|
||||
return {
|
||||
tag: inputTag?.uuid,
|
||||
product: productUuid,
|
||||
};
|
||||
});
|
||||
|
||||
switch (type) {
|
||||
case AllocationsOwnerType.SPACE: {
|
||||
// todo: take to consideration original method implementation
|
||||
await this.spaceProductAllocationService.createProductAllocations(
|
||||
dto.space,
|
||||
productTagMapping,
|
||||
queryRunner,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case AllocationsOwnerType.SUBSPACE: {
|
||||
await this.subSpaceProductAllocationService.createProductAllocations(
|
||||
dto.subspace,
|
||||
productTagMapping,
|
||||
queryRunner,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,113 +1,37 @@
|
||||
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';
|
||||
import { SpaceModelProductAllocationEntity } from '@app/common/modules/space-model';
|
||||
import { DeviceEntity } from '@app/common/modules/device/entities';
|
||||
import { ProjectEntity } from '@app/common/modules/project/entities';
|
||||
import { ValidationService } from './space-validation.service';
|
||||
|
||||
@Injectable()
|
||||
export class SpaceProductAllocationService {
|
||||
constructor(
|
||||
private readonly tagService: NewTagService,
|
||||
private readonly spaceProductAllocationRepository: SpaceProductAllocationRepository,
|
||||
private readonly spaceService: ValidationService,
|
||||
) {}
|
||||
|
||||
async createSpaceProductAllocations(
|
||||
async createProductAllocations(
|
||||
space: SpaceEntity,
|
||||
processedTags: NewTagEntity[],
|
||||
allocationsData: { product: string; tag: string }[],
|
||||
queryRunner: QueryRunner,
|
||||
modifySubspaces?: ModifySubspaceDto[],
|
||||
): Promise<void> {
|
||||
try {
|
||||
if (!processedTags.length) return;
|
||||
if (!allocationsData.length) return;
|
||||
|
||||
const productAllocations: SpaceProductAllocationEntity[] = [];
|
||||
const existingAllocations = new Map<
|
||||
string,
|
||||
SpaceProductAllocationEntity
|
||||
>();
|
||||
|
||||
for (const tag of processedTags) {
|
||||
let isTagNeeded = true;
|
||||
for (const allocationData of allocationsData) {
|
||||
if (await this.isAllocationExist(queryRunner, allocationData, space))
|
||||
continue;
|
||||
|
||||
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) {
|
||||
const isDuplicated = await this.validateTagWithinSpace(
|
||||
queryRunner,
|
||||
tag,
|
||||
space,
|
||||
);
|
||||
if (isDuplicated) continue;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
const allocation = this.createNewAllocation(
|
||||
space,
|
||||
allocationData,
|
||||
queryRunner,
|
||||
);
|
||||
productAllocations.push(allocation);
|
||||
}
|
||||
|
||||
if (productAllocations.length > 0) {
|
||||
await this.saveAllocations(productAllocations, queryRunner);
|
||||
}
|
||||
@ -118,97 +42,6 @@ export class SpaceProductAllocationService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async createAllocationFromModel(
|
||||
modelAllocations: SpaceModelProductAllocationEntity[],
|
||||
queryRunner: QueryRunner,
|
||||
spaces?: SpaceEntity[],
|
||||
) {
|
||||
if (!spaces || spaces.length === 0 || !modelAllocations.length) return;
|
||||
|
||||
const allocations: SpaceProductAllocationEntity[] = [];
|
||||
|
||||
for (const space of spaces) {
|
||||
for (const modelAllocation of modelAllocations) {
|
||||
const allocation = queryRunner.manager.create(
|
||||
SpaceProductAllocationEntity,
|
||||
{
|
||||
space,
|
||||
product: modelAllocation.product,
|
||||
tags: modelAllocation.tags,
|
||||
inheritedFromModel: modelAllocation,
|
||||
},
|
||||
);
|
||||
allocations.push(allocation);
|
||||
}
|
||||
}
|
||||
|
||||
if (allocations.length > 0) {
|
||||
await queryRunner.manager.save(SpaceProductAllocationEntity, allocations);
|
||||
}
|
||||
}
|
||||
|
||||
async addTagToAllocationFromModel(
|
||||
modelAllocation: SpaceModelProductAllocationEntity,
|
||||
queryRunner: QueryRunner,
|
||||
tag: NewTagEntity,
|
||||
spaces?: SpaceEntity[],
|
||||
) {
|
||||
try {
|
||||
if (!spaces || spaces.length === 0 || !modelAllocation) return;
|
||||
|
||||
const spaceAllocations = await queryRunner.manager.find(
|
||||
SpaceProductAllocationEntity,
|
||||
{
|
||||
where: { inheritedFromModel: { uuid: modelAllocation.uuid } },
|
||||
relations: ['tags'],
|
||||
},
|
||||
);
|
||||
|
||||
if (spaceAllocations.length === 0) return;
|
||||
|
||||
for (const allocation of spaceAllocations) {
|
||||
allocation.tags.push(tag);
|
||||
}
|
||||
|
||||
await queryRunner.manager.save(
|
||||
SpaceProductAllocationEntity,
|
||||
spaceAllocations,
|
||||
);
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
'Failed to add tag to allocation from model',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async updateSpaceProductAllocations(
|
||||
dtos: ModifyTagDto[],
|
||||
projectUuid: string,
|
||||
space: SpaceEntity,
|
||||
queryRunner: QueryRunner,
|
||||
modifySubspace?: ModifySubspaceDto[],
|
||||
): Promise<void> {
|
||||
if (!dtos || dtos.length === 0) return;
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
this.processAddActions(
|
||||
dtos,
|
||||
projectUuid,
|
||||
space,
|
||||
queryRunner,
|
||||
modifySubspace,
|
||||
),
|
||||
|
||||
this.processDeleteActions(dtos, queryRunner, space),
|
||||
]);
|
||||
} catch (error) {
|
||||
throw this.handleError(error, 'Error while updating product allocations');
|
||||
}
|
||||
}
|
||||
|
||||
async unlinkModels(space: SpaceEntity, queryRunner: QueryRunner) {
|
||||
try {
|
||||
if (!space.productAllocations || space.productAllocations.length === 0)
|
||||
@ -231,307 +64,30 @@ export class SpaceProductAllocationService {
|
||||
}
|
||||
}
|
||||
|
||||
async propagateDeleteToInheritedAllocations(
|
||||
queryRunner: QueryRunner,
|
||||
allocationsToUpdate: SpaceModelProductAllocationEntity[],
|
||||
tagUuidsToDelete: string[],
|
||||
project: ProjectEntity,
|
||||
spaces?: SpaceEntity[],
|
||||
): Promise<void> {
|
||||
try {
|
||||
const inheritedAllocationUpdates: SpaceProductAllocationEntity[] = [];
|
||||
const inheritedAllocationsToDelete: SpaceProductAllocationEntity[] = [];
|
||||
|
||||
for (const allocation of allocationsToUpdate) {
|
||||
for (const inheritedAllocation of allocation.inheritedSpaceAllocations) {
|
||||
const updatedInheritedTags = inheritedAllocation.tags.filter(
|
||||
(tag) => !tagUuidsToDelete.includes(tag.uuid),
|
||||
);
|
||||
|
||||
if (updatedInheritedTags.length === inheritedAllocation.tags.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (updatedInheritedTags.length === 0) {
|
||||
inheritedAllocationsToDelete.push(inheritedAllocation);
|
||||
} else {
|
||||
inheritedAllocation.tags = updatedInheritedTags;
|
||||
inheritedAllocationUpdates.push(inheritedAllocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inheritedAllocationUpdates.length > 0) {
|
||||
await queryRunner.manager.save(
|
||||
SpaceProductAllocationEntity,
|
||||
inheritedAllocationUpdates,
|
||||
);
|
||||
}
|
||||
|
||||
if (inheritedAllocationsToDelete.length > 0) {
|
||||
await queryRunner.manager.remove(
|
||||
SpaceProductAllocationEntity,
|
||||
inheritedAllocationsToDelete,
|
||||
);
|
||||
}
|
||||
|
||||
if (spaces && spaces.length > 0) {
|
||||
await this.moveDevicesToOrphanSpace(
|
||||
queryRunner,
|
||||
spaces,
|
||||
tagUuidsToDelete,
|
||||
project,
|
||||
);
|
||||
}
|
||||
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from('space_product_tags')
|
||||
.where(
|
||||
'space_product_allocation_uuid NOT IN (' +
|
||||
queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.select('allocation.uuid')
|
||||
.from(SpaceProductAllocationEntity, 'allocation')
|
||||
.getQuery() +
|
||||
')',
|
||||
)
|
||||
.execute();
|
||||
} catch (error) {
|
||||
throw this.handleError(
|
||||
error,
|
||||
`Failed to propagate tag deletion to inherited allocations`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async moveDevicesToOrphanSpace(
|
||||
queryRunner: QueryRunner,
|
||||
spaces: SpaceEntity[],
|
||||
tagUuidsToDelete: string[],
|
||||
project: ProjectEntity,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const orphanSpace = await this.spaceService.getOrphanSpace(project);
|
||||
|
||||
const devicesToMove = await queryRunner.manager
|
||||
.createQueryBuilder(DeviceEntity, 'device')
|
||||
.leftJoinAndSelect('device.tag', 'tag')
|
||||
.where('device.spaceDevice IN (:...spaceUuids)', {
|
||||
spaceUuids: spaces.map((space) => space.uuid),
|
||||
})
|
||||
.andWhere('tag.uuid IN (:...tagUuidsToDelete)', { tagUuidsToDelete })
|
||||
.getMany();
|
||||
|
||||
if (devicesToMove.length === 0) return;
|
||||
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.update(DeviceEntity)
|
||||
.set({ spaceDevice: orphanSpace })
|
||||
.where('uuid IN (:...deviceUuids)', {
|
||||
deviceUuids: devicesToMove.map((device) => device.uuid),
|
||||
})
|
||||
.execute();
|
||||
} catch (error) {
|
||||
throw this.handleError(error, `Failed to move devices to orphan space`);
|
||||
}
|
||||
}
|
||||
|
||||
private async processDeleteActions(
|
||||
dtos: ModifyTagDto[],
|
||||
private async isAllocationExist(
|
||||
queryRunner: QueryRunner,
|
||||
allocation: { product: string; tag: string },
|
||||
space: SpaceEntity,
|
||||
): Promise<SpaceProductAllocationEntity[]> {
|
||||
try {
|
||||
if (!dtos || dtos.length === 0) return;
|
||||
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(
|
||||
): Promise<boolean> {
|
||||
const existingAllocations =
|
||||
await queryRunner.manager.findOne<SpaceProductAllocationEntity>(
|
||||
SpaceProductAllocationEntity,
|
||||
{
|
||||
where: {
|
||||
tags: { uuid: In(tagUuidsToDelete) },
|
||||
space: { uuid: space.uuid },
|
||||
},
|
||||
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_uuid NOT IN (' +
|
||||
queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.select('allocation.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<void> {
|
||||
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,
|
||||
): Promise<boolean> {
|
||||
const existingAllocationsForProduct = await queryRunner.manager.find(
|
||||
SpaceProductAllocationEntity,
|
||||
{
|
||||
where: {
|
||||
space: {
|
||||
uuid: space.uuid,
|
||||
},
|
||||
product: {
|
||||
uuid: tag.product.uuid,
|
||||
space: {
|
||||
uuid: space.uuid,
|
||||
},
|
||||
tag: {
|
||||
uuid: allocation.tag,
|
||||
},
|
||||
product: {
|
||||
uuid: allocation.product,
|
||||
},
|
||||
},
|
||||
},
|
||||
relations: ['tags'],
|
||||
},
|
||||
);
|
||||
);
|
||||
|
||||
if (
|
||||
!existingAllocationsForProduct ||
|
||||
existingAllocationsForProduct.length === 0
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const existingTagsForProduct = existingAllocationsForProduct.flatMap(
|
||||
(allocation) => allocation.tags || [],
|
||||
);
|
||||
|
||||
return existingTagsForProduct.some(
|
||||
(existingTag) => existingTag.uuid === tag.uuid,
|
||||
);
|
||||
}
|
||||
|
||||
private async getAllocationByProduct(
|
||||
product: ProductEntity,
|
||||
space: SpaceEntity,
|
||||
queryRunner?: QueryRunner,
|
||||
): Promise<SpaceProductAllocationEntity | null> {
|
||||
return queryRunner
|
||||
? queryRunner.manager.findOne(SpaceProductAllocationEntity, {
|
||||
where: {
|
||||
space: { uuid: space.uuid },
|
||||
product: { uuid: product.uuid },
|
||||
},
|
||||
relations: ['tags'],
|
||||
})
|
||||
: this.spaceProductAllocationRepository.findOne({
|
||||
where: {
|
||||
space: { uuid: space.uuid },
|
||||
product: { uuid: product.uuid },
|
||||
},
|
||||
relations: ['tags'],
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
return existingAllocations ? true : false;
|
||||
}
|
||||
|
||||
async saveAllocations(
|
||||
@ -546,6 +102,34 @@ export class SpaceProductAllocationService {
|
||||
: await this.spaceProductAllocationRepository.save(allocations);
|
||||
}
|
||||
|
||||
async clearAllAllocations(spaceUuid: string, queryRunner: QueryRunner) {
|
||||
try {
|
||||
await queryRunner.manager.delete(SpaceProductAllocationEntity, {
|
||||
space: { uuid: spaceUuid },
|
||||
});
|
||||
} catch (error) {
|
||||
throw this.handleError(error, 'Failed to clear all allocations');
|
||||
}
|
||||
}
|
||||
|
||||
private createNewAllocation(
|
||||
space: SpaceEntity,
|
||||
allocationData: { product: string; tag: string },
|
||||
queryRunner?: QueryRunner,
|
||||
): SpaceProductAllocationEntity {
|
||||
return queryRunner
|
||||
? queryRunner.manager.create(SpaceProductAllocationEntity, {
|
||||
space,
|
||||
product: { uuid: allocationData.product },
|
||||
tag: { uuid: allocationData.tag },
|
||||
})
|
||||
: this.spaceProductAllocationRepository.create({
|
||||
space,
|
||||
product: { uuid: allocationData.product },
|
||||
tag: { uuid: allocationData.tag },
|
||||
});
|
||||
}
|
||||
|
||||
private handleError(error: any, message: string): HttpException {
|
||||
return new HttpException(
|
||||
error instanceof HttpException ? error.message : message,
|
||||
@ -554,36 +138,4 @@ export class SpaceProductAllocationService {
|
||||
: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
async clearAllAllocations(spaceUuid: string, queryRunner: QueryRunner) {
|
||||
try {
|
||||
const allocationUuids = await queryRunner.manager
|
||||
.createQueryBuilder(SpaceProductAllocationEntity, 'allocation')
|
||||
.select('allocation.uuid')
|
||||
.where('allocation.space_uuid = :spaceUuid', { spaceUuid })
|
||||
.getRawMany()
|
||||
.then((results) => results.map((r) => r.allocation_uuid));
|
||||
|
||||
if (allocationUuids.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from('space_product_tags')
|
||||
.where('space_product_allocation_uuid IN (:...allocationUuids)', {
|
||||
allocationUuids,
|
||||
})
|
||||
.execute();
|
||||
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(SpaceProductAllocationEntity)
|
||||
.where('space_uuid = :spaceUuid', { spaceUuid })
|
||||
.execute();
|
||||
} catch (error) {
|
||||
throw this.handleError(error, 'Failed to clear all allocations');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
||||
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
||||
import {
|
||||
SpaceModelEntity,
|
||||
SpaceModelRepository,
|
||||
} from '@app/common/modules/space-model';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import {
|
||||
BadRequestException,
|
||||
@ -7,25 +16,11 @@ import {
|
||||
Inject,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
import { In } from 'typeorm';
|
||||
import { CommunityService } from '../../community/services';
|
||||
import { ProjectService } from '../../project/services';
|
||||
import {
|
||||
SpaceModelEntity,
|
||||
SpaceModelRepository,
|
||||
} from '@app/common/modules/space-model';
|
||||
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||
import { ValidateSpacesDto } from '../dtos/validation.space.dto';
|
||||
import { ProjectParam } from '../dtos';
|
||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||
import { In } from 'typeorm';
|
||||
import {
|
||||
ORPHAN_COMMUNITY_NAME,
|
||||
ORPHAN_SPACE_NAME,
|
||||
} from '@app/common/constants/orphan-constant';
|
||||
import { ProjectEntity } from '@app/common/modules/project/entities';
|
||||
import { ValidateSpacesDto } from '../dtos/validation.space.dto';
|
||||
|
||||
@Injectable()
|
||||
export class ValidationService {
|
||||
@ -130,9 +125,7 @@ export class ValidationService {
|
||||
'subspaces',
|
||||
'productAllocations',
|
||||
'productAllocations.product',
|
||||
'productAllocations.tags',
|
||||
'subspaces.productAllocations',
|
||||
'subspaces.productAllocations.tags',
|
||||
'subspaces.productAllocations.product',
|
||||
'subspaces.devices',
|
||||
'spaceModel',
|
||||
@ -146,13 +139,13 @@ export class ValidationService {
|
||||
);
|
||||
}
|
||||
|
||||
const devices = await this.deviceRepository.find({
|
||||
where: { spaceDevice: { uuid: spaceUuid } },
|
||||
select: ['uuid', 'deviceTuyaUuid', 'isActive', 'createdAt', 'updatedAt'],
|
||||
relations: ['productDevice', 'subspace'],
|
||||
});
|
||||
// const devices = await this.deviceRepository.find({
|
||||
// where: { spaceDevice: { uuid: spaceUuid } },
|
||||
// select: ['uuid', 'deviceTuyaUuid', 'isActive', 'createdAt', 'updatedAt'],
|
||||
// relations: ['productDevice', 'subspace'],
|
||||
// });
|
||||
|
||||
space.devices = devices;
|
||||
// space.devices = devices;
|
||||
|
||||
return space;
|
||||
}
|
||||
@ -191,8 +184,8 @@ export class ValidationService {
|
||||
'subspaceProductAllocations',
|
||||
)
|
||||
.leftJoinAndSelect(
|
||||
'subspaceProductAllocations.tags',
|
||||
'subspaceAllocationTags',
|
||||
'subspaceProductAllocations.tag',
|
||||
'subspaceAllocationTag',
|
||||
)
|
||||
.leftJoinAndSelect(
|
||||
'subspaceProductAllocations.product',
|
||||
@ -203,7 +196,7 @@ export class ValidationService {
|
||||
'productAllocations.product',
|
||||
'productAllocationProduct',
|
||||
)
|
||||
.leftJoinAndSelect('productAllocations.tags', 'productAllocationTags')
|
||||
.leftJoinAndSelect('productAllocations.tag', 'productAllocationTag')
|
||||
.andWhere('spaceModel.disabled = :disabled', { disabled: false })
|
||||
.where('spaceModel.uuid = :uuid', { uuid: spaceModelUuid });
|
||||
|
||||
@ -219,39 +212,6 @@ export class ValidationService {
|
||||
return spaceModel;
|
||||
}
|
||||
|
||||
async getFullSpaceHierarchy(
|
||||
space: SpaceEntity,
|
||||
): Promise<{ uuid: string; spaceName: string }[]> {
|
||||
try {
|
||||
// Fetch only the relevant spaces, starting with the target space
|
||||
const targetSpace = await this.spaceRepository.findOne({
|
||||
where: { uuid: space.uuid },
|
||||
relations: ['parent', 'children'],
|
||||
});
|
||||
|
||||
// Fetch only the ancestors of the target space
|
||||
const ancestors = await this.fetchAncestors(targetSpace);
|
||||
|
||||
// Optionally, fetch descendants if required
|
||||
const descendants = await this.fetchDescendants(targetSpace);
|
||||
|
||||
const fullHierarchy = [...ancestors, targetSpace, ...descendants].map(
|
||||
(space) => ({
|
||||
uuid: space.uuid,
|
||||
spaceName: space.spaceName,
|
||||
}),
|
||||
);
|
||||
|
||||
return fullHierarchy;
|
||||
} catch (error) {
|
||||
console.error('Error fetching space hierarchy:', error.message);
|
||||
throw new HttpException(
|
||||
'Error fetching space hierarchy',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchAncestors(space: SpaceEntity): Promise<SpaceEntity[]> {
|
||||
const ancestors: SpaceEntity[] = [];
|
||||
|
||||
@ -275,27 +235,6 @@ export class ValidationService {
|
||||
return ancestors.reverse();
|
||||
}
|
||||
|
||||
private async fetchDescendants(space: SpaceEntity): Promise<SpaceEntity[]> {
|
||||
const descendants: SpaceEntity[] = [];
|
||||
|
||||
// Fetch the immediate children of the current space
|
||||
const children = await this.spaceRepository.find({
|
||||
where: { parent: { uuid: space.uuid } },
|
||||
relations: ['children'], // To continue fetching downwards
|
||||
});
|
||||
|
||||
for (const child of children) {
|
||||
// Add the child to the descendants list
|
||||
descendants.push(child);
|
||||
|
||||
// Recursively fetch the child's descendants
|
||||
const childDescendants = await this.fetchDescendants(child);
|
||||
descendants.push(...childDescendants);
|
||||
}
|
||||
|
||||
return descendants;
|
||||
}
|
||||
|
||||
async getParentHierarchy(
|
||||
space: SpaceEntity,
|
||||
): Promise<{ uuid: string; spaceName: string }[]> {
|
||||
@ -323,24 +262,4 @@ export class ValidationService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getOrphanSpace(project: ProjectEntity): Promise<SpaceEntity> {
|
||||
const orphanSpace = await this.spaceRepository.findOne({
|
||||
where: {
|
||||
community: {
|
||||
name: `${ORPHAN_COMMUNITY_NAME}-${project.name}`,
|
||||
},
|
||||
spaceName: ORPHAN_SPACE_NAME,
|
||||
},
|
||||
});
|
||||
|
||||
if (!orphanSpace) {
|
||||
throw new HttpException(
|
||||
`Orphan space not found in community ${project.name}`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return orphanSpace;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,14 @@
|
||||
import {
|
||||
ORPHAN_COMMUNITY_NAME,
|
||||
ORPHAN_SPACE_NAME,
|
||||
} from '@app/common/constants/orphan-constant';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||
import { generateRandomString } from '@app/common/helper/randomString';
|
||||
import { removeCircularReferences } from '@app/common/helper/removeCircularReferences';
|
||||
import { SpaceProductAllocationEntity } from '@app/common/modules/space/entities/space-product-allocation.entity';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||
import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity';
|
||||
import {
|
||||
InviteSpaceRepository,
|
||||
SpaceRepository,
|
||||
@ -8,36 +19,25 @@ import {
|
||||
HttpStatus,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
import { CommandBus } from '@nestjs/cqrs';
|
||||
import { DeviceService } from 'src/device/services';
|
||||
import { SpaceModelService } from 'src/space-model/services';
|
||||
import { ProcessTagDto } from 'src/tags/dtos';
|
||||
import { TagService } from 'src/tags/services/tags.service';
|
||||
import { DataSource, In, Not, QueryRunner } from 'typeorm';
|
||||
import { DisableSpaceCommand } from '../commands';
|
||||
import {
|
||||
AddSpaceDto,
|
||||
AddSubspaceDto,
|
||||
CommunitySpaceParam,
|
||||
GetSpaceParam,
|
||||
UpdateSpaceDto,
|
||||
} from '../dtos';
|
||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { generateRandomString } from '@app/common/helper/randomString';
|
||||
import { SpaceLinkService } from './space-link';
|
||||
import { SubSpaceService } from './subspace';
|
||||
import { DataSource, QueryRunner } from 'typeorm';
|
||||
import { ValidationService } from './space-validation.service';
|
||||
import {
|
||||
ORPHAN_COMMUNITY_NAME,
|
||||
ORPHAN_SPACE_NAME,
|
||||
} from '@app/common/constants/orphan-constant';
|
||||
import { CommandBus } from '@nestjs/cqrs';
|
||||
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 { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity';
|
||||
import { DeviceService } from 'src/device/services';
|
||||
import { SpaceWithParentsDto } from '../dtos/space.parents.dto';
|
||||
import { SpaceLinkService } from './space-link';
|
||||
import { SpaceProductAllocationService } from './space-product-allocation.service';
|
||||
import { ValidationService } from './space-validation.service';
|
||||
import { SubSpaceService } from './subspace';
|
||||
@Injectable()
|
||||
export class SpaceService {
|
||||
constructor(
|
||||
@ -47,7 +47,7 @@ export class SpaceService {
|
||||
private readonly spaceLinkService: SpaceLinkService,
|
||||
private readonly subSpaceService: SubSpaceService,
|
||||
private readonly validationService: ValidationService,
|
||||
private readonly newTagService: NewTagService,
|
||||
private readonly tagService: TagService,
|
||||
private readonly spaceModelService: SpaceModelService,
|
||||
private readonly deviceService: DeviceService,
|
||||
private commandBus: CommandBus,
|
||||
@ -62,13 +62,6 @@ export class SpaceService {
|
||||
addSpaceDto;
|
||||
const { communityUuid, projectUuid } = params;
|
||||
|
||||
if (addSpaceDto.spaceName === ORPHAN_SPACE_NAME) {
|
||||
throw new HttpException(
|
||||
`Name ${ORPHAN_SPACE_NAME} cannot be used`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
@ -80,7 +73,7 @@ export class SpaceService {
|
||||
projectUuid,
|
||||
);
|
||||
|
||||
this.validateSpaceCreation(addSpaceDto, spaceModelUuid);
|
||||
this.validateSpaceCreationCriteria({ spaceModelUuid, subspaces, tags });
|
||||
|
||||
const parent = parentUuid
|
||||
? await this.validationService.validateSpace(parentUuid)
|
||||
@ -99,27 +92,24 @@ export class SpaceService {
|
||||
});
|
||||
|
||||
const newSpace = await queryRunner.manager.save(space);
|
||||
|
||||
const subspaceTags =
|
||||
this.subSpaceService.extractTagsFromSubspace(subspaces);
|
||||
const allTags = [...tags, ...subspaceTags];
|
||||
this.validateUniqueTags(allTags);
|
||||
subspaces?.flatMap((subspace) => subspace.tags || []) || [];
|
||||
|
||||
this.checkDuplicateTags([...tags, ...subspaceTags]);
|
||||
|
||||
if (spaceModelUuid) {
|
||||
const hasDependencies = subspaces?.length > 0 || tags?.length > 0;
|
||||
if (!hasDependencies) {
|
||||
await this.spaceModelService.linkToSpace(
|
||||
newSpace,
|
||||
spaceModel,
|
||||
queryRunner,
|
||||
);
|
||||
} else if (hasDependencies) {
|
||||
throw new HttpException(
|
||||
`Space cannot be linked to a model because it has existing dependencies (subspaces or tags).`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
// no need to check for existing dependencies here as validateSpaceCreationCriteria
|
||||
// ensures no tags or subspaces are present along with spaceModelUuid
|
||||
await this.spaceModelService.linkToSpace(
|
||||
newSpace,
|
||||
spaceModel,
|
||||
queryRunner,
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
// todo: remove this logic as we are not using space links anymore
|
||||
direction && parent
|
||||
? this.spaceLinkService.saveSpaceLink(
|
||||
parent.uuid,
|
||||
@ -129,16 +119,15 @@ export class SpaceService {
|
||||
)
|
||||
: Promise.resolve(),
|
||||
subspaces?.length
|
||||
? this.createSubspaces(
|
||||
? this.subSpaceService.createSubspacesFromDto(
|
||||
subspaces,
|
||||
newSpace,
|
||||
space,
|
||||
queryRunner,
|
||||
null,
|
||||
projectUuid,
|
||||
)
|
||||
: Promise.resolve(),
|
||||
tags?.length
|
||||
? this.createTags(tags, projectUuid, queryRunner, newSpace)
|
||||
? this.createAllocations(tags, projectUuid, queryRunner, newSpace)
|
||||
: Promise.resolve(),
|
||||
]);
|
||||
|
||||
@ -160,7 +149,7 @@ export class SpaceService {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
private validateUniqueTags(allTags: ProcessTagDto[]) {
|
||||
private checkDuplicateTags(allTags: ProcessTagDto[]) {
|
||||
const tagUuidSet = new Set<string>();
|
||||
const tagNameProductSet = new Set<string>();
|
||||
|
||||
@ -213,8 +202,8 @@ export class SpaceService {
|
||||
{ incomingConnectionDisabled: false },
|
||||
)
|
||||
.leftJoinAndSelect('space.productAllocations', 'productAllocations')
|
||||
// .leftJoinAndSelect('productAllocations.tags', 'tags')
|
||||
// .leftJoinAndSelect('productAllocations.product', 'product')
|
||||
.leftJoinAndSelect('productAllocations.tag', 'tag')
|
||||
.leftJoinAndSelect('productAllocations.product', 'product')
|
||||
.leftJoinAndSelect(
|
||||
'space.subspaces',
|
||||
'subspaces',
|
||||
@ -225,11 +214,11 @@ export class SpaceService {
|
||||
'subspaces.productAllocations',
|
||||
'subspaceProductAllocations',
|
||||
)
|
||||
// .leftJoinAndSelect('subspaceProductAllocations.tags', 'subspaceTag')
|
||||
// .leftJoinAndSelect(
|
||||
// 'subspaceProductAllocations.product',
|
||||
// 'subspaceProduct',
|
||||
// )
|
||||
.leftJoinAndSelect('subspaceProductAllocations.tag', 'subspaceTag')
|
||||
.leftJoinAndSelect(
|
||||
'subspaceProductAllocations.product',
|
||||
'subspaceProduct',
|
||||
)
|
||||
.leftJoinAndSelect('space.spaceModel', 'spaceModel')
|
||||
.where('space.community_id = :communityUuid', { communityUuid })
|
||||
.andWhere('space.spaceName != :orphanSpaceName', {
|
||||
@ -282,28 +271,7 @@ export class SpaceService {
|
||||
}
|
||||
}
|
||||
|
||||
private transformSpace(space) {
|
||||
const { productAllocations, subspaces, ...restSpace } = space;
|
||||
|
||||
const tags = productAllocations.flatMap((pa) => pa.tags);
|
||||
|
||||
const transformedSubspaces = subspaces.map((subspace) => {
|
||||
const {
|
||||
productAllocations: subspaceProductAllocations,
|
||||
...restSubspace
|
||||
} = subspace;
|
||||
const subspaceTags = subspaceProductAllocations.flatMap((pa) => pa.tags);
|
||||
return {
|
||||
...restSubspace,
|
||||
tags: subspaceTags,
|
||||
};
|
||||
});
|
||||
return {
|
||||
...restSpace,
|
||||
tags,
|
||||
subspaces: transformedSubspaces,
|
||||
};
|
||||
}
|
||||
// todo refactor this method to eliminate wrong use of tags
|
||||
async findOne(params: GetSpaceParam): Promise<BaseResponseDto> {
|
||||
const { communityUuid, spaceUuid, projectUuid } = params;
|
||||
try {
|
||||
@ -327,13 +295,9 @@ export class SpaceService {
|
||||
'incomingConnections.disabled = :incomingConnectionDisabled',
|
||||
{ incomingConnectionDisabled: false },
|
||||
)
|
||||
// .leftJoinAndSelect(
|
||||
// 'space.tags',
|
||||
// 'tags',
|
||||
// 'tags.disabled = :tagDisabled',
|
||||
// { tagDisabled: false },
|
||||
// )
|
||||
// .leftJoinAndSelect('tags.product', 'tagProduct')
|
||||
.leftJoinAndSelect('space.productAllocations', 'productAllocations')
|
||||
.leftJoinAndSelect('productAllocations.tag', 'spaceTag')
|
||||
.leftJoinAndSelect('productAllocations.product', 'spaceProduct')
|
||||
.leftJoinAndSelect(
|
||||
'space.subspaces',
|
||||
'subspaces',
|
||||
@ -341,12 +305,14 @@ export class SpaceService {
|
||||
{ subspaceDisabled: false },
|
||||
)
|
||||
.leftJoinAndSelect(
|
||||
'subspaces.tags',
|
||||
'subspaceTags',
|
||||
'subspaceTags.disabled = :subspaceTagsDisabled',
|
||||
{ subspaceTagsDisabled: false },
|
||||
'subspaces.productAllocations',
|
||||
'subspaceProductAllocations',
|
||||
)
|
||||
.leftJoinAndSelect('subspaceProductAllocations.tag', 'subspaceTag')
|
||||
.leftJoinAndSelect(
|
||||
'subspaceProductAllocations.product',
|
||||
'subspaceProduct',
|
||||
)
|
||||
// .leftJoinAndSelect('subspaceTags.product', 'subspaceTagProduct')
|
||||
.where('space.community_id = :communityUuid', { communityUuid })
|
||||
.andWhere('space.spaceName != :orphanSpaceName', {
|
||||
orphanSpaceName: ORPHAN_SPACE_NAME,
|
||||
@ -456,7 +422,7 @@ export class SpaceService {
|
||||
const { communityUuid, spaceUuid, projectUuid } = params;
|
||||
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
const hasSubspace = updateSpaceDto.subspace?.length > 0;
|
||||
const hasSubspace = updateSpaceDto.subspaces?.length > 0;
|
||||
const hasTags = updateSpaceDto.tags?.length > 0;
|
||||
|
||||
try {
|
||||
@ -473,13 +439,6 @@ export class SpaceService {
|
||||
spaceUuid,
|
||||
);
|
||||
|
||||
if (space.spaceName === ORPHAN_SPACE_NAME) {
|
||||
throw new HttpException(
|
||||
`Space "${ORPHAN_SPACE_NAME}" cannot be updated`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
if (space.spaceModel && !updateSpaceDto.spaceModelUuid) {
|
||||
await queryRunner.manager.update(SpaceEntity, space.uuid, {
|
||||
spaceModel: null,
|
||||
@ -512,7 +471,11 @@ export class SpaceService {
|
||||
queryRunner,
|
||||
);
|
||||
} else if (hasDependencies) {
|
||||
await this.spaceModelService.overwriteSpace(
|
||||
// check for uuids that didn't change,
|
||||
// get their device ids and check if they has a tag in device entity,
|
||||
// if so move them ot the orphan space
|
||||
|
||||
await this.spaceModelService.removeSpaceOldSubspacesAndAllocations(
|
||||
space,
|
||||
project,
|
||||
queryRunner,
|
||||
@ -536,23 +499,45 @@ export class SpaceService {
|
||||
);
|
||||
}
|
||||
|
||||
if (hasSubspace) {
|
||||
await this.subSpaceService.modifySubSpace(
|
||||
updateSpaceDto.subspace,
|
||||
if (updateSpaceDto.subspaces) {
|
||||
await this.subSpaceService.updateSubspaceInSpace(
|
||||
updateSpaceDto.subspaces,
|
||||
queryRunner,
|
||||
space,
|
||||
projectUuid,
|
||||
updateSpaceDto.tags,
|
||||
);
|
||||
}
|
||||
|
||||
if (updateSpaceDto.tags) {
|
||||
await this.spaceProductAllocationService.updateSpaceProductAllocations(
|
||||
updateSpaceDto.tags,
|
||||
await queryRunner.manager.delete(SpaceProductAllocationEntity, {
|
||||
space: { uuid: space.uuid },
|
||||
tag: {
|
||||
uuid: Not(
|
||||
In(
|
||||
updateSpaceDto.tags
|
||||
.filter((tag) => tag.tagUuid)
|
||||
.map((tag) => tag.tagUuid),
|
||||
),
|
||||
),
|
||||
},
|
||||
});
|
||||
await this.createAllocations(
|
||||
updateSpaceDto.tags.map((tag) => ({
|
||||
name: tag.name,
|
||||
uuid: tag.tagUuid,
|
||||
productUuid: tag.productUuid,
|
||||
})),
|
||||
projectUuid,
|
||||
space,
|
||||
queryRunner,
|
||||
updateSpaceDto.subspace,
|
||||
space,
|
||||
);
|
||||
}
|
||||
|
||||
if (space.devices?.length) {
|
||||
await this.deviceService.addDevicesToOrphanSpace(
|
||||
space,
|
||||
project,
|
||||
queryRunner,
|
||||
);
|
||||
}
|
||||
|
||||
@ -636,7 +621,7 @@ export class SpaceService {
|
||||
// Get all spaces that are children of the provided space, including the parent-child relations
|
||||
const spaces = await this.spaceRepository.find({
|
||||
where: { parent: { uuid: spaceUuid }, disabled: false },
|
||||
relations: ['parent', 'children'], // Include parent and children relations
|
||||
relations: ['parent', 'children'],
|
||||
});
|
||||
|
||||
// Organize spaces into a hierarchical structure
|
||||
@ -715,13 +700,13 @@ export class SpaceService {
|
||||
return rootSpaces;
|
||||
}
|
||||
|
||||
private validateSpaceCreation(
|
||||
addSpaceDto: AddSpaceDto,
|
||||
spaceModelUuid?: string,
|
||||
) {
|
||||
private validateSpaceCreationCriteria({
|
||||
spaceModelUuid,
|
||||
tags,
|
||||
subspaces,
|
||||
}: Pick<AddSpaceDto, 'spaceModelUuid' | 'tags' | 'subspaces'>): void {
|
||||
const hasTagsOrSubspaces =
|
||||
(addSpaceDto.tags && addSpaceDto.tags.length > 0) ||
|
||||
(addSpaceDto.subspaces && addSpaceDto.subspaces.length > 0);
|
||||
(tags && tags.length > 0) || (subspaces && subspaces.length > 0);
|
||||
|
||||
if (spaceModelUuid && hasTagsOrSubspaces) {
|
||||
throw new HttpException(
|
||||
@ -731,36 +716,36 @@ export class SpaceService {
|
||||
}
|
||||
}
|
||||
|
||||
private async createSubspaces(
|
||||
subspaces: AddSubspaceDto[],
|
||||
space: SpaceEntity,
|
||||
queryRunner: QueryRunner,
|
||||
tags: ProcessTagDto[],
|
||||
projectUuid: string,
|
||||
): Promise<void> {
|
||||
space.subspaces = await this.subSpaceService.createSubspacesFromDto(
|
||||
subspaces,
|
||||
space,
|
||||
queryRunner,
|
||||
null,
|
||||
projectUuid,
|
||||
);
|
||||
}
|
||||
|
||||
private async createTags(
|
||||
private async createAllocations(
|
||||
tags: ProcessTagDto[],
|
||||
projectUuid: string,
|
||||
queryRunner: QueryRunner,
|
||||
space: SpaceEntity,
|
||||
): Promise<void> {
|
||||
const processedTags = await this.newTagService.processTags(
|
||||
const allocationsData = await this.tagService.processTags(
|
||||
tags,
|
||||
projectUuid,
|
||||
queryRunner,
|
||||
);
|
||||
await this.spaceProductAllocationService.createSpaceProductAllocations(
|
||||
|
||||
// Create a mapping of created tags by UUID and name for quick lookup
|
||||
const createdTagsByUUID = new Map(allocationsData.map((t) => [t.uuid, t]));
|
||||
const createdTagsByName = new Map(allocationsData.map((t) => [t.name, t]));
|
||||
|
||||
// Create the product-tag mapping based on the processed tags
|
||||
const productTagMapping = tags.map(({ uuid, name, productUuid }) => {
|
||||
const inputTag = uuid
|
||||
? createdTagsByUUID.get(uuid)
|
||||
: createdTagsByName.get(name);
|
||||
return {
|
||||
tag: inputTag?.uuid,
|
||||
product: productUuid,
|
||||
};
|
||||
});
|
||||
|
||||
await this.spaceProductAllocationService.createProductAllocations(
|
||||
space,
|
||||
processedTags,
|
||||
productTagMapping,
|
||||
queryRunner,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||
import { DeviceEntity } from '@app/common/modules/device/entities';
|
||||
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
||||
import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { DeviceService } from 'src/device/services';
|
||||
import { In, QueryRunner } from 'typeorm';
|
||||
import { DeviceSubSpaceParam, GetSubSpaceParam } from '../../dtos';
|
||||
import { ValidationService } from '../space-validation.service';
|
||||
import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
|
||||
import { In, QueryRunner } from 'typeorm';
|
||||
import { DeviceEntity } from '@app/common/modules/device/entities';
|
||||
import { TagRepository } from '@app/common/modules/space';
|
||||
import { DeviceService } from 'src/device/services';
|
||||
|
||||
@Injectable()
|
||||
export class SubspaceDeviceService {
|
||||
@ -17,7 +16,6 @@ export class SubspaceDeviceService {
|
||||
private readonly deviceRepository: DeviceRepository,
|
||||
private readonly deviceService: DeviceService,
|
||||
private readonly validationService: ValidationService,
|
||||
private readonly tagRepository: TagRepository,
|
||||
) {}
|
||||
|
||||
async listDevicesInSubspace(
|
||||
@ -202,21 +200,6 @@ export class SubspaceDeviceService {
|
||||
);
|
||||
}
|
||||
|
||||
async findNextTag(): Promise<number> {
|
||||
const tags = await this.tagRepository.find({ select: ['tag'] });
|
||||
|
||||
const tagNumbers = tags
|
||||
.map((t) => t.tag.match(/^Tag (\d+)$/))
|
||||
.filter((match) => match)
|
||||
.map((match) => parseInt(match[1]))
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
const nextTagNumber = tagNumbers.length
|
||||
? tagNumbers[tagNumbers.length - 1] + 1
|
||||
: 1;
|
||||
return nextTagNumber;
|
||||
}
|
||||
|
||||
private async findDeviceWithSubspaceAndTag(deviceUuid: string) {
|
||||
return await this.deviceRepository.findOne({
|
||||
where: { uuid: deviceUuid },
|
||||
|
||||
@ -1,18 +1,11 @@
|
||||
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 { UpdateSpaceAllocationDto } from 'src/space/interfaces/update-subspace-allocation.dto';
|
||||
import { TagService as NewTagService } from 'src/tags/services';
|
||||
import { In, QueryRunner } from 'typeorm';
|
||||
import { In, Not, QueryRunner } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
export class SubspaceProductAllocationService {
|
||||
@ -23,44 +16,38 @@ export class SubspaceProductAllocationService {
|
||||
private readonly subspaceProductAllocationRepository: SubspaceProductAllocationRepository,
|
||||
) {}
|
||||
|
||||
async createSubspaceProductAllocations(
|
||||
async createProductAllocations(
|
||||
subspace: SubspaceEntity,
|
||||
processedTags: NewTagEntity[],
|
||||
allocationsData: { product: string; tag: string }[],
|
||||
queryRunner?: QueryRunner,
|
||||
spaceAllocationsToExclude?: SpaceProductAllocationEntity[],
|
||||
// spaceAllocationsToExclude?: SpaceProductAllocationEntity[],
|
||||
): Promise<void> {
|
||||
try {
|
||||
if (!processedTags.length) return;
|
||||
if (!allocationsData.length) return;
|
||||
|
||||
const allocations: SubspaceProductAllocationEntity[] = [];
|
||||
|
||||
for (const tag of processedTags) {
|
||||
await this.validateTagWithinSubspace(
|
||||
queryRunner,
|
||||
tag,
|
||||
subspace,
|
||||
spaceAllocationsToExclude,
|
||||
);
|
||||
for (const allocationData of allocationsData) {
|
||||
// await this.validateTagWithinSubspace(
|
||||
// queryRunner,
|
||||
// allocationData.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 (
|
||||
await this.isAllocationExist(allocationData, subspace, queryRunner)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const allocation = this.createNewSubspaceAllocation(
|
||||
subspace,
|
||||
allocationData,
|
||||
queryRunner,
|
||||
);
|
||||
allocations.push(allocation);
|
||||
}
|
||||
if (allocations.length > 0) {
|
||||
await this.saveAllocations(allocations, queryRunner);
|
||||
}
|
||||
@ -71,307 +58,130 @@ export class SubspaceProductAllocationService {
|
||||
);
|
||||
}
|
||||
}
|
||||
async updateSubspaceProductAllocations(
|
||||
subspaces: ISingleSubspace[],
|
||||
|
||||
async updateSubspaceProductAllocationsV2(
|
||||
subSpaces: UpdateSpaceAllocationDto[],
|
||||
projectUuid: string,
|
||||
queryRunner: QueryRunner,
|
||||
space: SpaceEntity,
|
||||
spaceTagUpdateDtos?: ModifyTagDto[],
|
||||
) {
|
||||
const spaceAllocationToExclude: SpaceProductAllocationEntity[] = [];
|
||||
for (const subspace of subspaces) {
|
||||
if (!subspace.tags || subspace.tags.length === 0) continue;
|
||||
const tagDtos = subspace.tags;
|
||||
const tagsToAddDto: ProcessTagDto[] = tagDtos
|
||||
.filter((dto) => dto.action === ModifyAction.ADD)
|
||||
.map((dto) => ({
|
||||
name: dto.name,
|
||||
productUuid: dto.productUuid,
|
||||
uuid: dto.newTagUuid,
|
||||
}));
|
||||
await Promise.all(
|
||||
subSpaces.map(async (subspace) => {
|
||||
await queryRunner.manager.delete(SubspaceProductAllocationEntity, {
|
||||
subspace: { uuid: subspace.uuid },
|
||||
tag: {
|
||||
uuid: Not(
|
||||
In(
|
||||
subspace.tags.filter((tag) => tag.uuid).map((tag) => tag.uuid),
|
||||
),
|
||||
),
|
||||
},
|
||||
});
|
||||
const subspaceEntity = await queryRunner.manager.findOne(
|
||||
SubspaceEntity,
|
||||
{
|
||||
where: { uuid: subspace.uuid },
|
||||
},
|
||||
);
|
||||
|
||||
const tagsToDeleteDto = tagDtos.filter(
|
||||
(dto) => dto.action === ModifyAction.DELETE,
|
||||
);
|
||||
|
||||
if (tagsToAddDto.length > 0) {
|
||||
let processedTags = await this.tagService.processTags(
|
||||
tagsToAddDto,
|
||||
const processedTags = await this.tagService.processTags(
|
||||
subspace.tags,
|
||||
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),
|
||||
);
|
||||
const createdTagsByUUID = new Map(
|
||||
processedTags.map((t) => [t.uuid, t]),
|
||||
);
|
||||
const createdTagsByName = new Map(
|
||||
processedTags.map((t) => [t.name, t]),
|
||||
);
|
||||
|
||||
for (const deletedTag of deletedTags) {
|
||||
const allocation = await queryRunner.manager.findOne(
|
||||
SubspaceProductAllocationEntity,
|
||||
{
|
||||
where: {
|
||||
subspace: { uuid: subspaceDto.subspace.uuid },
|
||||
},
|
||||
relations: ['tags', 'product', 'subspace'],
|
||||
},
|
||||
);
|
||||
// Create the product-tag mapping based on the processed tags
|
||||
const productTagMapping = subspace.tags.map(
|
||||
({ uuid, name, productUuid }) => {
|
||||
const inputTag = uuid
|
||||
? createdTagsByUUID.get(uuid)
|
||||
: createdTagsByName.get(name);
|
||||
return {
|
||||
tag: inputTag?.uuid,
|
||||
product: productUuid,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
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,
|
||||
await this.createProductAllocations(
|
||||
subspaceEntity,
|
||||
productTagMapping,
|
||||
queryRunner,
|
||||
spaceAllocationToExclude,
|
||||
);
|
||||
}
|
||||
if (tagsToDeleteDto.length > 0) {
|
||||
await this.processDeleteActions(tagsToDeleteDto, queryRunner);
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async processDeleteActions(
|
||||
dtos: ModifyTagDto[],
|
||||
queryRunner: QueryRunner,
|
||||
): Promise<SubspaceProductAllocationEntity[]> {
|
||||
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_uuid NOT IN ' +
|
||||
queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.select('allocation.uuid')
|
||||
.from(SubspaceProductAllocationEntity, 'allocation')
|
||||
.getQuery() +
|
||||
')',
|
||||
)
|
||||
.execute();
|
||||
|
||||
return deletedAllocations;
|
||||
} catch (error) {
|
||||
throw this.handleError(error, `Failed to delete tags in subspace`);
|
||||
}
|
||||
}
|
||||
// async processDeleteActions(dtos: ModifyTagDto[], queryRunner: QueryRunner) {
|
||||
// // : Promise<SubspaceProductAllocationEntity[]>
|
||||
// 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: { tag: In(tagUuidsToDelete) },
|
||||
// // },
|
||||
// // );
|
||||
// // 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_uuid NOT IN ' +
|
||||
// // queryRunner.manager
|
||||
// // .createQueryBuilder()
|
||||
// // .select('allocation.uuid')
|
||||
// // .from(SubspaceProductAllocationEntity, 'allocation')
|
||||
// // .getQuery() +
|
||||
// // ')',
|
||||
// // )
|
||||
// // .execute();
|
||||
// // return deletedAllocations;
|
||||
// } catch (error) {
|
||||
// throw this.handleError(error, `Failed to delete tags in subspace`);
|
||||
// }
|
||||
// }
|
||||
|
||||
async unlinkModels(
|
||||
allocations: SubspaceProductAllocationEntity[],
|
||||
@ -395,108 +205,106 @@ export class SubspaceProductAllocationService {
|
||||
}
|
||||
}
|
||||
|
||||
private async validateTagWithinSubspace(
|
||||
queryRunner: QueryRunner | undefined,
|
||||
tag: NewTagEntity,
|
||||
subspace: SubspaceEntity,
|
||||
spaceAllocationsToExclude?: SpaceProductAllocationEntity[],
|
||||
): Promise<void> {
|
||||
const existingTagInSpace = await (queryRunner
|
||||
? queryRunner.manager.findOne(SpaceProductAllocationEntity, {
|
||||
where: {
|
||||
product: { uuid: tag.product.uuid },
|
||||
space: { uuid: subspace.space.uuid },
|
||||
tags: { uuid: tag.uuid },
|
||||
},
|
||||
relations: ['tags'],
|
||||
})
|
||||
: this.spaceProductAllocationRepository.findOne({
|
||||
where: {
|
||||
product: { uuid: tag.product.uuid },
|
||||
space: { uuid: subspace.space.uuid },
|
||||
tags: { uuid: tag.uuid },
|
||||
},
|
||||
relations: ['tags'],
|
||||
}));
|
||||
// private async validateTagWithinSubspace(
|
||||
// queryRunner: QueryRunner | undefined,
|
||||
// tag: NewTagEntity & { product: string },
|
||||
// subspace: SubspaceEntity,
|
||||
// spaceAllocationsToExclude?: SpaceProductAllocationEntity[],
|
||||
// ): Promise<void> {
|
||||
// // const existingTagInSpace = await (queryRunner
|
||||
// // ? queryRunner.manager.findOne(SpaceProductAllocationEntity, {
|
||||
// // where: {
|
||||
// // product: { uuid: tag.product },
|
||||
// // space: { uuid: subspace.space.uuid },
|
||||
// // tag: { uuid: tag.uuid },
|
||||
// // },
|
||||
// // })
|
||||
// // : this.spaceProductAllocationRepository.findOne({
|
||||
// // where: {
|
||||
// // product: { uuid: tag.product },
|
||||
// // space: { uuid: subspace.space.uuid },
|
||||
// // tag: { uuid: tag.uuid },
|
||||
// // },
|
||||
// // }));
|
||||
// // const isExcluded = spaceAllocationsToExclude?.some(
|
||||
// // (excludedAllocation) =>
|
||||
// // excludedAllocation.product.uuid === tag.product &&
|
||||
// // excludedAllocation.tags.some((t) => t.uuid === tag.uuid),
|
||||
// // );
|
||||
// // if (!isExcluded && existingTagInSpace) {
|
||||
// // throw new HttpException(
|
||||
// // `Tag ${tag.uuid} (Product: ${tag.product}) is already allocated at the space level (${subspace.space.uuid}). Cannot allocate the same tag in a subspace.`,
|
||||
// // HttpStatus.BAD_REQUEST,
|
||||
// // );
|
||||
// // }
|
||||
// // // ?: Check if the tag is already allocated in another "subspace" within the same space
|
||||
// // const existingTagInSameSpace = await (queryRunner
|
||||
// // ? queryRunner.manager.findOne(SubspaceProductAllocationEntity, {
|
||||
// // where: {
|
||||
// // product: { uuid: tag.product },
|
||||
// // subspace: { space: subspace.space },
|
||||
// // tag: { uuid: tag.uuid },
|
||||
// // },
|
||||
// // relations: ['subspace'],
|
||||
// // })
|
||||
// // : this.subspaceProductAllocationRepository.findOne({
|
||||
// // where: {
|
||||
// // product: { uuid: tag.product },
|
||||
// // subspace: { space: subspace.space },
|
||||
// // tag: { uuid: tag.uuid },
|
||||
// // },
|
||||
// // relations: ['subspace'],
|
||||
// // }));
|
||||
// // if (
|
||||
// // existingTagInSameSpace &&
|
||||
// // existingTagInSameSpace.subspace.uuid !== subspace.uuid
|
||||
// // ) {
|
||||
// // throw new HttpException(
|
||||
// // `Tag ${tag.uuid} (Product: ${tag.product}) is already allocated in another subspace (${existingTagInSameSpace.subspace.uuid}) within the same space (${subspace.space.uuid}).`,
|
||||
// // HttpStatus.BAD_REQUEST,
|
||||
// // );
|
||||
// // }
|
||||
// }
|
||||
|
||||
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: { uuid: tag.product.uuid },
|
||||
subspace: { space: subspace.space },
|
||||
tags: { uuid: tag.uuid },
|
||||
},
|
||||
relations: ['subspace', 'tags'],
|
||||
})
|
||||
: this.subspaceProductAllocationRepository.findOne({
|
||||
where: {
|
||||
product: { uuid: tag.product.uuid },
|
||||
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,
|
||||
allocationData: { product: string; tag: string },
|
||||
queryRunner?: QueryRunner,
|
||||
): SubspaceProductAllocationEntity {
|
||||
return queryRunner
|
||||
? queryRunner.manager.create(SubspaceProductAllocationEntity, {
|
||||
subspace,
|
||||
product: tag.product,
|
||||
tags: [tag],
|
||||
product: { uuid: allocationData.product },
|
||||
tag: { uuid: allocationData.tag },
|
||||
})
|
||||
: this.subspaceProductAllocationRepository.create({
|
||||
subspace,
|
||||
product: tag.product,
|
||||
tags: [tag],
|
||||
product: { uuid: allocationData.product },
|
||||
tag: { uuid: allocationData.tag },
|
||||
});
|
||||
}
|
||||
private async getAllocationByProduct(
|
||||
product: ProductEntity,
|
||||
|
||||
private async isAllocationExist(
|
||||
allocationData: { product: string; tag: string },
|
||||
subspace: SubspaceEntity,
|
||||
queryRunner?: QueryRunner,
|
||||
): Promise<SubspaceProductAllocationEntity | null> {
|
||||
return queryRunner
|
||||
? queryRunner.manager.findOne(SubspaceProductAllocationEntity, {
|
||||
): Promise<boolean> {
|
||||
const allocation = queryRunner
|
||||
? await queryRunner.manager.findOne(SubspaceProductAllocationEntity, {
|
||||
where: {
|
||||
subspace: { uuid: subspace.uuid },
|
||||
product: { uuid: product.uuid },
|
||||
product: { uuid: allocationData.product },
|
||||
tag: { uuid: allocationData.tag },
|
||||
},
|
||||
relations: ['tags'],
|
||||
})
|
||||
: this.subspaceProductAllocationRepository.findOne({
|
||||
: await this.subspaceProductAllocationRepository.findOne({
|
||||
where: {
|
||||
subspace: { uuid: subspace.uuid },
|
||||
product: { uuid: product.uuid },
|
||||
product: { uuid: allocationData.product },
|
||||
tag: { uuid: allocationData.tag },
|
||||
},
|
||||
relations: ['tags'],
|
||||
});
|
||||
return !!allocation;
|
||||
}
|
||||
private async saveAllocation(
|
||||
allocation: SubspaceProductAllocationEntity,
|
||||
@ -535,34 +343,9 @@ export class SubspaceProductAllocationService {
|
||||
}
|
||||
async clearAllAllocations(subspaceUuids: string[], queryRunner: QueryRunner) {
|
||||
try {
|
||||
const allocationUuids = await queryRunner.manager
|
||||
.createQueryBuilder(SubspaceProductAllocationEntity, 'allocation')
|
||||
.select('allocation.uuid')
|
||||
.where('allocation.subspace_uuid IN (:...subspaceUuids)', {
|
||||
subspaceUuids,
|
||||
})
|
||||
.getRawMany()
|
||||
.then((results) => results.map((r) => r.allocation_uuid));
|
||||
|
||||
if (allocationUuids.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from('subspace_product_tags')
|
||||
.where('subspace_product_allocation_uuid IN (:...allocationUuids)', {
|
||||
allocationUuids,
|
||||
})
|
||||
.execute();
|
||||
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(SubspaceProductAllocationEntity)
|
||||
.where('subspace_uuid IN (:...subspaceUuids)', { subspaceUuids })
|
||||
.execute();
|
||||
await queryRunner.manager.delete(SubspaceProductAllocationEntity, {
|
||||
subspace: { uuid: In(subspaceUuids) },
|
||||
});
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error instanceof HttpException
|
||||
|
||||
@ -1,32 +1,33 @@
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { PageResponse } from '@app/common/dto/pagination.response.dto';
|
||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||
import {
|
||||
TypeORMCustomModel,
|
||||
TypeORMCustomModelFindAllQuery,
|
||||
} from '@app/common/models/typeOrmCustom.model';
|
||||
import { SubspaceDto } from '@app/common/modules/space/dtos';
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { In, Not, QueryRunner } from 'typeorm';
|
||||
import {
|
||||
AddSubspaceDto,
|
||||
GetSpaceParam,
|
||||
GetSubSpaceParam,
|
||||
ModifySubspaceDto,
|
||||
} from '../../dtos';
|
||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||
import {
|
||||
TypeORMCustomModel,
|
||||
TypeORMCustomModelFindAllQuery,
|
||||
} from '@app/common/models/typeOrmCustom.model';
|
||||
import { PageResponse } from '@app/common/dto/pagination.response.dto';
|
||||
import { SubspaceDto } from '@app/common/modules/space/dtos';
|
||||
import { In, QueryRunner } from 'typeorm';
|
||||
|
||||
import { SubspaceModelEntity } from '@app/common/modules/space-model';
|
||||
import { ValidationService } from '../space-validation.service';
|
||||
import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
|
||||
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
||||
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';
|
||||
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 { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
|
||||
import {
|
||||
AllocationsOwnerType,
|
||||
CreateAllocationsDto,
|
||||
} from 'src/space/dtos/create-allocations.dto';
|
||||
import { TagService as NewTagService } from 'src/tags/services/tags.service';
|
||||
import { ValidationService } from '../space-validation.service';
|
||||
import { SubspaceDeviceService } from './subspace-device.service';
|
||||
import { SubspaceProductAllocationService } from './subspace-product-allocation.service';
|
||||
|
||||
@Injectable()
|
||||
export class SubSpaceService {
|
||||
@ -55,9 +56,9 @@ export class SubSpaceService {
|
||||
);
|
||||
|
||||
const subspaces = subspaceData.map((data) =>
|
||||
queryRunner.manager.create(this.subspaceRepository.target, data),
|
||||
queryRunner.manager.create(SubspaceEntity, data),
|
||||
);
|
||||
return await queryRunner.manager.save(subspaces);
|
||||
return queryRunner.manager.save(subspaces);
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
`An unexpected error occurred while creating subspaces. ${error}`,
|
||||
@ -91,42 +92,30 @@ export class SubSpaceService {
|
||||
addSubspaceDtos: AddSubspaceDto[],
|
||||
space: SpaceEntity,
|
||||
queryRunner: QueryRunner,
|
||||
otherTags?: ProcessTagDto[],
|
||||
projectUuid?: string,
|
||||
): Promise<SubspaceEntity[]> {
|
||||
try {
|
||||
this.checkForDuplicateNames(
|
||||
addSubspaceDtos.map(({ subspaceName }) => subspaceName),
|
||||
const createdSubspaces = await this.createSubspaces(
|
||||
addSubspaceDtos.map((dto) => ({
|
||||
subspaceName: dto.subspaceName,
|
||||
space,
|
||||
})),
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
const subspaceData = addSubspaceDtos.map((dto) => ({
|
||||
subspaceName: dto.subspaceName,
|
||||
space,
|
||||
}));
|
||||
|
||||
const subspaces = await this.createSubspaces(subspaceData, queryRunner);
|
||||
await Promise.all(
|
||||
addSubspaceDtos.map(async (dto, index) => {
|
||||
const subspace = subspaces[index];
|
||||
|
||||
const allTags = [...(dto.tags || []), ...(otherTags || [])];
|
||||
|
||||
if (allTags.length) {
|
||||
const processedTags = await this.newTagService.processTags(
|
||||
allTags,
|
||||
projectUuid,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
await this.subspaceProductAllocationService.createSubspaceProductAllocations(
|
||||
subspace,
|
||||
processedTags,
|
||||
queryRunner,
|
||||
);
|
||||
}
|
||||
addSubspaceDtos.map(async ({ tags }, index) => {
|
||||
// map the dto to the corresponding subspace
|
||||
const subspace = createdSubspaces[index];
|
||||
await this.createAllocations({
|
||||
projectUuid,
|
||||
queryRunner,
|
||||
tags,
|
||||
type: AllocationsOwnerType.SUBSPACE,
|
||||
subspace,
|
||||
});
|
||||
}),
|
||||
);
|
||||
return subspaces;
|
||||
return createdSubspaces;
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) {
|
||||
throw error;
|
||||
@ -312,65 +301,62 @@ export class SubSpaceService {
|
||||
deleteResults.push({ uuid: dto.subspaceUuid });
|
||||
}
|
||||
|
||||
return deleteResults;
|
||||
return deleteResults;
|
||||
} */
|
||||
|
||||
async modifySubSpace(
|
||||
async updateSubspaceInSpace(
|
||||
subspaceDtos: ModifySubspaceDto[],
|
||||
queryRunner: QueryRunner,
|
||||
space?: SpaceEntity,
|
||||
projectUuid?: string,
|
||||
spaceTagUpdateDtos?: ModifyTagDto[],
|
||||
space: SpaceEntity,
|
||||
projectUuid: string,
|
||||
) {
|
||||
if (!subspaceDtos || subspaceDtos.length === 0) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const addedSubspaces = [];
|
||||
const updatedSubspaces = [];
|
||||
|
||||
for (const subspace of subspaceDtos) {
|
||||
switch (subspace.action) {
|
||||
case ModifyAction.ADD:
|
||||
const addedSubspace = await this.handleAddAction(
|
||||
subspace,
|
||||
space,
|
||||
queryRunner,
|
||||
);
|
||||
if (addedSubspace) addedSubspaces.push(addedSubspace);
|
||||
|
||||
break;
|
||||
case ModifyAction.UPDATE:
|
||||
const updatedSubspace = await this.handleUpdateAction(
|
||||
subspace,
|
||||
queryRunner,
|
||||
);
|
||||
if (updatedSubspace) {
|
||||
updatedSubspaces.push(updatedSubspace);
|
||||
}
|
||||
break;
|
||||
case ModifyAction.DELETE:
|
||||
await this.handleDeleteAction(subspace, queryRunner);
|
||||
break;
|
||||
default:
|
||||
throw new HttpException(
|
||||
`Invalid action "${subspace.action}".`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const combinedSubspaces = [...addedSubspaces, ...updatedSubspaces].filter(
|
||||
(subspace) => subspace !== undefined,
|
||||
// disable subspaces that are not in the provided list & delte their allocations
|
||||
await queryRunner.manager.update(
|
||||
SubspaceEntity,
|
||||
{
|
||||
uuid: Not(
|
||||
In(subspaceDtos.filter(({ uuid }) => uuid).map(({ uuid }) => uuid)),
|
||||
),
|
||||
space: { uuid: space.uuid },
|
||||
},
|
||||
{
|
||||
disabled: true,
|
||||
},
|
||||
);
|
||||
|
||||
if (combinedSubspaces.length > 0) {
|
||||
await this.subspaceProductAllocationService.updateSubspaceProductAllocations(
|
||||
combinedSubspaces,
|
||||
await queryRunner.manager.delete(SubspaceProductAllocationEntity, {
|
||||
subspace: { uuid: Not(In(subspaceDtos.map((dto) => dto.uuid))) },
|
||||
});
|
||||
|
||||
// create or update subspaces provided in the list
|
||||
const newSubspaces = this.subspaceRepository.create(
|
||||
subspaceDtos.filter((dto) => !dto.uuid),
|
||||
);
|
||||
|
||||
const updatedSubspaces: SubspaceEntity[] = await queryRunner.manager.save(
|
||||
SubspaceEntity,
|
||||
[...newSubspaces, ...subspaceDtos.filter((dto) => dto.uuid)].map(
|
||||
(subspace) => ({ ...subspace, space }),
|
||||
),
|
||||
);
|
||||
|
||||
// create or update allocations for the subspaces
|
||||
if (updatedSubspaces.length > 0) {
|
||||
await this.subspaceProductAllocationService.updateSubspaceProductAllocationsV2(
|
||||
subspaceDtos.map((dto) => {
|
||||
if (!dto.uuid) {
|
||||
dto.uuid = updatedSubspaces.find(
|
||||
(subspace) => subspace.subspaceName === dto.subspaceName,
|
||||
)?.uuid;
|
||||
}
|
||||
return {
|
||||
tags: dto.tags || [],
|
||||
uuid: dto.uuid,
|
||||
};
|
||||
}),
|
||||
projectUuid,
|
||||
queryRunner,
|
||||
space,
|
||||
spaceTagUpdateDtos,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
@ -427,70 +413,10 @@ export class SubSpaceService {
|
||||
});
|
||||
}
|
||||
|
||||
private async handleAddAction(
|
||||
subspace: ModifySubspaceDto,
|
||||
space: SpaceEntity,
|
||||
queryRunner: QueryRunner,
|
||||
): Promise<SubspaceEntity> {
|
||||
const createTagDtos: ProcessTagDto[] =
|
||||
subspace.tags?.map((tag) => ({
|
||||
name: tag.name as string,
|
||||
uuid: tag.tagUuid,
|
||||
productUuid: tag.productUuid as string,
|
||||
})) || [];
|
||||
const subSpace = await this.createSubspacesFromDto(
|
||||
[{ subspaceName: subspace.subspaceName, tags: createTagDtos }],
|
||||
space,
|
||||
queryRunner,
|
||||
);
|
||||
return subSpace[0];
|
||||
}
|
||||
|
||||
private async handleUpdateAction(
|
||||
modifyDto: ModifySubspaceDto,
|
||||
queryRunner: QueryRunner,
|
||||
): Promise<SubspaceEntity> {
|
||||
const subspace = await this.findOne(modifyDto.uuid);
|
||||
const updatedSubspace = await this.update(
|
||||
queryRunner,
|
||||
subspace,
|
||||
modifyDto.subspaceName,
|
||||
);
|
||||
return updatedSubspace;
|
||||
}
|
||||
|
||||
async update(
|
||||
queryRunner: QueryRunner,
|
||||
subspace: SubspaceEntity,
|
||||
subspaceName?: string,
|
||||
) {
|
||||
return await this.updateSubspaceName(queryRunner, subspace, subspaceName);
|
||||
}
|
||||
|
||||
async handleDeleteAction(
|
||||
modifyDto: ModifySubspaceDto,
|
||||
queryRunner: QueryRunner,
|
||||
): Promise<void> {
|
||||
const subspace = await this.findOne(modifyDto.uuid);
|
||||
|
||||
await queryRunner.manager.update(
|
||||
this.subspaceRepository.target,
|
||||
{ uuid: subspace.uuid },
|
||||
{ disabled: true },
|
||||
);
|
||||
|
||||
if (subspace.devices.length > 0) {
|
||||
await this.deviceService.deleteSubspaceDevices(
|
||||
subspace.devices,
|
||||
queryRunner,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async findOne(subspaceUuid: string): Promise<SubspaceEntity> {
|
||||
const subspace = await this.subspaceRepository.findOne({
|
||||
where: { uuid: subspaceUuid, disabled: false },
|
||||
relations: ['tags', 'space', 'devices', 'tags.product', 'tags.device'],
|
||||
relations: ['space', 'devices'],
|
||||
});
|
||||
if (!subspace) {
|
||||
throw new HttpException(
|
||||
@ -501,36 +427,6 @@ export class SubSpaceService {
|
||||
return subspace;
|
||||
}
|
||||
|
||||
async updateSubspaceName(
|
||||
queryRunner: QueryRunner,
|
||||
subSpace: SubspaceEntity,
|
||||
subspaceName?: string,
|
||||
): Promise<SubspaceEntity> {
|
||||
if (subspaceName) {
|
||||
subSpace.subspaceName = subspaceName;
|
||||
return await queryRunner.manager.save(subSpace);
|
||||
}
|
||||
return subSpace;
|
||||
}
|
||||
|
||||
private async checkForDuplicateNames(names: string[]): Promise<void> {
|
||||
const seenNames = new Set<string>();
|
||||
const duplicateNames = new Set<string>();
|
||||
|
||||
for (const name of names) {
|
||||
if (!seenNames.add(name)) {
|
||||
duplicateNames.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (duplicateNames.size > 0) {
|
||||
throw new HttpException(
|
||||
`Duplicate subspace names found: ${[...duplicateNames].join(', ')}`,
|
||||
HttpStatus.CONFLICT,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async checkExistingNamesInSpace(
|
||||
names: string[],
|
||||
space: SpaceEntity,
|
||||
@ -557,16 +453,6 @@ export class SubSpaceService {
|
||||
}
|
||||
}
|
||||
|
||||
private async validateName(
|
||||
names: string[],
|
||||
space: SpaceEntity,
|
||||
): Promise<void> {
|
||||
await this.checkForDuplicateNames(names);
|
||||
await this.checkExistingNamesInSpace(names, space);
|
||||
}
|
||||
extractTagsFromSubspace(addSubspaceDto: AddSubspaceDto[]): ProcessTagDto[] {
|
||||
return addSubspaceDto.flatMap((subspace) => subspace.tags || []);
|
||||
}
|
||||
async clearSubspaces(subspaceUuids: string[], queryRunner: QueryRunner) {
|
||||
try {
|
||||
await queryRunner.manager.update(
|
||||
@ -590,4 +476,40 @@ export class SubSpaceService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async createAllocations(dto: CreateAllocationsDto): Promise<void> {
|
||||
const { projectUuid, queryRunner, tags, type } = dto;
|
||||
|
||||
const allocationsData = await this.newTagService.processTags(
|
||||
tags,
|
||||
projectUuid,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
// Create a mapping of created tags by UUID and name for quick lookup
|
||||
const createdTagsByUUID = new Map(allocationsData.map((t) => [t.uuid, t]));
|
||||
const createdTagsByName = new Map(allocationsData.map((t) => [t.name, t]));
|
||||
|
||||
// Create the product-tag mapping based on the processed tags
|
||||
const productTagMapping = tags.map(({ uuid, name, productUuid }) => {
|
||||
const inputTag = uuid
|
||||
? createdTagsByUUID.get(uuid)
|
||||
: createdTagsByName.get(name);
|
||||
return {
|
||||
tag: inputTag?.uuid,
|
||||
product: productUuid,
|
||||
};
|
||||
});
|
||||
|
||||
switch (type) {
|
||||
case AllocationsOwnerType.SUBSPACE: {
|
||||
await this.subspaceProductAllocationService.createProductAllocations(
|
||||
dto.subspace,
|
||||
productTagMapping,
|
||||
queryRunner,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,597 +1,8 @@
|
||||
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
||||
import { TagRepository } from '@app/common/modules/space';
|
||||
import { TagModel } from '@app/common/modules/space-model';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||
import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity';
|
||||
import { TagEntity } from '@app/common/modules/space/entities/tag.entity';
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { ProductService } from 'src/product/services';
|
||||
import { ModifySubspaceDto } from 'src/space/dtos';
|
||||
import { ModifyTagDto } from 'src/space/dtos/tag/modify-tag.dto';
|
||||
import { ProcessTagDto } from 'src/tags/dtos';
|
||||
import { QueryRunner } from 'typeorm';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
// todo: find out why we need to import this
|
||||
// in community module in order for the whole system to work
|
||||
@Injectable()
|
||||
export class TagService {
|
||||
constructor(
|
||||
private readonly tagRepository: TagRepository,
|
||||
private readonly productService: ProductService,
|
||||
) {}
|
||||
|
||||
async createTags(
|
||||
tags: ProcessTagDto[],
|
||||
queryRunner: QueryRunner,
|
||||
space?: SpaceEntity,
|
||||
subspace?: SubspaceEntity,
|
||||
additionalTags?: ProcessTagDto[],
|
||||
tagsToDelete?: ModifyTagDto[],
|
||||
): Promise<TagEntity[]> {
|
||||
this.validateTagsInput(tags);
|
||||
|
||||
const combinedTags = this.combineTags(tags, additionalTags);
|
||||
this.ensureNoDuplicateTags(combinedTags);
|
||||
|
||||
const tagEntitiesToCreate = tags.filter((tagDto) => !tagDto.uuid);
|
||||
const tagEntitiesToUpdate = tags.filter((tagDto) => !!tagDto.uuid);
|
||||
|
||||
try {
|
||||
const createdTags = await this.bulkSaveTags(
|
||||
tagEntitiesToCreate,
|
||||
queryRunner,
|
||||
space,
|
||||
subspace,
|
||||
tagsToDelete,
|
||||
);
|
||||
const updatedTags = await this.moveTags(
|
||||
tagEntitiesToUpdate,
|
||||
queryRunner,
|
||||
space,
|
||||
subspace,
|
||||
);
|
||||
|
||||
return [...createdTags, ...updatedTags];
|
||||
} catch (error) {
|
||||
throw this.handleUnexpectedError('Failed to save tags', error);
|
||||
}
|
||||
}
|
||||
|
||||
async bulkSaveTags(
|
||||
tags: ProcessTagDto[],
|
||||
queryRunner: QueryRunner,
|
||||
space?: SpaceEntity,
|
||||
subspace?: SubspaceEntity,
|
||||
tagsToDelete?: ModifyTagDto[],
|
||||
): Promise<TagEntity[]> {
|
||||
if (!tags.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const tagEntities = await Promise.all(
|
||||
tags.map((tagDto) =>
|
||||
this.prepareTagEntity(
|
||||
tagDto,
|
||||
queryRunner,
|
||||
space,
|
||||
subspace,
|
||||
tagsToDelete,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
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: ${error.message}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async moveTags(
|
||||
tags: ProcessTagDto[],
|
||||
queryRunner: QueryRunner,
|
||||
space?: SpaceEntity,
|
||||
subspace?: SubspaceEntity,
|
||||
): Promise<TagEntity[]> {
|
||||
if (!tags.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
return await Promise.all(
|
||||
tags.map(async (tagDto) => {
|
||||
try {
|
||||
const tag = await this.getTagByUuid(tagDto.uuid);
|
||||
if (!tag) {
|
||||
throw new HttpException(
|
||||
`Tag with UUID ${tagDto.uuid} not found.`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
if (subspace && subspace.space) {
|
||||
await queryRunner.manager.update(
|
||||
this.tagRepository.target,
|
||||
{ uuid: tag.uuid },
|
||||
{ subspace },
|
||||
);
|
||||
tag.subspace = subspace;
|
||||
}
|
||||
|
||||
if (!subspace && space) {
|
||||
await queryRunner.manager.update(
|
||||
this.tagRepository.target,
|
||||
{ uuid: tag.uuid },
|
||||
{ subspace: null },
|
||||
);
|
||||
tag.subspace = null;
|
||||
}
|
||||
|
||||
return tag;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
`Failed to move tags due to an unexpected error: ${error.message}`,
|
||||
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(
|
||||
tag: ModifyTagDto,
|
||||
queryRunner: QueryRunner,
|
||||
space?: SpaceEntity,
|
||||
subspace?: SubspaceEntity,
|
||||
): Promise<TagEntity> {
|
||||
try {
|
||||
const existingTag = await this.getTagByUuid(tag.tagUuid);
|
||||
|
||||
const contextSpace = space ?? subspace?.space;
|
||||
|
||||
if (contextSpace && tag.name !== existingTag.tag) {
|
||||
await this.checkTagReuse(
|
||||
tag.name,
|
||||
existingTag.product.uuid,
|
||||
contextSpace,
|
||||
);
|
||||
}
|
||||
|
||||
return await queryRunner.manager.save(
|
||||
Object.assign(existingTag, { tag: tag.name }),
|
||||
);
|
||||
} catch (error) {
|
||||
throw this.handleUnexpectedError('Failed to update tags', error);
|
||||
}
|
||||
}
|
||||
|
||||
async updateTagsFromModel(
|
||||
model: TagModel,
|
||||
queryRunner: QueryRunner,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const tags = await this.tagRepository.find({
|
||||
where: {
|
||||
model: {
|
||||
uuid: model.uuid,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!tags.length) return;
|
||||
|
||||
await queryRunner.manager.update(
|
||||
this.tagRepository.target,
|
||||
{ model: { uuid: model.uuid } },
|
||||
{ tag: model.tag },
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw new HttpException(
|
||||
`Failed to update tags for model with UUID: ${model.uuid}. Reason: ${error.message}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteTags(tagUuids: string[], queryRunner: QueryRunner) {
|
||||
if (!tagUuids?.length) return;
|
||||
|
||||
try {
|
||||
await Promise.all(
|
||||
tagUuids.map((id) =>
|
||||
queryRunner.manager.update(
|
||||
this.tagRepository.target,
|
||||
{ uuid: id },
|
||||
{ disabled: true, device: null },
|
||||
),
|
||||
),
|
||||
);
|
||||
return { message: 'Tags deleted successfully', tagUuids };
|
||||
} catch (error) {
|
||||
throw this.handleUnexpectedError('Failed to delete tags', error);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteTagFromModel(modelUuid: string, queryRunner: QueryRunner) {
|
||||
try {
|
||||
const tags = await this.tagRepository.find({
|
||||
where: {
|
||||
model: {
|
||||
uuid: modelUuid,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!tags.length) return;
|
||||
|
||||
await queryRunner.manager.update(
|
||||
this.tagRepository.target,
|
||||
{ model: { uuid: modelUuid } },
|
||||
{ disabled: true, device: null },
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw new HttpException(
|
||||
`Failed to update tags for model with UUID: ${modelUuid}. Reason: ${error.message}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async modifyTags(
|
||||
tags: ModifyTagDto[],
|
||||
queryRunner: QueryRunner,
|
||||
space?: SpaceEntity,
|
||||
subspace?: SubspaceEntity,
|
||||
): Promise<void> {
|
||||
if (!tags?.length) return;
|
||||
|
||||
try {
|
||||
const tagsToDelete = tags.filter(
|
||||
(tag) => tag.action === ModifyAction.DELETE,
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
tags.map(async (tag) => {
|
||||
switch (tag.action) {
|
||||
case ModifyAction.ADD:
|
||||
await this.createTags(
|
||||
[
|
||||
{
|
||||
name: tag.name,
|
||||
productUuid: tag.productUuid,
|
||||
uuid: tag.tagUuid,
|
||||
},
|
||||
],
|
||||
queryRunner,
|
||||
space,
|
||||
subspace,
|
||||
null,
|
||||
tagsToDelete,
|
||||
);
|
||||
break;
|
||||
case ModifyAction.UPDATE:
|
||||
await this.updateTag(tag, queryRunner, space, subspace);
|
||||
break;
|
||||
case ModifyAction.DELETE:
|
||||
await this.deleteTags([tag.tagUuid], 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);
|
||||
}
|
||||
}
|
||||
|
||||
private findDuplicateTags(tags: ProcessTagDto[]): string[] {
|
||||
const seen = new Map<string, boolean>();
|
||||
const duplicates: string[] = [];
|
||||
|
||||
tags.forEach((tagDto) => {
|
||||
const key = `${tagDto.productUuid}-${tagDto.name}`;
|
||||
if (seen.has(key)) {
|
||||
duplicates.push(`${tagDto.name} for Product: ${tagDto.productUuid}`);
|
||||
} else {
|
||||
seen.set(key, true);
|
||||
}
|
||||
});
|
||||
|
||||
return duplicates;
|
||||
}
|
||||
|
||||
private async checkTagReuse(
|
||||
tag: string,
|
||||
productUuid: string,
|
||||
space: SpaceEntity,
|
||||
tagsToDelete?: ModifyTagDto[],
|
||||
): Promise<void> {
|
||||
const { uuid: spaceUuid } = space;
|
||||
|
||||
const tagExists = await this.tagRepository.find({
|
||||
where: [
|
||||
{
|
||||
tag,
|
||||
product: { uuid: productUuid },
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
tag,
|
||||
subspace: { space: { uuid: spaceUuid } },
|
||||
product: { uuid: productUuid },
|
||||
disabled: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const filteredTagExists = tagExists.filter(
|
||||
(existingTag) =>
|
||||
!tagsToDelete?.some(
|
||||
(deleteTag) => deleteTag.tagUuid === existingTag.uuid,
|
||||
),
|
||||
);
|
||||
|
||||
if (filteredTagExists.length > 0) {
|
||||
throw new HttpException(`Tag can't be reused`, HttpStatus.CONFLICT);
|
||||
}
|
||||
}
|
||||
|
||||
private async prepareTagEntity(
|
||||
tagDto: ProcessTagDto,
|
||||
queryRunner: QueryRunner,
|
||||
space?: SpaceEntity,
|
||||
subspace?: SubspaceEntity,
|
||||
tagsToDelete?: ModifyTagDto[],
|
||||
): Promise<TagEntity> {
|
||||
try {
|
||||
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.name,
|
||||
tagDto.productUuid,
|
||||
space,
|
||||
tagsToDelete,
|
||||
);
|
||||
} else if (subspace && subspace.space) {
|
||||
await this.checkTagReuse(
|
||||
tagDto.name,
|
||||
tagDto.productUuid,
|
||||
subspace.space,
|
||||
);
|
||||
} else {
|
||||
throw new HttpException(
|
||||
`Invalid subspace or space provided.`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
return queryRunner.manager.create(TagEntity, {
|
||||
tag: tagDto.name,
|
||||
product: product.data,
|
||||
space: space,
|
||||
subspace: subspace,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) {
|
||||
throw error;
|
||||
}
|
||||
throw new HttpException(
|
||||
`An error occurred while preparing the tag entity: ${error.message}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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: ProcessTagDto[],
|
||||
additionalTags?: ProcessTagDto[],
|
||||
): ProcessTagDto[] {
|
||||
return additionalTags ? [...primaryTags, ...additionalTags] : primaryTags;
|
||||
}
|
||||
|
||||
private ensureNoDuplicateTags(tags: ProcessTagDto[]): void {
|
||||
const duplicates = this.findDuplicateTags(tags);
|
||||
|
||||
if (duplicates.length > 0) {
|
||||
throw new HttpException(
|
||||
`Duplicate tags found: ${duplicates.join(', ')}`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private validateTagsInput(tags: ProcessTagDto[]): void {
|
||||
if (!tags?.length) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
getSubspaceTagsToBeAdded(
|
||||
spaceTags?: ModifyTagDto[],
|
||||
subspaceModels?: ModifySubspaceDto[],
|
||||
): ModifyTagDto[] {
|
||||
if (!subspaceModels || subspaceModels.length === 0) {
|
||||
return spaceTags;
|
||||
}
|
||||
|
||||
const spaceTagsToDelete = spaceTags?.filter(
|
||||
(tag) => tag.action === 'delete',
|
||||
);
|
||||
|
||||
const tagsToAdd = subspaceModels.flatMap(
|
||||
(subspace) => subspace.tags?.filter((tag) => tag.action === 'add') || [],
|
||||
);
|
||||
|
||||
const commonTagUuids = new Set(
|
||||
tagsToAdd
|
||||
.filter((tagToAdd) =>
|
||||
spaceTagsToDelete.some(
|
||||
(tagToDelete) => tagToAdd.tagUuid === tagToDelete.tagUuid,
|
||||
),
|
||||
)
|
||||
.map((tag) => tag.tagUuid),
|
||||
);
|
||||
|
||||
const remainingTags = spaceTags.filter(
|
||||
(tag) => !commonTagUuids.has(tag.tagUuid), // Exclude tags in commonTagUuids
|
||||
);
|
||||
|
||||
return remainingTags;
|
||||
}
|
||||
|
||||
getModifiedSubspaces(
|
||||
spaceTags?: ModifyTagDto[],
|
||||
subspaceModels?: ModifySubspaceDto[],
|
||||
): ModifySubspaceDto[] {
|
||||
if (!subspaceModels || subspaceModels.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Extract tags marked for addition in spaceTags
|
||||
const spaceTagsToAdd = spaceTags?.filter((tag) => tag.action === 'add');
|
||||
|
||||
const subspaceTagsToAdd = subspaceModels.flatMap(
|
||||
(subspace) => subspace.tags?.filter((tag) => tag.action === 'add') || [],
|
||||
);
|
||||
|
||||
const subspaceTagsToDelete = subspaceModels.flatMap(
|
||||
(subspace) =>
|
||||
subspace.tags?.filter((tag) => tag.action === 'delete') || [],
|
||||
);
|
||||
|
||||
const subspaceTagsToDeleteUuids = new Set(
|
||||
subspaceTagsToDelete.map((tag) => tag.tagUuid),
|
||||
);
|
||||
|
||||
const commonTagsInSubspaces = subspaceTagsToAdd.filter((tag) =>
|
||||
subspaceTagsToDeleteUuids.has(tag.tagUuid),
|
||||
);
|
||||
|
||||
// Find UUIDs of tags that are common between spaceTagsToAdd and subspace tags marked for deletion
|
||||
const commonTagUuids = new Set(
|
||||
spaceTagsToAdd
|
||||
.flatMap((tagToAdd) =>
|
||||
subspaceModels.flatMap(
|
||||
(subspace) =>
|
||||
subspace.tags?.filter(
|
||||
(tagToDelete) =>
|
||||
tagToDelete.action === 'delete' &&
|
||||
tagToAdd.tagUuid === tagToDelete.tagUuid,
|
||||
) || [],
|
||||
),
|
||||
)
|
||||
.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.tagUuid)) || [],
|
||||
}));
|
||||
|
||||
modifiedSubspaces = modifiedSubspaces.map((subspace) => ({
|
||||
...subspace,
|
||||
tags:
|
||||
subspace.tags?.filter(
|
||||
(tag) =>
|
||||
!(
|
||||
tag.action === 'delete' &&
|
||||
commonTagsInSubspaces.some(
|
||||
(commonTag) => commonTag.tagUuid === tag.tagUuid,
|
||||
)
|
||||
),
|
||||
) || [],
|
||||
}));
|
||||
|
||||
return modifiedSubspaces;
|
||||
}
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
@ -1,14 +1,82 @@
|
||||
import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service';
|
||||
import { AqiDataService } from '@app/common/helper/services/aqi.data.service';
|
||||
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
||||
import { PowerClampService } from '@app/common/helper/services/power.clamp.service';
|
||||
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||
import {
|
||||
InviteUserRepository,
|
||||
InviteUserSpaceRepository,
|
||||
} from '@app/common/modules/Invite-user/repositiories';
|
||||
import { AutomationRepository } from '@app/common/modules/automation/repositories';
|
||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||
import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories';
|
||||
import {
|
||||
DeviceRepository,
|
||||
DeviceUserPermissionRepository,
|
||||
} from '@app/common/modules/device/repositories';
|
||||
import { PermissionTypeRepository } from '@app/common/modules/permission/repositories';
|
||||
import {
|
||||
PowerClampDailyRepository,
|
||||
PowerClampHourlyRepository,
|
||||
PowerClampMonthlyRepository,
|
||||
} from '@app/common/modules/power-clamp/repositories';
|
||||
import { ProductRepository } from '@app/common/modules/product/repositories';
|
||||
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
||||
import { RegionRepository } from '@app/common/modules/region/repositories';
|
||||
import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories';
|
||||
import {
|
||||
SceneIconRepository,
|
||||
SceneRepository,
|
||||
} from '@app/common/modules/scene/repositories';
|
||||
import {
|
||||
SpaceModelProductAllocationRepoitory,
|
||||
SpaceModelRepository,
|
||||
SubspaceModelProductAllocationRepoitory,
|
||||
SubspaceModelRepository,
|
||||
} from '@app/common/modules/space-model';
|
||||
import {
|
||||
InviteSpaceRepository,
|
||||
SpaceLinkRepository,
|
||||
SpaceProductAllocationRepository,
|
||||
SpaceRepository,
|
||||
} from '@app/common/modules/space/repositories';
|
||||
import {
|
||||
SubspaceProductAllocationRepository,
|
||||
SubspaceRepository,
|
||||
} from '@app/common/modules/space/repositories/subspace.repository';
|
||||
import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module';
|
||||
import { NewTagRepository } from '@app/common/modules/tag/repositories/tag-repository';
|
||||
import { TimeZoneRepository } from '@app/common/modules/timezone/repositories';
|
||||
import {
|
||||
UserRepository,
|
||||
UserSpaceRepository,
|
||||
} from '@app/common/modules/user/repositories';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { CqrsModule } from '@nestjs/cqrs';
|
||||
import { CommunityModule } from 'src/community/community.module';
|
||||
import { DeviceService } from 'src/device/services';
|
||||
import {
|
||||
SpaceModelService,
|
||||
SubSpaceModelService,
|
||||
} from 'src/space-model/services';
|
||||
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 { TagService as NewTagService } from 'src/tags/services/tags.service';
|
||||
import { UserDevicePermissionService } from 'src/user-device-permission/services';
|
||||
import { UserService, UserSpaceService } from 'src/users/services';
|
||||
import { SceneService } from '../scene/services';
|
||||
import {
|
||||
SpaceController,
|
||||
SpaceDeviceController,
|
||||
SpaceSceneController,
|
||||
SpaceUserController,
|
||||
SubSpaceController,
|
||||
SubSpaceDeviceController,
|
||||
SpaceSceneController,
|
||||
} from './controllers';
|
||||
import { SpaceValidationController } from './controllers/space-validation.controller';
|
||||
import { DisableSpaceHandler } from './handlers';
|
||||
import {
|
||||
SpaceDeviceService,
|
||||
SpaceLinkService,
|
||||
@ -17,81 +85,10 @@ import {
|
||||
SpaceUserService,
|
||||
SubspaceDeviceService,
|
||||
SubSpaceService,
|
||||
ValidationService,
|
||||
} from './services';
|
||||
import {
|
||||
SpaceRepository,
|
||||
SpaceLinkRepository,
|
||||
TagRepository,
|
||||
InviteSpaceRepository,
|
||||
SpaceProductAllocationRepository,
|
||||
} from '@app/common/modules/space/repositories';
|
||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||
import {
|
||||
UserRepository,
|
||||
UserSpaceRepository,
|
||||
} from '@app/common/modules/user/repositories';
|
||||
import {
|
||||
DeviceRepository,
|
||||
DeviceUserPermissionRepository,
|
||||
} from '@app/common/modules/device/repositories';
|
||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||
import { ProductRepository } from '@app/common/modules/product/repositories';
|
||||
import { SceneService } from '../scene/services';
|
||||
import {
|
||||
SceneIconRepository,
|
||||
SceneRepository,
|
||||
} from '@app/common/modules/scene/repositories';
|
||||
import { DeviceService } from 'src/device/services';
|
||||
import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service';
|
||||
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 {
|
||||
SpaceModelProductAllocationRepoitory,
|
||||
SpaceModelRepository,
|
||||
SubspaceModelProductAllocationRepoitory,
|
||||
SubspaceModelRepository,
|
||||
TagModelRepository,
|
||||
} from '@app/common/modules/space-model';
|
||||
import { CommunityModule } from 'src/community/community.module';
|
||||
import { ValidationService } from './services';
|
||||
import {
|
||||
SubspaceProductAllocationRepository,
|
||||
SubspaceRepository,
|
||||
} from '@app/common/modules/space/repositories/subspace.repository';
|
||||
import { TagService } from './services/tag';
|
||||
import {
|
||||
SpaceModelService,
|
||||
SubSpaceModelService,
|
||||
} from 'src/space-model/services';
|
||||
import { UserService, UserSpaceService } from 'src/users/services';
|
||||
import { UserDevicePermissionService } from 'src/user-device-permission/services';
|
||||
import { PermissionTypeRepository } from '@app/common/modules/permission/repositories';
|
||||
import { CqrsModule } from '@nestjs/cqrs';
|
||||
import { DisableSpaceHandler } from './handlers';
|
||||
import { RegionRepository } from '@app/common/modules/region/repositories';
|
||||
import { TimeZoneRepository } from '@app/common/modules/timezone/repositories';
|
||||
import {
|
||||
InviteUserRepository,
|
||||
InviteUserSpaceRepository,
|
||||
} from '@app/common/modules/Invite-user/repositiories';
|
||||
import { AutomationRepository } from '@app/common/modules/automation/repositories';
|
||||
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';
|
||||
import { SpaceValidationController } from './controllers/space-validation.controller';
|
||||
import { PowerClampService } from '@app/common/helper/services/power.clamp.service';
|
||||
import {
|
||||
PowerClampHourlyRepository,
|
||||
PowerClampDailyRepository,
|
||||
PowerClampMonthlyRepository,
|
||||
} from '@app/common/modules/power-clamp/repositories';
|
||||
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
||||
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
||||
import { AqiDataService } from '@app/common/helper/services/aqi.data.service';
|
||||
|
||||
export const CommandHandlers = [DisableSpaceHandler];
|
||||
|
||||
@ -108,11 +105,8 @@ export const CommandHandlers = [DisableSpaceHandler];
|
||||
],
|
||||
providers: [
|
||||
ValidationService,
|
||||
TagModelRepository,
|
||||
TagRepository,
|
||||
SpaceService,
|
||||
TuyaService,
|
||||
TagService,
|
||||
ProductRepository,
|
||||
SubSpaceService,
|
||||
SpaceDeviceService,
|
||||
@ -152,7 +146,6 @@ export const CommandHandlers = [DisableSpaceHandler];
|
||||
InviteUserRepository,
|
||||
InviteUserSpaceRepository,
|
||||
AutomationRepository,
|
||||
TagService,
|
||||
NewTagService,
|
||||
SpaceModelProductAllocationRepoitory,
|
||||
SubspaceModelProductAllocationRepoitory,
|
||||
|
||||
Reference in New Issue
Block a user