mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-07-09 22:57:24 +00:00
Merge branch 'dev'
This commit is contained in:
10
.github/workflows/main_syncrow(staging).yml
vendored
10
.github/workflows/main_syncrow(staging).yml
vendored
@ -11,7 +11,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: "ubuntu-latest"
|
||||
runs-on: 'ubuntu-latest'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@ -37,7 +37,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
environment:
|
||||
name: "staging"
|
||||
name: 'staging'
|
||||
url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
|
||||
|
||||
steps:
|
||||
@ -45,7 +45,7 @@ jobs:
|
||||
id: deploy-to-webapp
|
||||
uses: azure/webapps-deploy@v2
|
||||
with:
|
||||
app-name: "syncrow"
|
||||
slot-name: "staging"
|
||||
app-name: 'syncrow'
|
||||
slot-name: 'staging'
|
||||
publish-profile: ${{ secrets.AzureAppService_PublishProfile_44f7766441ec4796b74789e9761ef589 }}
|
||||
images: "syncrow.azurecr.io/${{ secrets.AzureAppService_ContainerUsername_47395803300340b49931ea82f6d80be3 }}/syncrow/backend:${{ github.sha }}"
|
||||
images: 'syncrow.azurecr.io/${{ secrets.AzureAppService_ContainerUsername_47395803300340b49931ea82f6d80be3 }}/syncrow/backend:${{ github.sha }}'
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,6 +3,9 @@
|
||||
/node_modules
|
||||
/build
|
||||
|
||||
#github
|
||||
/.github
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
@ -197,7 +197,16 @@ export class ControllerRoute {
|
||||
'retrieves all the spaces associated with a given community, organized into a hierarchical structure.';
|
||||
};
|
||||
};
|
||||
static SPACE_VALIDATION = class {
|
||||
public static readonly ROUTE = '/projects/:projectUuid/spaces';
|
||||
static ACTIONS = class {
|
||||
public static readonly VALIDATE_SPACE_WITH_DEVICES_OR_SUBSPACES_SUMMARY =
|
||||
'Check if a space has devices or sub-spaces';
|
||||
|
||||
public static readonly VALIDATE_SPACE_WITH_DEVICES_OR_SUBSPACES_DESCRIPTION =
|
||||
'Checks if a space has any devices or sub-spaces associated with it.';
|
||||
};
|
||||
};
|
||||
static SPACE_SCENE = class {
|
||||
public static readonly ROUTE =
|
||||
'/projects/:projectUuid/communities/:communityUuid/spaces/:spaceUuid/scenes';
|
||||
@ -292,7 +301,7 @@ export class ControllerRoute {
|
||||
public static readonly CREATE_SPACE_MODEL_DESCRIPTION =
|
||||
'This endpoint allows you to create a new space model within a specified project. A space model defines the structure of spaces, including subspaces, products, and product items, and is uniquely identifiable within the project.';
|
||||
|
||||
public static readonly GET_SPACE_MODEL_SUMMARY = 'Get a New Space Model';
|
||||
public static readonly GET_SPACE_MODEL_SUMMARY = 'Get a Space Model';
|
||||
public static readonly GET_SPACE_MODEL_DESCRIPTION =
|
||||
'Fetch a space model details';
|
||||
|
||||
@ -307,6 +316,11 @@ export class ControllerRoute {
|
||||
public static readonly DELETE_SPACE_MODEL_SUMMARY = 'Delete Space Model';
|
||||
public static readonly DELETE_SPACE_MODEL_DESCRIPTION =
|
||||
'This endpoint allows you to delete a specified Space Model within a project. Deleting a Space Model disables the model and all its associated subspaces and tags, ensuring they are no longer active but remain in the system for auditing.';
|
||||
|
||||
public static readonly LINK_SPACE_MODEL_SUMMARY =
|
||||
'Link a Space Model to spaces';
|
||||
public static readonly LINK_SPACE_MODEL_DESCRIPTION =
|
||||
'This endpoint allows you to link a specified Space Model within a project to multiple spaces. Linking a Space Model applies the model and all its associated subspaces and tags, to the spaces.';
|
||||
};
|
||||
};
|
||||
|
||||
@ -318,6 +332,19 @@ export class ControllerRoute {
|
||||
'Fetches a list of all products along with their associated device details';
|
||||
};
|
||||
};
|
||||
|
||||
static TAG = class {
|
||||
public static readonly ROUTE = '/projects/:projectUuid/tags';
|
||||
static ACTIONS = class {
|
||||
public static readonly CREATE_TAG_SUMMARY = 'Create a new tag';
|
||||
public static readonly CREATE_TAG_DESCRIPTION =
|
||||
'Creates a new tag and assigns it to a specific project and product.';
|
||||
public static readonly GET_TAGS_BY_PROJECT_SUMMARY =
|
||||
'Get tags by project';
|
||||
public static readonly GET_TAGS_BY_PROJECT_DESCRIPTION =
|
||||
'Retrieves a list of tags associated with a specific project.';
|
||||
};
|
||||
};
|
||||
static USER = class {
|
||||
public static readonly ROUTE = '/user';
|
||||
|
||||
|
@ -23,6 +23,7 @@ export const RolePermissions = {
|
||||
'SPACE_MODEL_VIEW',
|
||||
'SPACE_MODEL_UPDATE',
|
||||
'SPACE_MODEL_DELETE',
|
||||
'SPACE_MODEL_LINK',
|
||||
'SPACE_ASSIGN_USER_TO_SPACE',
|
||||
'SPACE_DELETE_USER_FROM_SPACE',
|
||||
'SUBSPACE_VIEW',
|
||||
@ -75,6 +76,7 @@ export const RolePermissions = {
|
||||
'SPACE_MODEL_VIEW',
|
||||
'SPACE_MODEL_UPDATE',
|
||||
'SPACE_MODEL_DELETE',
|
||||
'SPACE_MODEL_LINK',
|
||||
'SPACE_ASSIGN_USER_TO_SPACE',
|
||||
'SPACE_DELETE_USER_FROM_SPACE',
|
||||
'SUBSPACE_VIEW',
|
||||
|
@ -8,12 +8,7 @@ import { UserOtpEntity } from '../modules/user/entities';
|
||||
import { ProductEntity } from '../modules/product/entities';
|
||||
import { DeviceEntity } from '../modules/device/entities';
|
||||
import { PermissionTypeEntity } from '../modules/permission/entities';
|
||||
import {
|
||||
SpaceEntity,
|
||||
SpaceLinkEntity,
|
||||
SubspaceEntity,
|
||||
TagEntity,
|
||||
} from '../modules/space/entities';
|
||||
|
||||
import { UserSpaceEntity } from '../modules/user/entities';
|
||||
import { DeviceUserPermissionEntity } from '../modules/device/entities';
|
||||
import { RoleTypeEntity } from '../modules/role-type/entities';
|
||||
@ -31,6 +26,8 @@ import {
|
||||
SpaceModelEntity,
|
||||
SubspaceModelEntity,
|
||||
TagModel,
|
||||
SpaceModelProductAllocationEntity,
|
||||
SubspaceModelProductAllocationEntity,
|
||||
} from '../modules/space-model/entities';
|
||||
import {
|
||||
InviteUserEntity,
|
||||
@ -38,6 +35,13 @@ import {
|
||||
} from '../modules/Invite-user/entities';
|
||||
import { InviteSpaceEntity } from '../modules/space/entities/invite-space.entity';
|
||||
import { AutomationEntity } from '../modules/automation/entities';
|
||||
import { SpaceProductAllocationEntity } from '../modules/space/entities/space-product-allocation.entity';
|
||||
import { NewTagEntity } from '../modules/tag/entities/tag.entity';
|
||||
import { SpaceEntity } from '../modules/space/entities/space.entity';
|
||||
import { SpaceLinkEntity } from '../modules/space/entities/space-link.entity';
|
||||
import { SubspaceProductAllocationEntity } from '../modules/space/entities/subspace/subspace-product-allocation.entity';
|
||||
import { SubspaceEntity } from '../modules/space/entities/subspace/subspace.entity';
|
||||
import { TagEntity } from '../modules/space/entities/tag.entity';
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forRootAsync({
|
||||
@ -52,6 +56,7 @@ import { AutomationEntity } from '../modules/automation/entities';
|
||||
password: configService.get('DB_PASSWORD'),
|
||||
database: configService.get('DB_NAME'),
|
||||
entities: [
|
||||
NewTagEntity,
|
||||
ProjectEntity,
|
||||
UserEntity,
|
||||
UserSessionEntity,
|
||||
@ -84,6 +89,10 @@ import { AutomationEntity } from '../modules/automation/entities';
|
||||
InviteUserSpaceEntity,
|
||||
InviteSpaceEntity,
|
||||
AutomationEntity,
|
||||
SpaceModelProductAllocationEntity,
|
||||
SubspaceModelProductAllocationEntity,
|
||||
SpaceProductAllocationEntity,
|
||||
SubspaceProductAllocationEntity,
|
||||
],
|
||||
namingStrategy: new SnakeNamingStrategy(),
|
||||
synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))),
|
||||
|
11
libs/common/src/dto/project-param.dto.ts
Normal file
11
libs/common/src/dto/project-param.dto.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsUUID } from 'class-validator';
|
||||
|
||||
export class ProjectParam {
|
||||
@ApiProperty({
|
||||
description: 'UUID of the Project',
|
||||
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
|
||||
})
|
||||
@IsUUID()
|
||||
projectUuid: string;
|
||||
}
|
@ -10,7 +10,13 @@ import { GetDeviceDetailsFunctionsStatusInterface } from 'src/device/interfaces/
|
||||
import { TuyaContext } from '@tuya/tuya-connector-nodejs';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { firebaseDataBase } from '../../firebase.config';
|
||||
import { Database, DataSnapshot, get, ref, set } from 'firebase/database';
|
||||
import {
|
||||
Database,
|
||||
DataSnapshot,
|
||||
get,
|
||||
ref,
|
||||
runTransaction,
|
||||
} from 'firebase/database';
|
||||
import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories';
|
||||
@Injectable()
|
||||
export class DeviceStatusFirebaseService {
|
||||
@ -154,39 +160,48 @@ export class DeviceStatusFirebaseService {
|
||||
this.firebaseDb,
|
||||
`device-status/${addDeviceStatusDto.deviceUuid}`,
|
||||
);
|
||||
const snapshot: DataSnapshot = await get(dataRef);
|
||||
const existingData = snapshot.val() || {};
|
||||
|
||||
// Assign default values if fields are not present
|
||||
if (!existingData.deviceTuyaUuid) {
|
||||
existingData.deviceTuyaUuid = addDeviceStatusDto.deviceTuyaUuid;
|
||||
}
|
||||
if (!existingData.productUuid) {
|
||||
existingData.productUuid = addDeviceStatusDto.productUuid;
|
||||
}
|
||||
if (!existingData.productType) {
|
||||
existingData.productType = addDeviceStatusDto.productType;
|
||||
}
|
||||
if (!existingData.status) {
|
||||
existingData.status = [];
|
||||
}
|
||||
// Use a transaction to handle concurrent updates
|
||||
await runTransaction(dataRef, (existingData) => {
|
||||
if (!existingData) {
|
||||
existingData = {};
|
||||
}
|
||||
|
||||
// Create a map to track existing status codes
|
||||
const statusMap = new Map(
|
||||
existingData.status.map((item) => [item.code, item.value]),
|
||||
);
|
||||
// Assign default values if fields are not present
|
||||
if (!existingData.deviceTuyaUuid) {
|
||||
existingData.deviceTuyaUuid = addDeviceStatusDto.deviceTuyaUuid;
|
||||
}
|
||||
if (!existingData.productUuid) {
|
||||
existingData.productUuid = addDeviceStatusDto.productUuid;
|
||||
}
|
||||
if (!existingData.productType) {
|
||||
existingData.productType = addDeviceStatusDto.productType;
|
||||
}
|
||||
if (!existingData.status) {
|
||||
existingData.status = [];
|
||||
}
|
||||
|
||||
// Update or add status codes
|
||||
// Create a map to track existing status codes
|
||||
const statusMap = new Map(
|
||||
existingData.status.map((item) => [item.code, item.value]),
|
||||
);
|
||||
|
||||
for (const statusItem of addDeviceStatusDto.status) {
|
||||
statusMap.set(statusItem.code, statusItem.value);
|
||||
}
|
||||
// Update or add status codes
|
||||
|
||||
// Convert the map back to an array format
|
||||
existingData.status = Array.from(statusMap, ([code, value]) => ({
|
||||
code,
|
||||
value,
|
||||
}));
|
||||
for (const statusItem of addDeviceStatusDto.status) {
|
||||
statusMap.set(statusItem.code, statusItem.value);
|
||||
}
|
||||
|
||||
// Convert the map back to an array format
|
||||
existingData.status = Array.from(statusMap, ([code, value]) => ({
|
||||
code,
|
||||
value,
|
||||
}));
|
||||
|
||||
return existingData;
|
||||
});
|
||||
|
||||
// Save logs to your repository
|
||||
const newLogs = addDeviceStatusDto.log.properties.map((property) => {
|
||||
return this.deviceStatusLogRepository.create({
|
||||
deviceId: addDeviceStatusDto.deviceUuid,
|
||||
@ -200,10 +215,9 @@ export class DeviceStatusFirebaseService {
|
||||
});
|
||||
});
|
||||
await this.deviceStatusLogRepository.save(newLogs);
|
||||
// Save the updated data to Firebase
|
||||
await set(dataRef, existingData);
|
||||
|
||||
// Return the updated data
|
||||
return existingData;
|
||||
const snapshot: DataSnapshot = await get(dataRef);
|
||||
return snapshot.val();
|
||||
}
|
||||
}
|
||||
|
@ -12,10 +12,10 @@ import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { RoleTypeEntity } from '../../role-type/entities';
|
||||
import { UserStatusEnum } from '@app/common/constants/user-status.enum';
|
||||
import { UserEntity } from '../../user/entities';
|
||||
import { SpaceEntity } from '../../space/entities';
|
||||
import { RoleType } from '@app/common/constants/role.type.enum';
|
||||
import { InviteUserDto, InviteUserSpaceDto } from '../dtos';
|
||||
import { ProjectEntity } from '../../project/entities';
|
||||
import { SpaceEntity } from '../../space/entities/space.entity';
|
||||
|
||||
@Entity({ name: 'invite-user' })
|
||||
@Unique(['email', 'project'])
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm';
|
||||
import { AutomationDto } from '../dtos';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { SpaceEntity } from '../../space/entities';
|
||||
import { SpaceEntity } from '../../space/entities/space.entity';
|
||||
|
||||
@Entity({ name: 'automation' })
|
||||
export class AutomationEntity extends AbstractEntity<AutomationDto> {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { CommunityDto } from '../dtos';
|
||||
import { SpaceEntity } from '../../space/entities';
|
||||
import { ProjectEntity } from '../../project/entities';
|
||||
import { SpaceEntity } from '../../space/entities/space.entity';
|
||||
|
||||
@Entity({ name: 'community' })
|
||||
@Unique(['name'])
|
||||
|
@ -6,16 +6,17 @@ import {
|
||||
Unique,
|
||||
Index,
|
||||
JoinColumn,
|
||||
OneToOne,
|
||||
} from 'typeorm';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { DeviceDto, DeviceUserPermissionDto } from '../dtos/device.dto';
|
||||
import { SpaceEntity, SubspaceEntity, TagEntity } from '../../space/entities';
|
||||
import { ProductEntity } from '../../product/entities';
|
||||
import { UserEntity } from '../../user/entities';
|
||||
import { DeviceNotificationDto } from '../dtos';
|
||||
import { PermissionTypeEntity } from '../../permission/entities';
|
||||
import { SceneDeviceEntity } from '../../scene-device/entities';
|
||||
import { SpaceEntity } from '../../space/entities/space.entity';
|
||||
import { SubspaceEntity } from '../../space/entities/subspace/subspace.entity';
|
||||
import { NewTagEntity } from '../../tag';
|
||||
|
||||
@Entity({ name: 'device' })
|
||||
@Unique(['deviceTuyaUuid'])
|
||||
@ -75,10 +76,9 @@ export class DeviceEntity extends AbstractEntity<DeviceDto> {
|
||||
@OneToMany(() => SceneDeviceEntity, (sceneDevice) => sceneDevice.device, {})
|
||||
sceneDevices: SceneDeviceEntity[];
|
||||
|
||||
@OneToOne(() => TagEntity, (tag) => tag.device, {
|
||||
nullable: true,
|
||||
})
|
||||
tag: TagEntity;
|
||||
@OneToMany(() => NewTagEntity, (tag) => tag.devices)
|
||||
// @JoinTable({ name: 'device_tags' })
|
||||
public tag: NewTagEntity[];
|
||||
|
||||
constructor(partial: Partial<DeviceEntity>) {
|
||||
super();
|
||||
|
@ -4,6 +4,7 @@ import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { DeviceEntity } from '../../device/entities';
|
||||
import { TagModel } from '../../space-model';
|
||||
import { TagEntity } from '../../space/entities/tag.entity';
|
||||
import { NewTagEntity } from '../../tag/entities';
|
||||
@Entity({ name: 'product' })
|
||||
export class ProductEntity extends AbstractEntity<ProductDto> {
|
||||
@Column({
|
||||
@ -27,6 +28,9 @@ export class ProductEntity extends AbstractEntity<ProductDto> {
|
||||
})
|
||||
public prodType: string;
|
||||
|
||||
@OneToMany(() => NewTagEntity, (tag) => tag.product, { cascade: true })
|
||||
public newTags: NewTagEntity[];
|
||||
|
||||
@OneToMany(() => TagModel, (tag) => tag.product)
|
||||
tagModels: TagModel[];
|
||||
|
||||
|
@ -5,6 +5,7 @@ import { CommunityEntity } from '../../community/entities';
|
||||
import { SpaceModelEntity } from '../../space-model';
|
||||
import { UserEntity } from '../../user/entities';
|
||||
import { InviteUserEntity } from '../../Invite-user/entities';
|
||||
import { NewTagEntity } from '../../tag/entities';
|
||||
|
||||
@Entity({ name: 'project' })
|
||||
@Unique(['name'])
|
||||
@ -36,6 +37,9 @@ export class ProjectEntity extends AbstractEntity<ProjectDto> {
|
||||
@OneToMany(() => InviteUserEntity, (inviteUser) => inviteUser.project)
|
||||
public invitedUsers: InviteUserEntity[];
|
||||
|
||||
@OneToMany(() => NewTagEntity, (tag) => tag.project, { cascade: true })
|
||||
public tags: NewTagEntity[];
|
||||
|
||||
constructor(partial: Partial<ProjectEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
|
@ -3,7 +3,7 @@ import { SceneDto, SceneIconDto } from '../dtos';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { SceneIconType } from '@app/common/constants/secne-icon-type.enum';
|
||||
import { SceneDeviceEntity } from '../../scene-device/entities';
|
||||
import { SpaceEntity } from '../../space/entities';
|
||||
import { SpaceEntity } from '../../space/entities/space.entity';
|
||||
|
||||
// Define SceneIconEntity before SceneEntity
|
||||
@Entity({ name: 'scene-icon' })
|
||||
|
@ -0,0 +1,30 @@
|
||||
import { IsUUID, IsOptional, ValidateNested, IsArray } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { SpaceModelDto } from '../../space-model/dtos/space-model.dto';
|
||||
import { ProductDto } from '../../product/dtos/product.dto';
|
||||
import { NewTagDto } from '../../tag/dtos/tag.dto';
|
||||
import { SpaceProductAllocationDto } from '../../space/dtos/space-product-allocation.dto';
|
||||
|
||||
export class SpaceModelProductAllocationDto {
|
||||
@IsUUID()
|
||||
uuid: string;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => SpaceModelDto)
|
||||
spaceModel: SpaceModelDto;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => ProductDto)
|
||||
product: ProductDto;
|
||||
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => NewTagDto)
|
||||
tags: NewTagDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => SpaceProductAllocationDto)
|
||||
inheritedSpaceAllocations?: SpaceProductAllocationDto[];
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import { IsUUID, ValidateNested, IsArray } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { SubSpaceModelDto } from './subspace-model.dto';
|
||||
import { ProductDto } from '@app/common/modules/product/dtos';
|
||||
import { NewTagDto } from '@app/common/modules/tag/dtos';
|
||||
|
||||
export class SubspaceModelProductAllocationDto {
|
||||
@IsUUID()
|
||||
uuid: string;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => SubSpaceModelDto)
|
||||
subspaceModel: SubSpaceModelDto;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => ProductDto)
|
||||
product: ProductDto;
|
||||
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => NewTagDto)
|
||||
tags: NewTagDto[];
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
export * from './space-model.entity';
|
||||
export * from './subspace-model';
|
||||
export * from './tag-model.entity';
|
||||
export * from './space-model-product-allocation.entity';
|
||||
|
@ -0,0 +1,51 @@
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
ManyToOne,
|
||||
ManyToMany,
|
||||
JoinTable,
|
||||
OneToMany,
|
||||
} from 'typeorm';
|
||||
import { SpaceModelEntity } from './space-model.entity';
|
||||
import { NewTagEntity } from '../../tag/entities/tag.entity';
|
||||
import { ProductEntity } from '../../product/entities/product.entity';
|
||||
import { SpaceProductAllocationEntity } from '../../space/entities/space-product-allocation.entity';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
|
||||
@Entity({ name: 'space_model_product_allocation' })
|
||||
export class SpaceModelProductAllocationEntity extends AbstractEntity<SpaceModelProductAllocationEntity> {
|
||||
@Column({
|
||||
type: 'uuid',
|
||||
default: () => 'gen_random_uuid()',
|
||||
nullable: false,
|
||||
})
|
||||
public uuid: string;
|
||||
|
||||
@ManyToOne(
|
||||
() => SpaceModelEntity,
|
||||
(spaceModel) => spaceModel.productAllocations,
|
||||
{ nullable: false, onDelete: 'CASCADE' },
|
||||
)
|
||||
public spaceModel: SpaceModelEntity;
|
||||
|
||||
@ManyToOne(() => ProductEntity, { nullable: false, onDelete: 'CASCADE' })
|
||||
public product: ProductEntity;
|
||||
|
||||
@ManyToMany(() => NewTagEntity, { cascade: true, onDelete: 'CASCADE' })
|
||||
@JoinTable({ name: 'space_model_product_tags' })
|
||||
public tags: NewTagEntity[];
|
||||
|
||||
@OneToMany(
|
||||
() => SpaceProductAllocationEntity,
|
||||
(allocation) => allocation.inheritedFromModel,
|
||||
{
|
||||
cascade: true,
|
||||
},
|
||||
)
|
||||
public inheritedSpaceAllocations: SpaceProductAllocationEntity[];
|
||||
|
||||
constructor(partial: Partial<SpaceModelProductAllocationEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
}
|
||||
}
|
@ -3,8 +3,9 @@ import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { SpaceModelDto } from '../dtos';
|
||||
import { SubspaceModelEntity } from './subspace-model';
|
||||
import { ProjectEntity } from '../../project/entities';
|
||||
import { SpaceEntity } from '../../space/entities';
|
||||
import { TagModel } from './tag-model.entity';
|
||||
import { SpaceModelProductAllocationEntity } from './space-model-product-allocation.entity';
|
||||
import { SpaceEntity } from '../../space/entities/space.entity';
|
||||
|
||||
@Entity({ name: 'space-model' })
|
||||
export class SpaceModelEntity extends AbstractEntity<SpaceModelDto> {
|
||||
@ -51,6 +52,13 @@ export class SpaceModelEntity extends AbstractEntity<SpaceModelDto> {
|
||||
@OneToMany(() => TagModel, (tag) => tag.spaceModel)
|
||||
tags: TagModel[];
|
||||
|
||||
@OneToMany(
|
||||
() => SpaceModelProductAllocationEntity,
|
||||
(allocation) => allocation.spaceModel,
|
||||
{ cascade: true },
|
||||
)
|
||||
public productAllocations: SpaceModelProductAllocationEntity[];
|
||||
|
||||
constructor(partial: Partial<SpaceModelEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
|
@ -1,2 +1,2 @@
|
||||
export * from './subspace-model.entity';
|
||||
|
||||
export * from './subspace-model-product-allocation.entity';
|
||||
|
@ -0,0 +1,41 @@
|
||||
import { Entity, Column, ManyToOne, ManyToMany, JoinTable } from 'typeorm';
|
||||
import { SubspaceModelEntity } from './subspace-model.entity';
|
||||
import { ProductEntity } from '@app/common/modules/product/entities/product.entity';
|
||||
import { NewTagEntity } from '@app/common/modules/tag/entities/tag.entity';
|
||||
import { SubspaceModelProductAllocationDto } from '../../dtos/subspace-model/subspace-model-product-allocation.dto';
|
||||
import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity';
|
||||
|
||||
@Entity({ name: 'subspace_model_product_allocation' })
|
||||
export class SubspaceModelProductAllocationEntity extends AbstractEntity<SubspaceModelProductAllocationDto> {
|
||||
@Column({
|
||||
type: 'uuid',
|
||||
default: () => 'gen_random_uuid()',
|
||||
nullable: false,
|
||||
})
|
||||
public uuid: string;
|
||||
|
||||
@ManyToOne(
|
||||
() => SubspaceModelEntity,
|
||||
(subspaceModel) => subspaceModel.productAllocations,
|
||||
{
|
||||
nullable: false,
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
)
|
||||
public subspaceModel: SubspaceModelEntity;
|
||||
|
||||
@ManyToOne(() => ProductEntity, { nullable: false, onDelete: 'CASCADE' })
|
||||
public product: ProductEntity;
|
||||
|
||||
@ManyToMany(() => NewTagEntity, (tag) => tag.subspaceModelAllocations, {
|
||||
cascade: true,
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinTable({ name: 'subspace_model_product_tags' })
|
||||
public tags: NewTagEntity[];
|
||||
|
||||
constructor(partial: Partial<SubspaceModelProductAllocationEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
}
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity';
|
||||
import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm';
|
||||
import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
|
||||
import { SubSpaceModelDto } from '../../dtos';
|
||||
import { SpaceModelEntity } from '../space-model.entity';
|
||||
import { SubspaceEntity } from '@app/common/modules/space/entities';
|
||||
import { TagModel } from '../tag-model.entity';
|
||||
import { SubspaceModelProductAllocationEntity } from './subspace-model-product-allocation.entity';
|
||||
import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity';
|
||||
|
||||
@Entity({ name: 'subspace-model' })
|
||||
export class SubspaceModelEntity extends AbstractEntity<SubSpaceModelDto> {
|
||||
@ -32,7 +33,7 @@ export class SubspaceModelEntity extends AbstractEntity<SubSpaceModelDto> {
|
||||
@OneToMany(() => SubspaceEntity, (subspace) => subspace.subSpaceModel, {
|
||||
cascade: true,
|
||||
})
|
||||
public subspaceModel: SubspaceEntity[];
|
||||
public subspace: SubspaceEntity[];
|
||||
|
||||
@Column({
|
||||
nullable: false,
|
||||
@ -42,4 +43,11 @@ export class SubspaceModelEntity extends AbstractEntity<SubSpaceModelDto> {
|
||||
|
||||
@OneToMany(() => TagModel, (tag) => tag.subspaceModel)
|
||||
tags: TagModel[];
|
||||
|
||||
@OneToMany(
|
||||
() => SubspaceModelProductAllocationEntity,
|
||||
(allocation) => allocation.subspaceModel,
|
||||
{ cascade: true },
|
||||
)
|
||||
public productAllocations: SubspaceModelProductAllocationEntity[];
|
||||
}
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { SpaceModelEntity, SubspaceModelEntity, TagModel } from '../entities';
|
||||
import {
|
||||
SpaceModelEntity,
|
||||
SpaceModelProductAllocationEntity,
|
||||
SubspaceModelEntity,
|
||||
SubspaceModelProductAllocationEntity,
|
||||
TagModel,
|
||||
} from '../entities';
|
||||
|
||||
@Injectable()
|
||||
export class SpaceModelRepository extends Repository<SpaceModelEntity> {
|
||||
@ -21,3 +27,20 @@ export class TagModelRepository extends Repository<TagModel> {
|
||||
super(TagModel, dataSource.createEntityManager());
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SpaceModelProductAllocationRepoitory extends Repository<SpaceModelProductAllocationEntity> {
|
||||
constructor(private dataSource: DataSource) {
|
||||
super(SpaceModelProductAllocationEntity, dataSource.createEntityManager());
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SubspaceModelProductAllocationRepoitory extends Repository<SubspaceModelProductAllocationEntity> {
|
||||
constructor(private dataSource: DataSource) {
|
||||
super(
|
||||
SubspaceModelProductAllocationEntity,
|
||||
dataSource.createEntityManager(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
export * from './space.dto';
|
||||
export * from './subspace.dto';
|
||||
export * from './tag.dto';
|
||||
export * from './space-product-allocation.dto';
|
||||
|
@ -0,0 +1,24 @@
|
||||
import { IsArray, IsNotEmpty, IsString, ValidateNested } from 'class-validator';
|
||||
import { SpaceDto } from './space.dto';
|
||||
import { Type } from 'class-transformer';
|
||||
import { ProductDto } from '../../product/dtos';
|
||||
import { NewTagDto } from '../../tag/dtos';
|
||||
|
||||
export class SpaceProductAllocationDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public uuid: string;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => SpaceDto)
|
||||
public space: SpaceDto;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => ProductDto)
|
||||
product: ProductDto;
|
||||
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => NewTagDto)
|
||||
tags: NewTagDto[];
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import { IsArray, IsNotEmpty, IsString, ValidateNested } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { ProductDto } from '../../product/dtos';
|
||||
import { NewTagDto } from '../../tag/dtos';
|
||||
import { SubspaceDto } from './subspace.dto';
|
||||
|
||||
export class SubspaceProductAllocationDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public uuid: string;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => SubspaceDto)
|
||||
public subspace: SubspaceDto;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => ProductDto)
|
||||
product: ProductDto;
|
||||
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => NewTagDto)
|
||||
tags: NewTagDto[];
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
export * from './space.entity';
|
||||
export * from './subspace';
|
||||
export * from './space-link.entity';
|
||||
export * from './tag.entity';
|
@ -0,0 +1,41 @@
|
||||
import { Entity, Column, ManyToOne, ManyToMany, JoinTable } from 'typeorm';
|
||||
import { SpaceEntity } from './space.entity';
|
||||
import { SpaceModelProductAllocationEntity } from '../../space-model/entities/space-model-product-allocation.entity';
|
||||
import { ProductEntity } from '../../product/entities/product.entity';
|
||||
import { NewTagEntity } from '../../tag/entities/tag.entity';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { SpaceProductAllocationDto } from '../dtos/space-product-allocation.dto';
|
||||
|
||||
@Entity({ name: 'space_product_allocation' })
|
||||
export class SpaceProductAllocationEntity extends AbstractEntity<SpaceProductAllocationDto> {
|
||||
@Column({
|
||||
type: 'uuid',
|
||||
default: () => 'gen_random_uuid()',
|
||||
nullable: false,
|
||||
})
|
||||
public uuid: string;
|
||||
|
||||
@ManyToOne(() => SpaceEntity, (space) => space.productAllocations, {
|
||||
nullable: false,
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
public space: SpaceEntity;
|
||||
|
||||
@ManyToOne(() => SpaceModelProductAllocationEntity, {
|
||||
nullable: true,
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
public inheritedFromModel?: SpaceModelProductAllocationEntity;
|
||||
|
||||
@ManyToOne(() => ProductEntity, { nullable: false, onDelete: 'CASCADE' })
|
||||
public product: ProductEntity;
|
||||
|
||||
@ManyToMany(() => NewTagEntity)
|
||||
@JoinTable({ name: 'space_product_tags' })
|
||||
public tags: NewTagEntity[];
|
||||
|
||||
constructor(partial: Partial<SpaceProductAllocationEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
}
|
||||
}
|
@ -4,12 +4,13 @@ import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { UserSpaceEntity } from '../../user/entities';
|
||||
import { DeviceEntity } from '../../device/entities';
|
||||
import { CommunityEntity } from '../../community/entities';
|
||||
import { SubspaceEntity } from './subspace';
|
||||
import { SpaceLinkEntity } from './space-link.entity';
|
||||
import { SceneEntity } from '../../scene/entities';
|
||||
import { SpaceModelEntity } from '../../space-model';
|
||||
import { InviteUserSpaceEntity } from '../../Invite-user/entities';
|
||||
import { TagEntity } from './tag.entity';
|
||||
import { SpaceProductAllocationEntity } from './space-product-allocation.entity';
|
||||
import { SubspaceEntity } from './subspace/subspace.entity';
|
||||
|
||||
@Entity({ name: 'space' })
|
||||
export class SpaceEntity extends AbstractEntity<SpaceDto> {
|
||||
@ -105,6 +106,15 @@ export class SpaceEntity extends AbstractEntity<SpaceDto> {
|
||||
@OneToMany(() => TagEntity, (tag) => tag.space)
|
||||
tags: TagEntity[];
|
||||
|
||||
@OneToMany(
|
||||
() => SpaceProductAllocationEntity,
|
||||
(allocation) => allocation.space,
|
||||
{
|
||||
cascade: true,
|
||||
},
|
||||
)
|
||||
public productAllocations: SpaceProductAllocationEntity[];
|
||||
|
||||
constructor(partial: Partial<SpaceEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
|
@ -1 +0,0 @@
|
||||
export * from './subspace.entity';
|
@ -0,0 +1,49 @@
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
ManyToOne,
|
||||
ManyToMany,
|
||||
JoinTable,
|
||||
Unique,
|
||||
} from 'typeorm';
|
||||
import { SubspaceEntity } from './subspace.entity';
|
||||
import { ProductEntity } from '@app/common/modules/product/entities';
|
||||
import { SubspaceModelProductAllocationEntity } from '@app/common/modules/space-model';
|
||||
import { NewTagEntity } from '@app/common/modules/tag/entities/tag.entity';
|
||||
import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity';
|
||||
import { SubspaceProductAllocationDto } from '../../dtos/subspace-product-allocation.dto';
|
||||
|
||||
@Entity({ name: 'subspace_product_allocation' })
|
||||
@Unique(['subspace', 'product'])
|
||||
export class SubspaceProductAllocationEntity extends AbstractEntity<SubspaceProductAllocationDto> {
|
||||
@Column({
|
||||
type: 'uuid',
|
||||
default: () => 'gen_random_uuid()',
|
||||
nullable: false,
|
||||
})
|
||||
public uuid: string;
|
||||
|
||||
@ManyToOne(() => SubspaceEntity, (subspace) => subspace.productAllocations, {
|
||||
nullable: false,
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
public subspace: SubspaceEntity;
|
||||
|
||||
@ManyToOne(() => SubspaceModelProductAllocationEntity, {
|
||||
nullable: true,
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
public inheritedFromModel?: SubspaceModelProductAllocationEntity;
|
||||
|
||||
@ManyToOne(() => ProductEntity, { nullable: false, onDelete: 'CASCADE' })
|
||||
public product: ProductEntity;
|
||||
|
||||
@ManyToMany(() => NewTagEntity)
|
||||
@JoinTable({ name: 'subspace_product_tags' })
|
||||
public tags: NewTagEntity[];
|
||||
|
||||
constructor(partial: Partial<SubspaceProductAllocationEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
|
||||
import { SubspaceDto } from '../../dtos';
|
||||
import { SpaceEntity } from '../space.entity';
|
||||
import { TagEntity } from '../tag.entity';
|
||||
import { SubspaceProductAllocationEntity } from './subspace-product-allocation.entity';
|
||||
|
||||
@Entity({ name: 'subspace' })
|
||||
export class SubspaceEntity extends AbstractEntity<SubspaceDto> {
|
||||
@ -37,7 +38,7 @@ export class SubspaceEntity extends AbstractEntity<SubspaceDto> {
|
||||
})
|
||||
devices: DeviceEntity[];
|
||||
|
||||
@ManyToOne(() => SubspaceModelEntity, (subspace) => subspace.subspaceModel, {
|
||||
@ManyToOne(() => SubspaceModelEntity, (model) => model.subspace, {
|
||||
nullable: true,
|
||||
})
|
||||
subSpaceModel?: SubspaceModelEntity;
|
||||
@ -45,6 +46,13 @@ export class SubspaceEntity extends AbstractEntity<SubspaceDto> {
|
||||
@OneToMany(() => TagEntity, (tag) => tag.subspace)
|
||||
tags: TagEntity[];
|
||||
|
||||
@OneToMany(
|
||||
() => SubspaceProductAllocationEntity,
|
||||
(allocation) => allocation.subspace,
|
||||
{ cascade: true },
|
||||
)
|
||||
public productAllocations: SubspaceProductAllocationEntity[];
|
||||
|
||||
constructor(partial: Partial<SubspaceEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
|
@ -4,12 +4,12 @@ import { ProductEntity } from '../../product/entities';
|
||||
import { TagDto } from '../dtos';
|
||||
import { TagModel } from '../../space-model/entities/tag-model.entity';
|
||||
import { SpaceEntity } from './space.entity';
|
||||
import { SubspaceEntity } from './subspace';
|
||||
import { DeviceEntity } from '../../device/entities';
|
||||
import { SubspaceEntity } from './subspace/subspace.entity';
|
||||
|
||||
@Entity({ name: 'tag' })
|
||||
export class TagEntity extends AbstractEntity<TagDto> {
|
||||
@Column({ type: 'varchar', length: 255 })
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
tag: string;
|
||||
|
||||
@ManyToOne(() => TagModel, (model) => model.tags, {
|
||||
|
@ -1,4 +1,3 @@
|
||||
export * from './dtos';
|
||||
export * from './entities';
|
||||
export * from './repositories';
|
||||
export * from './space.repository.module';
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { SpaceEntity, SpaceLinkEntity, TagEntity } from '../entities';
|
||||
import { InviteSpaceEntity } from '../entities/invite-space.entity';
|
||||
import { SpaceLinkEntity } from '../entities/space-link.entity';
|
||||
import { SpaceEntity } from '../entities/space.entity';
|
||||
import { TagEntity } from '../entities/tag.entity';
|
||||
import { SpaceProductAllocationEntity } from '../entities/space-product-allocation.entity';
|
||||
|
||||
@Injectable()
|
||||
export class SpaceRepository extends Repository<SpaceEntity> {
|
||||
@ -30,3 +33,9 @@ export class InviteSpaceRepository extends Repository<InviteSpaceEntity> {
|
||||
super(InviteSpaceEntity, dataSource.createEntityManager());
|
||||
}
|
||||
}
|
||||
@Injectable()
|
||||
export class SpaceProductAllocationRepository extends Repository<SpaceProductAllocationEntity> {
|
||||
constructor(private dataSource: DataSource) {
|
||||
super(SpaceProductAllocationEntity, dataSource.createEntityManager());
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { SubspaceEntity } from '../entities';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { SubspaceEntity } from '../entities/subspace/subspace.entity';
|
||||
import { SubspaceProductAllocationEntity } from '../entities/subspace/subspace-product-allocation.entity';
|
||||
|
||||
@Injectable()
|
||||
export class SubspaceRepository extends Repository<SubspaceEntity> {
|
||||
@ -8,3 +9,9 @@ export class SubspaceRepository extends Repository<SubspaceEntity> {
|
||||
super(SubspaceEntity, dataSource.createEntityManager());
|
||||
}
|
||||
}
|
||||
@Injectable()
|
||||
export class SubspaceProductAllocationRepository extends Repository<SubspaceProductAllocationEntity> {
|
||||
constructor(private dataSource: DataSource) {
|
||||
super(SubspaceProductAllocationEntity, dataSource.createEntityManager());
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { SpaceEntity, SubspaceEntity, TagEntity } from './entities';
|
||||
|
||||
import { InviteSpaceEntity } from './entities/invite-space.entity';
|
||||
import { SpaceProductAllocationEntity } from './entities/space-product-allocation.entity';
|
||||
import { SpaceEntity } from './entities/space.entity';
|
||||
import { SubspaceProductAllocationEntity } from './entities/subspace/subspace-product-allocation.entity';
|
||||
import { SubspaceEntity } from './entities/subspace/subspace.entity';
|
||||
import { TagEntity } from './entities/tag.entity';
|
||||
|
||||
@Module({
|
||||
providers: [],
|
||||
@ -13,6 +18,8 @@ import { InviteSpaceEntity } from './entities/invite-space.entity';
|
||||
SubspaceEntity,
|
||||
TagEntity,
|
||||
InviteSpaceEntity,
|
||||
SpaceProductAllocationEntity,
|
||||
SubspaceProductAllocationEntity,
|
||||
]),
|
||||
],
|
||||
})
|
||||
|
1
libs/common/src/modules/tag/dtos/index.ts
Normal file
1
libs/common/src/modules/tag/dtos/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './tag.dto';
|
19
libs/common/src/modules/tag/dtos/tag.dto.ts
Normal file
19
libs/common/src/modules/tag/dtos/tag.dto.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { IsNotEmpty, IsUUID, IsString } from 'class-validator';
|
||||
|
||||
export class NewTagDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public uuid: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name: string;
|
||||
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
productId: string;
|
||||
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
projectId: string;
|
||||
}
|
1
libs/common/src/modules/tag/entities/index.ts
Normal file
1
libs/common/src/modules/tag/entities/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './tag.entity';
|
58
libs/common/src/modules/tag/entities/tag.entity.ts
Normal file
58
libs/common/src/modules/tag/entities/tag.entity.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { Entity, Column, ManyToOne, Unique, ManyToMany } from 'typeorm';
|
||||
import { ProductEntity } from '../../product/entities';
|
||||
import { ProjectEntity } from '../../project/entities';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { NewTagDto } from '../dtos/tag.dto';
|
||||
import { SpaceModelProductAllocationEntity } from '../../space-model/entities/space-model-product-allocation.entity';
|
||||
import { SubspaceModelProductAllocationEntity } from '../../space-model/entities/subspace-model/subspace-model-product-allocation.entity';
|
||||
import { DeviceEntity } from '../../device/entities/device.entity';
|
||||
|
||||
@Entity({ name: 'new_tag' })
|
||||
@Unique(['name', 'project'])
|
||||
export class NewTagEntity extends AbstractEntity<NewTagDto> {
|
||||
@Column({
|
||||
type: 'uuid',
|
||||
default: () => 'gen_random_uuid()',
|
||||
nullable: false,
|
||||
})
|
||||
public uuid: string;
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
length: 255,
|
||||
nullable: true,
|
||||
})
|
||||
name: string;
|
||||
|
||||
@ManyToOne(() => ProductEntity, (product) => product.newTags, {
|
||||
nullable: false,
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
public product: ProductEntity;
|
||||
|
||||
@ManyToOne(() => ProjectEntity, (project) => project.tags, {
|
||||
nullable: false,
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
public project: ProjectEntity;
|
||||
|
||||
@ManyToMany(
|
||||
() => SpaceModelProductAllocationEntity,
|
||||
(allocation) => allocation.tags,
|
||||
)
|
||||
public spaceModelAllocations: SpaceModelProductAllocationEntity[];
|
||||
|
||||
@ManyToMany(
|
||||
() => SubspaceModelProductAllocationEntity,
|
||||
(allocation) => allocation.tags,
|
||||
)
|
||||
public subspaceModelAllocations: SubspaceModelProductAllocationEntity[];
|
||||
|
||||
@ManyToOne(() => DeviceEntity, (device) => device.tag)
|
||||
public devices: DeviceEntity[];
|
||||
|
||||
constructor(partial: Partial<NewTagEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
}
|
||||
}
|
1
libs/common/src/modules/tag/index.ts
Normal file
1
libs/common/src/modules/tag/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './entities';
|
0
libs/common/src/modules/tag/repositories/index.ts
Normal file
0
libs/common/src/modules/tag/repositories/index.ts
Normal file
10
libs/common/src/modules/tag/repositories/tag-repository.ts
Normal file
10
libs/common/src/modules/tag/repositories/tag-repository.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { NewTagEntity } from '../entities';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
export class NewTagRepository extends Repository<NewTagEntity> {
|
||||
constructor(private dataSource: DataSource) {
|
||||
super(NewTagEntity, dataSource.createEntityManager());
|
||||
}
|
||||
}
|
11
libs/common/src/modules/tag/tag.repository.module.ts
Normal file
11
libs/common/src/modules/tag/tag.repository.module.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { NewTagEntity } from './entities/tag.entity';
|
||||
|
||||
@Module({
|
||||
providers: [],
|
||||
exports: [],
|
||||
controllers: [],
|
||||
imports: [TypeOrmModule.forFeature([NewTagEntity])],
|
||||
})
|
||||
export class NewTagRepositoryModule {}
|
@ -25,10 +25,10 @@ import { RegionEntity } from '../../region/entities';
|
||||
import { TimeZoneEntity } from '../../timezone/entities';
|
||||
import { OtpType } from '../../../../src/constants/otp-type.enum';
|
||||
import { RoleTypeEntity } from '../../role-type/entities';
|
||||
import { SpaceEntity } from '../../space/entities';
|
||||
import { VisitorPasswordEntity } from '../../visitor-password/entities';
|
||||
import { InviteUserEntity } from '../../Invite-user/entities';
|
||||
import { ProjectEntity } from '../../project/entities';
|
||||
import { SpaceEntity } from '../../space/entities/space.entity';
|
||||
|
||||
@Entity({ name: 'user' })
|
||||
export class UserEntity extends AbstractEntity<UserDto> {
|
||||
|
4850
package-lock.json
generated
4850
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -28,6 +28,7 @@ import { PermissionModule } from './permission/permission.module';
|
||||
import { RoleModule } from './role/role.module';
|
||||
import { TermsConditionsModule } from './terms-conditions/terms-conditions.module';
|
||||
import { PrivacyPolicyModule } from './privacy-policy/privacy-policy.module';
|
||||
import { TagModule } from './tags/tags.module';
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
@ -59,6 +60,7 @@ import { PrivacyPolicyModule } from './privacy-policy/privacy-policy.module';
|
||||
RoleModule,
|
||||
TermsConditionsModule,
|
||||
PrivacyPolicyModule,
|
||||
TagModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
|
@ -218,7 +218,7 @@ export class CommunityService {
|
||||
}
|
||||
}
|
||||
|
||||
private async validateProject(uuid: string) {
|
||||
async validateProject(uuid: string) {
|
||||
const project = await this.projectRepository.findOne({
|
||||
where: { uuid },
|
||||
});
|
||||
|
@ -48,7 +48,6 @@ import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status
|
||||
import { DeviceStatuses } from '@app/common/constants/device-status.enum';
|
||||
import { CommonErrorCodes } from '@app/common/constants/error-codes.enum';
|
||||
import { BatteryStatus } from '@app/common/constants/battery-status.enum';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities';
|
||||
import { SceneService } from 'src/scene/services';
|
||||
import { AddAutomationDto } from 'src/automation/dtos';
|
||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||
@ -60,6 +59,7 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||
import { DeleteSceneFromSceneDeviceDto } from '../dtos/delete.device.dto';
|
||||
import { DeviceEntity } from '@app/common/modules/device/entities';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
||||
import { ProjectParam } from '../dtos';
|
||||
|
||||
@ -1546,4 +1546,21 @@ export class DeviceService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async moveDevicesToSpace(
|
||||
targetSpace: SpaceEntity,
|
||||
deviceIds: string[],
|
||||
): Promise<void> {
|
||||
if (!deviceIds || deviceIds.length === 0) {
|
||||
throw new HttpException(
|
||||
'No device IDs provided for transfer',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
await this.deviceRepository.update(
|
||||
{ uuid: In(deviceIds) },
|
||||
{ spaceDevice: targetSpace },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ import {
|
||||
import { CheckEmailDto } from '../dtos/check-email.dto';
|
||||
import { UserRepository } from '@app/common/modules/user/repositories';
|
||||
import { EmailService } from '@app/common/util/email.service';
|
||||
import { SpaceEntity, SpaceRepository } from '@app/common/modules/space';
|
||||
import { SpaceRepository } from '@app/common/modules/space';
|
||||
import { ActivateCodeDto } from '../dtos/active-code.dto';
|
||||
import { UserSpaceService } from 'src/users/services';
|
||||
import { SpaceUserService } from 'src/space/services';
|
||||
@ -30,6 +30,7 @@ import {
|
||||
} from '../dtos/update.invite-user.dto';
|
||||
import { RoleTypeRepository } from '@app/common/modules/role-type/repositories';
|
||||
import { InviteUserEntity } from '@app/common/modules/Invite-user/entities';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||
|
||||
@Injectable()
|
||||
export class InviteUserService {
|
||||
|
@ -26,7 +26,7 @@ export class CreateOrphanSpaceHandler
|
||||
const orphanCommunityName = `${ORPHAN_COMMUNITY_NAME}-${project.name}`;
|
||||
|
||||
let orphanCommunity = await this.communityRepository.findOne({
|
||||
where: { name: orphanCommunityName, project },
|
||||
where: { name: orphanCommunityName, project: { uuid: project.uuid } },
|
||||
});
|
||||
|
||||
if (!orphanCommunity) {
|
||||
|
@ -94,7 +94,7 @@ export class ProjectUserService {
|
||||
'invitedBy',
|
||||
'isEnabled',
|
||||
],
|
||||
relations: ['roleType', 'spaces.space'],
|
||||
relations: ['roleType', 'spaces.space', 'spaces.space.community'],
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
@ -114,7 +114,14 @@ export class ProjectUserService {
|
||||
roleType: user.roleType.type,
|
||||
createdDate,
|
||||
createdTime,
|
||||
spaces: user.spaces.map((space) => space.space),
|
||||
spaces: user.spaces.map(({ space }) => {
|
||||
const { community, ...spaceWithoutCommunity } = space;
|
||||
return {
|
||||
...spaceWithoutCommunity,
|
||||
communityUuid: community.uuid,
|
||||
communityName: community.name,
|
||||
};
|
||||
}),
|
||||
},
|
||||
statusCode: HttpStatus.OK,
|
||||
});
|
||||
|
@ -183,6 +183,12 @@ export class ProjectService {
|
||||
|
||||
async findOne(uuid: string): Promise<ProjectEntity> {
|
||||
const project = await this.projectRepository.findOne({ where: { uuid } });
|
||||
if (!project) {
|
||||
throw new HttpException(
|
||||
`Invalid project with uuid ${uuid}`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
return project;
|
||||
}
|
||||
|
||||
|
@ -1,2 +1,2 @@
|
||||
export * from './propogate-subspace-update-command';
|
||||
export * from './propagate-space-model-deletion.command';
|
||||
export * from './propagate-subspace-model-update-command';
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { SpaceModelEntity } from '@app/common/modules/space-model';
|
||||
import { QueryRunner } from 'typeorm';
|
||||
|
||||
export class PropogateDeleteSpaceModelCommand {
|
||||
constructor(
|
||||
public readonly param: {
|
||||
spaceModel: SpaceModelEntity;
|
||||
queryRunner: QueryRunner;
|
||||
},
|
||||
) {}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { ICommand } from '@nestjs/cqrs';
|
||||
import { SpaceModelEntity } from '@app/common/modules/space-model';
|
||||
import { ModifyspaceModelPayload } from '../interfaces';
|
||||
import { QueryRunner } from 'typeorm';
|
||||
import { ISingleSubspaceModel } from '../interfaces';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||
|
||||
export class PropogateUpdateSpaceModelCommand implements ICommand {
|
||||
constructor(
|
||||
public readonly param: {
|
||||
spaceModel: SpaceModelEntity;
|
||||
modifiedSpaceModels: ModifyspaceModelPayload;
|
||||
queryRunner: QueryRunner;
|
||||
subspaceModels: ISingleSubspaceModel[];
|
||||
spaces: SpaceEntity[];
|
||||
},
|
||||
) {}
|
||||
}
|
@ -14,6 +14,7 @@ import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { SpaceModelService } from '../services';
|
||||
import {
|
||||
CreateSpaceModelDto,
|
||||
LinkSpacesToModelDto,
|
||||
SpaceModelParam,
|
||||
UpdateSpaceModelDto,
|
||||
} from '../dtos';
|
||||
@ -70,9 +71,9 @@ export class SpaceModelController {
|
||||
@UseGuards(PermissionsGuard)
|
||||
@Permissions('SPACE_MODEL_VIEW')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.SPACE_MODEL.ACTIONS.LIST_SPACE_MODEL_SUMMARY,
|
||||
summary: ControllerRoute.SPACE_MODEL.ACTIONS.GET_SPACE_MODEL_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.SPACE_MODEL.ACTIONS.LIST_SPACE_MODEL_DESCRIPTION,
|
||||
ControllerRoute.SPACE_MODEL.ACTIONS.GET_SPACE_MODEL_DESCRIPTION,
|
||||
})
|
||||
@Get(':spaceModelUuid')
|
||||
async get(@Param() param: SpaceModelParam): Promise<BaseResponseDto> {
|
||||
@ -107,4 +108,20 @@ export class SpaceModelController {
|
||||
async delete(@Param() param: SpaceModelParam): Promise<BaseResponseDto> {
|
||||
return await this.spaceModelService.deleteSpaceModel(param);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(PermissionsGuard)
|
||||
@Permissions('SPACE_MODEL_LINK')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.SPACE_MODEL.ACTIONS.LINK_SPACE_MODEL_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.SPACE_MODEL.ACTIONS.LINK_SPACE_MODEL_DESCRIPTION,
|
||||
})
|
||||
@Post(':spaceModelUuid/spaces/link')
|
||||
async link(
|
||||
@Param() params: SpaceModelParam,
|
||||
@Body() dto: LinkSpacesToModelDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
return await this.spaceModelService.linkSpaceModel(params, dto);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString, IsArray, ValidateNested } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { CreateSubspaceModelDto } from './subspaces-model-dtos/create-subspace-model.dto';
|
||||
import { CreateTagModelDto } from './tag-model-dtos/create-tag-model.dto';
|
||||
import { ProcessTagDto } from 'src/tags/dtos';
|
||||
|
||||
export class CreateSpaceModelDto {
|
||||
@ApiProperty({
|
||||
@ -24,10 +24,10 @@ export class CreateSpaceModelDto {
|
||||
|
||||
@ApiProperty({
|
||||
description: 'List of tags associated with the space model',
|
||||
type: [CreateTagModelDto],
|
||||
type: [ProcessTagDto],
|
||||
})
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CreateTagModelDto)
|
||||
tags?: CreateTagModelDto[];
|
||||
@Type(() => ProcessTagDto)
|
||||
tags?: ProcessTagDto[];
|
||||
}
|
||||
|
@ -4,3 +4,4 @@ export * from './update-space-model.dto';
|
||||
export * from './space-model-param';
|
||||
export * from './subspaces-model-dtos';
|
||||
export * from './tag-model-dtos';
|
||||
export * from './link-space-model.dto';
|
||||
|
25
src/space-model/dtos/link-space-model.dto.ts
Normal file
25
src/space-model/dtos/link-space-model.dto.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { IsArray, ArrayNotEmpty, IsUUID, IsBoolean } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class LinkSpacesToModelDto {
|
||||
@ApiProperty({
|
||||
description: 'List of space UUIDs to be linked to the space model',
|
||||
type: [String],
|
||||
example: [
|
||||
'550e8400-e29b-41d4-a716-446655440000',
|
||||
'f47ac10b-58cc-4372-a567-0e02b2c3d479',
|
||||
],
|
||||
})
|
||||
@IsArray()
|
||||
@ArrayNotEmpty()
|
||||
@IsUUID('4', { each: true })
|
||||
spaceUuids: string[];
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Whether to overwrite existing space model links',
|
||||
type: Boolean,
|
||||
example: false,
|
||||
})
|
||||
@IsBoolean()
|
||||
overwrite: boolean;
|
||||
}
|
@ -3,7 +3,7 @@ import { IsUUID } from 'class-validator';
|
||||
import { ProjectParam } from './project-param.dto';
|
||||
export class SpaceModelParam extends ProjectParam {
|
||||
@ApiProperty({
|
||||
description: 'UUID of the Space',
|
||||
description: 'UUID of the Space model',
|
||||
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
|
||||
})
|
||||
@IsUUID()
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsArray, IsNotEmpty, IsString, ValidateNested } from 'class-validator';
|
||||
import { CreateTagModelDto } from '../tag-model-dtos/create-tag-model.dto';
|
||||
import { Type } from 'class-transformer';
|
||||
import { ProcessTagDto } from 'src/tags/dtos';
|
||||
|
||||
export class CreateSubspaceModelDto {
|
||||
@ApiProperty({
|
||||
@ -14,10 +14,10 @@ export class CreateSubspaceModelDto {
|
||||
|
||||
@ApiProperty({
|
||||
description: 'List of tag models associated with the subspace',
|
||||
type: [CreateTagModelDto],
|
||||
type: [ProcessTagDto],
|
||||
})
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CreateTagModelDto)
|
||||
tags?: CreateTagModelDto[];
|
||||
@Type(() => ProcessTagDto)
|
||||
tags?: ProcessTagDto[];
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class CreateTagModelDto {
|
||||
@ApiProperty({
|
||||
description: 'Tag models associated with the space or subspace models',
|
||||
description: 'Tag associated with the space or subspace models',
|
||||
example: 'Temperature Control',
|
||||
})
|
||||
@IsNotEmpty()
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsString, IsOptional, IsEnum } from 'class-validator';
|
||||
import { IsString, IsOptional, IsEnum, IsUUID } from 'class-validator';
|
||||
|
||||
export class ModifyTagModelDto {
|
||||
@ApiProperty({
|
||||
@ -11,20 +11,29 @@ export class ModifyTagModelDto {
|
||||
action: ModifyAction;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'UUID of the tag model (required for update/delete)',
|
||||
description: 'UUID of the new tag',
|
||||
example: '123e4567-e89b-12d3-a456-426614174000',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
uuid?: string;
|
||||
@IsUUID()
|
||||
newTagUuid: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Name of the tag model (required for add/update)',
|
||||
description:
|
||||
'UUID of an existing tag (required for update/delete, optional for add)',
|
||||
example: 'a1b2c3d4-5678-90ef-abcd-1234567890ef',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
tagUuid?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Name of the tag (required for add/update)',
|
||||
example: 'Temperature Sensor',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
tag?: string;
|
||||
name?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
@ -32,6 +41,6 @@ export class ModifyTagModelDto {
|
||||
example: 'c789a91e-549a-4753-9006-02f89e8170e0',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsUUID()
|
||||
productUuid?: string;
|
||||
}
|
||||
|
@ -1,19 +1,27 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||
import { PropogateUpdateSpaceModelCommand } from '../commands';
|
||||
import { SpaceEntity, SpaceRepository } from '@app/common/modules/space';
|
||||
import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
|
||||
import {
|
||||
SpaceProductAllocationRepository,
|
||||
SpaceRepository,
|
||||
} from '@app/common/modules/space';
|
||||
import {
|
||||
SubspaceProductAllocationRepository,
|
||||
SubspaceRepository,
|
||||
} from '@app/common/modules/space/repositories/subspace.repository';
|
||||
import {
|
||||
SpaceModelEntity,
|
||||
SubspaceModelEntity,
|
||||
SubspaceModelProductAllocationRepoitory,
|
||||
TagModel,
|
||||
} from '@app/common/modules/space-model';
|
||||
import { DataSource, QueryRunner } from 'typeorm';
|
||||
import { DataSource, In, QueryRunner } from 'typeorm';
|
||||
import { SubSpaceService } from 'src/space/services';
|
||||
import { TagService } from 'src/space/services/tag';
|
||||
import { TagModelService } from '../services';
|
||||
import { UpdatedSubspaceModelPayload } from '../interfaces';
|
||||
import {
|
||||
ISingleSubspaceModel,
|
||||
UpdatedSubspaceModelPayload,
|
||||
} from '../interfaces';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
||||
import { ModifySubspaceDto } from 'src/space/dtos';
|
||||
|
||||
@CommandHandler(PropogateUpdateSpaceModelCommand)
|
||||
export class PropogateUpdateSpaceModelHandler
|
||||
@ -25,89 +33,122 @@ export class PropogateUpdateSpaceModelHandler
|
||||
private readonly dataSource: DataSource,
|
||||
private readonly subSpaceService: SubSpaceService,
|
||||
private readonly tagService: TagService,
|
||||
private readonly tagModelService: TagModelService,
|
||||
private readonly subspaceModelProductRepository: SubspaceModelProductAllocationRepoitory,
|
||||
private readonly subspaceProductRepository: SubspaceProductAllocationRepository,
|
||||
private readonly spaceProductRepository: SpaceProductAllocationRepository,
|
||||
) {}
|
||||
|
||||
async execute(command: PropogateUpdateSpaceModelCommand): Promise<void> {
|
||||
const { spaceModel, modifiedSpaceModels, queryRunner } = command.param;
|
||||
const { subspaceModels, spaces } = command.param;
|
||||
|
||||
try {
|
||||
const spaces = await queryRunner.manager.find(SpaceEntity, {
|
||||
where: { spaceModel },
|
||||
});
|
||||
if (!subspaceModels || subspaceModels.length === 0) return;
|
||||
if (!spaces || spaces.length === 0) return;
|
||||
|
||||
const { modifiedSubspaceModels = {}, modifiedTags = {} } =
|
||||
modifiedSpaceModels;
|
||||
|
||||
const {
|
||||
addedSubspaceModels = [],
|
||||
updatedSubspaceModels = [],
|
||||
deletedSubspaceModels = [],
|
||||
} = modifiedSubspaceModels;
|
||||
|
||||
const { added = [], updated = [], deleted = [] } = modifiedTags;
|
||||
|
||||
if (addedSubspaceModels.length > 0) {
|
||||
await this.addSubspaceModels(
|
||||
modifiedSpaceModels.modifiedSubspaceModels.addedSubspaceModels,
|
||||
spaces,
|
||||
queryRunner,
|
||||
);
|
||||
} else if (updatedSubspaceModels.length > 0) {
|
||||
await this.updateSubspaceModels(
|
||||
modifiedSpaceModels.modifiedSubspaceModels.updatedSubspaceModels,
|
||||
queryRunner,
|
||||
);
|
||||
for (const subspaceModel of subspaceModels) {
|
||||
if (subspaceModel.action === ModifyAction.ADD) {
|
||||
await this.addSubspaceModel(subspaceModel, spaces);
|
||||
} else if (subspaceModel.action === ModifyAction.DELETE) {
|
||||
await this.deleteSubspaceModel(subspaceModel, spaces);
|
||||
}
|
||||
if (deletedSubspaceModels.length > 0) {
|
||||
const dtos: ModifySubspaceDto[] =
|
||||
modifiedSpaceModels.modifiedSubspaceModels.deletedSubspaceModels.map(
|
||||
(model) => ({
|
||||
action: ModifyAction.DELETE,
|
||||
uuid: model,
|
||||
}),
|
||||
);
|
||||
await this.subSpaceService.modifySubSpace(dtos, queryRunner);
|
||||
}
|
||||
|
||||
if (added.length > 0) {
|
||||
await this.createTags(
|
||||
modifiedSpaceModels.modifiedTags.added,
|
||||
queryRunner,
|
||||
null,
|
||||
spaceModel,
|
||||
);
|
||||
}
|
||||
|
||||
if (updated.length > 0) {
|
||||
await this.updateTags(
|
||||
modifiedSpaceModels.modifiedTags.updated,
|
||||
queryRunner,
|
||||
);
|
||||
}
|
||||
|
||||
if (deleted.length > 0) {
|
||||
await this.deleteTags(
|
||||
modifiedSpaceModels.modifiedTags.deleted,
|
||||
queryRunner,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async addSubspaceModels(
|
||||
subspaceModels: SubspaceModelEntity[],
|
||||
async addSubspaceModel(
|
||||
subspaceModel: ISingleSubspaceModel,
|
||||
spaces: SpaceEntity[],
|
||||
queryRunner: QueryRunner,
|
||||
) {
|
||||
const subspaceModelAllocations =
|
||||
await this.subspaceModelProductRepository.find({
|
||||
where: {
|
||||
subspaceModel: {
|
||||
uuid: subspaceModel.subspaceModel.uuid,
|
||||
},
|
||||
},
|
||||
relations: ['tags', 'product'],
|
||||
});
|
||||
|
||||
for (const space of spaces) {
|
||||
await this.subSpaceService.createSubSpaceFromModel(
|
||||
subspaceModels,
|
||||
space,
|
||||
queryRunner,
|
||||
);
|
||||
const subspace = this.subspaceRepository.create({
|
||||
subspaceName: subspaceModel.subspaceModel.subspaceName,
|
||||
space: space,
|
||||
});
|
||||
|
||||
subspace.subSpaceModel = subspaceModel.subspaceModel;
|
||||
await this.subspaceRepository.save(subspace);
|
||||
|
||||
if (subspaceModelAllocations?.length > 0) {
|
||||
for (const allocation of subspaceModelAllocations) {
|
||||
const subspaceAllocation = this.subspaceProductRepository.create({
|
||||
subspace: subspace,
|
||||
product: allocation.product,
|
||||
tags: allocation.tags,
|
||||
inheritedFromModel: allocation,
|
||||
});
|
||||
await this.subspaceProductRepository.save(subspaceAllocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async deleteSubspaceModel(
|
||||
subspaceModel: ISingleSubspaceModel,
|
||||
spaces: SpaceEntity[],
|
||||
) {
|
||||
const subspaces = await this.subspaceRepository.find({
|
||||
where: {
|
||||
subSpaceModel: { uuid: subspaceModel.subspaceModel.uuid },
|
||||
disabled: false,
|
||||
},
|
||||
relations: [
|
||||
'productAllocations',
|
||||
'productAllocations.product',
|
||||
'productAllocations.tags',
|
||||
],
|
||||
});
|
||||
|
||||
if (!subspaces.length) return;
|
||||
|
||||
const allocationUuidsToRemove = subspaces.flatMap((subspace) =>
|
||||
subspace.productAllocations.map((allocation) => allocation.uuid),
|
||||
);
|
||||
|
||||
if (allocationUuidsToRemove.length) {
|
||||
await this.subspaceProductRepository.delete(allocationUuidsToRemove);
|
||||
}
|
||||
|
||||
await this.subspaceRepository.update(
|
||||
{ uuid: In(subspaces.map((s) => s.uuid)) },
|
||||
{ disabled: true },
|
||||
);
|
||||
|
||||
const relocatedAllocations = subspaceModel.relocatedAllocations || [];
|
||||
if (!relocatedAllocations.length) return;
|
||||
|
||||
for (const space of spaces) {
|
||||
for (const { allocation, tags = [] } of relocatedAllocations) {
|
||||
const spaceAllocation = await this.spaceProductRepository.findOne({
|
||||
where: {
|
||||
inheritedFromModel: { uuid: allocation.uuid },
|
||||
space: { uuid: space.uuid },
|
||||
},
|
||||
relations: ['tags'],
|
||||
});
|
||||
|
||||
if (spaceAllocation) {
|
||||
if (tags.length) {
|
||||
spaceAllocation.tags.push(...tags);
|
||||
await this.spaceProductRepository.save(spaceAllocation);
|
||||
}
|
||||
} else {
|
||||
const newSpaceAllocation = this.spaceProductRepository.create({
|
||||
space,
|
||||
inheritedFromModel: allocation,
|
||||
tags: allocation.tags,
|
||||
product: allocation.product,
|
||||
});
|
||||
await this.spaceProductRepository.save(newSpaceAllocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@ import { Logger } from '@nestjs/common';
|
||||
import { PropogateDeleteSpaceModelCommand } from '../commands';
|
||||
import { SpaceRepository } from '@app/common/modules/space';
|
||||
import { SpaceService } from '../../space/services/space.service';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
@CommandHandler(PropogateDeleteSpaceModelCommand)
|
||||
export class PropogateDeleteSpaceModelHandler
|
||||
@ -15,15 +14,12 @@ export class PropogateDeleteSpaceModelHandler
|
||||
constructor(
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
private readonly spaceService: SpaceService,
|
||||
private readonly dataSource: DataSource,
|
||||
) {}
|
||||
|
||||
async execute(command: PropogateDeleteSpaceModelCommand): Promise<void> {
|
||||
const { spaceModel } = command.param;
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
const { spaceModel, queryRunner } = command.param;
|
||||
|
||||
try {
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
const spaces = await this.spaceRepository.find({
|
||||
where: {
|
||||
@ -31,7 +27,11 @@ export class PropogateDeleteSpaceModelHandler
|
||||
uuid: spaceModel.uuid,
|
||||
},
|
||||
},
|
||||
relations: ['subspaces', 'tags', 'subspaces.tags'],
|
||||
relations: [
|
||||
'subspaces',
|
||||
'productAllocations',
|
||||
'subspaces.productAllocations',
|
||||
],
|
||||
});
|
||||
|
||||
for (const space of spaces) {
|
||||
@ -44,6 +44,7 @@ export class PropogateDeleteSpaceModelHandler
|
||||
);
|
||||
}
|
||||
}
|
||||
await queryRunner.commitTransaction();
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
this.logger.error(
|
||||
|
@ -1,2 +1,3 @@
|
||||
export * from './update-subspace.interface';
|
||||
export * from './modify-subspace.interface';
|
||||
export * from './single-subspace.interface';
|
||||
|
18
src/space-model/interfaces/single-subspace.interface.ts
Normal file
18
src/space-model/interfaces/single-subspace.interface.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {
|
||||
SpaceModelProductAllocationEntity,
|
||||
SubspaceModelEntity,
|
||||
} from '@app/common/modules/space-model';
|
||||
import { ModifyTagModelDto } from '../dtos';
|
||||
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
||||
import { NewTagEntity } from '@app/common/modules/tag';
|
||||
|
||||
export interface IRelocatedAllocation {
|
||||
allocation: SpaceModelProductAllocationEntity;
|
||||
tags?: NewTagEntity[];
|
||||
}
|
||||
export interface ISingleSubspaceModel {
|
||||
subspaceModel: SubspaceModelEntity;
|
||||
action: ModifyAction;
|
||||
tags?: ModifyTagModelDto[];
|
||||
relocatedAllocations?: IRelocatedAllocation[];
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
import { SubspaceModelProductAllocationEntity } from '@app/common/modules/space-model';
|
||||
import { NewTagEntity } from '@app/common/modules/tag';
|
||||
|
||||
export type IUpdatedAllocations = {
|
||||
allocation?: SubspaceModelProductAllocationEntity;
|
||||
tagsRemoved?: NewTagEntity[];
|
||||
tagsAdded?: NewTagEntity[];
|
||||
newAllocation?: SubspaceModelProductAllocationEntity;
|
||||
deletedAllocation?: SubspaceModelProductAllocationEntity;
|
||||
};
|
||||
|
||||
export interface ISubspaceProductAllocationUpdateResult {
|
||||
createdAllocations: ICreatedAllocation[];
|
||||
updatedAllocations: IUpdatedAllocation[];
|
||||
deletedAllocations: IDeletedAllocation[];
|
||||
}
|
||||
|
||||
export interface ICreatedAllocation {
|
||||
allocation: SubspaceModelProductAllocationEntity;
|
||||
tags: NewTagEntity[];
|
||||
}
|
||||
|
||||
export interface IUpdatedAllocation {
|
||||
allocation: SubspaceModelProductAllocationEntity;
|
||||
tagsAdded: NewTagEntity[];
|
||||
tagsRemoved: NewTagEntity[];
|
||||
}
|
||||
|
||||
export interface IDeletedAllocation {
|
||||
allocation: SubspaceModelProductAllocationEntity;
|
||||
tagsRemoved: NewTagEntity[];
|
||||
}
|
@ -1,3 +1,2 @@
|
||||
export * from './space-model.service';
|
||||
export * from './subspace';
|
||||
export * from './tag-model.service';
|
||||
|
@ -0,0 +1,430 @@
|
||||
import { In, QueryRunner } from 'typeorm';
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
SpaceModelEntity,
|
||||
SpaceModelProductAllocationEntity,
|
||||
SpaceModelProductAllocationRepoitory,
|
||||
SubspaceModelProductAllocationEntity,
|
||||
} from '@app/common/modules/space-model';
|
||||
import { TagService as NewTagService } from 'src/tags/services';
|
||||
import { ProcessTagDto } from 'src/tags/dtos';
|
||||
import { ModifySubspaceModelDto, ModifyTagModelDto } from '../dtos';
|
||||
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
||||
import { NewTagEntity } from '@app/common/modules/tag';
|
||||
import { ProductEntity } from '@app/common/modules/product/entities';
|
||||
import { SpaceRepository } from '@app/common/modules/space';
|
||||
import { ProjectEntity } from '@app/common/modules/project/entities';
|
||||
|
||||
@Injectable()
|
||||
export class SpaceModelProductAllocationService {
|
||||
constructor(
|
||||
private readonly tagService: NewTagService,
|
||||
private readonly spaceModelProductAllocationRepository: SpaceModelProductAllocationRepoitory,
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
) {}
|
||||
|
||||
async createProductAllocations(
|
||||
projectUuid: string,
|
||||
spaceModel: SpaceModelEntity,
|
||||
tags: ProcessTagDto[],
|
||||
queryRunner?: QueryRunner,
|
||||
modifySubspaceModels?: ModifySubspaceModelDto[],
|
||||
): Promise<SpaceModelProductAllocationEntity[]> {
|
||||
try {
|
||||
if (!tags.length) return [];
|
||||
|
||||
const processedTags = await this.tagService.processTags(
|
||||
tags,
|
||||
projectUuid,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
const productAllocations: SpaceModelProductAllocationEntity[] = [];
|
||||
const existingAllocations = new Map<
|
||||
string,
|
||||
SpaceModelProductAllocationEntity
|
||||
>();
|
||||
|
||||
for (const tag of processedTags) {
|
||||
let isTagNeeded = true;
|
||||
|
||||
if (modifySubspaceModels) {
|
||||
const relatedSubspaces = await queryRunner.manager.find(
|
||||
SubspaceModelProductAllocationEntity,
|
||||
{
|
||||
where: {
|
||||
product: { uuid: tag.product.uuid },
|
||||
subspaceModel: { spaceModel: { uuid: spaceModel.uuid } },
|
||||
tags: { uuid: tag.uuid },
|
||||
},
|
||||
relations: ['subspaceModel', 'tags'],
|
||||
},
|
||||
);
|
||||
|
||||
for (const subspaceWithTag of relatedSubspaces) {
|
||||
const modifyingSubspace = modifySubspaceModels.find(
|
||||
(subspace) =>
|
||||
subspace.action === ModifyAction.UPDATE &&
|
||||
subspace.uuid === subspaceWithTag.subspaceModel.uuid,
|
||||
);
|
||||
|
||||
if (
|
||||
modifyingSubspace &&
|
||||
modifyingSubspace.tags &&
|
||||
modifyingSubspace.tags.some(
|
||||
(subspaceTag) =>
|
||||
subspaceTag.action === ModifyAction.DELETE &&
|
||||
subspaceTag.tagUuid === tag.uuid,
|
||||
)
|
||||
) {
|
||||
isTagNeeded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isTagNeeded) {
|
||||
const hasTags = await this.validateTagWithinSpaceModel(
|
||||
queryRunner,
|
||||
tag,
|
||||
spaceModel,
|
||||
);
|
||||
|
||||
if (hasTags) continue;
|
||||
|
||||
let allocation = existingAllocations.get(tag.product.uuid);
|
||||
if (!allocation) {
|
||||
allocation = await this.getAllocationByProduct(
|
||||
tag.product,
|
||||
spaceModel,
|
||||
queryRunner,
|
||||
);
|
||||
if (allocation) {
|
||||
existingAllocations.set(tag.product.uuid, allocation);
|
||||
}
|
||||
}
|
||||
|
||||
if (!allocation) {
|
||||
allocation = this.createNewAllocation(spaceModel, tag, queryRunner);
|
||||
productAllocations.push(allocation);
|
||||
} else if (!allocation.tags.some((t) => t.uuid === tag.uuid)) {
|
||||
allocation.tags.push(tag);
|
||||
await this.saveAllocation(allocation, queryRunner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (productAllocations.length > 0) {
|
||||
await this.saveAllocations(productAllocations, queryRunner);
|
||||
}
|
||||
|
||||
return productAllocations;
|
||||
} catch (error) {
|
||||
throw this.handleError(error, 'Failed to create product allocations');
|
||||
}
|
||||
}
|
||||
|
||||
async updateProductAllocations(
|
||||
dtos: ModifyTagModelDto[],
|
||||
project: ProjectEntity,
|
||||
spaceModel: SpaceModelEntity,
|
||||
queryRunner: QueryRunner,
|
||||
modifySubspaceModels?: ModifySubspaceModelDto[],
|
||||
): Promise<void> {
|
||||
try {
|
||||
const addDtos = dtos.filter((dto) => dto.action === ModifyAction.ADD);
|
||||
const deleteDtos = dtos.filter(
|
||||
(dto) => dto.action === ModifyAction.DELETE,
|
||||
);
|
||||
|
||||
const addTagDtos: ProcessTagDto[] = addDtos.map((dto) => ({
|
||||
name: dto.name,
|
||||
productUuid: dto.productUuid,
|
||||
uuid: dto.newTagUuid,
|
||||
}));
|
||||
|
||||
const processedTags = await this.tagService.processTags(
|
||||
addTagDtos,
|
||||
project.uuid,
|
||||
queryRunner,
|
||||
);
|
||||
const addTagUuidMap = new Map<string, ModifyTagModelDto>();
|
||||
processedTags.forEach((tag, index) => {
|
||||
addTagUuidMap.set(tag.uuid, addDtos[index]);
|
||||
});
|
||||
|
||||
const addTagUuids = new Set(processedTags.map((tag) => tag.uuid));
|
||||
const deleteTagUuids = new Set(deleteDtos.map((dto) => dto.tagUuid));
|
||||
|
||||
const tagsToIgnore = new Set(
|
||||
[...addTagUuids].filter((uuid) => deleteTagUuids.has(uuid)),
|
||||
);
|
||||
|
||||
const filteredDtos = dtos.filter(
|
||||
(dto) =>
|
||||
!(
|
||||
tagsToIgnore.has(dto.tagUuid) ||
|
||||
(dto.action === ModifyAction.ADD &&
|
||||
tagsToIgnore.has(
|
||||
[...addTagUuidMap.keys()].find(
|
||||
(uuid) => addTagUuidMap.get(uuid) === dto,
|
||||
),
|
||||
))
|
||||
),
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
this.processAddActions(
|
||||
filteredDtos,
|
||||
project.uuid,
|
||||
spaceModel,
|
||||
queryRunner,
|
||||
modifySubspaceModels,
|
||||
),
|
||||
this.processDeleteActions(filteredDtos, queryRunner, spaceModel),
|
||||
]);
|
||||
} catch (error) {
|
||||
throw this.handleError(error, 'Error while updating product allocations');
|
||||
}
|
||||
}
|
||||
|
||||
private async processAddActions(
|
||||
dtos: ModifyTagModelDto[],
|
||||
projectUuid: string,
|
||||
spaceModel: SpaceModelEntity,
|
||||
queryRunner: QueryRunner,
|
||||
modifySubspaceModels?: ModifySubspaceModelDto[],
|
||||
): 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) {
|
||||
await this.createProductAllocations(
|
||||
projectUuid,
|
||||
spaceModel,
|
||||
addDtos,
|
||||
queryRunner,
|
||||
modifySubspaceModels,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private createNewAllocation(
|
||||
spaceModel: SpaceModelEntity,
|
||||
tag: NewTagEntity,
|
||||
queryRunner?: QueryRunner,
|
||||
): SpaceModelProductAllocationEntity {
|
||||
return queryRunner
|
||||
? queryRunner.manager.create(SpaceModelProductAllocationEntity, {
|
||||
spaceModel,
|
||||
product: tag.product,
|
||||
tags: [tag],
|
||||
})
|
||||
: this.spaceModelProductAllocationRepository.create({
|
||||
spaceModel,
|
||||
product: tag.product,
|
||||
tags: [tag],
|
||||
});
|
||||
}
|
||||
|
||||
private async getAllocationByProduct(
|
||||
product: ProductEntity,
|
||||
spaceModel: SpaceModelEntity,
|
||||
queryRunner?: QueryRunner,
|
||||
): Promise<SpaceModelProductAllocationEntity | null> {
|
||||
return queryRunner
|
||||
? queryRunner.manager.findOne(SpaceModelProductAllocationEntity, {
|
||||
where: {
|
||||
spaceModel: { uuid: spaceModel.uuid },
|
||||
product: { uuid: product.uuid },
|
||||
},
|
||||
relations: ['tags'],
|
||||
})
|
||||
: this.spaceModelProductAllocationRepository.findOne({
|
||||
where: {
|
||||
spaceModel: { uuid: spaceModel.uuid },
|
||||
product: { uuid: product.uuid },
|
||||
},
|
||||
relations: ['tags'],
|
||||
});
|
||||
}
|
||||
|
||||
private async saveAllocation(
|
||||
allocation: SpaceModelProductAllocationEntity,
|
||||
queryRunner?: QueryRunner,
|
||||
) {
|
||||
queryRunner
|
||||
? await queryRunner.manager.save(
|
||||
SpaceModelProductAllocationEntity,
|
||||
allocation,
|
||||
)
|
||||
: await this.spaceModelProductAllocationRepository.save(allocation);
|
||||
}
|
||||
|
||||
private async saveAllocations(
|
||||
allocations: SpaceModelProductAllocationEntity[],
|
||||
queryRunner?: QueryRunner,
|
||||
) {
|
||||
queryRunner
|
||||
? await queryRunner.manager.save(
|
||||
SpaceModelProductAllocationEntity,
|
||||
allocations,
|
||||
)
|
||||
: await this.spaceModelProductAllocationRepository.save(allocations);
|
||||
}
|
||||
|
||||
private handleError(error: any, message: string): HttpException {
|
||||
return new HttpException(
|
||||
error instanceof HttpException ? error.message : message,
|
||||
error instanceof HttpException
|
||||
? error.getStatus()
|
||||
: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
private async processDeleteActions(
|
||||
dtos: ModifyTagModelDto[],
|
||||
queryRunner: QueryRunner,
|
||||
spaceModel: SpaceModelEntity,
|
||||
): Promise<SpaceModelProductAllocationEntity[]> {
|
||||
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(
|
||||
SpaceModelProductAllocationEntity,
|
||||
{
|
||||
where: {
|
||||
tags: { uuid: In(tagUuidsToDelete) },
|
||||
spaceModel: {
|
||||
uuid: spaceModel.uuid,
|
||||
},
|
||||
},
|
||||
relations: [
|
||||
'tags',
|
||||
'inheritedSpaceAllocations',
|
||||
'inheritedSpaceAllocations.tags',
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
if (!allocationsToUpdate || allocationsToUpdate.length === 0) return [];
|
||||
|
||||
const deletedAllocations: SpaceModelProductAllocationEntity[] = [];
|
||||
const allocationUpdates: SpaceModelProductAllocationEntity[] = [];
|
||||
|
||||
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(
|
||||
SpaceModelProductAllocationEntity,
|
||||
allocationUpdates,
|
||||
);
|
||||
}
|
||||
|
||||
if (deletedAllocations.length > 0) {
|
||||
await queryRunner.manager.remove(
|
||||
SpaceModelProductAllocationEntity,
|
||||
deletedAllocations,
|
||||
);
|
||||
}
|
||||
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from('space_model_product_tags')
|
||||
.where(
|
||||
'space_model_product_allocation_uuid NOT IN (' +
|
||||
queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.select('allocation.uuid')
|
||||
.from(SpaceModelProductAllocationEntity, 'allocation')
|
||||
.getQuery() +
|
||||
')',
|
||||
)
|
||||
.execute();
|
||||
|
||||
return deletedAllocations;
|
||||
} catch (error) {
|
||||
throw this.handleError(error, `Failed to delete tags in space model`);
|
||||
}
|
||||
}
|
||||
|
||||
private async validateTagWithinSpaceModel(
|
||||
queryRunner: QueryRunner,
|
||||
tag: NewTagEntity,
|
||||
spaceModel: SpaceModelEntity,
|
||||
): Promise<boolean> {
|
||||
const existingAllocationsForProduct = await queryRunner.manager.find(
|
||||
SpaceModelProductAllocationEntity,
|
||||
{
|
||||
where: {
|
||||
spaceModel: {
|
||||
uuid: spaceModel.uuid,
|
||||
},
|
||||
product: {
|
||||
uuid: tag.product.uuid,
|
||||
},
|
||||
},
|
||||
relations: ['tags'],
|
||||
},
|
||||
);
|
||||
|
||||
const existingTagsForProduct = existingAllocationsForProduct.flatMap(
|
||||
(allocation) => allocation.tags,
|
||||
);
|
||||
|
||||
const isDuplicateTag = existingTagsForProduct.some(
|
||||
(existingTag) => existingTag.uuid === tag.uuid,
|
||||
);
|
||||
|
||||
if (isDuplicateTag) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async clearAllAllocations(spaceModelUuid: string, queryRunner: QueryRunner) {
|
||||
try {
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(SpaceModelProductAllocationEntity)
|
||||
.where('space_model_uuid = :spaceModelUuid', { spaceModelUuid })
|
||||
.execute();
|
||||
} catch (error) {
|
||||
throw this.handleError(
|
||||
error,
|
||||
`Failed to clear all allocations in the space model product allocation`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,30 +1,53 @@
|
||||
import {
|
||||
SpaceModelEntity,
|
||||
SpaceModelProductAllocationEntity,
|
||||
SpaceModelRepository,
|
||||
SubspaceModelProductAllocationEntity,
|
||||
} from '@app/common/modules/space-model';
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { CreateSpaceModelDto, UpdateSpaceModelDto } from '../dtos';
|
||||
import {
|
||||
CreateSpaceModelDto,
|
||||
LinkSpacesToModelDto,
|
||||
UpdateSpaceModelDto,
|
||||
} from '../dtos';
|
||||
import { ProjectParam } from 'src/community/dtos';
|
||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||
import { SubSpaceModelService } from './subspace/subspace-model.service';
|
||||
import { DataSource, QueryRunner } from 'typeorm';
|
||||
import { DataSource, In, QueryRunner, SelectQueryBuilder } from 'typeorm';
|
||||
import {
|
||||
TypeORMCustomModel,
|
||||
TypeORMCustomModelFindAllQuery,
|
||||
} from '@app/common/models/typeOrmCustom.model';
|
||||
import { PageResponse } from '@app/common/dto/pagination.response.dto';
|
||||
import { SpaceModelParam } from '../dtos/space-model-param';
|
||||
import { ProjectService } from 'src/project/services';
|
||||
import { ProjectEntity } from '@app/common/modules/project/entities';
|
||||
import { TagModelService } from './tag-model.service';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { CommandBus } from '@nestjs/cqrs';
|
||||
import { PropogateUpdateSpaceModelCommand } from '../commands';
|
||||
import { ProcessTagDto } from 'src/tags/dtos';
|
||||
import { SpaceModelProductAllocationService } from './space-model-product-allocation.service';
|
||||
import {
|
||||
ModifiedTagsModelPayload,
|
||||
ModifySubspaceModelPayload,
|
||||
} from '../interfaces';
|
||||
import { SpaceModelDto } from '@app/common/modules/space-model/dtos';
|
||||
PropogateDeleteSpaceModelCommand,
|
||||
PropogateUpdateSpaceModelCommand,
|
||||
} from '../commands';
|
||||
import {
|
||||
SpaceProductAllocationRepository,
|
||||
SpaceRepository,
|
||||
} from '@app/common/modules/space';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||
import {
|
||||
SubspaceProductAllocationRepository,
|
||||
SubspaceRepository,
|
||||
} from '@app/common/modules/space/repositories/subspace.repository';
|
||||
import {
|
||||
ORPHAN_COMMUNITY_NAME,
|
||||
ORPHAN_SPACE_NAME,
|
||||
} from '@app/common/constants/orphan-constant';
|
||||
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
||||
import { SpaceProductAllocationEntity } from '@app/common/modules/space/entities/space-product-allocation.entity';
|
||||
import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity';
|
||||
import { SubspaceProductAllocationEntity } from '@app/common/modules/space/entities/subspace/subspace-product-allocation.entity';
|
||||
import { DeviceEntity } from '@app/common/modules/device/entities';
|
||||
import { ISingleSubspaceModel } from '../interfaces';
|
||||
|
||||
@Injectable()
|
||||
export class SpaceModelService {
|
||||
@ -33,8 +56,13 @@ export class SpaceModelService {
|
||||
private readonly spaceModelRepository: SpaceModelRepository,
|
||||
private readonly projectService: ProjectService,
|
||||
private readonly subSpaceModelService: SubSpaceModelService,
|
||||
private readonly tagModelService: TagModelService,
|
||||
private commandBus: CommandBus,
|
||||
private readonly spaceModelProductAllocationService: SpaceModelProductAllocationService,
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
private readonly spaceProductAllocationRepository: SpaceProductAllocationRepository,
|
||||
private readonly subspaceRepository: SubspaceRepository,
|
||||
private readonly subspaceProductAllocationRepository: SubspaceProductAllocationRepository,
|
||||
private readonly deviceRepository: DeviceRepository,
|
||||
) {}
|
||||
|
||||
async createSpaceModel(
|
||||
@ -43,11 +71,15 @@ export class SpaceModelService {
|
||||
): Promise<BaseResponseDto> {
|
||||
const { modelName, subspaceModels, tags } = createSpaceModelDto;
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const project = await this.validateProject(params.projectUuid);
|
||||
const project = await this.projectService.findOne(params.projectUuid);
|
||||
if (!project) {
|
||||
throw new HttpException('Project not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
await this.validateNameUsingQueryRunner(
|
||||
modelName,
|
||||
@ -62,22 +94,27 @@ export class SpaceModelService {
|
||||
|
||||
const savedSpaceModel = await queryRunner.manager.save(spaceModel);
|
||||
|
||||
const subspaceTags =
|
||||
this.subSpaceModelService.extractTagsFromSubspaceModels(subspaceModels);
|
||||
|
||||
const allTags = [...tags, ...subspaceTags];
|
||||
this.validateUniqueTags(allTags);
|
||||
|
||||
if (subspaceModels?.length) {
|
||||
savedSpaceModel.subspaceModels =
|
||||
await this.subSpaceModelService.createSubSpaceModels(
|
||||
subspaceModels,
|
||||
await this.subSpaceModelService.createModels(
|
||||
savedSpaceModel,
|
||||
subspaceModels,
|
||||
queryRunner,
|
||||
tags,
|
||||
);
|
||||
}
|
||||
|
||||
if (tags?.length) {
|
||||
savedSpaceModel.tags = await this.tagModelService.createTags(
|
||||
await this.spaceModelProductAllocationService.createProductAllocations(
|
||||
params.projectUuid,
|
||||
spaceModel,
|
||||
tags,
|
||||
queryRunner,
|
||||
savedSpaceModel,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
@ -94,7 +131,7 @@ export class SpaceModelService {
|
||||
const errorMessage =
|
||||
error instanceof HttpException
|
||||
? error.message
|
||||
: 'An unexpected error occurred';
|
||||
: `An unexpected error occurred: ${error.message}`;
|
||||
const statusCode =
|
||||
error instanceof HttpException
|
||||
? error.getStatus()
|
||||
@ -119,34 +156,9 @@ export class SpaceModelService {
|
||||
disabled: false,
|
||||
};
|
||||
pageable.include =
|
||||
'subspaceModels,tags,subspaceModels.tags,tags.product,subspaceModels.tags.product';
|
||||
'subspaceModels.productAllocations,subspaceModelProductAllocations.tags,subspaceModels, productAllocations, productAllocations.tags';
|
||||
|
||||
const queryBuilder = await this.spaceModelRepository
|
||||
.createQueryBuilder('spaceModel')
|
||||
.leftJoinAndSelect(
|
||||
'spaceModel.subspaceModels',
|
||||
'subspaceModels',
|
||||
'subspaceModels.disabled = :subspaceDisabled',
|
||||
{ subspaceDisabled: false },
|
||||
)
|
||||
.leftJoinAndSelect(
|
||||
'spaceModel.tags',
|
||||
'tags',
|
||||
'tags.disabled = :tagsDisabled',
|
||||
{ tagsDisabled: false },
|
||||
)
|
||||
.leftJoinAndSelect('tags.product', 'spaceTagproduct')
|
||||
.leftJoinAndSelect(
|
||||
'subspaceModels.tags',
|
||||
'subspaceModelTags',
|
||||
'subspaceModelTags.disabled = :subspaceModelTagsDisabled',
|
||||
{ subspaceModelTagsDisabled: false },
|
||||
)
|
||||
.leftJoinAndSelect('subspaceModelTags.product', 'subspaceTagproduct')
|
||||
.where('spaceModel.disabled = :disabled', { disabled: false })
|
||||
.andWhere('spaceModel.project = :projectUuid', {
|
||||
projectUuid: param.projectUuid,
|
||||
});
|
||||
const queryBuilder = this.buildSpaceModelQuery(param.projectUuid);
|
||||
|
||||
const customModel = TypeORMCustomModel(this.spaceModelRepository);
|
||||
const { baseResponseDto, paginationResponseDto } =
|
||||
@ -159,10 +171,14 @@ export class SpaceModelService {
|
||||
queryBuilder,
|
||||
);
|
||||
|
||||
return new PageResponse<SpaceModelDto>(
|
||||
baseResponseDto,
|
||||
paginationResponseDto,
|
||||
);
|
||||
const formattedData = this.transformSpaceModelData(baseResponseDto.data);
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
data: formattedData,
|
||||
message: 'Success get list spaceModel',
|
||||
...paginationResponseDto,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
`Error fetching paginated list: ${error.message}`,
|
||||
@ -177,14 +193,16 @@ export class SpaceModelService {
|
||||
|
||||
async update(dto: UpdateSpaceModelDto, param: SpaceModelParam) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await this.validateProject(param.projectUuid);
|
||||
const spaceModel = await this.validateSpaceModel(param.spaceModelUuid);
|
||||
const project = await this.validateProject(param.projectUuid);
|
||||
const spaceModel = await this.validateSpaceModel(
|
||||
param.spaceModelUuid,
|
||||
param.projectUuid,
|
||||
);
|
||||
await queryRunner.connect();
|
||||
|
||||
let modifiedSubspaceModels: ModifySubspaceModelPayload = {};
|
||||
let modifiedTagsModelPayload: ModifiedTagsModelPayload = {};
|
||||
let modifiedSubspaces: ISingleSubspaceModel[] = [];
|
||||
try {
|
||||
await queryRunner.startTransaction();
|
||||
const spaces = await this.fetchModelSpaces(spaceModel);
|
||||
|
||||
const { modelName } = dto;
|
||||
if (modelName) {
|
||||
@ -200,44 +218,33 @@ export class SpaceModelService {
|
||||
);
|
||||
}
|
||||
|
||||
const spaceTagsAfterMove = this.tagModelService.getSubspaceTagsToBeAdded(
|
||||
dto.tags,
|
||||
dto.subspaceModels,
|
||||
);
|
||||
|
||||
const modifiedSubspaces = this.tagModelService.getModifiedSubspaces(
|
||||
dto.tags,
|
||||
dto.subspaceModels,
|
||||
);
|
||||
|
||||
if (dto.subspaceModels) {
|
||||
modifiedSubspaceModels =
|
||||
await this.subSpaceModelService.modifySubSpaceModels(
|
||||
modifiedSubspaces,
|
||||
modifiedSubspaces =
|
||||
await this.subSpaceModelService.modifySubspaceModels(
|
||||
dto.subspaceModels,
|
||||
spaceModel,
|
||||
queryRunner,
|
||||
param.projectUuid,
|
||||
dto.tags,
|
||||
);
|
||||
}
|
||||
|
||||
if (dto.tags) {
|
||||
modifiedTagsModelPayload = await this.tagModelService.modifyTags(
|
||||
spaceTagsAfterMove,
|
||||
queryRunner,
|
||||
await this.spaceModelProductAllocationService.updateProductAllocations(
|
||||
dto.tags,
|
||||
project,
|
||||
spaceModel,
|
||||
null,
|
||||
queryRunner,
|
||||
dto.subspaceModels,
|
||||
);
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
await this.commandBus.execute(
|
||||
new PropogateUpdateSpaceModelCommand({
|
||||
spaceModel: spaceModel,
|
||||
modifiedSpaceModels: {
|
||||
modifiedSubspaceModels,
|
||||
modifiedTags: modifiedTagsModelPayload,
|
||||
},
|
||||
queryRunner,
|
||||
subspaceModels: modifiedSubspaces,
|
||||
spaces: spaces,
|
||||
}),
|
||||
);
|
||||
|
||||
@ -266,25 +273,27 @@ export class SpaceModelService {
|
||||
|
||||
try {
|
||||
await this.validateProject(param.projectUuid);
|
||||
const spaceModel = await this.validateSpaceModel(param.spaceModelUuid);
|
||||
const spaceModel = await this.validateSpaceModel(
|
||||
param.spaceModelUuid,
|
||||
param.projectUuid,
|
||||
);
|
||||
|
||||
if (spaceModel.subspaceModels?.length) {
|
||||
const deleteSubspaceDtos = spaceModel.subspaceModels.map(
|
||||
(subspace) => ({
|
||||
subspaceUuid: subspace.uuid,
|
||||
}),
|
||||
const deleteSubspaceUuids = spaceModel.subspaceModels.map(
|
||||
(subspace) => subspace.uuid,
|
||||
);
|
||||
|
||||
await this.subSpaceModelService.deleteSubspaceModels(
|
||||
deleteSubspaceDtos,
|
||||
await this.subSpaceModelService.clearModels(
|
||||
deleteSubspaceUuids,
|
||||
queryRunner,
|
||||
);
|
||||
}
|
||||
|
||||
if (spaceModel.tags?.length) {
|
||||
const deleteSpaceTagsDtos = spaceModel.tags.map((tag) => tag.uuid);
|
||||
|
||||
await this.tagModelService.deleteTags(deleteSpaceTagsDtos, queryRunner);
|
||||
if (spaceModel.productAllocations?.length) {
|
||||
await this.spaceModelProductAllocationService.clearAllAllocations(
|
||||
spaceModel.uuid,
|
||||
queryRunner,
|
||||
);
|
||||
}
|
||||
|
||||
await queryRunner.manager.update(
|
||||
@ -292,9 +301,15 @@ export class SpaceModelService {
|
||||
{ uuid: param.spaceModelUuid },
|
||||
{ disabled: true },
|
||||
);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
await this.commandBus.execute(
|
||||
new PropogateDeleteSpaceModelCommand({
|
||||
spaceModel: spaceModel,
|
||||
queryRunner,
|
||||
}),
|
||||
);
|
||||
|
||||
return new SuccessResponseDto({
|
||||
message: `SpaceModel with UUID ${param.spaceModelUuid} deleted successfully.`,
|
||||
statusCode: HttpStatus.OK,
|
||||
@ -317,6 +332,268 @@ export class SpaceModelService {
|
||||
}
|
||||
}
|
||||
|
||||
async fetchModelSpaces(
|
||||
spaceModel: SpaceModelEntity,
|
||||
queryRunner?: QueryRunner,
|
||||
) {
|
||||
const spaces = await (queryRunner
|
||||
? queryRunner.manager.find(SpaceEntity, {
|
||||
where: {
|
||||
spaceModel: {
|
||||
uuid: spaceModel.uuid,
|
||||
},
|
||||
disabled: false,
|
||||
},
|
||||
})
|
||||
: this.spaceRepository.find({
|
||||
where: {
|
||||
spaceModel: {
|
||||
uuid: spaceModel.uuid,
|
||||
},
|
||||
disabled: false,
|
||||
},
|
||||
}));
|
||||
return spaces;
|
||||
}
|
||||
|
||||
async linkSpaceModel(
|
||||
params: SpaceModelParam,
|
||||
dto: LinkSpacesToModelDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
const project = await this.validateProject(params.projectUuid);
|
||||
|
||||
try {
|
||||
const spaceModel = await this.spaceModelRepository.findOne({
|
||||
where: { uuid: params.spaceModelUuid },
|
||||
relations: [
|
||||
'productAllocations',
|
||||
'subspaceModels',
|
||||
'productAllocations.product',
|
||||
'productAllocations.tags',
|
||||
'subspaceModels.productAllocations',
|
||||
'subspaceModels.productAllocations.product',
|
||||
'subspaceModels.productAllocations.tags',
|
||||
],
|
||||
});
|
||||
|
||||
if (!spaceModel) {
|
||||
throw new HttpException(
|
||||
`Space Model with UUID ${params.spaceModelUuid} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const spaces = await this.spaceRepository.find({
|
||||
where: { uuid: In(dto.spaceUuids), disabled: false },
|
||||
relations: [
|
||||
'spaceModel',
|
||||
'devices',
|
||||
'subspaces',
|
||||
'productAllocations',
|
||||
'productAllocations.product',
|
||||
'productAllocations.tags',
|
||||
'subspaces.productAllocations',
|
||||
'subspaces.productAllocations.product',
|
||||
'subspaces.productAllocations.product.tags',
|
||||
'community',
|
||||
],
|
||||
});
|
||||
|
||||
if (!spaces.length) {
|
||||
throw new HttpException(
|
||||
`No spaces found for the given UUIDs`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
spaces.map(async (space) => {
|
||||
const hasDependencies =
|
||||
space.devices.length > 0 ||
|
||||
space.subspaces.length > 0 ||
|
||||
space.productAllocations.length > 0;
|
||||
|
||||
if (!hasDependencies && !space.spaceModel) {
|
||||
await this.linkToSpace(space, spaceModel);
|
||||
} else if (dto.overwrite) {
|
||||
await this.overwriteSpace(space, project);
|
||||
await this.linkToSpace(space, spaceModel);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return new SuccessResponseDto({
|
||||
message: 'Spaces linked successfully',
|
||||
data: dto.spaceUuids,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
`Failed to link space model: ${error.message}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async linkToSpace(
|
||||
space: SpaceEntity,
|
||||
spaceModel: SpaceModelEntity,
|
||||
queryRunner?: QueryRunner, // Make queryRunner optional
|
||||
): Promise<void> {
|
||||
try {
|
||||
space.spaceModel = spaceModel;
|
||||
if (queryRunner) {
|
||||
await queryRunner.manager.save(SpaceEntity, space);
|
||||
} else {
|
||||
await this.spaceRepository.save(space);
|
||||
}
|
||||
const spaceProductAllocations = spaceModel.productAllocations.map(
|
||||
(modelAllocation) =>
|
||||
this.spaceProductAllocationRepository.create({
|
||||
space,
|
||||
inheritedFromModel: modelAllocation,
|
||||
product: modelAllocation.product,
|
||||
tags: modelAllocation.tags,
|
||||
}),
|
||||
);
|
||||
|
||||
if (queryRunner) {
|
||||
await queryRunner.manager.save(
|
||||
SpaceProductAllocationEntity,
|
||||
spaceProductAllocations,
|
||||
);
|
||||
} else {
|
||||
await this.spaceProductAllocationRepository.save(
|
||||
spaceProductAllocations,
|
||||
);
|
||||
}
|
||||
await Promise.all(
|
||||
spaceModel.subspaceModels.map(async (subspaceModel) => {
|
||||
const subspace = this.subspaceRepository.create({
|
||||
subspaceName: subspaceModel.subspaceName,
|
||||
subSpaceModel: subspaceModel,
|
||||
space: space,
|
||||
});
|
||||
|
||||
if (queryRunner) {
|
||||
await queryRunner.manager.save(SubspaceEntity, subspace);
|
||||
} else {
|
||||
await this.subspaceRepository.save(subspace);
|
||||
}
|
||||
|
||||
const subspaceAllocations = subspaceModel.productAllocations.map(
|
||||
(modelAllocation) =>
|
||||
this.subspaceProductAllocationRepository.create({
|
||||
subspace,
|
||||
inheritedFromModel: modelAllocation,
|
||||
product: modelAllocation.product,
|
||||
tags: modelAllocation.tags,
|
||||
}),
|
||||
);
|
||||
|
||||
if (subspaceAllocations.length) {
|
||||
if (queryRunner) {
|
||||
await queryRunner.manager.save(
|
||||
SubspaceProductAllocationEntity,
|
||||
subspaceAllocations,
|
||||
);
|
||||
} else {
|
||||
await this.subspaceProductAllocationRepository.save(
|
||||
subspaceAllocations,
|
||||
);
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
`Failed to link space ${space.uuid} to space model ${spaceModel.uuid}: ${error.message}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async overwriteSpace(
|
||||
space: SpaceEntity,
|
||||
project: ProjectEntity,
|
||||
queryRunner?: QueryRunner,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const spaceProductAllocationRepository = queryRunner
|
||||
? queryRunner.manager.getRepository(SpaceProductAllocationEntity)
|
||||
: this.spaceProductAllocationRepository;
|
||||
|
||||
const subspaceRepository = queryRunner
|
||||
? queryRunner.manager.getRepository(SubspaceEntity)
|
||||
: this.subspaceRepository;
|
||||
|
||||
const subspaceProductAllocationRepository = queryRunner
|
||||
? queryRunner.manager.getRepository(SubspaceProductAllocationEntity)
|
||||
: this.subspaceProductAllocationRepository;
|
||||
|
||||
const spaceRepository = queryRunner
|
||||
? queryRunner.manager.getRepository(SpaceEntity)
|
||||
: this.spaceRepository;
|
||||
|
||||
const deviceRepository = queryRunner
|
||||
? queryRunner.manager.getRepository(DeviceEntity)
|
||||
: this.deviceRepository;
|
||||
|
||||
if (space.productAllocations.length) {
|
||||
await spaceProductAllocationRepository.delete({
|
||||
uuid: In(
|
||||
space.productAllocations.map((allocation) => allocation.uuid),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
space.subspaces.map(async (subspace) => {
|
||||
await subspaceRepository.update(
|
||||
{ uuid: subspace.uuid },
|
||||
{ disabled: true },
|
||||
);
|
||||
|
||||
if (subspace.productAllocations.length) {
|
||||
await subspaceProductAllocationRepository.delete({
|
||||
uuid: In(
|
||||
subspace.productAllocations.map(
|
||||
(allocation) => allocation.uuid,
|
||||
),
|
||||
),
|
||||
});
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
if (space.devices.length > 0) {
|
||||
const orphanSpace = await 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,
|
||||
);
|
||||
}
|
||||
|
||||
await deviceRepository.update(
|
||||
{ uuid: In(space.devices.map((device) => device.uuid)) },
|
||||
{ spaceDevice: orphanSpace },
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to overwrite space ${space.uuid}: ${error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async validateName(modelName: string, projectUuid: string): Promise<void> {
|
||||
const isModelExist = await this.spaceModelRepository.findOne({
|
||||
where: { modelName, project: { uuid: projectUuid }, disabled: false },
|
||||
@ -350,11 +627,15 @@ export class SpaceModelService {
|
||||
async findOne(params: SpaceModelParam): Promise<BaseResponseDto> {
|
||||
try {
|
||||
await this.validateProject(params.projectUuid);
|
||||
const spaceModel = await this.validateSpaceModel(params.spaceModelUuid);
|
||||
const spaceModel = await this.validateSpaceModel(
|
||||
params.spaceModelUuid,
|
||||
params.projectUuid,
|
||||
);
|
||||
const response = this.formatSpaceModelResponse(spaceModel);
|
||||
|
||||
return new SuccessResponseDto({
|
||||
message: 'SpaceModel retrieved successfully',
|
||||
data: spaceModel,
|
||||
data: response,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
@ -364,8 +645,57 @@ export class SpaceModelService {
|
||||
}
|
||||
}
|
||||
|
||||
async validateSpaceModel(uuid: string): Promise<SpaceModelEntity> {
|
||||
const spaceModel = await this.spaceModelRepository
|
||||
async validateSpaceModel(
|
||||
uuid: string,
|
||||
projectUuid?: string,
|
||||
): Promise<SpaceModelEntity> {
|
||||
const query = this.buildSpaceModelQuery(projectUuid);
|
||||
const result = await query
|
||||
.andWhere('spaceModel.uuid = :uuid', { uuid })
|
||||
.getOne();
|
||||
|
||||
if (!result) {
|
||||
throw new HttpException('space model not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private validateUniqueTags(allTags: ProcessTagDto[]) {
|
||||
const tagUuidSet = new Set<string>();
|
||||
const tagNameProductSet = new Set<string>();
|
||||
|
||||
for (const tag of allTags) {
|
||||
if (tag.uuid) {
|
||||
if (tagUuidSet.has(tag.uuid)) {
|
||||
throw new HttpException(
|
||||
`Duplicate tag UUID found: ${tag.uuid}`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
tagUuidSet.add(tag.uuid);
|
||||
} else {
|
||||
if (!tag.name || !tag.productUuid) {
|
||||
throw new HttpException(
|
||||
`Tag name and product should not be null.`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
const tagKey = `${tag.name}-${tag.productUuid}`;
|
||||
if (tagNameProductSet.has(tagKey)) {
|
||||
throw new HttpException(
|
||||
`Duplicate tag found with name "${tag.name}" and product "${tag.productUuid}".`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
tagNameProductSet.add(tagKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private buildSpaceModelQuery(
|
||||
projectUuid: string,
|
||||
): SelectQueryBuilder<SpaceModelEntity> {
|
||||
return this.spaceModelRepository
|
||||
.createQueryBuilder('spaceModel')
|
||||
.leftJoinAndSelect(
|
||||
'spaceModel.subspaceModels',
|
||||
@ -374,27 +704,90 @@ export class SpaceModelService {
|
||||
{ subspaceDisabled: false },
|
||||
)
|
||||
.leftJoinAndSelect(
|
||||
'spaceModel.tags',
|
||||
'tags',
|
||||
'tags.disabled = :tagsDisabled',
|
||||
{ tagsDisabled: false },
|
||||
'subspaceModels.productAllocations',
|
||||
'subspaceModelProductAllocations',
|
||||
)
|
||||
.leftJoinAndSelect('tags.product', 'spaceTagproduct')
|
||||
.leftJoinAndSelect(
|
||||
'subspaceModels.tags',
|
||||
'subspaceModelProductAllocations.tags',
|
||||
'subspaceModelTags',
|
||||
'subspaceModelTags.disabled = :subspaceModelTagsDisabled',
|
||||
{ subspaceModelTagsDisabled: false },
|
||||
)
|
||||
.leftJoinAndSelect('subspaceModelTags.product', 'subspaceTagproduct')
|
||||
.where('spaceModel.disabled = :disabled', { disabled: false })
|
||||
.where('spaceModel.disabled = :disabled', { disabled: false })
|
||||
.andWhere('spaceModel.uuid = :uuid', { uuid })
|
||||
.getOne();
|
||||
.leftJoinAndSelect('subspaceModelTags.product', 'subspaceModelTagProduct')
|
||||
.leftJoinAndSelect('spaceModel.productAllocations', 'productAllocations')
|
||||
.leftJoinAndSelect('productAllocations.product', 'allocatedProduct')
|
||||
.leftJoinAndSelect('productAllocations.tags', 'productTags')
|
||||
.leftJoinAndSelect('productTags.product', 'productTagProduct')
|
||||
.where('spaceModel.disabled = false')
|
||||
.andWhere('spaceModel.project = :projectUuid', { projectUuid });
|
||||
}
|
||||
|
||||
if (!spaceModel) {
|
||||
throw new HttpException('space model not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
return spaceModel;
|
||||
private transformSpaceModelData(spaceModelsArray: SpaceModelEntity[]): any[] {
|
||||
if (!Array.isArray(spaceModelsArray)) return [];
|
||||
|
||||
return spaceModelsArray.map((spaceModel) => ({
|
||||
uuid: spaceModel.uuid,
|
||||
createdAt: spaceModel.createdAt,
|
||||
updatedAt: spaceModel.updatedAt,
|
||||
modelName: spaceModel.modelName,
|
||||
disabled: spaceModel.disabled,
|
||||
subspaceModels: (spaceModel.subspaceModels ?? []).map((subspace) => ({
|
||||
uuid: subspace.uuid,
|
||||
createdAt: subspace.createdAt,
|
||||
updatedAt: subspace.updatedAt,
|
||||
subspaceName: subspace.subspaceName,
|
||||
disabled: subspace.disabled,
|
||||
tags: this.extractTags(subspace.productAllocations),
|
||||
})),
|
||||
tags: this.extractTags(spaceModel.productAllocations),
|
||||
}));
|
||||
}
|
||||
|
||||
private extractTags(
|
||||
productAllocations:
|
||||
| SpaceModelProductAllocationEntity[]
|
||||
| SubspaceModelProductAllocationEntity[]
|
||||
| undefined,
|
||||
): any[] {
|
||||
if (!productAllocations) return [];
|
||||
|
||||
return productAllocations
|
||||
.flatMap((allocation) => allocation.tags ?? [])
|
||||
.map((tag) => ({
|
||||
uuid: tag.uuid,
|
||||
createdAt: tag.createdAt,
|
||||
updatedAt: tag.updatedAt,
|
||||
name: tag.name,
|
||||
disabled: tag.disabled,
|
||||
product: tag.product
|
||||
? {
|
||||
uuid: tag.product.uuid,
|
||||
createdAt: tag.product.createdAt,
|
||||
updatedAt: tag.product.updatedAt,
|
||||
catName: tag.product.catName,
|
||||
prodId: tag.product.prodId,
|
||||
name: tag.product.name,
|
||||
prodType: tag.product.prodType,
|
||||
}
|
||||
: null,
|
||||
}));
|
||||
}
|
||||
|
||||
private formatSpaceModelResponse(spaceModel: SpaceModelEntity): any {
|
||||
return {
|
||||
uuid: spaceModel.uuid,
|
||||
createdAt: spaceModel.createdAt,
|
||||
updatedAt: spaceModel.updatedAt,
|
||||
modelName: spaceModel.modelName,
|
||||
disabled: spaceModel.disabled,
|
||||
subspaceModels:
|
||||
spaceModel.subspaceModels?.map((subspace) => ({
|
||||
uuid: subspace.uuid,
|
||||
createdAt: subspace.createdAt,
|
||||
updatedAt: subspace.updatedAt,
|
||||
subspaceName: subspace.subspaceName,
|
||||
disabled: subspace.disabled,
|
||||
tags: this.extractTags(subspace.productAllocations),
|
||||
})) ?? [],
|
||||
tags: this.extractTags(spaceModel.productAllocations),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,563 @@
|
||||
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
||||
import {
|
||||
SpaceModelEntity,
|
||||
SpaceModelProductAllocationEntity,
|
||||
SpaceModelProductAllocationRepoitory,
|
||||
SubspaceModelEntity,
|
||||
SubspaceModelProductAllocationEntity,
|
||||
SubspaceModelProductAllocationRepoitory,
|
||||
} from '@app/common/modules/space-model';
|
||||
import { NewTagEntity } from '@app/common/modules/tag';
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { ModifyTagModelDto } from 'src/space-model/dtos';
|
||||
import { ISingleSubspaceModel } from 'src/space-model/interfaces';
|
||||
import { IUpdatedAllocations } from 'src/space-model/interfaces/subspace-product-allocation-update-result.interface';
|
||||
import { ProcessTagDto } from 'src/tags/dtos';
|
||||
import { TagService as NewTagService } from 'src/tags/services';
|
||||
import { In, QueryRunner } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
export class SubspaceModelProductAllocationService {
|
||||
constructor(
|
||||
private readonly tagService: NewTagService,
|
||||
private readonly subspaceModelProductAllocationRepository: SubspaceModelProductAllocationRepoitory,
|
||||
private readonly spaceModelAllocationRepository: SpaceModelProductAllocationRepoitory,
|
||||
) {}
|
||||
|
||||
async createProductAllocations(
|
||||
subspaceModel: SubspaceModelEntity,
|
||||
spaceModel: SpaceModelEntity,
|
||||
tags: NewTagEntity[],
|
||||
queryRunner?: QueryRunner,
|
||||
spaceAllocationsToExclude?: SpaceModelProductAllocationEntity[],
|
||||
): Promise<IUpdatedAllocations[]> {
|
||||
try {
|
||||
const updatedAllocations: IUpdatedAllocations[] = [];
|
||||
|
||||
const allocations: SubspaceModelProductAllocationEntity[] = [];
|
||||
|
||||
for (const tag of tags) {
|
||||
// Step 1: Check if this specific tag is already allocated at the space level
|
||||
const existingTagInSpaceModel = await (queryRunner
|
||||
? queryRunner.manager.findOne(SpaceModelProductAllocationEntity, {
|
||||
where: {
|
||||
spaceModel: {
|
||||
uuid: spaceModel.uuid,
|
||||
}, // Check at the space level
|
||||
tags: { uuid: tag.uuid }, // Check for the specific tag
|
||||
},
|
||||
relations: ['tags'],
|
||||
})
|
||||
: this.spaceModelAllocationRepository.findOne({
|
||||
where: {
|
||||
spaceModel: {
|
||||
uuid: spaceModel.uuid,
|
||||
},
|
||||
tags: { uuid: tag.uuid },
|
||||
},
|
||||
relations: ['tags', 'product'],
|
||||
}));
|
||||
|
||||
const isExcluded = spaceAllocationsToExclude?.some(
|
||||
(excludedAllocation) =>
|
||||
excludedAllocation.product.uuid === tag.product.uuid &&
|
||||
excludedAllocation.tags.some((t) => t.uuid === tag.uuid),
|
||||
);
|
||||
|
||||
// If tag is found at the space level, prevent allocation at the subspace level
|
||||
if (!isExcluded && existingTagInSpaceModel) {
|
||||
throw new HttpException(
|
||||
`Tag ${tag.uuid} (Product: ${tag.product.uuid}) is already allocated at the space level (${subspaceModel.spaceModel.uuid}). Cannot allocate the same tag in a subspace.`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
// Check if this specific tag is already allocated within another subspace of the same space
|
||||
const existingTagInSameSpace = await (queryRunner
|
||||
? queryRunner.manager.findOne(SubspaceModelProductAllocationEntity, {
|
||||
where: {
|
||||
product: { uuid: tag.product.uuid },
|
||||
subspaceModel: { spaceModel: subspaceModel.spaceModel },
|
||||
tags: { uuid: tag.uuid }, // Ensure the exact tag is checked
|
||||
},
|
||||
relations: ['subspaceModel', 'tags'],
|
||||
})
|
||||
: this.subspaceModelProductAllocationRepository.findOne({
|
||||
where: {
|
||||
product: { uuid: tag.product.uuid },
|
||||
subspaceModel: { spaceModel: subspaceModel.spaceModel },
|
||||
tags: { uuid: tag.uuid },
|
||||
},
|
||||
relations: ['subspaceModel', 'tags'],
|
||||
}));
|
||||
|
||||
// Prevent duplicate allocation if tag exists in another subspace of the same space
|
||||
if (
|
||||
existingTagInSameSpace &&
|
||||
existingTagInSameSpace.subspaceModel.uuid !== subspaceModel.uuid
|
||||
) {
|
||||
throw new HttpException(
|
||||
`Tag ${tag.uuid} (Product: ${tag.product.uuid}) is already allocated in another subspace (${existingTagInSameSpace.subspaceModel.uuid}) within the same space (${subspaceModel.spaceModel.uuid}).`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
//Check if there are existing allocations for this product in the subspace
|
||||
const existingAllocationsForProduct = await (queryRunner
|
||||
? queryRunner.manager.find(SubspaceModelProductAllocationEntity, {
|
||||
where: {
|
||||
subspaceModel: { uuid: subspaceModel.uuid },
|
||||
product: { uuid: tag.product.uuid },
|
||||
},
|
||||
relations: ['tags'],
|
||||
})
|
||||
: this.subspaceModelProductAllocationRepository.find({
|
||||
where: {
|
||||
subspaceModel: { uuid: subspaceModel.uuid },
|
||||
product: { uuid: tag.product.uuid },
|
||||
},
|
||||
relations: ['tags'],
|
||||
}));
|
||||
|
||||
//Flatten all existing tags for this product in the subspace
|
||||
const existingTagsForProduct = existingAllocationsForProduct.flatMap(
|
||||
(allocation) => allocation.tags,
|
||||
);
|
||||
|
||||
// Check if the tag is already assigned to the same product in this subspace
|
||||
const isDuplicateTag = existingTagsForProduct.some(
|
||||
(existingTag) => existingTag.uuid === tag.uuid,
|
||||
);
|
||||
|
||||
if (isDuplicateTag) {
|
||||
throw new HttpException(
|
||||
`Tag ${tag.uuid} is already allocated to product ${tag.product.uuid} within this subspace (${subspaceModel.uuid}).`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
// If no existing allocation, create a new one
|
||||
if (existingAllocationsForProduct.length === 0) {
|
||||
const allocation = queryRunner
|
||||
? queryRunner.manager.create(SubspaceModelProductAllocationEntity, {
|
||||
subspaceModel,
|
||||
product: tag.product,
|
||||
tags: [tag],
|
||||
})
|
||||
: this.subspaceModelProductAllocationRepository.create({
|
||||
subspaceModel,
|
||||
product: tag.product,
|
||||
tags: [tag],
|
||||
});
|
||||
allocations.push(allocation);
|
||||
} else {
|
||||
//If allocation exists, add the tag to it
|
||||
existingAllocationsForProduct[0].tags.push(tag);
|
||||
if (queryRunner) {
|
||||
await queryRunner.manager.save(
|
||||
SubspaceModelProductAllocationEntity,
|
||||
existingAllocationsForProduct[0],
|
||||
);
|
||||
} else {
|
||||
await this.subspaceModelProductAllocationRepository.save(
|
||||
existingAllocationsForProduct[0],
|
||||
);
|
||||
}
|
||||
updatedAllocations.push({
|
||||
allocation: existingAllocationsForProduct[0],
|
||||
tagsAdded: [tag],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Save newly created allocations
|
||||
if (allocations.length > 0) {
|
||||
if (queryRunner) {
|
||||
await queryRunner.manager.save(
|
||||
SubspaceModelProductAllocationEntity,
|
||||
allocations,
|
||||
);
|
||||
} else {
|
||||
await this.subspaceModelProductAllocationRepository.save(allocations);
|
||||
}
|
||||
|
||||
allocations.forEach((allocation) => {
|
||||
updatedAllocations.push({ newAllocation: allocation });
|
||||
});
|
||||
}
|
||||
|
||||
return updatedAllocations;
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
`An unexpected error occurred while creating subspace product allocations ${error}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async processDeleteActions(
|
||||
dtos: ModifyTagModelDto[],
|
||||
queryRunner: QueryRunner,
|
||||
): Promise<SubspaceModelProductAllocationEntity[]> {
|
||||
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(
|
||||
SubspaceModelProductAllocationEntity,
|
||||
{
|
||||
where: { tags: { uuid: In(tagUuidsToDelete) } },
|
||||
relations: ['tags'],
|
||||
},
|
||||
);
|
||||
|
||||
if (!allocationsToUpdate || allocationsToUpdate.length === 0) return [];
|
||||
|
||||
const deletedAllocations: SubspaceModelProductAllocationEntity[] = [];
|
||||
const allocationUpdates: SubspaceModelProductAllocationEntity[] = [];
|
||||
|
||||
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(
|
||||
SubspaceModelProductAllocationEntity,
|
||||
allocationUpdates,
|
||||
);
|
||||
}
|
||||
|
||||
if (deletedAllocations.length > 0) {
|
||||
await queryRunner.manager.remove(
|
||||
SubspaceModelProductAllocationEntity,
|
||||
deletedAllocations,
|
||||
);
|
||||
}
|
||||
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from('subspace_model_product_tags')
|
||||
.where(
|
||||
'subspace_model_product_allocation_uuid NOT IN (' +
|
||||
queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.select('allocation.uuid')
|
||||
.from(SubspaceModelProductAllocationEntity, 'allocation')
|
||||
.getQuery() +
|
||||
')',
|
||||
)
|
||||
.execute();
|
||||
|
||||
return deletedAllocations;
|
||||
} catch (error) {
|
||||
throw this.handleError(error, `Failed to delete tags in subspace model`);
|
||||
}
|
||||
}
|
||||
|
||||
async updateAllocations(
|
||||
subspaceModels: ISingleSubspaceModel[],
|
||||
projectUuid: string,
|
||||
queryRunner: QueryRunner,
|
||||
spaceModel: SpaceModelEntity,
|
||||
spaceTagUpdateDtos?: ModifyTagModelDto[],
|
||||
) {
|
||||
const spaceAllocationToExclude: SpaceModelProductAllocationEntity[] = [];
|
||||
const updatedAllocations: IUpdatedAllocations[] = [];
|
||||
|
||||
for (const subspaceModel of subspaceModels) {
|
||||
const tagDtos = subspaceModel.tags;
|
||||
if (tagDtos.length > 0) {
|
||||
const tagsToAddDto: ProcessTagDto[] = tagDtos
|
||||
.filter((dto) => dto.action === ModifyAction.ADD)
|
||||
.map((dto) => ({
|
||||
name: dto.name,
|
||||
productUuid: dto.productUuid,
|
||||
uuid: dto.newTagUuid,
|
||||
}));
|
||||
|
||||
const tagsToDeleteDto = tagDtos.filter(
|
||||
(dto) => dto.action === ModifyAction.DELETE,
|
||||
);
|
||||
|
||||
if (tagsToAddDto.length > 0) {
|
||||
let processedTags = await this.tagService.processTags(
|
||||
tagsToAddDto,
|
||||
projectUuid,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
for (const subspaceDto of subspaceModels) {
|
||||
if (
|
||||
subspaceDto !== subspaceModel &&
|
||||
subspaceDto.action === ModifyAction.UPDATE &&
|
||||
subspaceDto.tags
|
||||
) {
|
||||
// Tag is deleted from one subspace and added in another subspace
|
||||
const deletedTags = subspaceDto.tags.filter(
|
||||
(tagDto) =>
|
||||
tagDto.action === ModifyAction.DELETE &&
|
||||
processedTags.some((tag) => tag.uuid === tagDto.tagUuid),
|
||||
);
|
||||
|
||||
for (const deletedTag of deletedTags) {
|
||||
const allocation = await queryRunner.manager.findOne(
|
||||
SubspaceModelProductAllocationEntity,
|
||||
{
|
||||
where: {
|
||||
tags: {
|
||||
uuid: deletedTag.tagUuid,
|
||||
},
|
||||
subspaceModel: {
|
||||
uuid: subspaceDto.subspaceModel.uuid,
|
||||
},
|
||||
},
|
||||
relations: ['tags', 'product', 'subspaceModel'],
|
||||
},
|
||||
);
|
||||
if (allocation) {
|
||||
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,
|
||||
);
|
||||
|
||||
updatedAllocations.push({
|
||||
allocation,
|
||||
tagsRemoved: [tagEntity],
|
||||
});
|
||||
|
||||
await queryRunner.manager.save(allocation);
|
||||
|
||||
const productAllocationExistInSubspace =
|
||||
await queryRunner.manager.findOne(
|
||||
SubspaceModelProductAllocationEntity,
|
||||
{
|
||||
where: {
|
||||
subspaceModel: {
|
||||
uuid: subspaceDto.subspaceModel.uuid,
|
||||
},
|
||||
product: { uuid: allocation.product.uuid },
|
||||
},
|
||||
relations: ['tags'],
|
||||
},
|
||||
);
|
||||
|
||||
if (productAllocationExistInSubspace) {
|
||||
productAllocationExistInSubspace.tags.push(tagEntity);
|
||||
|
||||
updatedAllocations.push({
|
||||
allocation: productAllocationExistInSubspace,
|
||||
tagsAdded: [tagEntity],
|
||||
});
|
||||
|
||||
await queryRunner.manager.save(
|
||||
productAllocationExistInSubspace,
|
||||
);
|
||||
} else {
|
||||
const newProductAllocation = queryRunner.manager.create(
|
||||
SubspaceModelProductAllocationEntity,
|
||||
{
|
||||
subspaceModel: subspaceModel.subspaceModel,
|
||||
product: allocation.product,
|
||||
tags: [tagEntity],
|
||||
},
|
||||
);
|
||||
|
||||
updatedAllocations.push({
|
||||
allocation: newProductAllocation,
|
||||
});
|
||||
|
||||
await queryRunner.manager.save(newProductAllocation);
|
||||
}
|
||||
|
||||
// Remove the tag from processedTags to prevent duplication
|
||||
processedTags = processedTags.filter(
|
||||
(tag) => tag.uuid !== deletedTag.tagUuid,
|
||||
);
|
||||
|
||||
// Remove the tag from subspaceDto.tags to ensure it's not processed again while processing other dtos.
|
||||
subspaceDto.tags = subspaceDto.tags.filter(
|
||||
(tagDto) => tagDto.tagUuid !== deletedTag.tagUuid,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
subspaceDto !== subspaceModel &&
|
||||
subspaceDto.action === ModifyAction.DELETE
|
||||
) {
|
||||
const allocation = await queryRunner.manager.findOne(
|
||||
SubspaceModelProductAllocationEntity,
|
||||
{
|
||||
where: {
|
||||
subspaceModel: { uuid: subspaceDto.subspaceModel.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,
|
||||
),
|
||||
);
|
||||
|
||||
updatedAllocations.push({
|
||||
allocation: allocation,
|
||||
tagsRemoved: repeatedTags,
|
||||
});
|
||||
|
||||
await queryRunner.manager.save(allocation);
|
||||
|
||||
const productAllocationExistInSubspace =
|
||||
await queryRunner.manager.findOne(
|
||||
SubspaceModelProductAllocationEntity,
|
||||
{
|
||||
where: {
|
||||
subspaceModel: { uuid: subspaceDto.subspaceModel.uuid },
|
||||
product: { uuid: allocation.product.uuid },
|
||||
},
|
||||
relations: ['tags'],
|
||||
},
|
||||
);
|
||||
|
||||
if (productAllocationExistInSubspace) {
|
||||
updatedAllocations.push({
|
||||
allocation: productAllocationExistInSubspace,
|
||||
tagsAdded: repeatedTags,
|
||||
});
|
||||
|
||||
productAllocationExistInSubspace.tags.push(...repeatedTags);
|
||||
await queryRunner.manager.save(
|
||||
productAllocationExistInSubspace,
|
||||
);
|
||||
} else {
|
||||
const newProductAllocation = queryRunner.manager.create(
|
||||
SubspaceModelProductAllocationEntity,
|
||||
{
|
||||
subspaceModel: subspaceModel.subspaceModel,
|
||||
product: allocation.product,
|
||||
tags: repeatedTags,
|
||||
},
|
||||
);
|
||||
|
||||
updatedAllocations.push({
|
||||
newAllocation: newProductAllocation,
|
||||
});
|
||||
|
||||
await queryRunner.manager.save(newProductAllocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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(
|
||||
SpaceModelProductAllocationEntity,
|
||||
{
|
||||
where: {
|
||||
spaceModel: { uuid: spaceModel.uuid },
|
||||
tags: { uuid: deletedTag.tagUuid },
|
||||
},
|
||||
relations: ['tags', 'product'],
|
||||
},
|
||||
);
|
||||
|
||||
if (
|
||||
allocation &&
|
||||
allocation.tags.some((tag) => tag.uuid === deletedTag.tagUuid)
|
||||
) {
|
||||
spaceAllocationToExclude.push(allocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create new product allocations
|
||||
const newAllocations = await this.createProductAllocations(
|
||||
subspaceModel.subspaceModel,
|
||||
spaceModel,
|
||||
processedTags,
|
||||
queryRunner,
|
||||
spaceAllocationToExclude,
|
||||
);
|
||||
return [...updatedAllocations, ...newAllocations];
|
||||
}
|
||||
if (tagsToDeleteDto.length > 0) {
|
||||
await this.processDeleteActions(tagsToDeleteDto, queryRunner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async clearAllAllocations(subspaceIds: string[], queryRunner: QueryRunner) {
|
||||
try {
|
||||
await queryRunner.manager.delete(SubspaceModelProductAllocationEntity, {
|
||||
subspaceModel: In(subspaceIds),
|
||||
});
|
||||
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(SubspaceModelProductAllocationEntity)
|
||||
.where('"subspace_model_uuid" IN (:...subspaceIds)', {
|
||||
subspaceIds,
|
||||
})
|
||||
.execute();
|
||||
} catch (error) {
|
||||
throw this.handleError(
|
||||
error,
|
||||
`Failed to clear all allocations subspace model product allocation`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private handleError(error: any, message: string): HttpException {
|
||||
return new HttpException(
|
||||
error instanceof HttpException ? error.message : message,
|
||||
error instanceof HttpException
|
||||
? error.getStatus()
|
||||
: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,308 +1,434 @@
|
||||
import {
|
||||
SpaceModelEntity,
|
||||
SpaceModelProductAllocationEntity,
|
||||
SubspaceModelEntity,
|
||||
SubspaceModelProductAllocationEntity,
|
||||
SubspaceModelRepository,
|
||||
} from '@app/common/modules/space-model';
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { CreateSubspaceModelDto, CreateTagModelDto } from '../../dtos';
|
||||
import { Not, QueryRunner } from 'typeorm';
|
||||
import {
|
||||
IDeletedSubsaceModelInterface,
|
||||
ModifySubspaceModelPayload,
|
||||
UpdatedSubspaceModelPayload,
|
||||
} from 'src/space-model/interfaces';
|
||||
import {
|
||||
DeleteSubspaceModelDto,
|
||||
ModifySubspaceModelDto,
|
||||
} from 'src/space-model/dtos/subspaces-model-dtos';
|
||||
import { TagModelService } from '../tag-model.service';
|
||||
import { CreateSubspaceModelDto, ModifyTagModelDto } from '../../dtos';
|
||||
import { In, Not, QueryFailedError, QueryRunner } from 'typeorm';
|
||||
import { ModifySubspaceModelDto } from 'src/space-model/dtos/subspaces-model-dtos';
|
||||
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
||||
import { ProcessTagDto } from 'src/tags/dtos';
|
||||
import { TagService } from 'src/tags/services';
|
||||
import { SubspaceModelProductAllocationService } from './subspace-model-product-allocation.service';
|
||||
import {
|
||||
IRelocatedAllocation,
|
||||
ISingleSubspaceModel,
|
||||
} from 'src/space-model/interfaces';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||
import { SubSpaceService } from 'src/space/services/subspace';
|
||||
|
||||
@Injectable()
|
||||
export class SubSpaceModelService {
|
||||
constructor(
|
||||
private readonly subspaceModelRepository: SubspaceModelRepository,
|
||||
private readonly tagModelService: TagModelService,
|
||||
private readonly tagService: TagService,
|
||||
private readonly productAllocationService: SubspaceModelProductAllocationService,
|
||||
private readonly subspaceService: SubSpaceService,
|
||||
) {}
|
||||
|
||||
async createSubSpaceModels(
|
||||
subSpaceModelDtos: CreateSubspaceModelDto[],
|
||||
async createModels(
|
||||
spaceModel: SpaceModelEntity,
|
||||
dtos: CreateSubspaceModelDto[],
|
||||
queryRunner: QueryRunner,
|
||||
otherTags?: CreateTagModelDto[],
|
||||
): Promise<SubspaceModelEntity[]> {
|
||||
) {
|
||||
try {
|
||||
await this.validateInputDtos(subSpaceModelDtos, spaceModel);
|
||||
this.validateNamesInDTO(dtos.map((dto) => dto.subspaceName));
|
||||
|
||||
const subspaces = subSpaceModelDtos.map((subspaceDto) =>
|
||||
const subspaceEntities: SubspaceModelEntity[] = dtos.map((dto) =>
|
||||
queryRunner.manager.create(this.subspaceModelRepository.target, {
|
||||
subspaceName: subspaceDto.subspaceName,
|
||||
subspaceName: dto.subspaceName,
|
||||
spaceModel,
|
||||
}),
|
||||
);
|
||||
|
||||
const savedSubspaces = await queryRunner.manager.save(subspaces);
|
||||
const savedSubspaces = await queryRunner.manager.save(subspaceEntities);
|
||||
|
||||
await Promise.all(
|
||||
subSpaceModelDtos.map(async (dto, index) => {
|
||||
const subspace = savedSubspaces[index];
|
||||
for (const [index, dto] of dtos.entries()) {
|
||||
const subspaceModel = savedSubspaces[index];
|
||||
|
||||
const otherDtoTags = subSpaceModelDtos
|
||||
.filter((_, i) => i !== index)
|
||||
.flatMap((otherDto) => otherDto.tags || []);
|
||||
if (dto.tags && dto.tags.length > 0) {
|
||||
subspace.tags = await this.tagModelService.createTags(
|
||||
dto.tags,
|
||||
queryRunner,
|
||||
null,
|
||||
subspace,
|
||||
[...(otherTags || []), ...otherDtoTags],
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
const processedTags = await this.tagService.processTags(
|
||||
dto.tags,
|
||||
spaceModel.project.uuid,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
await this.productAllocationService.createProductAllocations(
|
||||
subspaceModel,
|
||||
spaceModel,
|
||||
processedTags,
|
||||
queryRunner,
|
||||
);
|
||||
}
|
||||
|
||||
return savedSubspaces;
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) {
|
||||
throw error; // Rethrow known HttpExceptions
|
||||
throw new HttpException(
|
||||
error instanceof HttpException
|
||||
? error.message
|
||||
: 'An unexpected error occurred while creating subspace models',
|
||||
error instanceof HttpException
|
||||
? error.getStatus()
|
||||
: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async handleAddAction(
|
||||
dtos: ModifySubspaceModelDto[],
|
||||
spaceModel: SpaceModelEntity,
|
||||
queryRunner: QueryRunner,
|
||||
spaces?: SpaceEntity[],
|
||||
): Promise<ISingleSubspaceModel[]> {
|
||||
if (!dtos.length) return [];
|
||||
|
||||
const subspaceNames = dtos.map((dto) => dto.subspaceName);
|
||||
await this.checkDuplicateNamesBatch(subspaceNames, spaceModel.uuid);
|
||||
|
||||
const subspaceEntities = dtos.map((dto) =>
|
||||
queryRunner.manager.create(SubspaceModelEntity, {
|
||||
subspaceName: dto.subspaceName,
|
||||
spaceModel,
|
||||
}),
|
||||
);
|
||||
|
||||
const savedSubspaces = await queryRunner.manager.save(subspaceEntities);
|
||||
|
||||
if (spaces) {
|
||||
await this.subspaceService.createSubSpaceFromModel(
|
||||
savedSubspaces,
|
||||
spaces,
|
||||
queryRunner,
|
||||
);
|
||||
}
|
||||
|
||||
return savedSubspaces.map((subspace, index) => ({
|
||||
subspaceModel: subspace,
|
||||
action: ModifyAction.ADD,
|
||||
tags: dtos[index].tags ? dtos[index].tags : [],
|
||||
}));
|
||||
}
|
||||
|
||||
async modifySubspaceModels(
|
||||
dtos: ModifySubspaceModelDto[],
|
||||
spaceModel: SpaceModelEntity,
|
||||
queryRunner: QueryRunner,
|
||||
projectUuid: string,
|
||||
spaceTagUpdateDtos?: ModifyTagModelDto[],
|
||||
spaces?: SpaceEntity[],
|
||||
): Promise<ISingleSubspaceModel[]> {
|
||||
try {
|
||||
if (!dtos || dtos.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle unexpected errors
|
||||
const addDtos = dtos.filter((dto) => dto.action === ModifyAction.ADD);
|
||||
const combinedDtos = dtos.filter(
|
||||
(dto) => dto.action !== ModifyAction.ADD,
|
||||
);
|
||||
const deleteDtos = dtos.filter(
|
||||
(dto) => dto.action === ModifyAction.DELETE,
|
||||
);
|
||||
|
||||
const updatedSubspaces = await this.updateSubspaceModel(
|
||||
combinedDtos,
|
||||
spaceModel,
|
||||
queryRunner,
|
||||
);
|
||||
const createdSubspaces = await this.handleAddAction(
|
||||
addDtos,
|
||||
spaceModel,
|
||||
queryRunner,
|
||||
spaces,
|
||||
);
|
||||
const combineModels = [...createdSubspaces, ...updatedSubspaces];
|
||||
|
||||
await this.productAllocationService.updateAllocations(
|
||||
combineModels,
|
||||
projectUuid,
|
||||
queryRunner,
|
||||
spaceModel,
|
||||
spaceTagUpdateDtos,
|
||||
);
|
||||
|
||||
const deletedSubspaces = await this.deleteSubspaceModels(
|
||||
deleteDtos,
|
||||
queryRunner,
|
||||
spaceModel,
|
||||
spaceTagUpdateDtos,
|
||||
);
|
||||
return [
|
||||
createdSubspaces ?? [],
|
||||
updatedSubspaces ?? [],
|
||||
deletedSubspaces ?? [],
|
||||
]
|
||||
.filter((arr) => arr.length > 0)
|
||||
.flat();
|
||||
} catch (error) {
|
||||
console.error('Error in modifySubspaceModels:', error);
|
||||
throw new HttpException(
|
||||
`An error occurred while creating subspace models: ${error.message}`,
|
||||
{
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
message: 'An error occurred while modifying subspace models',
|
||||
error: error.message,
|
||||
},
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteSubspaceModels(
|
||||
deleteDtos: DeleteSubspaceModelDto[],
|
||||
deleteDtos: ModifySubspaceModelDto[],
|
||||
queryRunner: QueryRunner,
|
||||
): Promise<IDeletedSubsaceModelInterface[]> {
|
||||
const deleteResults: IDeletedSubsaceModelInterface[] = [];
|
||||
spaceModel: SpaceModelEntity,
|
||||
spaceTagUpdateDtos?: ModifyTagModelDto[],
|
||||
): Promise<ISingleSubspaceModel[]> {
|
||||
try {
|
||||
if (!deleteDtos || deleteDtos.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
for (const dto of deleteDtos) {
|
||||
const subspaceModel = await this.findOne(dto.subspaceUuid);
|
||||
const deleteResults = [];
|
||||
const subspaceUuids = deleteDtos.map((dto) => dto.uuid).filter(Boolean);
|
||||
|
||||
if (subspaceUuids.length === 0) {
|
||||
throw new Error('Invalid subspace UUIDs provided.');
|
||||
}
|
||||
|
||||
const subspaces = await this.getSubspacesByUuids(
|
||||
queryRunner,
|
||||
subspaceUuids,
|
||||
);
|
||||
|
||||
if (!subspaces.length) return deleteResults;
|
||||
|
||||
await queryRunner.manager.update(
|
||||
this.subspaceModelRepository.target,
|
||||
{ uuid: dto.subspaceUuid },
|
||||
SubspaceModelEntity,
|
||||
{ uuid: In(subspaceUuids) },
|
||||
{ disabled: true },
|
||||
);
|
||||
|
||||
if (subspaceModel.tags?.length) {
|
||||
const modifyTagDtos = subspaceModel.tags.map((tag) => ({
|
||||
uuid: tag.uuid,
|
||||
action: ModifyAction.DELETE,
|
||||
}));
|
||||
await this.tagModelService.modifyTags(
|
||||
modifyTagDtos,
|
||||
queryRunner,
|
||||
null,
|
||||
subspaceModel,
|
||||
const allocationsToRemove = subspaces.flatMap((subspace) =>
|
||||
(subspace.productAllocations || []).map((allocation) => ({
|
||||
...allocation,
|
||||
subspaceModel: subspace,
|
||||
})),
|
||||
);
|
||||
|
||||
const relocatedAllocationsMap = new Map<string, IRelocatedAllocation[]>();
|
||||
if (allocationsToRemove.length > 0) {
|
||||
const spaceAllocationsMap = new Map<
|
||||
string,
|
||||
SpaceModelProductAllocationEntity
|
||||
>();
|
||||
|
||||
for (const allocation of allocationsToRemove) {
|
||||
const product = allocation.product;
|
||||
const tags = allocation.tags;
|
||||
const subspaceUuid = allocation.subspaceModel.uuid;
|
||||
const spaceAllocationKey = `${spaceModel.uuid}-${product.uuid}`;
|
||||
|
||||
if (!spaceAllocationsMap.has(spaceAllocationKey)) {
|
||||
const spaceAllocation = await queryRunner.manager.findOne(
|
||||
SpaceModelProductAllocationEntity,
|
||||
{
|
||||
where: {
|
||||
spaceModel: { uuid: spaceModel.uuid },
|
||||
product: { uuid: product.uuid },
|
||||
},
|
||||
relations: ['tags'],
|
||||
},
|
||||
);
|
||||
if (spaceAllocation) {
|
||||
spaceAllocationsMap.set(spaceAllocationKey, spaceAllocation);
|
||||
}
|
||||
}
|
||||
|
||||
const movedToAlreadyExistingSpaceAllocations: IRelocatedAllocation[] =
|
||||
[];
|
||||
const spaceAllocation = spaceAllocationsMap.get(spaceAllocationKey);
|
||||
|
||||
if (spaceAllocation) {
|
||||
const existingTagUuids = new Set(
|
||||
spaceAllocation.tags.map((tag) => tag.uuid),
|
||||
);
|
||||
const newTags = tags.filter(
|
||||
(tag) => !existingTagUuids.has(tag.uuid),
|
||||
);
|
||||
|
||||
if (newTags.length > 0) {
|
||||
movedToAlreadyExistingSpaceAllocations.push({
|
||||
tags: newTags,
|
||||
allocation: spaceAllocation,
|
||||
});
|
||||
spaceAllocation.tags.push(...newTags);
|
||||
|
||||
await queryRunner.manager.save(spaceAllocation);
|
||||
}
|
||||
} else {
|
||||
let tagsToAdd = [...tags];
|
||||
|
||||
if (spaceTagUpdateDtos && spaceTagUpdateDtos.length > 0) {
|
||||
const spaceTagDtosToAdd = spaceTagUpdateDtos.filter(
|
||||
(dto) => dto.action === ModifyAction.ADD,
|
||||
);
|
||||
|
||||
tagsToAdd = tagsToAdd.filter(
|
||||
(tag) =>
|
||||
!spaceTagDtosToAdd.some(
|
||||
(addDto) =>
|
||||
(addDto.name && addDto.name === tag.name) ||
|
||||
(addDto.newTagUuid && addDto.newTagUuid === tag.uuid),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (tagsToAdd.length > 0) {
|
||||
const newSpaceAllocation = queryRunner.manager.create(
|
||||
SpaceModelProductAllocationEntity,
|
||||
{
|
||||
spaceModel: spaceModel,
|
||||
product: product,
|
||||
tags: tags,
|
||||
},
|
||||
);
|
||||
|
||||
movedToAlreadyExistingSpaceAllocations.push({
|
||||
allocation: newSpaceAllocation,
|
||||
tags: tags,
|
||||
});
|
||||
await queryRunner.manager.save(newSpaceAllocation);
|
||||
}
|
||||
}
|
||||
|
||||
if (movedToAlreadyExistingSpaceAllocations.length > 0) {
|
||||
if (!relocatedAllocationsMap.has(subspaceUuid)) {
|
||||
relocatedAllocationsMap.set(subspaceUuid, []);
|
||||
}
|
||||
relocatedAllocationsMap
|
||||
.get(subspaceUuid)
|
||||
.push(...movedToAlreadyExistingSpaceAllocations);
|
||||
}
|
||||
}
|
||||
|
||||
await queryRunner.manager.remove(
|
||||
SubspaceModelProductAllocationEntity,
|
||||
allocationsToRemove,
|
||||
);
|
||||
}
|
||||
|
||||
deleteResults.push({ uuid: dto.subspaceUuid });
|
||||
}
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from('subspace_model_product_tags')
|
||||
.where(
|
||||
'subspace_model_product_allocation_uuid NOT IN (' +
|
||||
queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.select('allocation.uuid')
|
||||
.from(SubspaceModelProductAllocationEntity, 'allocation')
|
||||
.getQuery() +
|
||||
')',
|
||||
)
|
||||
.execute();
|
||||
|
||||
return deleteResults;
|
||||
}
|
||||
deleteResults.push(...subspaceUuids.map((uuid) => ({ uuid })));
|
||||
|
||||
async modifySubSpaceModels(
|
||||
subspaceDtos: ModifySubspaceModelDto[],
|
||||
spaceModel: SpaceModelEntity,
|
||||
queryRunner: QueryRunner,
|
||||
): Promise<ModifySubspaceModelPayload> {
|
||||
const modifiedSubspaceModels: ModifySubspaceModelPayload = {
|
||||
addedSubspaceModels: [],
|
||||
updatedSubspaceModels: [],
|
||||
deletedSubspaceModels: [],
|
||||
};
|
||||
try {
|
||||
for (const subspace of subspaceDtos) {
|
||||
switch (subspace.action) {
|
||||
case ModifyAction.ADD:
|
||||
const subspaceModel = await this.handleAddAction(
|
||||
subspace,
|
||||
spaceModel,
|
||||
queryRunner,
|
||||
);
|
||||
modifiedSubspaceModels.addedSubspaceModels.push(subspaceModel);
|
||||
break;
|
||||
case ModifyAction.UPDATE:
|
||||
const updatedSubspaceModel = await this.handleUpdateAction(
|
||||
subspace,
|
||||
queryRunner,
|
||||
);
|
||||
modifiedSubspaceModels.updatedSubspaceModels.push(
|
||||
updatedSubspaceModel,
|
||||
);
|
||||
break;
|
||||
case ModifyAction.DELETE:
|
||||
await this.handleDeleteAction(subspace, queryRunner);
|
||||
modifiedSubspaceModels.deletedSubspaceModels.push(subspace.uuid);
|
||||
break;
|
||||
default:
|
||||
throw new HttpException(
|
||||
`Invalid action "${subspace.action}".`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
}
|
||||
return modifiedSubspaceModels;
|
||||
return subspaces.map((subspace) => ({
|
||||
subspaceModel: subspace,
|
||||
action: ModifyAction.DELETE,
|
||||
relocatedAllocations: relocatedAllocationsMap.get(subspace.uuid) || [],
|
||||
}));
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw new HttpException(
|
||||
`An error occurred while modifying subspace models: ${error.message}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleAddAction(
|
||||
subspace: ModifySubspaceModelDto,
|
||||
spaceModel: SpaceModelEntity,
|
||||
queryRunner: QueryRunner,
|
||||
): Promise<SubspaceModelEntity> {
|
||||
try {
|
||||
const createTagDtos: CreateTagModelDto[] =
|
||||
subspace.tags?.map((tag) => ({
|
||||
tag: tag.tag,
|
||||
uuid: tag.uuid,
|
||||
productUuid: tag.productUuid,
|
||||
})) || [];
|
||||
|
||||
const [createdSubspaceModel] = await this.createSubSpaceModels(
|
||||
[
|
||||
{
|
||||
subspaceName: subspace.subspaceName,
|
||||
tags: createTagDtos,
|
||||
},
|
||||
],
|
||||
spaceModel,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
return createdSubspaceModel;
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) {
|
||||
throw error; // Rethrow known HttpExceptions
|
||||
}
|
||||
|
||||
throw new HttpException(
|
||||
`An error occurred while adding subspace: ${error.message}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleUpdateAction(
|
||||
modifyDto: ModifySubspaceModelDto,
|
||||
queryRunner: QueryRunner,
|
||||
): Promise<UpdatedSubspaceModelPayload> {
|
||||
const updatePayload: UpdatedSubspaceModelPayload = {
|
||||
subspaceModelUuid: modifyDto.uuid,
|
||||
};
|
||||
|
||||
const subspace = await this.findOne(modifyDto.uuid);
|
||||
|
||||
await this.updateSubspaceName(
|
||||
queryRunner,
|
||||
subspace,
|
||||
modifyDto.subspaceName,
|
||||
);
|
||||
updatePayload.subspaceName = modifyDto.subspaceName;
|
||||
|
||||
if (modifyDto.tags?.length) {
|
||||
updatePayload.modifiedTags = await this.tagModelService.modifyTags(
|
||||
modifyDto.tags,
|
||||
queryRunner,
|
||||
null,
|
||||
subspace,
|
||||
);
|
||||
}
|
||||
|
||||
return updatePayload;
|
||||
}
|
||||
|
||||
private async handleDeleteAction(
|
||||
subspace: ModifySubspaceModelDto,
|
||||
queryRunner: QueryRunner,
|
||||
) {
|
||||
const subspaceModel = await this.findOne(subspace.uuid);
|
||||
|
||||
await queryRunner.manager.update(
|
||||
this.subspaceModelRepository.target,
|
||||
{ uuid: subspace.uuid },
|
||||
{ disabled: true },
|
||||
);
|
||||
|
||||
if (subspaceModel.tags?.length) {
|
||||
const modifyTagDtos: CreateTagModelDto[] = subspaceModel.tags.map(
|
||||
(tag) => ({
|
||||
uuid: tag.uuid,
|
||||
action: ModifyAction.ADD,
|
||||
tag: tag.tag,
|
||||
productUuid: tag.product.uuid,
|
||||
}),
|
||||
);
|
||||
await this.tagModelService.moveTags(
|
||||
modifyTagDtos,
|
||||
queryRunner,
|
||||
subspaceModel.spaceModel,
|
||||
null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async findOne(subspaceUuid: string): Promise<SubspaceModelEntity> {
|
||||
const subspace = await this.subspaceModelRepository.findOne({
|
||||
where: { uuid: subspaceUuid, disabled: false },
|
||||
relations: ['tags', 'spaceModel', 'tags.product'],
|
||||
});
|
||||
if (!subspace) {
|
||||
throw new HttpException(
|
||||
`SubspaceModel with UUID ${subspaceUuid} not found.`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
return subspace;
|
||||
}
|
||||
|
||||
private async validateInputDtos(
|
||||
subSpaceModelDtos: CreateSubspaceModelDto[],
|
||||
spaceModel: SpaceModelEntity,
|
||||
): Promise<void> {
|
||||
try {
|
||||
if (subSpaceModelDtos.length === 0) {
|
||||
if (error instanceof QueryFailedError) {
|
||||
throw new HttpException(
|
||||
'Subspace models cannot be empty.',
|
||||
`Database query failed: ${error.message}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
} else if (error instanceof TypeError) {
|
||||
throw new HttpException(
|
||||
`Invalid data encountered: ${error.message}`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
} else {
|
||||
throw new HttpException(
|
||||
`Unexpected error during subspace deletion: ${error.message}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.validateName(
|
||||
subSpaceModelDtos.map((dto) => dto.subspaceName),
|
||||
spaceModel,
|
||||
async clearModels(subspaceUuids: string[], queryRunner: QueryRunner) {
|
||||
try {
|
||||
const subspaces = await this.getSubspacesByUuids(
|
||||
queryRunner,
|
||||
subspaceUuids,
|
||||
);
|
||||
if (!subspaces.length) return;
|
||||
await queryRunner.manager.update(
|
||||
SubspaceModelEntity,
|
||||
{ uuid: In(subspaceUuids) },
|
||||
{ disabled: true },
|
||||
);
|
||||
|
||||
await this.productAllocationService.clearAllAllocations(
|
||||
subspaceUuids,
|
||||
queryRunner,
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) {
|
||||
throw error; // Rethrow known HttpExceptions to preserve their message and status
|
||||
}
|
||||
|
||||
// Wrap unexpected errors
|
||||
throw new HttpException(
|
||||
`An error occurred while validating subspace models: ${error.message}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
error instanceof HttpException
|
||||
? error.message
|
||||
: 'An unexpected error occurred while clearing subspace models',
|
||||
error instanceof HttpException
|
||||
? error.getStatus()
|
||||
: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async updateSubspaceModel(
|
||||
dtos: ModifySubspaceModelDto[],
|
||||
spaceModel: SpaceModelEntity,
|
||||
queryRunner: QueryRunner,
|
||||
): Promise<ISingleSubspaceModel[]> {
|
||||
if (!dtos.length) return [];
|
||||
|
||||
const updatedSubspaces: {
|
||||
subspaceModel: SubspaceModelEntity;
|
||||
tags: ModifyTagModelDto[];
|
||||
action: ModifyAction.UPDATE;
|
||||
}[] = [];
|
||||
|
||||
for (const dto of dtos) {
|
||||
if (!dto.subspaceName) continue;
|
||||
|
||||
const existingSubspace = await queryRunner.manager.findOne(
|
||||
this.subspaceModelRepository.target,
|
||||
{ where: { uuid: dto.uuid } },
|
||||
);
|
||||
|
||||
if (existingSubspace) {
|
||||
if (existingSubspace.subspaceName !== dto.subspaceName) {
|
||||
await this.checkDuplicateNames(dto.subspaceName, spaceModel.uuid);
|
||||
existingSubspace.subspaceName = dto.subspaceName;
|
||||
await queryRunner.manager.save(existingSubspace);
|
||||
}
|
||||
|
||||
updatedSubspaces.push({
|
||||
subspaceModel: existingSubspace,
|
||||
tags: dto.tags ?? [],
|
||||
action: ModifyAction.UPDATE,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return updatedSubspaces;
|
||||
}
|
||||
|
||||
private async checkDuplicateNames(
|
||||
subspaceName: string,
|
||||
spaceModelUuid: string,
|
||||
@ -327,10 +453,33 @@ export class SubSpaceModelService {
|
||||
}
|
||||
}
|
||||
|
||||
private async validateName(
|
||||
names: string[],
|
||||
spaceModel: SpaceModelEntity,
|
||||
async checkDuplicateNamesBatch(
|
||||
subspaceNames: string[],
|
||||
spaceModelUuid: string,
|
||||
): Promise<void> {
|
||||
if (!subspaceNames.length) return;
|
||||
|
||||
const existingSubspaces = await this.subspaceModelRepository.find({
|
||||
where: {
|
||||
subspaceName: In(subspaceNames),
|
||||
spaceModel: { uuid: spaceModelUuid },
|
||||
disabled: false,
|
||||
},
|
||||
select: ['subspaceName'],
|
||||
});
|
||||
|
||||
if (existingSubspaces.length > 0) {
|
||||
const duplicateNames = existingSubspaces.map(
|
||||
(subspace) => subspace.subspaceName,
|
||||
);
|
||||
throw new HttpException(
|
||||
`Duplicate subspace names found: ${duplicateNames.join(', ')}`,
|
||||
HttpStatus.CONFLICT,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async validateNamesInDTO(names: string[]) {
|
||||
const seenNames = new Set<string>();
|
||||
const duplicateNames = new Set<string>();
|
||||
|
||||
@ -346,26 +495,32 @@ export class SubSpaceModelService {
|
||||
HttpStatus.CONFLICT,
|
||||
);
|
||||
}
|
||||
|
||||
for (const name of names) {
|
||||
await this.checkDuplicateNames(name, spaceModel.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
private async updateSubspaceName(
|
||||
queryRunner: QueryRunner,
|
||||
subSpaceModel: SubspaceModelEntity,
|
||||
subspaceName?: string,
|
||||
): Promise<void> {
|
||||
if (subspaceName) {
|
||||
await this.checkDuplicateNames(
|
||||
subspaceName,
|
||||
subSpaceModel.spaceModel.uuid,
|
||||
subSpaceModel.uuid,
|
||||
);
|
||||
extractTagsFromSubspaceModels(
|
||||
subspaceModels: CreateSubspaceModelDto[],
|
||||
): ProcessTagDto[] {
|
||||
return subspaceModels.flatMap((subspace) => subspace.tags || []);
|
||||
}
|
||||
|
||||
subSpaceModel.subspaceName = subspaceName;
|
||||
await queryRunner.manager.save(subSpaceModel);
|
||||
}
|
||||
extractTagsFromModifiedSubspaceModels(
|
||||
subspaceModels: ModifySubspaceModelDto[],
|
||||
): ModifyTagModelDto[] {
|
||||
return subspaceModels.flatMap((subspace) => subspace.tags || []);
|
||||
}
|
||||
|
||||
private async getSubspacesByUuids(
|
||||
queryRunner: QueryRunner,
|
||||
subspaceUuids: string[],
|
||||
): Promise<SubspaceModelEntity[]> {
|
||||
return await queryRunner.manager.find(SubspaceModelEntity, {
|
||||
where: { uuid: In(subspaceUuids) },
|
||||
relations: [
|
||||
'productAllocations',
|
||||
'productAllocations.product',
|
||||
'productAllocations.tags',
|
||||
'spaceModel',
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,577 +0,0 @@
|
||||
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
|
||||
import { QueryRunner } from 'typeorm';
|
||||
import {
|
||||
SpaceModelEntity,
|
||||
TagModel,
|
||||
} from '@app/common/modules/space-model/entities';
|
||||
import { SubspaceModelEntity } from '@app/common/modules/space-model/entities';
|
||||
import { TagModelRepository } from '@app/common/modules/space-model';
|
||||
import {
|
||||
CreateTagModelDto,
|
||||
ModifySubspaceModelDto,
|
||||
ModifyTagModelDto,
|
||||
} from '../dtos';
|
||||
import { ProductService } from 'src/product/services';
|
||||
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
||||
import { ModifiedTagsModelPayload } from '../interfaces';
|
||||
|
||||
@Injectable()
|
||||
export class TagModelService {
|
||||
constructor(
|
||||
private readonly tagModelRepository: TagModelRepository,
|
||||
private readonly productService: ProductService,
|
||||
) {}
|
||||
|
||||
async createTags(
|
||||
tags: CreateTagModelDto[],
|
||||
queryRunner: QueryRunner,
|
||||
spaceModel?: SpaceModelEntity,
|
||||
subspaceModel?: SubspaceModelEntity,
|
||||
additionalTags?: CreateTagModelDto[],
|
||||
tagsToDelete?: ModifyTagModelDto[],
|
||||
): Promise<TagModel[]> {
|
||||
if (!tags.length) {
|
||||
throw new HttpException('Tags cannot be empty.', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
const combinedTags = additionalTags ? [...tags, ...additionalTags] : tags;
|
||||
const duplicateTags = this.findDuplicateTags(combinedTags);
|
||||
|
||||
if (duplicateTags.length > 0) {
|
||||
throw new HttpException(
|
||||
`Duplicate tags found for the same product: ${duplicateTags.join(', ')}`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
const tagEntitiesToCreate = tags.filter((tagDto) => !tagDto.uuid);
|
||||
const tagEntitiesToUpdate = tags.filter((tagDto) => !!tagDto.uuid);
|
||||
|
||||
try {
|
||||
const createdTags = await this.bulkSaveTags(
|
||||
tagEntitiesToCreate,
|
||||
queryRunner,
|
||||
spaceModel,
|
||||
subspaceModel,
|
||||
tagsToDelete,
|
||||
);
|
||||
|
||||
// Update existing tags
|
||||
const updatedTags = await this.moveTags(
|
||||
tagEntitiesToUpdate,
|
||||
queryRunner,
|
||||
spaceModel,
|
||||
subspaceModel,
|
||||
);
|
||||
|
||||
// Combine created and updated tags
|
||||
return [...createdTags, ...updatedTags];
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw new HttpException(
|
||||
`Failed to create tag models due to an unexpected error.: ${error}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async bulkSaveTags(
|
||||
tags: CreateTagModelDto[],
|
||||
queryRunner: QueryRunner,
|
||||
spaceModel?: SpaceModelEntity,
|
||||
subspaceModel?: SubspaceModelEntity,
|
||||
tagsToDelete?: ModifyTagModelDto[],
|
||||
): Promise<TagModel[]> {
|
||||
if (!tags.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const tagEntities = await Promise.all(
|
||||
tags.map((tagDto) =>
|
||||
this.prepareTagEntity(
|
||||
tagDto,
|
||||
queryRunner,
|
||||
spaceModel,
|
||||
subspaceModel,
|
||||
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: CreateTagModelDto[],
|
||||
queryRunner: QueryRunner,
|
||||
spaceModel?: SpaceModelEntity,
|
||||
subspaceModel?: SubspaceModelEntity,
|
||||
): Promise<TagModel[]> {
|
||||
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 (subspaceModel && subspaceModel.spaceModel) {
|
||||
await queryRunner.manager.update(
|
||||
this.tagModelRepository.target,
|
||||
{ uuid: tag.uuid },
|
||||
{ subspaceModel, spaceModel: null },
|
||||
);
|
||||
tag.subspaceModel = subspaceModel;
|
||||
}
|
||||
|
||||
if (!subspaceModel && spaceModel) {
|
||||
await queryRunner.manager.update(
|
||||
this.tagModelRepository.target,
|
||||
{ uuid: tag.uuid },
|
||||
{ subspaceModel: null, spaceModel: spaceModel },
|
||||
);
|
||||
tag.subspaceModel = null;
|
||||
tag.spaceModel = spaceModel;
|
||||
}
|
||||
|
||||
return tag;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error moving tag with UUID ${tagDto.uuid}: ${error.message}`,
|
||||
);
|
||||
throw error; // Re-throw the error to propagate it to the parent Promise.all
|
||||
}
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`Error in moveTags: ${error.message}`);
|
||||
throw new HttpException(
|
||||
`Failed to move tags due to an unexpected error: ${error.message}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async updateTag(
|
||||
tag: ModifyTagModelDto,
|
||||
queryRunner: QueryRunner,
|
||||
spaceModel?: SpaceModelEntity,
|
||||
subspaceModel?: SubspaceModelEntity,
|
||||
): Promise<TagModel> {
|
||||
try {
|
||||
const existingTag = await this.getTagByUuid(tag.uuid);
|
||||
|
||||
if (tag.tag !== existingTag.tag) {
|
||||
if (spaceModel) {
|
||||
await this.checkTagReuse(
|
||||
tag.tag,
|
||||
existingTag.product.uuid,
|
||||
spaceModel,
|
||||
);
|
||||
} else {
|
||||
await this.checkTagReuse(
|
||||
tag.tag,
|
||||
existingTag.product.uuid,
|
||||
subspaceModel.spaceModel,
|
||||
);
|
||||
}
|
||||
|
||||
if (tag.tag) {
|
||||
existingTag.tag = tag.tag;
|
||||
}
|
||||
}
|
||||
return await queryRunner.manager.save(existingTag);
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) {
|
||||
throw error;
|
||||
}
|
||||
throw new HttpException(
|
||||
error.message || 'Failed to update tags',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteTags(tagUuids: string[], queryRunner: QueryRunner) {
|
||||
try {
|
||||
const deletePromises = tagUuids.map((id) =>
|
||||
queryRunner.manager.update(
|
||||
this.tagModelRepository.target,
|
||||
{ uuid: id },
|
||||
{ disabled: true },
|
||||
),
|
||||
);
|
||||
|
||||
await Promise.all(deletePromises);
|
||||
return { message: 'Tags deleted successfully', tagUuids };
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) {
|
||||
throw error;
|
||||
}
|
||||
throw new HttpException(
|
||||
error.message || 'Failed to delete tags',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private findDuplicateTags(tags: CreateTagModelDto[]): string[] {
|
||||
const seen = new Map<string, boolean>();
|
||||
const duplicates: string[] = [];
|
||||
|
||||
tags.forEach((tagDto) => {
|
||||
const key = `${tagDto.productUuid}-${tagDto.tag}`;
|
||||
if (seen.has(key)) {
|
||||
duplicates.push(`${tagDto.tag} for Product: ${tagDto.productUuid}`);
|
||||
} else {
|
||||
seen.set(key, true);
|
||||
}
|
||||
});
|
||||
|
||||
return duplicates;
|
||||
}
|
||||
|
||||
async modifyTags(
|
||||
tags: ModifyTagModelDto[],
|
||||
queryRunner: QueryRunner,
|
||||
spaceModel?: SpaceModelEntity,
|
||||
subspaceModel?: SubspaceModelEntity,
|
||||
): Promise<ModifiedTagsModelPayload> {
|
||||
const modifiedTagModels: ModifiedTagsModelPayload = {
|
||||
added: [],
|
||||
updated: [],
|
||||
deleted: [],
|
||||
};
|
||||
try {
|
||||
const tagsToDelete = tags.filter(
|
||||
(tag) => tag.action === ModifyAction.DELETE,
|
||||
);
|
||||
|
||||
for (const tag of tags) {
|
||||
if (tag.action === ModifyAction.ADD) {
|
||||
const createTagDto: CreateTagModelDto = {
|
||||
tag: tag.tag as string,
|
||||
uuid: tag.uuid,
|
||||
productUuid: tag.productUuid as string,
|
||||
};
|
||||
|
||||
const newModel = await this.createTags(
|
||||
[createTagDto],
|
||||
queryRunner,
|
||||
spaceModel,
|
||||
subspaceModel,
|
||||
null,
|
||||
tagsToDelete,
|
||||
);
|
||||
modifiedTagModels.added.push(...newModel);
|
||||
} else if (tag.action === ModifyAction.UPDATE) {
|
||||
const updatedModel = await this.updateTag(
|
||||
tag,
|
||||
queryRunner,
|
||||
spaceModel,
|
||||
subspaceModel,
|
||||
);
|
||||
modifiedTagModels.updated.push(updatedModel);
|
||||
} else if (tag.action === ModifyAction.DELETE) {
|
||||
await queryRunner.manager.update(
|
||||
this.tagModelRepository.target,
|
||||
{ uuid: tag.uuid },
|
||||
{ disabled: true },
|
||||
);
|
||||
modifiedTagModels.deleted.push(tag.uuid);
|
||||
} else {
|
||||
throw new HttpException(
|
||||
`Invalid action "${tag.action}" provided.`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
}
|
||||
return modifiedTagModels;
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw new HttpException(
|
||||
`An error occurred while modifying tag models: ${error.message}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async checkTagReuse(
|
||||
tag: string,
|
||||
productUuid: string,
|
||||
spaceModel: SpaceModelEntity,
|
||||
tagsToDelete?: ModifyTagModelDto[],
|
||||
): Promise<void> {
|
||||
try {
|
||||
// Query to find existing tags
|
||||
const tagExists = await this.tagModelRepository.find({
|
||||
where: [
|
||||
{
|
||||
tag,
|
||||
spaceModel: { uuid: spaceModel.uuid },
|
||||
product: { uuid: productUuid },
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
tag,
|
||||
subspaceModel: { spaceModel: { uuid: spaceModel.uuid } },
|
||||
product: { uuid: productUuid },
|
||||
disabled: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Remove tags that are marked for deletion
|
||||
const filteredTagExists = tagExists.filter(
|
||||
(existingTag) =>
|
||||
!tagsToDelete?.some(
|
||||
(deleteTag) => deleteTag.uuid === existingTag.uuid,
|
||||
),
|
||||
);
|
||||
|
||||
// If any tags remain, throw an exception
|
||||
if (filteredTagExists.length > 0) {
|
||||
throw new HttpException(
|
||||
`Tag ${tag} can't be reused`,
|
||||
HttpStatus.CONFLICT,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) {
|
||||
throw error;
|
||||
}
|
||||
console.error(`Error while checking tag reuse: ${error.message}`);
|
||||
throw new HttpException(
|
||||
`An error occurred while checking tag reuse: ${error.message}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async prepareTagEntity(
|
||||
tagDto: CreateTagModelDto,
|
||||
queryRunner: QueryRunner,
|
||||
spaceModel?: SpaceModelEntity,
|
||||
subspaceModel?: SubspaceModelEntity,
|
||||
tagsToDelete?: ModifyTagModelDto[],
|
||||
): Promise<TagModel> {
|
||||
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 (spaceModel) {
|
||||
await this.checkTagReuse(
|
||||
tagDto.tag,
|
||||
tagDto.productUuid,
|
||||
spaceModel,
|
||||
tagsToDelete,
|
||||
);
|
||||
} else if (subspaceModel && subspaceModel.spaceModel) {
|
||||
await this.checkTagReuse(
|
||||
tagDto.tag,
|
||||
tagDto.productUuid,
|
||||
subspaceModel.spaceModel,
|
||||
);
|
||||
} else {
|
||||
throw new HttpException(
|
||||
`Invalid subspaceModel or spaceModel provided.`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
return queryRunner.manager.create(TagModel, {
|
||||
tag: tagDto.tag,
|
||||
product: product.data,
|
||||
spaceModel: spaceModel,
|
||||
subspaceModel: subspaceModel,
|
||||
});
|
||||
} 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getTagByUuid(uuid: string): Promise<TagModel> {
|
||||
const tag = await this.tagModelRepository.findOne({
|
||||
where: { uuid, disabled: false },
|
||||
relations: ['product'],
|
||||
});
|
||||
if (!tag) {
|
||||
throw new HttpException(
|
||||
`Tag model with ID ${uuid} not found.`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
async getTagByName(
|
||||
tag: string,
|
||||
subspaceUuid?: string,
|
||||
spaceUuid?: string,
|
||||
): Promise<TagModel> {
|
||||
const queryConditions: any = { tag };
|
||||
|
||||
if (spaceUuid) {
|
||||
queryConditions.spaceModel = { uuid: spaceUuid };
|
||||
} else if (subspaceUuid) {
|
||||
queryConditions.subspaceModel = { uuid: subspaceUuid };
|
||||
} else {
|
||||
throw new HttpException(
|
||||
'Either spaceUuid or subspaceUuid must be provided.',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
queryConditions.disabled = false;
|
||||
|
||||
const existingTag = await this.tagModelRepository.findOne({
|
||||
where: queryConditions,
|
||||
relations: ['product'],
|
||||
});
|
||||
|
||||
if (!existingTag) {
|
||||
throw new HttpException(
|
||||
`Tag model with tag "${tag}" not found.`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return existingTag;
|
||||
}
|
||||
|
||||
getSubspaceTagsToBeAdded(
|
||||
spaceTags?: ModifyTagModelDto[],
|
||||
subspaceModels?: ModifySubspaceModelDto[],
|
||||
): ModifyTagModelDto[] {
|
||||
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.uuid === tagToDelete.uuid,
|
||||
),
|
||||
)
|
||||
.map((tag) => tag.uuid),
|
||||
);
|
||||
|
||||
const remainingTags = spaceTags.filter(
|
||||
(tag) => !commonTagUuids.has(tag.uuid), // Exclude tags in commonTagUuids
|
||||
);
|
||||
|
||||
return remainingTags;
|
||||
}
|
||||
|
||||
getModifiedSubspaces(
|
||||
spaceTags: ModifyTagModelDto[],
|
||||
subspaceModels: ModifySubspaceModelDto[],
|
||||
): ModifySubspaceModelDto[] {
|
||||
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.uuid),
|
||||
);
|
||||
|
||||
const commonTagsInSubspaces = subspaceTagsToAdd.filter((tag) =>
|
||||
subspaceTagsToDeleteUuids.has(tag.uuid),
|
||||
);
|
||||
|
||||
// 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.uuid === tagToDelete.uuid,
|
||||
) || [],
|
||||
),
|
||||
)
|
||||
.map((tag) => tag.uuid),
|
||||
);
|
||||
|
||||
// Modify subspaceModels by removing tags with UUIDs present in commonTagUuids
|
||||
let modifiedSubspaces = subspaceModels.map((subspace) => ({
|
||||
...subspace,
|
||||
tags: subspace.tags?.filter((tag) => !commonTagUuids.has(tag.uuid)) || [],
|
||||
}));
|
||||
|
||||
modifiedSubspaces = modifiedSubspaces.map((subspace) => ({
|
||||
...subspace,
|
||||
tags:
|
||||
subspace.tags?.filter(
|
||||
(tag) =>
|
||||
!(
|
||||
tag.action === 'delete' &&
|
||||
commonTagsInSubspaces.some(
|
||||
(commonTag) => commonTag.uuid === tag.uuid,
|
||||
)
|
||||
),
|
||||
) || [],
|
||||
}));
|
||||
|
||||
return modifiedSubspaces;
|
||||
}
|
||||
}
|
@ -2,13 +2,11 @@ import { SpaceRepositoryModule } from '@app/common/modules/space/space.repositor
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { SpaceModelController } from './controllers';
|
||||
import { SpaceModelService, SubSpaceModelService } from './services';
|
||||
import {
|
||||
SpaceModelService,
|
||||
SubSpaceModelService,
|
||||
TagModelService,
|
||||
} from './services';
|
||||
import {
|
||||
SpaceModelProductAllocationRepoitory,
|
||||
SpaceModelRepository,
|
||||
SubspaceModelProductAllocationRepoitory,
|
||||
SubspaceModelRepository,
|
||||
TagModelRepository,
|
||||
} from '@app/common/modules/space-model';
|
||||
@ -22,10 +20,14 @@ import { CqrsModule } from '@nestjs/cqrs';
|
||||
import {
|
||||
InviteSpaceRepository,
|
||||
SpaceLinkRepository,
|
||||
SpaceProductAllocationRepository,
|
||||
SpaceRepository,
|
||||
TagRepository,
|
||||
} from '@app/common/modules/space';
|
||||
import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
|
||||
import {
|
||||
SubspaceProductAllocationRepository,
|
||||
SubspaceRepository,
|
||||
} from '@app/common/modules/space/repositories/subspace.repository';
|
||||
import {
|
||||
SpaceLinkService,
|
||||
SpaceService,
|
||||
@ -38,6 +40,22 @@ import { CommunityService } from 'src/community/services';
|
||||
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||
import { TagService as NewTagService } from 'src/tags/services/tags.service';
|
||||
import { NewTagRepository } from '@app/common/modules/tag/repositories/tag-repository';
|
||||
import { SpaceModelProductAllocationService } from './services/space-model-product-allocation.service';
|
||||
import { SubspaceModelProductAllocationService } from './services/subspace/subspace-model-product-allocation.service';
|
||||
import { SpaceProductAllocationService } from 'src/space/services/space-product-allocation.service';
|
||||
import { SubspaceProductAllocationService } from 'src/space/services/subspace/subspace-product-allocation.service';
|
||||
import { DeviceService } from 'src/device/services';
|
||||
import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories';
|
||||
import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service';
|
||||
import { SceneService } from 'src/scene/services';
|
||||
import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories';
|
||||
import {
|
||||
SceneIconRepository,
|
||||
SceneRepository,
|
||||
} from '@app/common/modules/scene/repositories';
|
||||
import { AutomationRepository } from '@app/common/modules/automation/repositories';
|
||||
|
||||
const CommandHandlers = [
|
||||
PropogateUpdateSpaceModelHandler,
|
||||
@ -58,7 +76,6 @@ const CommandHandlers = [
|
||||
SubspaceModelRepository,
|
||||
ProductRepository,
|
||||
SubspaceRepository,
|
||||
TagModelService,
|
||||
TagModelRepository,
|
||||
SubSpaceService,
|
||||
ValidationService,
|
||||
@ -72,6 +89,26 @@ const CommandHandlers = [
|
||||
SpaceLinkService,
|
||||
SpaceLinkRepository,
|
||||
InviteSpaceRepository,
|
||||
NewTagService,
|
||||
DeviceService,
|
||||
DeviceStatusFirebaseService,
|
||||
DeviceStatusLogRepository,
|
||||
SceneService,
|
||||
SceneIconRepository,
|
||||
SceneDeviceRepository,
|
||||
SceneRepository,
|
||||
AutomationRepository,
|
||||
SpaceModelProductAllocationRepoitory,
|
||||
SubspaceModelProductAllocationRepoitory,
|
||||
NewTagRepository,
|
||||
SpaceModelProductAllocationService,
|
||||
SubspaceModelProductAllocationRepoitory,
|
||||
SubspaceModelProductAllocationService,
|
||||
SpaceProductAllocationService,
|
||||
SubspaceProductAllocationService,
|
||||
SpaceProductAllocationRepository,
|
||||
SubspaceProductAllocationRepository,
|
||||
SubSpaceService,
|
||||
],
|
||||
exports: [CqrsModule, SpaceModelService],
|
||||
})
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { SpaceEntity } from '@app/common/modules/space';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||
|
||||
export class DisableSpaceCommand {
|
||||
constructor(
|
||||
|
38
src/space/controllers/space-validation.controller.ts
Normal file
38
src/space/controllers/space-validation.controller.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||
import { Body, Controller, Param, Post, UseGuards } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { ValidationService } from '../services';
|
||||
import { ValidateSpacesDto } from '../dtos/validation.space.dto';
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
import { ProjectParam } from '../dtos';
|
||||
|
||||
@ApiTags('Space Module')
|
||||
@Controller({
|
||||
version: '1',
|
||||
path: ControllerRoute.SPACE_VALIDATION.ROUTE,
|
||||
})
|
||||
export class SpaceValidationController {
|
||||
constructor(private readonly validationService: ValidationService) {}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
summary:
|
||||
ControllerRoute.SPACE_VALIDATION.ACTIONS
|
||||
.VALIDATE_SPACE_WITH_DEVICES_OR_SUBSPACES_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.SPACE_VALIDATION.ACTIONS
|
||||
.VALIDATE_SPACE_WITH_DEVICES_OR_SUBSPACES_DESCRIPTION,
|
||||
})
|
||||
@Post('validate')
|
||||
async validateSpaces(
|
||||
@Body() validateSpacesDto: ValidateSpacesDto,
|
||||
@Param() projectParam: ProjectParam,
|
||||
): Promise<BaseResponseDto> {
|
||||
return this.validationService.validateSpacesWithDevicesOrSubspaces(
|
||||
validateSpacesDto,
|
||||
projectParam,
|
||||
);
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ import {
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import { AddSubspaceDto } from './subspace';
|
||||
import { CreateTagDto } from './tag';
|
||||
import { ProcessTagDto } from 'src/tags/dtos';
|
||||
|
||||
export class AddSpaceDto {
|
||||
@ApiProperty({
|
||||
@ -79,11 +79,11 @@ export class AddSpaceDto {
|
||||
|
||||
@ApiProperty({
|
||||
description: 'List of tags associated with the space model',
|
||||
type: [CreateTagDto],
|
||||
type: [ProcessTagDto],
|
||||
})
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CreateTagDto)
|
||||
tags?: CreateTagDto[];
|
||||
@Type(() => ProcessTagDto)
|
||||
tags?: ProcessTagDto[];
|
||||
}
|
||||
|
||||
export class AddUserSpaceDto {
|
||||
|
@ -6,8 +6,8 @@ import {
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import { CreateTagDto } from '../tag';
|
||||
import { Type } from 'class-transformer';
|
||||
import { ProcessTagDto } from 'src/tags/dtos';
|
||||
|
||||
export class AddSubspaceDto {
|
||||
@ApiProperty({
|
||||
@ -20,11 +20,11 @@ export class AddSubspaceDto {
|
||||
|
||||
@ApiProperty({
|
||||
description: 'List of tags associated with the subspace',
|
||||
type: [CreateTagDto],
|
||||
type: [ProcessTagDto],
|
||||
})
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CreateTagDto)
|
||||
@Type(() => ProcessTagDto)
|
||||
@IsOptional()
|
||||
tags?: CreateTagDto[];
|
||||
tags?: ProcessTagDto[];
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsEnum, IsOptional, IsString } from 'class-validator';
|
||||
import { IsEnum, IsOptional, IsString, IsUUID } from 'class-validator';
|
||||
|
||||
export class ModifyTagDto {
|
||||
@ApiProperty({
|
||||
@ -11,12 +11,21 @@ export class ModifyTagDto {
|
||||
action: ModifyAction;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'UUID of the tag (required for update/delete)',
|
||||
description: 'UUID of the new tag',
|
||||
example: '123e4567-e89b-12d3-a456-426614174000',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
uuid?: string;
|
||||
@IsUUID()
|
||||
newTagUuid: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
'UUID of an existing tag (required for update/delete, optional for add)',
|
||||
example: 'a1b2c3d4-5678-90ef-abcd-1234567890ef',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
tagUuid?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Name of the tag (required for add/update)',
|
||||
@ -24,7 +33,7 @@ export class ModifyTagDto {
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
tag?: string;
|
||||
name?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
@ -32,6 +41,6 @@ export class ModifyTagDto {
|
||||
example: 'c789a91e-549a-4753-9006-02f89e8170e0',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsUUID()
|
||||
productUuid?: string;
|
||||
}
|
||||
|
@ -58,4 +58,11 @@ export class UpdateSpaceDto {
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => ModifyTagDto)
|
||||
tags?: ModifyTagDto[];
|
||||
@ApiProperty({
|
||||
description: 'UUID of the Space',
|
||||
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
spaceModelUuid?: string;
|
||||
}
|
||||
|
14
src/space/dtos/validation.space.dto.ts
Normal file
14
src/space/dtos/validation.space.dto.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsArray, IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class ValidateSpacesDto {
|
||||
@ApiProperty({
|
||||
description: 'Array of space UUIDs to be validated',
|
||||
type: [String],
|
||||
example: ['space-1', 'space-2', 'space-3'],
|
||||
})
|
||||
@IsArray()
|
||||
@IsNotEmpty()
|
||||
@IsString({ each: true })
|
||||
spacesUuids: string[];
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
import { SpaceEntity } from '@app/common/modules/space';
|
||||
import { HttpException, HttpStatus } from '@nestjs/common';
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||
import { DeviceService } from 'src/device/services';
|
||||
@ -11,6 +10,7 @@ import {
|
||||
SpaceSceneService,
|
||||
} from '../services';
|
||||
import { TagService } from '../services/tag';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||
|
||||
@CommandHandler(DisableSpaceCommand)
|
||||
export class DisableSpaceHandler
|
||||
@ -66,12 +66,12 @@ export class DisableSpaceHandler
|
||||
}
|
||||
|
||||
const tagUuids = space.tags?.map((tag) => tag.uuid) || [];
|
||||
const subspaceDtos =
|
||||
/* const subspaceDtos =
|
||||
space.subspaces?.map((subspace) => ({
|
||||
subspaceUuid: subspace.uuid,
|
||||
})) || [];
|
||||
})) || []; */
|
||||
const deletionTasks = [
|
||||
this.subSpaceService.deleteSubspaces(subspaceDtos, queryRunner),
|
||||
// this.subSpaceService.deleteSubspaces(subspaceDtos, queryRunner),
|
||||
this.userService.deleteUserSpace(space.uuid),
|
||||
this.tagService.deleteTags(tagUuids, queryRunner),
|
||||
this.deviceService.deleteDevice(
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { SubspaceEntity } from '@app/common/modules/space';
|
||||
import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity';
|
||||
|
||||
export interface ModifySubspacePayload {
|
||||
addedSubspaces?: SubspaceEntity[];
|
||||
|
9
src/space/interfaces/single-subspace.interface.ts
Normal file
9
src/space/interfaces/single-subspace.interface.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
||||
import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity';
|
||||
import { ModifyTagDto } from '../dtos/tag/modify-tag.dto';
|
||||
|
||||
export interface ISingleSubspace {
|
||||
subspace: SubspaceEntity;
|
||||
action: ModifyAction;
|
||||
tags: ModifyTagDto[];
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import { SpaceEntity, SpaceLinkEntity } from '@app/common/modules/space';
|
||||
import { SpaceLinkEntity } from '@app/common/modules/space/entities/space-link.entity';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||
import { SpaceLinkRepository } from '@app/common/modules/space/repositories';
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { QueryRunner } from 'typeorm';
|
||||
|
589
src/space/services/space-product-allocation.service.ts
Normal file
589
src/space/services/space-product-allocation.service.ts
Normal file
@ -0,0 +1,589 @@
|
||||
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(
|
||||
space: SpaceEntity,
|
||||
processedTags: NewTagEntity[],
|
||||
queryRunner: QueryRunner,
|
||||
modifySubspaces?: ModifySubspaceDto[],
|
||||
): Promise<void> {
|
||||
try {
|
||||
if (!processedTags.length) return;
|
||||
|
||||
const productAllocations: SpaceProductAllocationEntity[] = [];
|
||||
const existingAllocations = new Map<
|
||||
string,
|
||||
SpaceProductAllocationEntity
|
||||
>();
|
||||
|
||||
for (const tag of processedTags) {
|
||||
let isTagNeeded = true;
|
||||
|
||||
if (modifySubspaces) {
|
||||
const relatedSubspaces = await queryRunner.manager.find(
|
||||
SubspaceProductAllocationEntity,
|
||||
{
|
||||
where: {
|
||||
product: tag.product,
|
||||
subspace: { space: { uuid: space.uuid } },
|
||||
tags: { uuid: tag.uuid },
|
||||
},
|
||||
relations: ['subspace', 'tags'],
|
||||
},
|
||||
);
|
||||
|
||||
for (const subspaceWithTag of relatedSubspaces) {
|
||||
const modifyingSubspace = modifySubspaces.find(
|
||||
(subspace) =>
|
||||
subspace.action === ModifyAction.UPDATE &&
|
||||
subspace.uuid === subspaceWithTag.subspace.uuid,
|
||||
);
|
||||
|
||||
if (
|
||||
modifyingSubspace &&
|
||||
modifyingSubspace.tags &&
|
||||
modifyingSubspace.tags.some(
|
||||
(subspaceTag) =>
|
||||
subspaceTag.action === ModifyAction.DELETE &&
|
||||
subspaceTag.tagUuid === tag.uuid,
|
||||
)
|
||||
) {
|
||||
isTagNeeded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isTagNeeded) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (productAllocations.length > 0) {
|
||||
await this.saveAllocations(productAllocations, queryRunner);
|
||||
}
|
||||
} catch (error) {
|
||||
throw this.handleError(
|
||||
error,
|
||||
'Failed to create space product allocations',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
return;
|
||||
|
||||
const allocationUuids = space.productAllocations.map(
|
||||
(allocation) => allocation.uuid,
|
||||
);
|
||||
|
||||
await queryRunner.manager.update(
|
||||
SpaceProductAllocationEntity,
|
||||
{ uuid: In(allocationUuids) },
|
||||
{ inheritedFromModel: null },
|
||||
);
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
'Failed to unlink models',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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[],
|
||||
queryRunner: QueryRunner,
|
||||
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(
|
||||
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,
|
||||
},
|
||||
},
|
||||
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);
|
||||
}
|
||||
|
||||
async saveAllocations(
|
||||
allocations: SpaceProductAllocationEntity[],
|
||||
queryRunner?: QueryRunner,
|
||||
) {
|
||||
queryRunner
|
||||
? await queryRunner.manager.save(
|
||||
SpaceProductAllocationEntity,
|
||||
allocations,
|
||||
)
|
||||
: await this.spaceProductAllocationRepository.save(allocations);
|
||||
}
|
||||
|
||||
private handleError(error: any, message: string): HttpException {
|
||||
return new HttpException(
|
||||
error instanceof HttpException ? error.message : message,
|
||||
error instanceof HttpException
|
||||
? error.getStatus()
|
||||
: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
@ -5,9 +5,9 @@ import { SceneService } from '../../scene/services';
|
||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||
import { GetSceneDto } from '../../scene/dtos';
|
||||
import { ValidationService } from './space-validation.service';
|
||||
import { SpaceEntity } from '@app/common/modules/space';
|
||||
import { QueryRunner } from 'typeorm';
|
||||
import { SceneEntity } from '@app/common/modules/scene/entities';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||
|
||||
@Injectable()
|
||||
export class SpaceSceneService {
|
||||
|
@ -1,6 +1,10 @@
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import {
|
||||
BadRequestException,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
import { CommunityService } from '../../community/services';
|
||||
import { ProjectService } from '../../project/services';
|
||||
import {
|
||||
@ -10,6 +14,16 @@ import {
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class ValidationService {
|
||||
@ -22,7 +36,37 @@ export class ValidationService {
|
||||
private readonly spaceModelRepository: SpaceModelRepository,
|
||||
private readonly deviceRepository: DeviceRepository,
|
||||
) {}
|
||||
async validateSpacesWithDevicesOrSubspaces(
|
||||
validateSpacesDto: ValidateSpacesDto,
|
||||
projectParam: ProjectParam,
|
||||
) {
|
||||
const { spacesUuids } = validateSpacesDto;
|
||||
const { projectUuid } = projectParam;
|
||||
await this.communityService.validateProject(projectUuid);
|
||||
const spaces = await this.spaceRepository.find({
|
||||
where: { uuid: In(spacesUuids), disabled: false },
|
||||
relations: ['devices', 'subspaces', 'productAllocations'],
|
||||
});
|
||||
|
||||
const hasInvalidSpaces = spaces.some(
|
||||
(space) =>
|
||||
space.devices.length > 0 ||
|
||||
space.subspaces.length > 0 ||
|
||||
space.productAllocations.length > 0,
|
||||
);
|
||||
|
||||
if (hasInvalidSpaces) {
|
||||
throw new BadRequestException(
|
||||
'Selected spaces already have linked space model / sub-spaces and devices',
|
||||
);
|
||||
}
|
||||
|
||||
return new SuccessResponseDto({
|
||||
statusCode: HttpStatus.OK,
|
||||
message:
|
||||
'Validation completed successfully. No spaces have linked devices or sub-spaces.',
|
||||
});
|
||||
}
|
||||
async validateCommunityAndProject(
|
||||
communityUuid: string,
|
||||
projectUuid: string,
|
||||
@ -81,8 +125,15 @@ export class ValidationService {
|
||||
'children',
|
||||
'subspaces',
|
||||
'tags',
|
||||
'productAllocations',
|
||||
'productAllocations.product',
|
||||
'productAllocations.tags',
|
||||
'subspaces.productAllocations',
|
||||
'subspaces.productAllocations.tags',
|
||||
'subspaces.productAllocations.product',
|
||||
'subspaces.tags',
|
||||
'subspaces.devices',
|
||||
'spaceModel',
|
||||
],
|
||||
});
|
||||
|
||||
@ -96,7 +147,7 @@ export class ValidationService {
|
||||
const devices = await this.deviceRepository.find({
|
||||
where: { spaceDevice: { uuid: spaceUuid } },
|
||||
select: ['uuid', 'deviceTuyaUuid', 'isActive', 'createdAt', 'updatedAt'],
|
||||
relations: ['productDevice', 'tag', 'subspace'],
|
||||
relations: ['productDevice', 'subspace'],
|
||||
});
|
||||
|
||||
space.devices = devices;
|
||||
@ -129,10 +180,11 @@ export class ValidationService {
|
||||
where: { uuid: spaceModelUuid },
|
||||
relations: [
|
||||
'subspaceModels',
|
||||
'subspaceModels.tags',
|
||||
'tags',
|
||||
'subspaceModels.tags.product',
|
||||
'tags.product',
|
||||
'subspaceModels.productAllocations',
|
||||
'subspaceModels.productAllocations.tags',
|
||||
'subspaceModels.productAllocations.product',
|
||||
'productAllocations.product',
|
||||
'productAllocations.tags',
|
||||
],
|
||||
});
|
||||
|
||||
@ -250,4 +302,24 @@ 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;
|
||||
}
|
||||
}
|
||||
|
@ -12,13 +12,11 @@ import {
|
||||
AddSpaceDto,
|
||||
AddSubspaceDto,
|
||||
CommunitySpaceParam,
|
||||
CreateTagDto,
|
||||
GetSpaceParam,
|
||||
UpdateSpaceDto,
|
||||
} from '../dtos';
|
||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities';
|
||||
import { generateRandomString } from '@app/common/helper/randomString';
|
||||
import { SpaceLinkService } from './space-link';
|
||||
import { SubSpaceService } from './subspace';
|
||||
@ -29,11 +27,15 @@ import {
|
||||
ORPHAN_SPACE_NAME,
|
||||
} from '@app/common/constants/orphan-constant';
|
||||
import { CommandBus } from '@nestjs/cqrs';
|
||||
import { TagService } from './tag';
|
||||
import { TagService as NewTagService } from 'src/tags/services/tags.service';
|
||||
import { SpaceModelService } from 'src/space-model/services';
|
||||
import { DisableSpaceCommand } from '../commands';
|
||||
import { GetSpaceDto } from '../dtos/get.space.dto';
|
||||
import { removeCircularReferences } from '@app/common/helper/removeCircularReferences';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||
import { ProcessTagDto } from 'src/tags/dtos';
|
||||
import { SpaceProductAllocationService } from './space-product-allocation.service';
|
||||
import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity';
|
||||
@Injectable()
|
||||
export class SpaceService {
|
||||
constructor(
|
||||
@ -43,9 +45,10 @@ export class SpaceService {
|
||||
private readonly spaceLinkService: SpaceLinkService,
|
||||
private readonly subSpaceService: SubSpaceService,
|
||||
private readonly validationService: ValidationService,
|
||||
private readonly tagService: TagService,
|
||||
private readonly newTagService: NewTagService,
|
||||
private readonly spaceModelService: SpaceModelService,
|
||||
private commandBus: CommandBus,
|
||||
private readonly spaceProductAllocationService: SpaceProductAllocationService,
|
||||
) {}
|
||||
|
||||
async createSpace(
|
||||
@ -93,10 +96,27 @@ export class SpaceService {
|
||||
});
|
||||
|
||||
const newSpace = await queryRunner.manager.save(space);
|
||||
const subspaceTags =
|
||||
this.subSpaceService.extractTagsFromSubspace(subspaces);
|
||||
const allTags = [...tags, ...subspaceTags];
|
||||
this.validateUniqueTags(allTags);
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
spaceModelUuid &&
|
||||
this.createFromModel(spaceModelUuid, queryRunner, newSpace),
|
||||
direction && parent
|
||||
? this.spaceLinkService.saveSpaceLink(
|
||||
parent.uuid,
|
||||
@ -106,10 +126,16 @@ export class SpaceService {
|
||||
)
|
||||
: Promise.resolve(),
|
||||
subspaces?.length
|
||||
? this.createSubspaces(subspaces, newSpace, queryRunner, tags)
|
||||
? this.createSubspaces(
|
||||
subspaces,
|
||||
newSpace,
|
||||
queryRunner,
|
||||
null,
|
||||
projectUuid,
|
||||
)
|
||||
: Promise.resolve(),
|
||||
tags?.length
|
||||
? this.createTags(tags, queryRunner, newSpace)
|
||||
? this.createTags(tags, projectUuid, queryRunner, newSpace)
|
||||
: Promise.resolve(),
|
||||
]);
|
||||
|
||||
@ -131,39 +157,29 @@ export class SpaceService {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
private validateUniqueTags(allTags: ProcessTagDto[]) {
|
||||
const tagUuidSet = new Set<string>();
|
||||
const tagNameProductSet = new Set<string>();
|
||||
|
||||
async createFromModel(
|
||||
spaceModelUuid: string,
|
||||
queryRunner: QueryRunner,
|
||||
space: SpaceEntity,
|
||||
) {
|
||||
try {
|
||||
const spaceModel =
|
||||
await this.spaceModelService.validateSpaceModel(spaceModelUuid);
|
||||
|
||||
space.spaceModel = spaceModel;
|
||||
await queryRunner.manager.save(SpaceEntity, space);
|
||||
|
||||
await this.subSpaceService.createSubSpaceFromModel(
|
||||
spaceModel.subspaceModels,
|
||||
space,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
await this.tagService.createTagsFromModel(
|
||||
queryRunner,
|
||||
spaceModel.tags,
|
||||
space,
|
||||
null,
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) {
|
||||
throw error;
|
||||
for (const tag of allTags) {
|
||||
if (tag.uuid) {
|
||||
if (tagUuidSet.has(tag.uuid)) {
|
||||
throw new HttpException(
|
||||
`Duplicate tag UUID found: ${tag.uuid}`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
tagUuidSet.add(tag.uuid);
|
||||
} else {
|
||||
const tagKey = `${tag.name}-${tag.productUuid}`;
|
||||
if (tagNameProductSet.has(tagKey)) {
|
||||
throw new HttpException(
|
||||
`Duplicate tag found with name "${tag.name}" and product "${tag.productUuid}".`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
tagNameProductSet.add(tagKey);
|
||||
}
|
||||
throw new HttpException(
|
||||
'An error occurred while creating the space from space model',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,12 +209,8 @@ export class SpaceService {
|
||||
'incomingConnections.disabled = :incomingConnectionDisabled',
|
||||
{ incomingConnectionDisabled: false },
|
||||
)
|
||||
.leftJoinAndSelect(
|
||||
'space.tags',
|
||||
'tags',
|
||||
'tags.disabled = :tagDisabled',
|
||||
{ tagDisabled: false },
|
||||
)
|
||||
.leftJoinAndSelect('space.productAllocations', 'productAllocations')
|
||||
.leftJoinAndSelect('productAllocations.tags', 'tags')
|
||||
.leftJoinAndSelect('tags.product', 'tagProduct')
|
||||
.leftJoinAndSelect(
|
||||
'space.subspaces',
|
||||
@ -207,11 +219,10 @@ export class SpaceService {
|
||||
{ subspaceDisabled: false },
|
||||
)
|
||||
.leftJoinAndSelect(
|
||||
'subspaces.tags',
|
||||
'subspaceTags',
|
||||
'subspaceTags.disabled = :subspaceTagsDisabled',
|
||||
{ subspaceTagsDisabled: false },
|
||||
'subspaces.productAllocations',
|
||||
'subspaceProductAllocations',
|
||||
)
|
||||
.leftJoinAndSelect('subspaceProductAllocations.tags', 'subspaceTags')
|
||||
.leftJoinAndSelect('subspaceTags.product', 'subspaceTagProduct')
|
||||
.leftJoinAndSelect('space.spaceModel', 'spaceModel')
|
||||
.where('space.community_id = :communityUuid', { communityUuid })
|
||||
@ -226,7 +237,8 @@ export class SpaceService {
|
||||
|
||||
const spaces = await queryBuilder.getMany();
|
||||
|
||||
const spaceHierarchy = this.buildSpaceHierarchy(spaces);
|
||||
const transformedSpaces = spaces.map(this.transformSpace);
|
||||
const spaceHierarchy = this.buildSpaceHierarchy(transformedSpaces);
|
||||
|
||||
return new SuccessResponseDto({
|
||||
message: `Spaces in community ${communityUuid} successfully fetched in hierarchy`,
|
||||
@ -241,6 +253,28 @@ 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,
|
||||
};
|
||||
}
|
||||
async findOne(params: GetSpaceParam): Promise<BaseResponseDto> {
|
||||
const { communityUuid, spaceUuid, projectUuid } = params;
|
||||
try {
|
||||
@ -310,6 +344,10 @@ export class SpaceService {
|
||||
}
|
||||
|
||||
async delete(params: GetSpaceParam): Promise<BaseResponseDto> {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const { communityUuid, spaceUuid, projectUuid } = params;
|
||||
|
||||
@ -337,20 +375,42 @@ export class SpaceService {
|
||||
},
|
||||
});
|
||||
|
||||
await this.spaceProductAllocationService.clearAllAllocations(
|
||||
spaceUuid,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
const subspaces = await queryRunner.manager.find(SubspaceEntity, {
|
||||
where: { space: { uuid: spaceUuid } },
|
||||
});
|
||||
|
||||
const subspaceUuids = subspaces.map((subspace) => subspace.uuid);
|
||||
|
||||
if (subspaceUuids.length > 0) {
|
||||
await this.subSpaceService.clearSubspaces(subspaceUuids, queryRunner);
|
||||
}
|
||||
|
||||
await this.disableSpace(space, orphanSpace);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
return new SuccessResponseDto({
|
||||
message: `Space with ID ${spaceUuid} successfully deleted`,
|
||||
statusCode: HttpStatus.OK,
|
||||
});
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
console.error('Error deleting space:', error);
|
||||
|
||||
if (error instanceof HttpException) {
|
||||
throw error;
|
||||
}
|
||||
throw new HttpException(
|
||||
`An error occurred while deleting the space ${error.message}`,
|
||||
`An error occurred while deleting the space: ${error.message}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
@ -371,6 +431,9 @@ export class SpaceService {
|
||||
try {
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
const project = await this.spaceModelService.validateProject(
|
||||
params.projectUuid,
|
||||
);
|
||||
|
||||
const space =
|
||||
await this.validationService.validateSpaceWithinCommunityAndProject(
|
||||
@ -386,44 +449,70 @@ export class SpaceService {
|
||||
);
|
||||
}
|
||||
|
||||
if (space.spaceModel && !updateSpaceDto.spaceModelUuid) {
|
||||
space.spaceModel = null;
|
||||
await this.unlinkSpaceFromModel(space, queryRunner);
|
||||
}
|
||||
|
||||
this.updateSpaceProperties(space, updateSpaceDto);
|
||||
|
||||
const hasSubspace = updateSpaceDto.subspace?.length > 0;
|
||||
const hasTags = updateSpaceDto.tags?.length > 0;
|
||||
if (updateSpaceDto.spaceModelUuid) {
|
||||
const spaceModel = await this.validationService.validateSpaceModel(
|
||||
updateSpaceDto.spaceModelUuid,
|
||||
);
|
||||
|
||||
if (hasSubspace || hasTags) {
|
||||
const hasDependencies =
|
||||
space.devices?.length > 0 ||
|
||||
space.subspaces?.length > 0 ||
|
||||
space.productAllocations?.length > 0;
|
||||
|
||||
if (!hasDependencies && !space.spaceModel) {
|
||||
await this.spaceModelService.linkToSpace(
|
||||
space,
|
||||
spaceModel,
|
||||
queryRunner,
|
||||
);
|
||||
} else if (hasDependencies) {
|
||||
await this.spaceModelService.overwriteSpace(
|
||||
space,
|
||||
project,
|
||||
queryRunner,
|
||||
);
|
||||
await this.spaceModelService.linkToSpace(
|
||||
space,
|
||||
spaceModel,
|
||||
queryRunner,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const hasSubspace = updateSpaceDto.subspace?.length > 0;
|
||||
|
||||
if (hasSubspace) {
|
||||
space.spaceModel = null;
|
||||
await this.tagService.unlinkModels(space.tags, queryRunner);
|
||||
await this.subSpaceService.unlinkModels(space.subspaces, queryRunner);
|
||||
}
|
||||
await queryRunner.manager.save(space);
|
||||
|
||||
if (hasSubspace) {
|
||||
const modifiedSubspaces = this.tagService.getModifiedSubspaces(
|
||||
updateSpaceDto.tags,
|
||||
updateSpaceDto.subspace,
|
||||
);
|
||||
|
||||
await this.subSpaceService.modifySubSpace(
|
||||
modifiedSubspaces,
|
||||
updateSpaceDto.subspace,
|
||||
queryRunner,
|
||||
space,
|
||||
projectUuid,
|
||||
updateSpaceDto.tags,
|
||||
);
|
||||
}
|
||||
|
||||
if (hasTags) {
|
||||
const spaceTagsAfterMove = this.tagService.getSubspaceTagsToBeAdded(
|
||||
if (updateSpaceDto.tags) {
|
||||
await this.spaceProductAllocationService.updateSpaceProductAllocations(
|
||||
updateSpaceDto.tags,
|
||||
projectUuid,
|
||||
space,
|
||||
queryRunner,
|
||||
updateSpaceDto.subspace,
|
||||
);
|
||||
|
||||
await this.tagService.modifyTags(
|
||||
spaceTagsAfterMove,
|
||||
queryRunner,
|
||||
space,
|
||||
);
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
return new SuccessResponseDto({
|
||||
@ -438,7 +527,7 @@ export class SpaceService {
|
||||
}
|
||||
|
||||
throw new HttpException(
|
||||
'An error occurred while updating the space',
|
||||
`An error occurred while updating the space: error ${error}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
} finally {
|
||||
@ -451,18 +540,12 @@ export class SpaceService {
|
||||
queryRunner: QueryRunner,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await queryRunner.manager.update(
|
||||
this.spaceRepository.target,
|
||||
{ uuid: space.uuid },
|
||||
{
|
||||
spaceModel: null,
|
||||
},
|
||||
);
|
||||
|
||||
// Unlink subspaces and tags if they exist
|
||||
if (space.subspaces || space.tags) {
|
||||
if (space.tags) {
|
||||
await this.tagService.unlinkModels(space.tags, queryRunner);
|
||||
await this.spaceProductAllocationService.unlinkModels(
|
||||
space,
|
||||
queryRunner,
|
||||
);
|
||||
}
|
||||
|
||||
if (space.subspaces) {
|
||||
@ -559,14 +642,8 @@ export class SpaceService {
|
||||
const map = new Map<string, SpaceEntity>();
|
||||
|
||||
// Step 1: Create a map of spaces by UUID
|
||||
spaces.forEach((space) => {
|
||||
map.set(
|
||||
space.uuid,
|
||||
this.spaceRepository.create({
|
||||
...space,
|
||||
children: [], // Add children if needed
|
||||
}),
|
||||
);
|
||||
spaces.forEach((space: any) => {
|
||||
map.set(space.uuid, { ...space, children: [] }); // Ensure children are reset
|
||||
});
|
||||
|
||||
// Step 2: Organize the hierarchy
|
||||
@ -574,9 +651,14 @@ export class SpaceService {
|
||||
spaces.forEach((space) => {
|
||||
if (space.parent && space.parent.uuid) {
|
||||
const parent = map.get(space.parent.uuid);
|
||||
parent?.children?.push(map.get(space.uuid));
|
||||
if (parent) {
|
||||
const child = map.get(space.uuid);
|
||||
if (child && !parent.children.some((c) => c.uuid === child.uuid)) {
|
||||
parent.children.push(child);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rootSpaces.push(map.get(space.uuid));
|
||||
rootSpaces.push(map.get(space.uuid)!); // Push only root spaces
|
||||
}
|
||||
});
|
||||
|
||||
@ -603,21 +685,33 @@ export class SpaceService {
|
||||
subspaces: AddSubspaceDto[],
|
||||
space: SpaceEntity,
|
||||
queryRunner: QueryRunner,
|
||||
tags: CreateTagDto[],
|
||||
tags: ProcessTagDto[],
|
||||
projectUuid: string,
|
||||
): Promise<void> {
|
||||
space.subspaces = await this.subSpaceService.createSubspacesFromDto(
|
||||
subspaces,
|
||||
space,
|
||||
queryRunner,
|
||||
tags,
|
||||
null,
|
||||
projectUuid,
|
||||
);
|
||||
}
|
||||
|
||||
private async createTags(
|
||||
tags: CreateTagDto[],
|
||||
tags: ProcessTagDto[],
|
||||
projectUuid: string,
|
||||
queryRunner: QueryRunner,
|
||||
space: SpaceEntity,
|
||||
): Promise<void> {
|
||||
space.tags = await this.tagService.createTags(tags, queryRunner, space);
|
||||
const processedTags = await this.newTagService.processTags(
|
||||
tags,
|
||||
projectUuid,
|
||||
queryRunner,
|
||||
);
|
||||
await this.spaceProductAllocationService.createSpaceProductAllocations(
|
||||
space,
|
||||
processedTags,
|
||||
queryRunner,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -81,24 +81,6 @@ export class SubspaceDeviceService {
|
||||
const subspace = await this.findSubspace(subSpaceUuid);
|
||||
const device = await this.findDevice(deviceUuid);
|
||||
|
||||
if (device.tag?.subspace?.uuid !== subspace.uuid) {
|
||||
await this.tagRepository.update(
|
||||
{ uuid: device.tag.uuid },
|
||||
{ subspace, space: null },
|
||||
);
|
||||
}
|
||||
|
||||
if (!device.tag) {
|
||||
const tag = this.tagRepository.create({
|
||||
tag: `Tag ${this.findNextTag()}`,
|
||||
product: device.productDevice,
|
||||
subspace: subspace,
|
||||
device: device,
|
||||
});
|
||||
await this.tagRepository.save(tag);
|
||||
device.tag = tag;
|
||||
}
|
||||
|
||||
device.subspace = subspace;
|
||||
|
||||
const newDevice = await this.deviceRepository.save(device);
|
||||
@ -140,26 +122,6 @@ export class SubspaceDeviceService {
|
||||
);
|
||||
}
|
||||
|
||||
if (device.tag?.subspace !== null) {
|
||||
await this.tagRepository.update(
|
||||
{ uuid: device.tag.uuid },
|
||||
{ subspace: null, space: device.spaceDevice },
|
||||
);
|
||||
}
|
||||
|
||||
if (!device.tag) {
|
||||
const tag = this.tagRepository.create({
|
||||
tag: `Tag ${this.findNextTag()}`,
|
||||
product: device.productDevice,
|
||||
subspace: null,
|
||||
space: device.spaceDevice,
|
||||
device: device,
|
||||
});
|
||||
|
||||
await this.tagRepository.save(tag);
|
||||
device.tag = tag;
|
||||
}
|
||||
|
||||
device.subspace = null;
|
||||
const updatedDevice = await this.deviceRepository.save(device);
|
||||
|
||||
|
@ -0,0 +1,577 @@
|
||||
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
||||
import { ProductEntity } from '@app/common/modules/product/entities';
|
||||
import { SpaceProductAllocationRepository } from '@app/common/modules/space';
|
||||
import { SpaceProductAllocationEntity } from '@app/common/modules/space/entities/space-product-allocation.entity';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||
import { SubspaceProductAllocationEntity } from '@app/common/modules/space/entities/subspace/subspace-product-allocation.entity';
|
||||
import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity';
|
||||
import { SubspaceProductAllocationRepository } from '@app/common/modules/space/repositories/subspace.repository';
|
||||
import { NewTagEntity } from '@app/common/modules/tag';
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { ModifyTagDto } from 'src/space/dtos/tag/modify-tag.dto';
|
||||
import { ISingleSubspace } from 'src/space/interfaces/single-subspace.interface';
|
||||
import { ProcessTagDto } from 'src/tags/dtos';
|
||||
import { TagService as NewTagService } from 'src/tags/services';
|
||||
import { In, QueryRunner } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
export class SubspaceProductAllocationService {
|
||||
constructor(
|
||||
private readonly tagService: NewTagService,
|
||||
|
||||
private readonly spaceProductAllocationRepository: SpaceProductAllocationRepository,
|
||||
private readonly subspaceProductAllocationRepository: SubspaceProductAllocationRepository,
|
||||
) {}
|
||||
|
||||
async createSubspaceProductAllocations(
|
||||
subspace: SubspaceEntity,
|
||||
processedTags: NewTagEntity[],
|
||||
queryRunner?: QueryRunner,
|
||||
spaceAllocationsToExclude?: SpaceProductAllocationEntity[],
|
||||
): Promise<void> {
|
||||
try {
|
||||
if (!processedTags.length) return;
|
||||
|
||||
const allocations: SubspaceProductAllocationEntity[] = [];
|
||||
|
||||
for (const tag of processedTags) {
|
||||
await this.validateTagWithinSubspace(
|
||||
queryRunner,
|
||||
tag,
|
||||
subspace,
|
||||
spaceAllocationsToExclude,
|
||||
);
|
||||
|
||||
let allocation = await this.getAllocationByProduct(
|
||||
tag.product,
|
||||
subspace,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
if (!allocation) {
|
||||
allocation = this.createNewSubspaceAllocation(
|
||||
subspace,
|
||||
tag,
|
||||
queryRunner,
|
||||
);
|
||||
allocations.push(allocation);
|
||||
} else if (!allocation.tags.some((t) => t.uuid === tag.uuid)) {
|
||||
allocation.tags.push(tag);
|
||||
await this.saveAllocation(allocation, queryRunner);
|
||||
}
|
||||
}
|
||||
|
||||
if (allocations.length > 0) {
|
||||
await this.saveAllocations(allocations, queryRunner);
|
||||
}
|
||||
} catch (error) {
|
||||
throw this.handleError(
|
||||
error,
|
||||
'Failed to create subspace product allocations',
|
||||
);
|
||||
}
|
||||
}
|
||||
async updateSubspaceProductAllocations(
|
||||
subspaces: ISingleSubspace[],
|
||||
projectUuid: string,
|
||||
queryRunner: QueryRunner,
|
||||
space: SpaceEntity,
|
||||
spaceTagUpdateDtos?: ModifyTagDto[],
|
||||
) {
|
||||
const spaceAllocationToExclude: SpaceProductAllocationEntity[] = [];
|
||||
for (const subspace of subspaces) {
|
||||
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,
|
||||
}));
|
||||
|
||||
const tagsToDeleteDto = tagDtos.filter(
|
||||
(dto) => dto.action === ModifyAction.DELETE,
|
||||
);
|
||||
|
||||
if (tagsToAddDto.length > 0) {
|
||||
let processedTags = await this.tagService.processTags(
|
||||
tagsToAddDto,
|
||||
projectUuid,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
for (const subspaceDto of subspaces) {
|
||||
if (
|
||||
subspaceDto !== subspace &&
|
||||
subspaceDto.action === ModifyAction.UPDATE &&
|
||||
subspaceDto.tags
|
||||
) {
|
||||
const deletedTags = subspaceDto.tags.filter(
|
||||
(tagDto) =>
|
||||
tagDto.action === ModifyAction.DELETE &&
|
||||
processedTags.some((tag) => tag.uuid === tagDto.tagUuid),
|
||||
);
|
||||
|
||||
for (const deletedTag of deletedTags) {
|
||||
const allocation = await queryRunner.manager.findOne(
|
||||
SubspaceProductAllocationEntity,
|
||||
{
|
||||
where: {
|
||||
subspace: { uuid: subspaceDto.subspace.uuid },
|
||||
},
|
||||
relations: ['tags', 'product', 'subspace'],
|
||||
},
|
||||
);
|
||||
|
||||
const isCommonTag = allocation.tags.some(
|
||||
(tag) => tag.uuid === deletedTag.tagUuid,
|
||||
);
|
||||
|
||||
if (allocation && isCommonTag) {
|
||||
const tagEntity = allocation.tags.find(
|
||||
(tag) => tag.uuid === deletedTag.tagUuid,
|
||||
);
|
||||
|
||||
allocation.tags = allocation.tags.filter(
|
||||
(tag) => tag.uuid !== deletedTag.tagUuid,
|
||||
);
|
||||
|
||||
await queryRunner.manager.save(allocation);
|
||||
|
||||
const productAllocationExistInSubspace =
|
||||
await queryRunner.manager.findOne(
|
||||
SubspaceProductAllocationEntity,
|
||||
{
|
||||
where: {
|
||||
subspace: {
|
||||
uuid: subspaceDto.subspace.uuid,
|
||||
},
|
||||
product: { uuid: allocation.product.uuid },
|
||||
},
|
||||
relations: ['tags'],
|
||||
},
|
||||
);
|
||||
|
||||
if (productAllocationExistInSubspace) {
|
||||
productAllocationExistInSubspace.tags.push(tagEntity);
|
||||
await queryRunner.manager.save(
|
||||
productAllocationExistInSubspace,
|
||||
);
|
||||
} else {
|
||||
const newProductAllocation = queryRunner.manager.create(
|
||||
SubspaceProductAllocationEntity,
|
||||
{
|
||||
subspace: subspace.subspace,
|
||||
product: allocation.product,
|
||||
tags: [tagEntity],
|
||||
},
|
||||
);
|
||||
|
||||
await queryRunner.manager.save(newProductAllocation);
|
||||
}
|
||||
|
||||
processedTags = processedTags.filter(
|
||||
(tag) => tag.uuid !== deletedTag.tagUuid,
|
||||
);
|
||||
|
||||
subspaceDto.tags = subspaceDto.tags.filter(
|
||||
(tagDto) => tagDto.tagUuid !== deletedTag.tagUuid,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
subspaceDto !== subspace &&
|
||||
subspaceDto.action === ModifyAction.DELETE
|
||||
) {
|
||||
const allocation = await queryRunner.manager.findOne(
|
||||
SubspaceProductAllocationEntity,
|
||||
{
|
||||
where: {
|
||||
subspace: { uuid: subspaceDto.subspace.uuid },
|
||||
},
|
||||
relations: ['tags'],
|
||||
},
|
||||
);
|
||||
|
||||
const repeatedTags = allocation?.tags.filter((tag) =>
|
||||
processedTags.some(
|
||||
(processedTag) => processedTag.uuid === tag.uuid,
|
||||
),
|
||||
);
|
||||
if (repeatedTags.length > 0) {
|
||||
allocation.tags = allocation.tags.filter(
|
||||
(tag) =>
|
||||
!repeatedTags.some(
|
||||
(repeatedTag) => repeatedTag.uuid === tag.uuid,
|
||||
),
|
||||
);
|
||||
|
||||
await queryRunner.manager.save(allocation);
|
||||
|
||||
const productAllocationExistInSubspace =
|
||||
await queryRunner.manager.findOne(
|
||||
SubspaceProductAllocationEntity,
|
||||
{
|
||||
where: {
|
||||
subspace: { uuid: subspaceDto.subspace.uuid },
|
||||
product: { uuid: allocation.product.uuid },
|
||||
},
|
||||
relations: ['tags'],
|
||||
},
|
||||
);
|
||||
|
||||
if (productAllocationExistInSubspace) {
|
||||
productAllocationExistInSubspace.tags.push(...repeatedTags);
|
||||
await queryRunner.manager.save(
|
||||
productAllocationExistInSubspace,
|
||||
);
|
||||
} else {
|
||||
const newProductAllocation = queryRunner.manager.create(
|
||||
SubspaceProductAllocationEntity,
|
||||
{
|
||||
subspace: subspace.subspace,
|
||||
product: allocation.product,
|
||||
tags: repeatedTags,
|
||||
},
|
||||
);
|
||||
|
||||
await queryRunner.manager.save(newProductAllocation);
|
||||
}
|
||||
|
||||
const newAllocation = queryRunner.manager.create(
|
||||
SubspaceProductAllocationEntity,
|
||||
{
|
||||
subspace: subspace.subspace,
|
||||
product: allocation.product,
|
||||
tags: repeatedTags,
|
||||
},
|
||||
);
|
||||
|
||||
await queryRunner.manager.save(newAllocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (spaceTagUpdateDtos) {
|
||||
const deletedSpaceTags = spaceTagUpdateDtos.filter(
|
||||
(tagDto) =>
|
||||
tagDto.action === ModifyAction.DELETE &&
|
||||
processedTags.some((tag) => tag.uuid === tagDto.tagUuid),
|
||||
);
|
||||
for (const deletedTag of deletedSpaceTags) {
|
||||
const allocation = await queryRunner.manager.findOne(
|
||||
SpaceProductAllocationEntity,
|
||||
{
|
||||
where: {
|
||||
space: { uuid: space.uuid },
|
||||
tags: { uuid: deletedTag.tagUuid },
|
||||
},
|
||||
relations: ['tags', 'subspace'],
|
||||
},
|
||||
);
|
||||
|
||||
if (
|
||||
allocation &&
|
||||
allocation.tags.some((tag) => tag.uuid === deletedTag.tagUuid)
|
||||
) {
|
||||
spaceAllocationToExclude.push(allocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.createSubspaceProductAllocations(
|
||||
subspace.subspace,
|
||||
processedTags,
|
||||
queryRunner,
|
||||
spaceAllocationToExclude,
|
||||
);
|
||||
}
|
||||
if (tagsToDeleteDto.length > 0) {
|
||||
await this.processDeleteActions(tagsToDeleteDto, queryRunner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async processDeleteActions(
|
||||
dtos: ModifyTagDto[],
|
||||
queryRunner: QueryRunner,
|
||||
): Promise<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 unlinkModels(
|
||||
allocations: SubspaceProductAllocationEntity[],
|
||||
queryRunner: QueryRunner,
|
||||
) {
|
||||
try {
|
||||
if (allocations.length === 0) return;
|
||||
|
||||
const allocationUuids = allocations.map((allocation) => allocation.uuid);
|
||||
|
||||
await queryRunner.manager.update(
|
||||
SubspaceProductAllocationEntity,
|
||||
{ uuid: In(allocationUuids) },
|
||||
{ inheritedFromModel: null },
|
||||
);
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
'Failed to unlink models',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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'],
|
||||
}));
|
||||
|
||||
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,
|
||||
queryRunner?: QueryRunner,
|
||||
): SubspaceProductAllocationEntity {
|
||||
return queryRunner
|
||||
? queryRunner.manager.create(SubspaceProductAllocationEntity, {
|
||||
subspace,
|
||||
product: tag.product,
|
||||
tags: [tag],
|
||||
})
|
||||
: this.subspaceProductAllocationRepository.create({
|
||||
subspace,
|
||||
product: tag.product,
|
||||
tags: [tag],
|
||||
});
|
||||
}
|
||||
private async getAllocationByProduct(
|
||||
product: ProductEntity,
|
||||
subspace: SubspaceEntity,
|
||||
queryRunner?: QueryRunner,
|
||||
): Promise<SubspaceProductAllocationEntity | null> {
|
||||
return queryRunner
|
||||
? queryRunner.manager.findOne(SubspaceProductAllocationEntity, {
|
||||
where: {
|
||||
subspace: { uuid: subspace.uuid },
|
||||
product: { uuid: product.uuid },
|
||||
},
|
||||
relations: ['tags'],
|
||||
})
|
||||
: this.subspaceProductAllocationRepository.findOne({
|
||||
where: {
|
||||
subspace: { uuid: subspace.uuid },
|
||||
product: { uuid: product.uuid },
|
||||
},
|
||||
relations: ['tags'],
|
||||
});
|
||||
}
|
||||
private async saveAllocation(
|
||||
allocation: SubspaceProductAllocationEntity,
|
||||
queryRunner?: QueryRunner,
|
||||
): Promise<void> {
|
||||
if (queryRunner) {
|
||||
await queryRunner.manager.save(
|
||||
SubspaceProductAllocationEntity,
|
||||
allocation,
|
||||
);
|
||||
} else {
|
||||
await this.subspaceProductAllocationRepository.save(allocation);
|
||||
}
|
||||
}
|
||||
|
||||
private async saveAllocations(
|
||||
allocations: SubspaceProductAllocationEntity[],
|
||||
queryRunner?: QueryRunner,
|
||||
): Promise<void> {
|
||||
if (queryRunner) {
|
||||
await queryRunner.manager.save(
|
||||
SubspaceProductAllocationEntity,
|
||||
allocations,
|
||||
);
|
||||
} else {
|
||||
await this.subspaceProductAllocationRepository.save(allocations);
|
||||
}
|
||||
}
|
||||
private handleError(error: any, message: string): HttpException {
|
||||
return new HttpException(
|
||||
error instanceof HttpException ? error.message : message,
|
||||
error instanceof HttpException
|
||||
? error.getStatus()
|
||||
: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
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();
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error instanceof HttpException
|
||||
? error.message
|
||||
: 'An unexpected error occurred while clearing allocations',
|
||||
error instanceof HttpException
|
||||
? error.getStatus()
|
||||
: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,8 +2,6 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import {
|
||||
AddSubspaceDto,
|
||||
CreateTagDto,
|
||||
DeleteSubspaceDto,
|
||||
GetSpaceParam,
|
||||
GetSubSpaceParam,
|
||||
ModifySubspaceDto,
|
||||
@ -16,10 +14,7 @@ import {
|
||||
import { PageResponse } from '@app/common/dto/pagination.response.dto';
|
||||
import { SubspaceDto } from '@app/common/modules/space/dtos';
|
||||
import { In, QueryRunner } from 'typeorm';
|
||||
import {
|
||||
SpaceEntity,
|
||||
SubspaceEntity,
|
||||
} from '@app/common/modules/space/entities';
|
||||
|
||||
import { SubspaceModelEntity } from '@app/common/modules/space-model';
|
||||
import { ValidationService } from '../space-validation.service';
|
||||
import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
|
||||
@ -27,6 +22,12 @@ import { TagService } from '../tag';
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class SubSpaceService {
|
||||
@ -34,7 +35,9 @@ export class SubSpaceService {
|
||||
private readonly subspaceRepository: SubspaceRepository,
|
||||
private readonly validationService: ValidationService,
|
||||
private readonly tagService: TagService,
|
||||
private readonly newTagService: NewTagService,
|
||||
public readonly deviceService: SubspaceDeviceService,
|
||||
private readonly subspaceProductAllocationService: SubspaceProductAllocationService,
|
||||
) {}
|
||||
|
||||
async createSubspaces(
|
||||
@ -52,6 +55,7 @@ export class SubSpaceService {
|
||||
subspaceNames,
|
||||
subspaceData[0].space,
|
||||
);
|
||||
|
||||
const subspaces = subspaceData.map((data) =>
|
||||
queryRunner.manager.create(this.subspaceRepository.target, data),
|
||||
);
|
||||
@ -66,36 +70,31 @@ export class SubSpaceService {
|
||||
|
||||
async createSubSpaceFromModel(
|
||||
subspaceModels: SubspaceModelEntity[],
|
||||
space: SpaceEntity,
|
||||
spaces: SpaceEntity[],
|
||||
queryRunner: QueryRunner,
|
||||
): Promise<void> {
|
||||
if (!subspaceModels?.length) return;
|
||||
if (!subspaceModels?.length || !spaces?.length) return;
|
||||
|
||||
const subspaceData = subspaceModels.map((subSpaceModel) => ({
|
||||
subspaceName: subSpaceModel.subspaceName,
|
||||
space,
|
||||
subSpaceModel,
|
||||
}));
|
||||
const subspaceData = [];
|
||||
|
||||
const subspaces = await this.createSubspaces(subspaceData, queryRunner);
|
||||
|
||||
await Promise.all(
|
||||
subspaceModels.map((model, index) =>
|
||||
this.tagService.createTagsFromModel(
|
||||
queryRunner,
|
||||
model.tags || [],
|
||||
null,
|
||||
subspaces[index],
|
||||
),
|
||||
),
|
||||
);
|
||||
for (const space of spaces) {
|
||||
for (const subSpaceModel of subspaceModels) {
|
||||
subspaceData.push({
|
||||
subspaceName: subSpaceModel.subspaceName,
|
||||
space,
|
||||
subSpaceModel,
|
||||
});
|
||||
}
|
||||
}
|
||||
await this.createSubspaces(subspaceData, queryRunner);
|
||||
}
|
||||
|
||||
async createSubspacesFromDto(
|
||||
addSubspaceDtos: AddSubspaceDto[],
|
||||
space: SpaceEntity,
|
||||
queryRunner: QueryRunner,
|
||||
otherTags?: CreateTagDto[],
|
||||
otherTags?: ProcessTagDto[],
|
||||
projectUuid?: string,
|
||||
): Promise<SubspaceEntity[]> {
|
||||
try {
|
||||
this.checkForDuplicateNames(
|
||||
@ -108,20 +107,23 @@ export class SubSpaceService {
|
||||
}));
|
||||
|
||||
const subspaces = await this.createSubspaces(subspaceData, queryRunner);
|
||||
|
||||
await Promise.all(
|
||||
addSubspaceDtos.map(async (dto, index) => {
|
||||
const otherDtoTags = addSubspaceDtos
|
||||
.filter((_, i) => i !== index)
|
||||
.flatMap((otherDto) => otherDto.tags || []);
|
||||
const subspace = subspaces[index];
|
||||
if (dto.tags?.length) {
|
||||
subspace.tags = await this.tagService.createTags(
|
||||
dto.tags,
|
||||
|
||||
const allTags = [...(dto.tags || []), ...(otherTags || [])];
|
||||
|
||||
if (allTags.length) {
|
||||
const processedTags = await this.newTagService.processTags(
|
||||
allTags,
|
||||
projectUuid,
|
||||
queryRunner,
|
||||
null,
|
||||
);
|
||||
|
||||
await this.subspaceProductAllocationService.createSubspaceProductAllocations(
|
||||
subspace,
|
||||
[...(otherTags || []), ...otherDtoTags],
|
||||
processedTags,
|
||||
queryRunner,
|
||||
);
|
||||
}
|
||||
}),
|
||||
@ -275,11 +277,11 @@ export class SubSpaceService {
|
||||
}
|
||||
}
|
||||
|
||||
async deleteSubspaces(
|
||||
/* async deleteSubspaces(
|
||||
deleteDtos: DeleteSubspaceDto[],
|
||||
queryRunner: QueryRunner,
|
||||
) {
|
||||
const deleteResults: { uuid: string }[] = [];
|
||||
/* const deleteResults: { uuid: string }[] = [];
|
||||
|
||||
for (const dto of deleteDtos) {
|
||||
const subspace = await this.findOne(dto.subspaceUuid);
|
||||
@ -312,21 +314,41 @@ export class SubSpaceService {
|
||||
deleteResults.push({ uuid: dto.subspaceUuid });
|
||||
}
|
||||
|
||||
return deleteResults;
|
||||
}
|
||||
return deleteResults;
|
||||
} */
|
||||
|
||||
async modifySubSpace(
|
||||
subspaceDtos: ModifySubspaceDto[],
|
||||
queryRunner: QueryRunner,
|
||||
space?: SpaceEntity,
|
||||
projectUuid?: string,
|
||||
spaceTagUpdateDtos?: ModifyTagDto[],
|
||||
) {
|
||||
if (!subspaceDtos || subspaceDtos.length === 0) {
|
||||
return;
|
||||
}
|
||||
const addedSubspaces = [];
|
||||
const updatedSubspaces = [];
|
||||
|
||||
for (const subspace of subspaceDtos) {
|
||||
switch (subspace.action) {
|
||||
case ModifyAction.ADD:
|
||||
await this.handleAddAction(subspace, space, queryRunner);
|
||||
const addedSubspace = await this.handleAddAction(
|
||||
subspace,
|
||||
space,
|
||||
queryRunner,
|
||||
);
|
||||
if (addedSubspace) addedSubspaces.push(addedSubspace);
|
||||
|
||||
break;
|
||||
case ModifyAction.UPDATE:
|
||||
await this.handleUpdateAction(subspace, queryRunner);
|
||||
const updatedSubspace = await this.handleUpdateAction(
|
||||
subspace,
|
||||
queryRunner,
|
||||
);
|
||||
if (updatedSubspace) {
|
||||
updatedSubspaces.push(updatedSubspace);
|
||||
}
|
||||
break;
|
||||
case ModifyAction.DELETE:
|
||||
await this.handleDeleteAction(subspace, queryRunner);
|
||||
@ -338,28 +360,50 @@ export class SubSpaceService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const combinedSubspaces = [...addedSubspaces, ...updatedSubspaces].filter(
|
||||
(subspace) => subspace !== undefined,
|
||||
);
|
||||
|
||||
if (combinedSubspaces.length > 0) {
|
||||
await this.subspaceProductAllocationService.updateSubspaceProductAllocations(
|
||||
combinedSubspaces,
|
||||
projectUuid,
|
||||
queryRunner,
|
||||
space,
|
||||
spaceTagUpdateDtos,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async unlinkModels(
|
||||
subspaces: SubspaceEntity[],
|
||||
queryRunner: QueryRunner,
|
||||
): Promise<void> {
|
||||
if (!subspaces || subspaces.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (!subspaces || subspaces.length === 0) return;
|
||||
|
||||
try {
|
||||
const allTags = subspaces.flatMap((subSpace) => {
|
||||
subSpace.subSpaceModel = null;
|
||||
return subSpace.tags || [];
|
||||
});
|
||||
const subspaceUuids = subspaces.map((subSpace) => subSpace.uuid);
|
||||
|
||||
await this.tagService.unlinkModels(allTags, queryRunner);
|
||||
const allocations: SubspaceProductAllocationEntity[] = subspaces.flatMap(
|
||||
(subspace) => subspace.productAllocations || [],
|
||||
);
|
||||
|
||||
await queryRunner.manager.save(subspaces);
|
||||
if (allocations.length > 0) {
|
||||
await this.subspaceProductAllocationService.unlinkModels(
|
||||
allocations,
|
||||
queryRunner,
|
||||
);
|
||||
}
|
||||
|
||||
await queryRunner.manager.update(
|
||||
SubspaceEntity,
|
||||
{ uuid: In(subspaceUuids) },
|
||||
{ subSpaceModel: null },
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) throw error;
|
||||
throw new HttpException(
|
||||
`Failed to unlink subspace models: ${error.message}`,
|
||||
`Failed to unlink subspace models: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
@ -383,10 +427,10 @@ export class SubSpaceService {
|
||||
space: SpaceEntity,
|
||||
queryRunner: QueryRunner,
|
||||
): Promise<SubspaceEntity> {
|
||||
const createTagDtos: CreateTagDto[] =
|
||||
const createTagDtos: ProcessTagDto[] =
|
||||
subspace.tags?.map((tag) => ({
|
||||
tag: tag.tag as string,
|
||||
uuid: tag.uuid,
|
||||
name: tag.name as string,
|
||||
uuid: tag.tagUuid,
|
||||
productUuid: tag.productUuid as string,
|
||||
})) || [];
|
||||
const subSpace = await this.createSubspacesFromDto(
|
||||
@ -400,32 +444,22 @@ export class SubSpaceService {
|
||||
private async handleUpdateAction(
|
||||
modifyDto: ModifySubspaceDto,
|
||||
queryRunner: QueryRunner,
|
||||
): Promise<void> {
|
||||
): Promise<SubspaceEntity> {
|
||||
const subspace = await this.findOne(modifyDto.uuid);
|
||||
await this.update(
|
||||
const updatedSubspace = await this.update(
|
||||
queryRunner,
|
||||
subspace,
|
||||
modifyDto.subspaceName,
|
||||
modifyDto.tags,
|
||||
);
|
||||
return updatedSubspace;
|
||||
}
|
||||
|
||||
async update(
|
||||
queryRunner: QueryRunner,
|
||||
subspace: SubspaceEntity,
|
||||
subspaceName?: string,
|
||||
modifyTagDto?: ModifyTagDto[],
|
||||
) {
|
||||
await this.updateSubspaceName(queryRunner, subspace, subspaceName);
|
||||
|
||||
if (modifyTagDto?.length) {
|
||||
await this.tagService.modifyTags(
|
||||
modifyTagDto,
|
||||
queryRunner,
|
||||
null,
|
||||
subspace,
|
||||
);
|
||||
}
|
||||
return await this.updateSubspaceName(queryRunner, subspace, subspaceName);
|
||||
}
|
||||
|
||||
async handleDeleteAction(
|
||||
@ -441,10 +475,10 @@ export class SubSpaceService {
|
||||
);
|
||||
|
||||
if (subspace.tags?.length) {
|
||||
const modifyTagDtos: CreateTagDto[] = subspace.tags.map((tag) => ({
|
||||
const modifyTagDtos: ProcessTagDto[] = subspace.tags.map((tag) => ({
|
||||
uuid: tag.uuid,
|
||||
action: ModifyAction.ADD,
|
||||
tag: tag.tag,
|
||||
name: tag.tag,
|
||||
productUuid: tag.product.uuid,
|
||||
}));
|
||||
await this.tagService.moveTags(
|
||||
@ -481,11 +515,12 @@ export class SubSpaceService {
|
||||
queryRunner: QueryRunner,
|
||||
subSpace: SubspaceEntity,
|
||||
subspaceName?: string,
|
||||
): Promise<void> {
|
||||
): Promise<SubspaceEntity> {
|
||||
if (subspaceName) {
|
||||
subSpace.subspaceName = subspaceName;
|
||||
await queryRunner.manager.save(subSpace);
|
||||
return await queryRunner.manager.save(subSpace);
|
||||
}
|
||||
return subSpace;
|
||||
}
|
||||
|
||||
private async checkForDuplicateNames(names: string[]): Promise<void> {
|
||||
@ -539,4 +574,30 @@ export class SubSpaceService {
|
||||
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(
|
||||
SubspaceEntity,
|
||||
{ uuid: In(subspaceUuids) },
|
||||
{ disabled: true },
|
||||
);
|
||||
|
||||
await this.subspaceProductAllocationService.clearAllAllocations(
|
||||
subspaceUuids,
|
||||
queryRunner,
|
||||
);
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error instanceof HttpException
|
||||
? error.message
|
||||
: 'An unexpected error occurred while clearing subspaces',
|
||||
error instanceof HttpException
|
||||
? error.getStatus()
|
||||
: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,14 @@
|
||||
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
||||
import {
|
||||
SpaceEntity,
|
||||
SubspaceEntity,
|
||||
TagEntity,
|
||||
TagRepository,
|
||||
} from '@app/common/modules/space';
|
||||
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 { CreateTagDto, ModifySubspaceDto } from 'src/space/dtos';
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
@ -20,11 +19,11 @@ export class TagService {
|
||||
) {}
|
||||
|
||||
async createTags(
|
||||
tags: CreateTagDto[],
|
||||
tags: ProcessTagDto[],
|
||||
queryRunner: QueryRunner,
|
||||
space?: SpaceEntity,
|
||||
subspace?: SubspaceEntity,
|
||||
additionalTags?: CreateTagDto[],
|
||||
additionalTags?: ProcessTagDto[],
|
||||
tagsToDelete?: ModifyTagDto[],
|
||||
): Promise<TagEntity[]> {
|
||||
this.validateTagsInput(tags);
|
||||
@ -57,7 +56,7 @@ export class TagService {
|
||||
}
|
||||
|
||||
async bulkSaveTags(
|
||||
tags: CreateTagDto[],
|
||||
tags: ProcessTagDto[],
|
||||
queryRunner: QueryRunner,
|
||||
space?: SpaceEntity,
|
||||
subspace?: SubspaceEntity,
|
||||
@ -94,7 +93,7 @@ export class TagService {
|
||||
}
|
||||
|
||||
async moveTags(
|
||||
tags: CreateTagDto[],
|
||||
tags: ProcessTagDto[],
|
||||
queryRunner: QueryRunner,
|
||||
space?: SpaceEntity,
|
||||
subspace?: SubspaceEntity,
|
||||
@ -136,15 +135,11 @@ export class TagService {
|
||||
|
||||
return tag;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error moving tag with UUID ${tagDto.uuid}: ${error.message}`,
|
||||
);
|
||||
throw error; // Re-throw the error to propagate it to the parent Promise.all
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`Error in moveTags: ${error.message}`);
|
||||
throw new HttpException(
|
||||
`Failed to move tags due to an unexpected error: ${error.message}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
@ -179,20 +174,20 @@ export class TagService {
|
||||
subspace?: SubspaceEntity,
|
||||
): Promise<TagEntity> {
|
||||
try {
|
||||
const existingTag = await this.getTagByUuid(tag.uuid);
|
||||
const existingTag = await this.getTagByUuid(tag.tagUuid);
|
||||
|
||||
const contextSpace = space ?? subspace?.space;
|
||||
|
||||
if (contextSpace && tag.tag !== existingTag.tag) {
|
||||
if (contextSpace && tag.name !== existingTag.tag) {
|
||||
await this.checkTagReuse(
|
||||
tag.tag,
|
||||
tag.name,
|
||||
existingTag.product.uuid,
|
||||
contextSpace,
|
||||
);
|
||||
}
|
||||
|
||||
return await queryRunner.manager.save(
|
||||
Object.assign(existingTag, { tag: tag.tag }),
|
||||
Object.assign(existingTag, { tag: tag.name }),
|
||||
);
|
||||
} catch (error) {
|
||||
throw this.handleUnexpectedError('Failed to update tags', error);
|
||||
@ -299,9 +294,9 @@ export class TagService {
|
||||
await this.createTags(
|
||||
[
|
||||
{
|
||||
tag: tag.tag,
|
||||
name: tag.name,
|
||||
productUuid: tag.productUuid,
|
||||
uuid: tag.uuid,
|
||||
uuid: tag.tagUuid,
|
||||
},
|
||||
],
|
||||
queryRunner,
|
||||
@ -315,7 +310,7 @@ export class TagService {
|
||||
await this.updateTag(tag, queryRunner, space, subspace);
|
||||
break;
|
||||
case ModifyAction.DELETE:
|
||||
await this.deleteTags([tag.uuid], queryRunner);
|
||||
await this.deleteTags([tag.tagUuid], queryRunner);
|
||||
break;
|
||||
default:
|
||||
throw new HttpException(
|
||||
@ -344,14 +339,14 @@ export class TagService {
|
||||
}
|
||||
}
|
||||
|
||||
private findDuplicateTags(tags: CreateTagDto[]): string[] {
|
||||
private findDuplicateTags(tags: ProcessTagDto[]): string[] {
|
||||
const seen = new Map<string, boolean>();
|
||||
const duplicates: string[] = [];
|
||||
|
||||
tags.forEach((tagDto) => {
|
||||
const key = `${tagDto.productUuid}-${tagDto.tag}`;
|
||||
const key = `${tagDto.productUuid}-${tagDto.name}`;
|
||||
if (seen.has(key)) {
|
||||
duplicates.push(`${tagDto.tag} for Product: ${tagDto.productUuid}`);
|
||||
duplicates.push(`${tagDto.name} for Product: ${tagDto.productUuid}`);
|
||||
} else {
|
||||
seen.set(key, true);
|
||||
}
|
||||
@ -387,7 +382,9 @@ export class TagService {
|
||||
|
||||
const filteredTagExists = tagExists.filter(
|
||||
(existingTag) =>
|
||||
!tagsToDelete?.some((deleteTag) => deleteTag.uuid === existingTag.uuid),
|
||||
!tagsToDelete?.some(
|
||||
(deleteTag) => deleteTag.tagUuid === existingTag.uuid,
|
||||
),
|
||||
);
|
||||
|
||||
if (filteredTagExists.length > 0) {
|
||||
@ -396,7 +393,7 @@ export class TagService {
|
||||
}
|
||||
|
||||
private async prepareTagEntity(
|
||||
tagDto: CreateTagDto,
|
||||
tagDto: ProcessTagDto,
|
||||
queryRunner: QueryRunner,
|
||||
space?: SpaceEntity,
|
||||
subspace?: SubspaceEntity,
|
||||
@ -414,14 +411,14 @@ export class TagService {
|
||||
|
||||
if (space) {
|
||||
await this.checkTagReuse(
|
||||
tagDto.tag,
|
||||
tagDto.name,
|
||||
tagDto.productUuid,
|
||||
space,
|
||||
tagsToDelete,
|
||||
);
|
||||
} else if (subspace && subspace.space) {
|
||||
await this.checkTagReuse(
|
||||
tagDto.tag,
|
||||
tagDto.name,
|
||||
tagDto.productUuid,
|
||||
subspace.space,
|
||||
);
|
||||
@ -433,7 +430,7 @@ export class TagService {
|
||||
}
|
||||
|
||||
return queryRunner.manager.create(TagEntity, {
|
||||
tag: tagDto.tag,
|
||||
tag: tagDto.name,
|
||||
product: product.data,
|
||||
space: space,
|
||||
subspace: subspace,
|
||||
@ -476,13 +473,13 @@ export class TagService {
|
||||
}
|
||||
|
||||
private combineTags(
|
||||
primaryTags: CreateTagDto[],
|
||||
additionalTags?: CreateTagDto[],
|
||||
): CreateTagDto[] {
|
||||
primaryTags: ProcessTagDto[],
|
||||
additionalTags?: ProcessTagDto[],
|
||||
): ProcessTagDto[] {
|
||||
return additionalTags ? [...primaryTags, ...additionalTags] : primaryTags;
|
||||
}
|
||||
|
||||
private ensureNoDuplicateTags(tags: CreateTagDto[]): void {
|
||||
private ensureNoDuplicateTags(tags: ProcessTagDto[]): void {
|
||||
const duplicates = this.findDuplicateTags(tags);
|
||||
|
||||
if (duplicates.length > 0) {
|
||||
@ -493,7 +490,7 @@ export class TagService {
|
||||
}
|
||||
}
|
||||
|
||||
private validateTagsInput(tags: CreateTagDto[]): void {
|
||||
private validateTagsInput(tags: ProcessTagDto[]): void {
|
||||
if (!tags?.length) {
|
||||
return;
|
||||
}
|
||||
@ -519,14 +516,14 @@ export class TagService {
|
||||
tagsToAdd
|
||||
.filter((tagToAdd) =>
|
||||
spaceTagsToDelete.some(
|
||||
(tagToDelete) => tagToAdd.uuid === tagToDelete.uuid,
|
||||
(tagToDelete) => tagToAdd.tagUuid === tagToDelete.tagUuid,
|
||||
),
|
||||
)
|
||||
.map((tag) => tag.uuid),
|
||||
.map((tag) => tag.tagUuid),
|
||||
);
|
||||
|
||||
const remainingTags = spaceTags.filter(
|
||||
(tag) => !commonTagUuids.has(tag.uuid), // Exclude tags in commonTagUuids
|
||||
(tag) => !commonTagUuids.has(tag.tagUuid), // Exclude tags in commonTagUuids
|
||||
);
|
||||
|
||||
return remainingTags;
|
||||
@ -553,11 +550,11 @@ export class TagService {
|
||||
);
|
||||
|
||||
const subspaceTagsToDeleteUuids = new Set(
|
||||
subspaceTagsToDelete.map((tag) => tag.uuid),
|
||||
subspaceTagsToDelete.map((tag) => tag.tagUuid),
|
||||
);
|
||||
|
||||
const commonTagsInSubspaces = subspaceTagsToAdd.filter((tag) =>
|
||||
subspaceTagsToDeleteUuids.has(tag.uuid),
|
||||
subspaceTagsToDeleteUuids.has(tag.tagUuid),
|
||||
);
|
||||
|
||||
// Find UUIDs of tags that are common between spaceTagsToAdd and subspace tags marked for deletion
|
||||
@ -569,17 +566,18 @@ export class TagService {
|
||||
subspace.tags?.filter(
|
||||
(tagToDelete) =>
|
||||
tagToDelete.action === 'delete' &&
|
||||
tagToAdd.uuid === tagToDelete.uuid,
|
||||
tagToAdd.tagUuid === tagToDelete.tagUuid,
|
||||
) || [],
|
||||
),
|
||||
)
|
||||
.map((tag) => tag.uuid),
|
||||
.map((tag) => tag.tagUuid),
|
||||
);
|
||||
|
||||
// Modify subspaceModels by removing tags with UUIDs present in commonTagUuids
|
||||
let modifiedSubspaces = subspaceModels.map((subspace) => ({
|
||||
...subspace,
|
||||
tags: subspace.tags?.filter((tag) => !commonTagUuids.has(tag.uuid)) || [],
|
||||
tags:
|
||||
subspace.tags?.filter((tag) => !commonTagUuids.has(tag.tagUuid)) || [],
|
||||
}));
|
||||
|
||||
modifiedSubspaces = modifiedSubspaces.map((subspace) => ({
|
||||
@ -590,7 +588,7 @@ export class TagService {
|
||||
!(
|
||||
tag.action === 'delete' &&
|
||||
commonTagsInSubspaces.some(
|
||||
(commonTag) => commonTag.uuid === tag.uuid,
|
||||
(commonTag) => commonTag.tagUuid === tag.tagUuid,
|
||||
)
|
||||
),
|
||||
) || [],
|
||||
|
@ -23,6 +23,7 @@ import {
|
||||
SpaceLinkRepository,
|
||||
TagRepository,
|
||||
InviteSpaceRepository,
|
||||
SpaceProductAllocationRepository,
|
||||
} from '@app/common/modules/space/repositories';
|
||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||
import {
|
||||
@ -46,18 +47,22 @@ import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log
|
||||
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 { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
|
||||
import {
|
||||
SubspaceProductAllocationRepository,
|
||||
SubspaceRepository,
|
||||
} from '@app/common/modules/space/repositories/subspace.repository';
|
||||
import { TagService } from './services/tag';
|
||||
import {
|
||||
SpaceModelService,
|
||||
SubSpaceModelService,
|
||||
TagModelService,
|
||||
} from 'src/space-model/services';
|
||||
import { UserService, UserSpaceService } from 'src/users/services';
|
||||
import { UserDevicePermissionService } from 'src/user-device-permission/services';
|
||||
@ -71,6 +76,13 @@ import {
|
||||
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';
|
||||
|
||||
export const CommandHandlers = [DisableSpaceHandler];
|
||||
|
||||
@ -83,6 +95,7 @@ export const CommandHandlers = [DisableSpaceHandler];
|
||||
SubSpaceController,
|
||||
SubSpaceDeviceController,
|
||||
SpaceSceneController,
|
||||
SpaceValidationController,
|
||||
],
|
||||
providers: [
|
||||
ValidationService,
|
||||
@ -105,6 +118,7 @@ export const CommandHandlers = [DisableSpaceHandler];
|
||||
UserRepository,
|
||||
SpaceUserService,
|
||||
SpaceSceneService,
|
||||
DeviceService,
|
||||
SceneService,
|
||||
SceneIconRepository,
|
||||
SceneRepository,
|
||||
@ -114,7 +128,6 @@ export const CommandHandlers = [DisableSpaceHandler];
|
||||
SceneDeviceRepository,
|
||||
SpaceModelService,
|
||||
SubSpaceModelService,
|
||||
TagModelService,
|
||||
ProjectRepository,
|
||||
SpaceModelRepository,
|
||||
SubspaceModelRepository,
|
||||
@ -130,6 +143,17 @@ export const CommandHandlers = [DisableSpaceHandler];
|
||||
InviteUserRepository,
|
||||
InviteUserSpaceRepository,
|
||||
AutomationRepository,
|
||||
TagService,
|
||||
NewTagService,
|
||||
SpaceModelProductAllocationRepoitory,
|
||||
SubspaceModelProductAllocationRepoitory,
|
||||
NewTagRepository,
|
||||
SpaceModelProductAllocationService,
|
||||
SubspaceModelProductAllocationService,
|
||||
SpaceProductAllocationService,
|
||||
SubspaceProductAllocationService,
|
||||
SpaceProductAllocationRepository,
|
||||
SubspaceProductAllocationRepository,
|
||||
],
|
||||
exports: [SpaceService],
|
||||
})
|
||||
|
1
src/tags/controllers/index.ts
Normal file
1
src/tags/controllers/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './tags.controller';
|
45
src/tags/controllers/tags.controller.ts
Normal file
45
src/tags/controllers/tags.controller.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common';
|
||||
import { TagService } from '../services/tags.service';
|
||||
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
||||
import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard';
|
||||
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
|
||||
import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { CreateTagDto } from '../dtos/tags.dto';
|
||||
import { ProjectParam } from '@app/common/dto/project-param.dto';
|
||||
|
||||
@ApiTags('Tag Module')
|
||||
@Controller({
|
||||
version: EnableDisableStatusEnum.ENABLED,
|
||||
path: ControllerRoute.TAG.ROUTE,
|
||||
})
|
||||
export class TagController {
|
||||
constructor(private readonly tagService: TagService) {}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post()
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.TAG.ACTIONS.CREATE_TAG_SUMMARY,
|
||||
description: ControllerRoute.TAG.ACTIONS.CREATE_TAG_DESCRIPTION,
|
||||
})
|
||||
async createTag(
|
||||
@Body() dto: CreateTagDto,
|
||||
@Param() param: ProjectParam,
|
||||
): Promise<BaseResponseDto> {
|
||||
return this.tagService.createTag(dto, param);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get()
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.TAG.ACTIONS.GET_TAGS_BY_PROJECT_SUMMARY,
|
||||
description: ControllerRoute.TAG.ACTIONS.GET_TAGS_BY_PROJECT_DESCRIPTION,
|
||||
})
|
||||
async getTagsByProject(
|
||||
@Param() params: ProjectParam,
|
||||
): Promise<BaseResponseDto> {
|
||||
return this.tagService.getTagsByProjectUuid(params);
|
||||
}
|
||||
}
|
23
src/tags/dtos/bulk-create-tag.dto.ts
Normal file
23
src/tags/dtos/bulk-create-tag.dto.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsArray, IsNotEmpty, IsString, ValidateNested } from 'class-validator';
|
||||
import { CreateTagDto } from './tags.dto';
|
||||
|
||||
export class BulkCreateTagsDto {
|
||||
@ApiProperty({
|
||||
description: 'Project UUID for which the tags are being created',
|
||||
example: '760e8400-e29b-41d4-a716-446655440001',
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
projectUuid: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'List of tags to be created',
|
||||
type: [CreateTagDto],
|
||||
})
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CreateTagDto)
|
||||
tags: CreateTagDto[];
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user