Merge pull request #173 from SyncrowIOT/feat/link-space-to-space-model

Feat/link-space-to-space-model
This commit is contained in:
hannathkadher
2024-12-18 12:58:31 +04:00
committed by GitHub
74 changed files with 1840 additions and 410 deletions

View File

@ -9,6 +9,15 @@ import { EmailService } from './util/email.service';
import { ErrorMessageService } from 'src/error-message/error-message.service'; import { ErrorMessageService } from 'src/error-message/error-message.service';
import { TuyaService } from './integrations/tuya/services/tuya.service'; import { TuyaService } from './integrations/tuya/services/tuya.service';
import { SceneDeviceRepository } from './modules/scene-device/repositories'; import { SceneDeviceRepository } from './modules/scene-device/repositories';
import { SpaceProductItemRepository, SpaceRepository } from './modules/space';
import {
SpaceModelRepository,
SpaceProductModelRepository,
SubspaceModelRepository,
SubspaceProductItemModelRepository,
SubspaceProductModelRepository,
} from './modules/space-model';
import { SubspaceRepository } from './modules/space/repositories/subspace.repository';
@Module({ @Module({
providers: [ providers: [
CommonService, CommonService,
@ -16,6 +25,14 @@ import { SceneDeviceRepository } from './modules/scene-device/repositories';
ErrorMessageService, ErrorMessageService,
TuyaService, TuyaService,
SceneDeviceRepository, SceneDeviceRepository,
SpaceRepository,
SubspaceRepository,
SubspaceModelRepository,
SubspaceProductModelRepository,
SubspaceProductItemModelRepository,
SpaceModelRepository,
SpaceProductModelRepository,
SpaceProductItemRepository,
], ],
exports: [ exports: [
CommonService, CommonService,
@ -25,6 +42,13 @@ import { SceneDeviceRepository } from './modules/scene-device/repositories';
EmailService, EmailService,
ErrorMessageService, ErrorMessageService,
SceneDeviceRepository, SceneDeviceRepository,
SpaceRepository,
SubspaceRepository,
SubspaceModelRepository,
SubspaceProductModelRepository,
SubspaceProductItemModelRepository,
SpaceModelRepository,
SpaceProductModelRepository,
], ],
imports: [ imports: [
ConfigModule.forRoot({ ConfigModule.forRoot({

View File

@ -11,7 +11,10 @@ import { PermissionTypeEntity } from '../modules/permission/entities';
import { import {
SpaceEntity, SpaceEntity,
SpaceLinkEntity, SpaceLinkEntity,
SpaceProductItemEntity,
SubspaceEntity, SubspaceEntity,
SubspaceProductEntity,
SubspaceProductItemEntity,
} from '../modules/space/entities'; } from '../modules/space/entities';
import { UserSpaceEntity } from '../modules/user/entities'; import { UserSpaceEntity } from '../modules/user/entities';
import { DeviceUserPermissionEntity } from '../modules/device/entities'; import { DeviceUserPermissionEntity } from '../modules/device/entities';
@ -32,6 +35,8 @@ import {
SpaceProductItemModelEntity, SpaceProductItemModelEntity,
SpaceProductModelEntity, SpaceProductModelEntity,
SubspaceModelEntity, SubspaceModelEntity,
SubspaceProductItemModelEntity,
SubspaceProductModelEntity,
} from '../modules/space-model/entities'; } from '../modules/space-model/entities';
import { import {
InviteUserEntity, InviteUserEntity,
@ -80,6 +85,12 @@ import {
SpaceProductModelEntity, SpaceProductModelEntity,
SpaceProductItemModelEntity, SpaceProductItemModelEntity,
SubspaceModelEntity, SubspaceModelEntity,
SpaceProductEntity,
SpaceProductItemEntity,
SubspaceProductModelEntity,
SubspaceProductItemModelEntity,
SubspaceProductEntity,
SubspaceProductItemEntity,
InviteUserEntity, InviteUserEntity,
InviteUserSpaceEntity, InviteUserSpaceEntity,
], ],

View File

@ -4,6 +4,7 @@ import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { DeviceEntity } from '../../device/entities'; import { DeviceEntity } from '../../device/entities';
import { SpaceProductEntity } from '../../space/entities/space-product.entity'; import { SpaceProductEntity } from '../../space/entities/space-product.entity';
import { SpaceProductModelEntity } from '../../space-model/entities'; import { SpaceProductModelEntity } from '../../space-model/entities';
import { SubspaceProductModelEntity } from '../../space-model/entities/subspace-model/subspace-product-model.entity';
@Entity({ name: 'product' }) @Entity({ name: 'product' })
export class ProductEntity extends AbstractEntity<ProductDto> { export class ProductEntity extends AbstractEntity<ProductDto> {
@ -37,6 +38,12 @@ export class ProductEntity extends AbstractEntity<ProductDto> {
) )
spaceProductModels: SpaceProductModelEntity[]; spaceProductModels: SpaceProductModelEntity[];
@OneToMany(
() => SubspaceProductModelEntity,
(subspaceProductModel) => subspaceProductModel.product,
)
subpaceProductModels: SubspaceProductModelEntity[];
@OneToMany( @OneToMany(
() => DeviceEntity, () => DeviceEntity,
(devicesProductEntity) => devicesProductEntity.productDevice, (devicesProductEntity) => devicesProductEntity.productDevice,

View File

@ -1,4 +1,4 @@
export * from './subspace-model.dto'; export * from './subspace-model';
export * from './space-model.dto'; export * from './space-model.dto';
export * from './space-product-item.dto'; export * from './space-product-item-model.dto';
export * from './space-product-model.dto'; export * from './space-product-model.dto';

View File

@ -0,0 +1,15 @@
import { IsString, IsNotEmpty } from 'class-validator';
export class SpaceProductItemModelDto {
@IsString()
@IsNotEmpty()
uuid: string;
@IsString()
@IsNotEmpty()
tag: string;
@IsString()
@IsNotEmpty()
spaceProductModelUuid: string;
}

View File

@ -1,6 +1,6 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
import { SpaceProductItemDto } from './space-product-item.dto'; import { SpaceProductItemModelDto } from './space-product-item-model.dto';
export class SpaceProductModelDto { export class SpaceProductModelDto {
@IsString() @IsString()
@ -17,7 +17,7 @@ export class SpaceProductModelDto {
@ApiProperty({ @ApiProperty({
description: 'List of individual items with specific names for the product', description: 'List of individual items with specific names for the product',
type: [SpaceProductItemDto], type: [SpaceProductItemModelDto],
}) })
items: SpaceProductItemDto[]; items: SpaceProductItemModelDto[];
} }

View File

@ -0,0 +1,3 @@
export * from './subspace-model.dto';
export * from './subspace-product-item-model.dto';
export * from './subspace-product-model.dto';

View File

@ -0,0 +1,15 @@
import { IsString, IsNotEmpty } from 'class-validator';
export class SubspaceProductItemModelDto {
@IsString()
@IsNotEmpty()
uuid: string;
@IsString()
@IsNotEmpty()
tag: string;
@IsString()
@IsNotEmpty()
subspaceProductModelUuid: string;
}

View File

@ -0,0 +1,23 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
import { SubspaceProductItemModelDto } from './subspace-product-item-model.dto';
export class SubpaceProductModelDto {
@IsString()
@IsNotEmpty()
uuid: string;
@IsNumber()
@IsNotEmpty()
productCount: number;
@IsString()
@IsNotEmpty()
productUuid: string;
@ApiProperty({
description: 'List of individual items with specific names for the product',
type: [SubspaceProductItemModelDto],
})
items: SubspaceProductItemModelDto[];
}

View File

@ -1,4 +1,4 @@
export * from './space-model.entity'; export * from './space-model.entity';
export * from './space-product-item.entity'; export * from './space-product-item-model.entity';
export * from './space-product-model.entity'; export * from './space-product-model.entity';
export * from './subspace-model.entity'; export * from './subspace-model';

View File

@ -8,9 +8,10 @@ import {
} from 'typeorm'; } from 'typeorm';
import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { SpaceModelDto } from '../dtos'; import { SpaceModelDto } from '../dtos';
import { SubspaceModelEntity } from './subspace-model.entity'; import { SubspaceModelEntity } from './subspace-model';
import { SpaceProductModelEntity } from './space-product-model.entity'; import { SpaceProductModelEntity } from './space-product-model.entity';
import { ProjectEntity } from '../../project/entities'; import { ProjectEntity } from '../../project/entities';
import { SpaceEntity } from '../../space/entities';
@Entity({ name: 'space-model' }) @Entity({ name: 'space-model' })
@Unique(['modelName', 'project']) @Unique(['modelName', 'project'])
@ -48,9 +49,13 @@ export class SpaceModelEntity extends AbstractEntity<SpaceModelDto> {
() => SpaceProductModelEntity, () => SpaceProductModelEntity,
(productModel) => productModel.spaceModel, (productModel) => productModel.spaceModel,
{ {
cascade: true,
nullable: true, nullable: true,
}, },
) )
public spaceProductModels: SpaceProductModelEntity[]; public spaceProductModels: SpaceProductModelEntity[];
@OneToMany(() => SpaceEntity, (space) => space.spaceModel, {
cascade: true,
})
public spaces: SpaceEntity[];
} }

View File

@ -1,12 +1,11 @@
import { Entity, Column, ManyToOne, Unique } from 'typeorm'; import { Entity, Column, ManyToOne, OneToMany } from 'typeorm';
import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { SpaceProductItemDto } from '../dtos'; import { SpaceProductItemModelDto } from '../dtos';
import { SpaceProductModelEntity } from './space-product-model.entity'; import { SpaceProductModelEntity } from './space-product-model.entity';
import { SpaceModelEntity } from './space-model.entity'; import { SpaceProductItemEntity } from '../../space/entities';
@Entity({ name: 'space-product-item-model' }) @Entity({ name: 'space-product-item-model' })
@Unique(['tag', 'spaceProductModel', 'spaceModel']) export class SpaceProductItemModelEntity extends AbstractEntity<SpaceProductItemModelDto> {
export class SpaceProductItemModelEntity extends AbstractEntity<SpaceProductItemDto> {
@Column({ @Column({
nullable: false, nullable: false,
}) })
@ -21,12 +20,10 @@ export class SpaceProductItemModelEntity extends AbstractEntity<SpaceProductItem
) )
public spaceProductModel: SpaceProductModelEntity; public spaceProductModel: SpaceProductModelEntity;
@ManyToOne( @OneToMany(
() => SpaceModelEntity, () => SpaceProductItemEntity,
(spaceModel) => spaceModel.spaceProductModels, (spaceProductItem) => spaceProductItem.spaceProductItemModel,
{ { cascade: true },
nullable: false,
},
) )
public spaceModel: SpaceModelEntity; public items: SpaceProductItemEntity[];
} }

View File

@ -2,8 +2,9 @@ import { Entity, Column, ManyToOne, OneToMany } from 'typeorm';
import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { ProductEntity } from '../../product/entities'; import { ProductEntity } from '../../product/entities';
import { SpaceModelEntity } from './space-model.entity'; import { SpaceModelEntity } from './space-model.entity';
import { SpaceProductItemModelEntity } from './space-product-item.entity'; import { SpaceProductItemModelEntity } from './space-product-item-model.entity';
import { SpaceProductModelDto } from '../dtos'; import { SpaceProductModelDto } from '../dtos';
import { SpaceProductEntity } from '../../space/entities';
@Entity({ name: 'space-product-model' }) @Entity({ name: 'space-product-model' })
export class SpaceProductModelEntity extends AbstractEntity<SpaceProductModelDto> { export class SpaceProductModelEntity extends AbstractEntity<SpaceProductModelDto> {
@ -37,4 +38,13 @@ export class SpaceProductModelEntity extends AbstractEntity<SpaceProductModelDto
}, },
) )
public items: SpaceProductItemModelEntity[]; public items: SpaceProductItemModelEntity[];
@OneToMany(
() => SpaceProductEntity,
(spaceProduct) => spaceProduct.spaceProductModel,
{
cascade: true,
},
)
public spaceProducts: SpaceProductEntity[];
} }

View File

@ -1,30 +0,0 @@
import { Column, Entity, ManyToOne, Unique } from 'typeorm';
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { SpaceModelEntity } from './space-model.entity';
import { SubSpaceModelDto } from '../dtos';
@Entity({ name: 'subspace-model' })
@Unique(['subspaceName', 'spaceModel'])
export class SubspaceModelEntity extends AbstractEntity<SubSpaceModelDto> {
@Column({
type: 'uuid',
default: () => 'gen_random_uuid()',
nullable: false,
})
public uuid: string;
@Column({
nullable: false,
})
public subspaceName: string;
@ManyToOne(
() => SpaceModelEntity,
(spaceModel) => spaceModel.subspaceModels,
{
nullable: false,
onDelete: 'CASCADE',
},
)
public spaceModel: SpaceModelEntity;
}

View File

@ -0,0 +1,3 @@
export * from './subspace-model.entity';
export * from './subspace-product-item-model.entity';
export * from './subspace-product-model.entity';

View File

@ -0,0 +1,46 @@
import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity';
import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm';
import { SubSpaceModelDto } from '../../dtos';
import { SpaceModelEntity } from '../space-model.entity';
import { SubspaceEntity } from '@app/common/modules/space/entities';
import { SubspaceProductModelEntity } from './subspace-product-model.entity';
@Entity({ name: 'subspace-model' })
@Unique(['subspaceName', 'spaceModel'])
export class SubspaceModelEntity extends AbstractEntity<SubSpaceModelDto> {
@Column({
type: 'uuid',
default: () => 'gen_random_uuid()',
nullable: false,
})
public uuid: string;
@Column({
nullable: false,
})
public subspaceName: string;
@ManyToOne(
() => SpaceModelEntity,
(spaceModel) => spaceModel.subspaceModels,
{
nullable: false,
onDelete: 'CASCADE',
},
)
public spaceModel: SpaceModelEntity;
@OneToMany(() => SubspaceEntity, (space) => space.subSpaceModel, {
cascade: true,
})
public spaces: SubspaceEntity[];
@OneToMany(
() => SubspaceProductModelEntity,
(productModel) => productModel.subspaceModel,
{
nullable: true,
},
)
public productModels: SubspaceProductModelEntity[];
}

View File

@ -0,0 +1,27 @@
import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity';
import { Entity, Column, ManyToOne, OneToMany } from 'typeorm';
import { SubspaceProductItemModelDto } from '../../dtos';
import { SubspaceProductModelEntity } from './subspace-product-model.entity';
import { SubspaceProductItemEntity } from '@app/common/modules/space/entities';
@Entity({ name: 'subspace-product-item-model' })
export class SubspaceProductItemModelEntity extends AbstractEntity<SubspaceProductItemModelDto> {
@Column({
nullable: false,
})
public tag: string;
@ManyToOne(
() => SubspaceProductModelEntity,
(productModel) => productModel.itemModels,
{
nullable: false,
},
)
public subspaceProductModel: SubspaceProductModelEntity;
@OneToMany(() => SubspaceProductItemEntity, (item) => item.subspaceProduct, {
nullable: true,
})
items: SubspaceProductItemEntity[];
}

View File

@ -0,0 +1,44 @@
import { Entity, Column, ManyToOne, OneToMany } from 'typeorm';
import { SubpaceProductModelDto } from '../../dtos';
import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity';
import { SubspaceModelEntity } from './subspace-model.entity';
import { ProductEntity } from '@app/common/modules/product/entities';
import { SubspaceProductEntity } from '@app/common/modules/space/entities';
import { SubspaceProductItemModelEntity } from './subspace-product-item-model.entity';
@Entity({ name: 'subspace-product-model' })
export class SubspaceProductModelEntity extends AbstractEntity<SubpaceProductModelDto> {
@Column({
nullable: false,
type: 'int',
})
productCount: number;
@ManyToOne(
() => SubspaceModelEntity,
(spaceModel) => spaceModel.productModels,
{
nullable: false,
},
)
public subspaceModel: SubspaceModelEntity;
@ManyToOne(() => ProductEntity, (product) => product.subpaceProductModels, {
nullable: false,
})
public product: ProductEntity;
@OneToMany(() => SubspaceProductEntity, (product) => product.model, {
nullable: true,
})
public subspaceProducts: SubspaceProductEntity[];
@OneToMany(
() => SubspaceProductItemModelEntity,
(product) => product.subspaceProductModel,
{
nullable: true,
},
)
public itemModels: SubspaceProductItemModelEntity[];
}

View File

@ -5,6 +5,8 @@ import {
SpaceProductItemModelEntity, SpaceProductItemModelEntity,
SpaceProductModelEntity, SpaceProductModelEntity,
SubspaceModelEntity, SubspaceModelEntity,
SubspaceProductItemModelEntity,
SubspaceProductModelEntity,
} from '../entities'; } from '../entities';
@Injectable() @Injectable()
@ -20,6 +22,20 @@ export class SubspaceModelRepository extends Repository<SubspaceModelEntity> {
} }
} }
@Injectable()
export class SubspaceProductModelRepository extends Repository<SubspaceProductModelEntity> {
constructor(private dataSource: DataSource) {
super(SubspaceProductModelEntity, dataSource.createEntityManager());
}
}
@Injectable()
export class SubspaceProductItemModelRepository extends Repository<SubspaceProductItemModelEntity> {
constructor(private dataSource: DataSource) {
super(SubspaceProductItemModelEntity, dataSource.createEntityManager());
}
}
@Injectable() @Injectable()
export class SpaceProductModelRepository extends Repository<SpaceProductModelEntity> { export class SpaceProductModelRepository extends Repository<SpaceProductModelEntity> {
constructor(private dataSource: DataSource) { constructor(private dataSource: DataSource) {

View File

@ -1,2 +1,4 @@
export * from './space.dto'; export * from './space.dto';
export * from './subspace.dto'; export * from './subspace.dto';
export * from './space-product-item.dto';
export * from './space-product.dto';

View File

@ -11,5 +11,5 @@ export class SpaceProductItemDto {
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
spaceProductModelUuid: string; spaceProductUuid: string;
} }

View File

@ -0,0 +1,23 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty, IsNumber } from 'class-validator';
import { SpaceProductItemDto } from './space-product-item.dto';
export class SpaceProductModelDto {
@IsString()
@IsNotEmpty()
uuid: string;
@IsNumber()
@IsNotEmpty()
productCount: number;
@IsString()
@IsNotEmpty()
productUuid: string;
@ApiProperty({
description: 'List of individual items with specific names for the product',
type: [SpaceProductItemDto],
})
items: SpaceProductItemDto[];
}

View File

@ -1,3 +1,5 @@
export * from './space.entity'; export * from './space.entity';
export * from './subspace.entity'; export * from './subspace';
export * from './space-product.entity';
export * from './space-product-item.entity';
export * from './space-link.entity'; export * from './space-link.entity';

View File

@ -0,0 +1,27 @@
import { Column, Entity, ManyToOne } from 'typeorm';
import { SpaceProductEntity } from './space-product.entity';
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { SpaceProductItemDto } from '../dtos';
import { SpaceProductItemModelEntity } from '../../space-model';
@Entity({ name: 'space-product-item' })
export class SpaceProductItemEntity extends AbstractEntity<SpaceProductItemDto> {
@Column({
nullable: false,
})
public tag: string;
@ManyToOne(() => SpaceProductEntity, (spaceProduct) => spaceProduct.items, {
nullable: false,
})
public spaceProduct: SpaceProductEntity;
@ManyToOne(
() => SpaceProductItemModelEntity,
(spaceProductItemModel) => spaceProductItemModel.items,
{
nullable: true,
},
)
public spaceProductItemModel?: SpaceProductItemModelEntity;
}

View File

@ -1,7 +1,9 @@
import { Column, Entity, ManyToOne, JoinColumn } from 'typeorm'; import { Column, Entity, ManyToOne, JoinColumn, OneToMany } from 'typeorm';
import { SpaceEntity } from './space.entity'; import { SpaceEntity } from './space.entity';
import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { ProductEntity } from '../../product/entities'; import { ProductEntity } from '../../product/entities';
import { SpaceProductItemEntity } from './space-product-item.entity';
import { SpaceProductModelEntity } from '../../space-model';
@Entity({ name: 'space-product' }) @Entity({ name: 'space-product' })
export class SpaceProductEntity extends AbstractEntity<SpaceProductEntity> { export class SpaceProductEntity extends AbstractEntity<SpaceProductEntity> {
@ -25,6 +27,21 @@ export class SpaceProductEntity extends AbstractEntity<SpaceProductEntity> {
}) })
productCount: number; productCount: number;
@OneToMany(() => SpaceProductItemEntity, (item) => item.spaceProduct, {
cascade: true,
})
public items: SpaceProductItemEntity[];
@ManyToOne(
() => SpaceProductModelEntity,
(spaceProductModel) => spaceProductModel.spaceProducts,
{
nullable: true,
},
)
@JoinColumn({ name: 'space_product_model_uuid' })
public spaceProductModel?: SpaceProductModelEntity;
constructor(partial: Partial<SpaceProductEntity>) { constructor(partial: Partial<SpaceProductEntity>) {
super(); super();
Object.assign(this, partial); Object.assign(this, partial);

View File

@ -11,10 +11,11 @@ import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { UserSpaceEntity } from '../../user/entities'; import { UserSpaceEntity } from '../../user/entities';
import { DeviceEntity } from '../../device/entities'; import { DeviceEntity } from '../../device/entities';
import { CommunityEntity } from '../../community/entities'; import { CommunityEntity } from '../../community/entities';
import { SubspaceEntity } from './subspace.entity'; import { SubspaceEntity } from './subspace';
import { SpaceLinkEntity } from './space-link.entity'; import { SpaceLinkEntity } from './space-link.entity';
import { SpaceProductEntity } from './space-product.entity'; import { SpaceProductEntity } from './space-product.entity';
import { SceneEntity } from '../../scene/entities'; import { SceneEntity } from '../../scene/entities';
import { SpaceModelEntity } from '../../space-model';
import { InviteUserSpaceEntity } from '../../Invite-user/entities'; import { InviteUserSpaceEntity } from '../../Invite-user/entities';
@Entity({ name: 'space' }) @Entity({ name: 'space' })
@ -98,6 +99,11 @@ export class SpaceEntity extends AbstractEntity<SpaceDto> {
@OneToMany(() => SceneEntity, (scene) => scene.space) @OneToMany(() => SceneEntity, (scene) => scene.space)
scenes: SceneEntity[]; scenes: SceneEntity[];
@ManyToOne(() => SpaceModelEntity, { nullable: true })
@JoinColumn({ name: 'space_model_uuid' })
spaceModel?: SpaceModelEntity;
@OneToMany( @OneToMany(
() => InviteUserSpaceEntity, () => InviteUserSpaceEntity,
(inviteUserSpace) => inviteUserSpace.space, (inviteUserSpace) => inviteUserSpace.space,

View File

@ -1,37 +0,0 @@
import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { DeviceEntity } from '../../device/entities';
import { SpaceEntity } from './space.entity';
import { SubspaceDto } from '../dtos';
@Entity({ name: 'subspace' })
export class SubspaceEntity extends AbstractEntity<SubspaceDto> {
@Column({
type: 'uuid',
default: () => 'gen_random_uuid()',
nullable: false,
})
public uuid: string;
@Column({
nullable: false,
})
public subspaceName: string;
@ManyToOne(() => SpaceEntity, (space) => space.subspaces, {
nullable: false,
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'space_id' })
space: SpaceEntity;
@OneToMany(() => DeviceEntity, (device) => device.subspace, {
nullable: true,
})
devices: DeviceEntity[];
constructor(partial: Partial<SubspaceEntity>) {
super();
Object.assign(this, partial);
}
}

View File

@ -0,0 +1,3 @@
export * from './subspace.entity';
export * from './subspace-product.entity';
export * from './subspace-product-item.entity';

View File

@ -0,0 +1,32 @@
import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity';
import { SpaceProductItemDto } from '../../dtos';
import { Column, Entity, ManyToOne } from 'typeorm';
import { SubspaceProductEntity } from './subspace-product.entity';
import { SubspaceProductItemModelEntity } from '@app/common/modules/space-model';
@Entity({ name: 'subspace-product-item' })
export class SubspaceProductItemEntity extends AbstractEntity<SpaceProductItemDto> {
@Column({
nullable: false,
})
public tag: string;
@ManyToOne(
() => SubspaceProductEntity,
(subspaceProduct) => subspaceProduct.items,
{
nullable: false,
},
)
public subspaceProduct: SubspaceProductEntity;
@ManyToOne(() => SubspaceProductItemModelEntity, (model) => model.items, {
nullable: true,
})
model: SubspaceProductItemModelEntity;
constructor(partial: Partial<SubspaceProductItemEntity>) {
super();
Object.assign(this, partial);
}
}

View File

@ -0,0 +1,50 @@
import { ProductEntity } from '@app/common/modules/product/entities';
import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
import { SubspaceEntity } from './subspace.entity';
import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity';
import { SubspaceProductItemEntity } from './subspace-product-item.entity';
import {
SubspaceProductItemModelEntity,
SubspaceProductModelEntity,
} from '@app/common/modules/space-model';
import { SpaceProductModelDto } from '../../dtos';
@Entity({ name: 'subspace-product' })
export class SubspaceProductEntity extends AbstractEntity<SpaceProductModelDto> {
@Column({
type: 'uuid',
default: () => 'gen_random_uuid()',
nullable: false,
})
public uuid: string;
@Column({
nullable: false,
type: 'int',
})
productCount: number;
@ManyToOne(() => SubspaceEntity, (subspace) => subspace.subspaceProducts, {
nullable: false,
})
public subspace: SubspaceEntity;
@ManyToOne(() => ProductEntity, (product) => product.subpaceProductModels, {
nullable: false,
})
public product: ProductEntity;
@OneToMany(() => SubspaceProductItemEntity, (item) => item.subspaceProduct, {
nullable: true,
})
public items: SubspaceProductItemEntity[];
@ManyToOne(
() => SubspaceProductModelEntity,
(model) => model.subspaceProducts,
{
nullable: true,
},
)
model: SubspaceProductModelEntity;
}

View File

@ -0,0 +1,52 @@
import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity';
import { DeviceEntity } from '@app/common/modules/device/entities';
import { SubspaceModelEntity } from '@app/common/modules/space-model';
import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
import { SubspaceDto } from '../../dtos';
import { SpaceEntity } from '../space.entity';
import { SubspaceProductEntity } from './subspace-product.entity';
@Entity({ name: 'subspace' })
export class SubspaceEntity extends AbstractEntity<SubspaceDto> {
@Column({
type: 'uuid',
default: () => 'gen_random_uuid()',
nullable: false,
})
public uuid: string;
@Column({
nullable: false,
})
public subspaceName: string;
@ManyToOne(() => SpaceEntity, (space) => space.subspaces, {
nullable: false,
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'space_uuid' })
space: SpaceEntity;
@OneToMany(() => DeviceEntity, (device) => device.subspace, {
nullable: true,
})
devices: DeviceEntity[];
@ManyToOne(() => SubspaceModelEntity, { nullable: true })
@JoinColumn({ name: 'subspace_model_uuid' })
subSpaceModel?: SubspaceModelEntity;
@OneToMany(
() => SubspaceProductEntity,
(subspaceProduct) => subspaceProduct.subspace,
{
nullable: true,
},
)
public subspaceProducts: SubspaceProductEntity[];
constructor(partial: Partial<SubspaceEntity>) {
super();
Object.assign(this, partial);
}
}

View File

@ -0,0 +1,4 @@
export * from './dtos';
export * from './entities';
export * from './repositories';
export * from './space.repository.module';

View File

@ -1,7 +1,11 @@
import { DataSource, Repository } from 'typeorm'; import { DataSource, Repository } from 'typeorm';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { SpaceProductEntity } from '../entities/space-product.entity'; import { SpaceProductEntity } from '../entities/space-product.entity';
import { SpaceEntity, SpaceLinkEntity, SubspaceEntity } from '../entities'; import {
SpaceEntity,
SpaceLinkEntity,
SpaceProductItemEntity,
} from '../entities';
@Injectable() @Injectable()
export class SpaceRepository extends Repository<SpaceEntity> { export class SpaceRepository extends Repository<SpaceEntity> {
@ -9,12 +13,6 @@ export class SpaceRepository extends Repository<SpaceEntity> {
super(SpaceEntity, dataSource.createEntityManager()); super(SpaceEntity, dataSource.createEntityManager());
} }
} }
@Injectable()
export class SubspaceRepository extends Repository<SubspaceEntity> {
constructor(private dataSource: DataSource) {
super(SubspaceEntity, dataSource.createEntityManager());
}
}
@Injectable() @Injectable()
export class SpaceLinkRepository extends Repository<SpaceLinkEntity> { export class SpaceLinkRepository extends Repository<SpaceLinkEntity> {
@ -28,3 +26,10 @@ export class SpaceProductRepository extends Repository<SpaceProductEntity> {
super(SpaceProductEntity, dataSource.createEntityManager()); super(SpaceProductEntity, dataSource.createEntityManager());
} }
} }
@Injectable()
export class SpaceProductItemRepository extends Repository<SpaceProductItemEntity> {
constructor(private dataSource: DataSource) {
super(SpaceProductItemEntity, dataSource.createEntityManager());
}
}

View File

@ -0,0 +1,28 @@
import { DataSource, Repository } from 'typeorm';
import {
SubspaceEntity,
SubspaceProductEntity,
SubspaceProductItemEntity,
} from '../entities';
import { Injectable } from '@nestjs/common';
@Injectable()
export class SubspaceRepository extends Repository<SubspaceEntity> {
constructor(private dataSource: DataSource) {
super(SubspaceEntity, dataSource.createEntityManager());
}
}
@Injectable()
export class SubspaceProductRepository extends Repository<SubspaceProductEntity> {
constructor(private dataSource: DataSource) {
super(SubspaceProductEntity, dataSource.createEntityManager());
}
}
@Injectable()
export class SubspaceProductItemRepository extends Repository<SubspaceProductItemEntity> {
constructor(private dataSource: DataSource) {
super(SubspaceProductItemEntity, dataSource.createEntityManager());
}
}

View File

@ -1,11 +1,17 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { SpaceEntity, SubspaceEntity } from './entities'; import { SpaceEntity, SubspaceEntity, SubspaceProductEntity } from './entities';
@Module({ @Module({
providers: [], providers: [],
exports: [], exports: [],
controllers: [], controllers: [],
imports: [TypeOrmModule.forFeature([SpaceEntity, SubspaceEntity])], imports: [
TypeOrmModule.forFeature([
SpaceEntity,
SubspaceEntity,
SubspaceProductEntity,
]),
],
}) })
export class SpaceRepositoryModule {} export class SpaceRepositoryModule {}

View File

@ -1,10 +1,12 @@
import { Module } from '@nestjs/common'; import { Global, Module } from '@nestjs/common';
import { ProductService } from './services'; import { ProductService } from './services';
import { ProductRepository } from '@app/common/modules/product/repositories'; import { ProductRepository } from '@app/common/modules/product/repositories';
import { ProductController } from './controllers'; import { ProductController } from './controllers';
@Global()
@Module({ @Module({
controllers: [ProductController], controllers: [ProductController],
providers: [ProductService, ProductRepository], providers: [ProductService, ProductRepository],
exports: [ProductService],
}) })
export class ProductModule {} export class ProductModule {}

View File

@ -21,4 +21,23 @@ export class ProductService {
message: 'List of products retrieved successfully', message: 'List of products retrieved successfully',
}); });
} }
async findOne(productUuid: string): Promise<BaseResponseDto> {
const product = await this.productRepository.findOne({
where: {
uuid: productUuid,
},
});
if (!product) {
throw new HttpException(
`No product with ${productUuid} found in the system`,
HttpStatus.NOT_FOUND,
);
}
return new SuccessResponseDto({
data: product,
message: 'Succefully retrieved product',
});
}
} }

View File

@ -1,8 +1,9 @@
import { Module } from '@nestjs/common'; import { Global, Module } from '@nestjs/common';
import { ProjectController } from './controllers'; import { ProjectController } from './controllers';
import { ProjectService } from './services'; import { ProjectService } from './services';
import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { ProjectRepository } from '@app/common/modules/project/repositiories';
@Global()
@Module({ @Module({
imports: [], imports: [],
controllers: [ProjectController], controllers: [ProjectController],

View File

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

View File

@ -0,0 +1,60 @@
import { HttpException, HttpStatus } from '@nestjs/common';
import { QueryRunner } from 'typeorm';
import { SpaceModelEntity } from '@app/common/modules/space-model';
import { CreateProductItemModelDto } from 'src/space-model/dtos';
export abstract class BaseProductItemService {
async validateTags(
itemModelDtos: CreateProductItemModelDto[],
queryRunner: QueryRunner,
spaceModel: SpaceModelEntity,
): Promise<void> {
const incomingTags = new Set(
itemModelDtos.map((item) => item.tag).filter(Boolean),
);
const duplicateTags = itemModelDtos
.map((item) => item.tag)
.filter((tag, index, array) => array.indexOf(tag) !== index);
if (duplicateTags.length > 0) {
throw new HttpException(
`Duplicate tags found in the request: ${[...new Set(duplicateTags)].join(', ')}`,
HttpStatus.BAD_REQUEST,
);
}
const existingTagsQuery = `
SELECT DISTINCT tag
FROM (
SELECT spi.tag
FROM "subspace-product-item-model" spi
INNER JOIN "subspace-product-model" spm ON spi.subspace_product_model_uuid = spm.uuid
INNER JOIN "subspace-model" sm ON spm.subspace_model_uuid = sm.uuid
WHERE sm.space_model_uuid = $1
UNION
SELECT spi.tag
FROM "space-product-item-model" spi
INNER JOIN "space-product-model" spm ON spi.space_product_model_uuid = spm.uuid
WHERE spm.space_model_uuid = $1
) AS combined_tags;
`;
const existingTags = await queryRunner.manager.query(existingTagsQuery, [
spaceModel.uuid,
]);
const existingTagsSet = new Set(
existingTags.map((row: { tag: string }) => row.tag),
);
const conflictingTags = [...incomingTags].filter((tag) =>
existingTagsSet.has(tag),
);
if (conflictingTags.length > 0) {
throw new HttpException(
`Tags already exist in the model: ${conflictingTags.join(', ')}`,
HttpStatus.CONFLICT,
);
}
}
}

View File

@ -0,0 +1,24 @@
import { HttpException, HttpStatus } from '@nestjs/common';
import { CreateSpaceProductModelDto } from 'src/space-model/dtos';
import { ProductService } from '../../../product/services';
export abstract class BaseProductModelService {
constructor(private readonly productService: ProductService) {}
protected async validateProductCount(
dto: CreateSpaceProductModelDto,
): Promise<void> {
const productItemCount = dto.items.length;
if (dto.productCount !== productItemCount) {
throw new HttpException(
`Product count (${dto.productCount}) does not match the number of items (${productItemCount}) for product ID ${dto.productUuid}.`,
HttpStatus.BAD_REQUEST,
);
}
}
protected async getProduct(productId: string) {
const product = await this.productService.findOne(productId);
return product.data;
}
}

View File

@ -0,0 +1,2 @@
export * from './base-product-item-model.service';
export * from './base-product-model.service';

View File

@ -1,7 +1,7 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator'; import { IsNotEmpty, IsString } from 'class-validator';
export class CreateSpaceProductItemModelDto { export class CreateProductItemModelDto {
@ApiProperty({ @ApiProperty({
description: 'Specific name for the product item', description: 'Specific name for the product item',
example: 'Light 1', example: 'Light 1',

View File

@ -8,7 +8,7 @@ import {
ArrayNotEmpty, ArrayNotEmpty,
} from 'class-validator'; } from 'class-validator';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { CreateSpaceProductItemModelDto } from './create-space-product-item-model.dto'; import { CreateProductItemModelDto } from './create-space-product-item-model.dto';
export class CreateSpaceProductModelDto { export class CreateSpaceProductModelDto {
@ApiProperty({ @ApiProperty({
@ -17,7 +17,7 @@ export class CreateSpaceProductModelDto {
}) })
@IsNotEmpty() @IsNotEmpty()
@IsString() @IsString()
productId: string; productUuid: string;
@ApiProperty({ @ApiProperty({
description: 'Number of products in the model', description: 'Number of products in the model',
@ -29,11 +29,11 @@ export class CreateSpaceProductModelDto {
@ApiProperty({ @ApiProperty({
description: 'Specific names for each product item', description: 'Specific names for each product item',
type: [CreateSpaceProductItemModelDto], type: [CreateProductItemModelDto],
}) })
@IsArray() @IsArray()
@ArrayNotEmpty() @ArrayNotEmpty()
@ValidateNested({ each: true }) @ValidateNested({ each: true })
@Type(() => CreateSpaceProductItemModelDto) @Type(() => CreateProductItemModelDto)
items: CreateSpaceProductItemModelDto[]; items: CreateProductItemModelDto[];
} }

View File

@ -1,5 +1,13 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator'; import {
IsArray,
IsNotEmpty,
IsOptional,
IsString,
ValidateNested,
} from 'class-validator';
import { CreateSpaceProductModelDto } from './create-space-product-model.dto';
import { Type } from 'class-transformer';
export class CreateSubspaceModelDto { export class CreateSubspaceModelDto {
@ApiProperty({ @ApiProperty({
@ -9,4 +17,14 @@ export class CreateSubspaceModelDto {
@IsNotEmpty() @IsNotEmpty()
@IsString() @IsString()
subspaceName: string; subspaceName: string;
@ApiProperty({
description: 'List of products included in the model',
type: [CreateSpaceProductModelDto],
})
@IsArray()
@IsOptional()
@ValidateNested({ each: true })
@Type(() => CreateSpaceProductModelDto)
spaceProductModels?: CreateSpaceProductModelDto[];
} }

View File

@ -1,4 +1,4 @@
export * from './space-model.service'; export * from './space-model.service';
export * from './space-product-item-model.service'; export * from './space-product-item-model.service';
export * from './space-product-model.service'; export * from './space-product-model.service';
export * from './subspace-model.service'; export * from './subspace';

View File

@ -4,7 +4,7 @@ import { CreateSpaceModelDto } from '../dtos';
import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { ProjectRepository } from '@app/common/modules/project/repositiories';
import { ProjectParam } from 'src/community/dtos'; import { ProjectParam } from 'src/community/dtos';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import { SubSpaceModelService } from './subspace-model.service'; import { SubSpaceModelService } from './subspace/subspace-model.service';
import { SpaceProductModelService } from './space-product-model.service'; import { SpaceProductModelService } from './space-product-model.service';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';

View File

@ -4,29 +4,30 @@ import {
SpaceProductModelEntity, SpaceProductModelEntity,
} from '@app/common/modules/space-model'; } from '@app/common/modules/space-model';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateSpaceProductItemModelDto } from '../dtos'; import { CreateProductItemModelDto } from '../dtos';
import { QueryRunner } from 'typeorm'; import { QueryRunner } from 'typeorm';
import { BaseProductItemService } from '../common';
@Injectable() @Injectable()
export class SpaceProductItemModelService { export class SpaceProductItemModelService extends BaseProductItemService {
constructor( constructor(
private readonly spaceProductItemRepository: SpaceProductItemModelRepository, private readonly spaceProductItemRepository: SpaceProductItemModelRepository,
) {} ) {
super();
}
async createProdutItemModel( async createProdutItemModel(
itemModelDtos: CreateSpaceProductItemModelDto[], itemModelDtos: CreateProductItemModelDto[],
spaceProductModel: SpaceProductModelEntity, spaceProductModel: SpaceProductModelEntity,
spaceModel: SpaceModelEntity, spaceModel: SpaceModelEntity,
queryRunner: QueryRunner, queryRunner: QueryRunner,
) { ) {
await this.validateTags(itemModelDtos, spaceModel, queryRunner); await this.validateTags(itemModelDtos, queryRunner, spaceModel);
try { try {
const productItems = itemModelDtos.map((dto) => const productItems = itemModelDtos.map((dto) =>
queryRunner.manager.create(this.spaceProductItemRepository.target, { queryRunner.manager.create(this.spaceProductItemRepository.target, {
tag: dto.tag, tag: dto.tag,
spaceProductModel, spaceProductModel,
spaceModel,
}), }),
); );
@ -43,41 +44,4 @@ export class SpaceProductItemModelService {
); );
} }
} }
private async validateTags(
itemModelDtos: CreateSpaceProductItemModelDto[],
spaceModel: SpaceModelEntity,
queryRunner: QueryRunner,
) {
const incomingTags = itemModelDtos.map((item) => item.tag);
const duplicateTags = incomingTags.filter(
(tag, index) => incomingTags.indexOf(tag) !== index,
);
if (duplicateTags.length > 0) {
throw new HttpException(
`Duplicate tags found in the request: ${[...new Set(duplicateTags)].join(', ')}`,
HttpStatus.BAD_REQUEST,
);
}
const existingTags = await queryRunner.manager.find(
this.spaceProductItemRepository.target,
{
where: { spaceModel },
select: ['tag'],
},
);
const existingTagSet = new Set(existingTags.map((item) => item.tag));
const conflictingTags = incomingTags.filter((tag) =>
existingTagSet.has(tag),
);
if (conflictingTags.length > 0) {
throw new HttpException(
`Tags already exist in the model: ${conflictingTags.join(', ')}`,
HttpStatus.CONFLICT,
);
}
}
} }

View File

@ -1,20 +1,23 @@
import { QueryRunner } from 'typeorm';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateSpaceProductModelDto } from '../dtos';
import { SpaceProductItemModelService } from './space-product-item-model.service';
import { BaseProductModelService } from '../common';
import { ProductService } from 'src/product/services';
import { import {
SpaceModelEntity, SpaceModelEntity,
SpaceProductModelRepository, SpaceProductModelRepository,
} from '@app/common/modules/space-model'; } from '@app/common/modules/space-model';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateSpaceProductModelDto } from '../dtos';
import { ProductRepository } from '@app/common/modules/product/repositories';
import { SpaceProductItemModelService } from './space-product-item-model.service';
import { QueryRunner } from 'typeorm';
@Injectable() @Injectable()
export class SpaceProductModelService { export class SpaceProductModelService extends BaseProductModelService {
constructor( constructor(
private readonly spaceProductModelRepository: SpaceProductModelRepository, private readonly spaceProductModelRepository: SpaceProductModelRepository,
private readonly productRepository: ProductRepository,
private readonly spaceProductItemModelService: SpaceProductItemModelService, private readonly spaceProductItemModelService: SpaceProductItemModelService,
) {} productService: ProductService,
) {
super(productService);
}
async createSpaceProductModels( async createSpaceProductModels(
spaceProductModelDtos: CreateSpaceProductModelDto[], spaceProductModelDtos: CreateSpaceProductModelDto[],
@ -25,7 +28,7 @@ export class SpaceProductModelService {
const productModels = await Promise.all( const productModels = await Promise.all(
spaceProductModelDtos.map(async (dto) => { spaceProductModelDtos.map(async (dto) => {
this.validateProductCount(dto); this.validateProductCount(dto);
const product = await this.getProduct(dto.productId); const product = await this.getProduct(dto.productUuid);
return queryRunner.manager.create( return queryRunner.manager.create(
this.spaceProductModelRepository.target, this.spaceProductModelRepository.target,
{ {
@ -40,14 +43,15 @@ export class SpaceProductModelService {
const savedProductModels = await queryRunner.manager.save(productModels); const savedProductModels = await queryRunner.manager.save(productModels);
await Promise.all( await Promise.all(
spaceProductModelDtos.map((dto, index) => spaceProductModelDtos.map((dto, index) => {
this.spaceProductItemModelService.createProdutItemModel( const savedModel = savedProductModels[index];
return this.spaceProductItemModelService.createProdutItemModel(
dto.items, dto.items,
savedProductModels[index], savedModel, // Pass the saved model
spaceModel, spaceModel,
queryRunner, queryRunner,
), );
), }),
); );
} catch (error) { } catch (error) {
if (error instanceof HttpException) { if (error instanceof HttpException) {
@ -60,25 +64,4 @@ export class SpaceProductModelService {
); );
} }
} }
private validateProductCount(dto: CreateSpaceProductModelDto) {
const productItemCount = dto.items.length;
if (dto.productCount !== productItemCount) {
throw new HttpException(
`Product count (${dto.productCount}) does not match the number of items (${productItemCount}) for product ID ${dto.productId}.`,
HttpStatus.BAD_REQUEST,
);
}
}
private async getProduct(productId: string) {
const product = await this.productRepository.findOneBy({ uuid: productId });
if (!product) {
throw new HttpException(
`Product with ID ${productId} not found.`,
HttpStatus.NOT_FOUND,
);
}
return product;
}
} }

View File

@ -0,0 +1,3 @@
export * from './subspace-model.service';
export * from './subspace-product-item-model.service';
export * from './subspace-product-model.service';

View File

@ -3,13 +3,15 @@ import {
SubspaceModelRepository, SubspaceModelRepository,
} from '@app/common/modules/space-model'; } from '@app/common/modules/space-model';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateSubspaceModelDto } from '../dtos'; import { CreateSubspaceModelDto } from '../../dtos';
import { QueryRunner } from 'typeorm'; import { QueryRunner } from 'typeorm';
import { SubspaceProductModelService } from './subspace-product-model.service';
@Injectable() @Injectable()
export class SubSpaceModelService { export class SubSpaceModelService {
constructor( constructor(
private readonly subspaceModelRepository: SubspaceModelRepository, private readonly subspaceModelRepository: SubspaceModelRepository,
private readonly subSpaceProducetModelService: SubspaceProductModelService,
) {} ) {}
async createSubSpaceModels( async createSubSpaceModels(
@ -28,6 +30,18 @@ export class SubSpaceModelService {
); );
await queryRunner.manager.save(subspaces); await queryRunner.manager.save(subspaces);
await Promise.all(
subSpaceModelDtos.map((dto, index) => {
const subspaceModel = subspaces[index];
return this.subSpaceProducetModelService.createSubspaceProductModels(
dto.spaceProductModels,
spaceModel,
subspaceModel,
queryRunner,
);
}),
);
} catch (error) { } catch (error) {
if (error instanceof HttpException) { if (error instanceof HttpException) {
throw error; throw error;

View File

@ -0,0 +1,53 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { QueryRunner } from 'typeorm';
import {
SpaceModelEntity,
SubspaceProductItemModelRepository,
SubspaceProductModelEntity,
} from '@app/common/modules/space-model';
import { BaseProductItemService } from '../../common';
import { CreateProductItemModelDto } from '../../dtos';
@Injectable()
export class SubspaceProductItemModelService extends BaseProductItemService {
constructor(
private readonly subspaceProductItemRepository: SubspaceProductItemModelRepository,
) {
super();
}
async createProdutItemModel(
itemModelDtos: CreateProductItemModelDto[],
subspaceProductModel: SubspaceProductModelEntity,
spaceModel: SpaceModelEntity,
queryRunner: QueryRunner,
) {
if (!subspaceProductModel) {
throw new HttpException(
'The spaceProductModel parameter is required but was not provided.',
HttpStatus.BAD_REQUEST,
);
}
await this.validateTags(itemModelDtos, queryRunner, spaceModel);
try {
const productItems = itemModelDtos.map((dto) =>
queryRunner.manager.create(this.subspaceProductItemRepository.target, {
tag: dto.tag,
subspaceProductModel,
}),
);
await queryRunner.manager.save(productItems);
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(
error.message ||
'An unexpected error occurred while creating product items.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}

View File

@ -0,0 +1,69 @@
import {
SpaceModelEntity,
SubspaceModelEntity,
SubspaceProductModelRepository,
} from '@app/common/modules/space-model';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { SubspaceProductItemModelService } from './subspace-product-item-model.service';
import { CreateSpaceProductModelDto } from '../../dtos';
import { QueryRunner } from 'typeorm';
import { BaseProductModelService } from '../../common';
import { ProductService } from 'src/product/services';
@Injectable()
export class SubspaceProductModelService extends BaseProductModelService {
constructor(
private readonly subpaceProductModelRepository: SubspaceProductModelRepository,
productService: ProductService,
private readonly subspaceProductItemModelService: SubspaceProductItemModelService,
) {
super(productService);
}
async createSubspaceProductModels(
spaceProductModelDtos: CreateSpaceProductModelDto[],
spaceModel: SpaceModelEntity,
subspaceModel: SubspaceModelEntity,
queryRunner: QueryRunner,
) {
try {
const productModels = await Promise.all(
spaceProductModelDtos.map(async (dto) => {
this.validateProductCount(dto);
const product = await this.getProduct(dto.productUuid);
return queryRunner.manager.create(
this.subpaceProductModelRepository.target,
{
product,
productCount: dto.productCount,
subspaceModel,
},
);
}),
);
const savedProductModels = await queryRunner.manager.save(productModels);
await Promise.all(
spaceProductModelDtos.map((dto, index) => {
const savedModel = savedProductModels[index];
return this.subspaceProductItemModelService.createProdutItemModel(
dto.items,
savedModel, // Pass the saved model
spaceModel,
queryRunner,
);
}),
);
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(
error.message ||
'An unexpected error occurred while creating product models.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}

View File

@ -7,15 +7,19 @@ import {
SpaceProductItemModelService, SpaceProductItemModelService,
SpaceProductModelService, SpaceProductModelService,
SubSpaceModelService, SubSpaceModelService,
SubspaceProductItemModelService,
} from './services'; } from './services';
import { import {
SpaceModelRepository, SpaceModelRepository,
SpaceProductItemModelRepository, SpaceProductItemModelRepository,
SpaceProductModelRepository, SpaceProductModelRepository,
SubspaceModelRepository, SubspaceModelRepository,
SubspaceProductItemModelRepository,
SubspaceProductModelRepository,
} from '@app/common/modules/space-model'; } from '@app/common/modules/space-model';
import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { ProjectRepository } from '@app/common/modules/project/repositiories';
import { ProductRepository } from '@app/common/modules/product/repositories'; import { ProductRepository } from '@app/common/modules/product/repositories';
import { SubspaceProductModelService } from './services/subspace/subspace-product-model.service';
@Module({ @Module({
imports: [ConfigModule, SpaceRepositoryModule], imports: [ConfigModule, SpaceRepositoryModule],
@ -31,6 +35,10 @@ import { ProductRepository } from '@app/common/modules/product/repositories';
ProductRepository, ProductRepository,
SpaceProductItemModelService, SpaceProductItemModelService,
SpaceProductItemModelRepository, SpaceProductItemModelRepository,
SubspaceProductItemModelService,
SubspaceProductItemModelRepository,
SubspaceProductModelService,
SubspaceProductModelRepository,
], ],
exports: [], exports: [],
}) })

View File

@ -0,0 +1,69 @@
import { HttpException, HttpStatus } from '@nestjs/common';
import { QueryRunner } from 'typeorm';
export abstract class BaseProductItemService {
protected async validateTags(
incomingTags: string[],
queryRunner: QueryRunner,
spaceUuid: string,
): Promise<void> {
const duplicateTags = incomingTags.filter(
(tag, index) => incomingTags.indexOf(tag) !== index,
);
if (duplicateTags.length > 0) {
throw new HttpException(
`Duplicate tags found in the request: ${[...new Set(duplicateTags)].join(', ')}`,
HttpStatus.BAD_REQUEST,
);
}
const existingTagsQuery = `
SELECT tag FROM (
SELECT spi.tag
FROM "space-product-item" spi
INNER JOIN "space-product" spm ON spi.space_product_uuid = spm.uuid
WHERE spm.space_uuid = $1
UNION
SELECT spi.tag
FROM "subspace-product-item" spi
INNER JOIN "subspace-product" spm ON spi.subspace_product_uuid = spm.uuid
INNER JOIN "subspace" sm ON spm.subspace_uuid = sm.uuid
WHERE sm.space_uuid = $1
) AS combined_tags;
`;
const existingTags = await queryRunner.manager.query(existingTagsQuery, [
spaceUuid,
]);
const existingTagSet = new Set(
existingTags.map((row: { tag: string }) => row.tag),
);
const conflictingTags = incomingTags.filter((tag) =>
existingTagSet.has(tag),
);
if (conflictingTags.length > 0) {
throw new HttpException(
`Tags already exist in the model: ${conflictingTags.join(', ')}`,
HttpStatus.CONFLICT,
);
}
}
protected async saveProductItems<T>(
productItems: T[],
targetRepository: any,
queryRunner: QueryRunner,
): Promise<void> {
try {
await queryRunner.manager.save(targetRepository, productItems);
} catch (error) {
throw new HttpException(
error.message || 'An error occurred while creating product items.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}

View File

@ -0,0 +1 @@
export * from './base-product-item.service';

View File

@ -10,6 +10,42 @@ import {
IsUUID, IsUUID,
ValidateNested, ValidateNested,
} from 'class-validator'; } from 'class-validator';
import { AddSubspaceDto } from './subspace';
export class CreateSpaceProductItemDto {
@ApiProperty({
description: 'Specific name for the product item',
example: 'Light 1',
})
@IsNotEmpty()
@IsString()
tag: string;
}
export class ProductAssignmentDto {
@ApiProperty({
description: 'UUID of the product to be assigned',
example: 'prod-uuid-1234',
})
@IsNotEmpty()
productId: string;
@ApiProperty({
description: 'Number of items to assign for the product',
example: 3,
})
count: number;
@ApiProperty({
description: 'Specific names for each product item',
type: [CreateSpaceProductItemDto],
example: [{ tag: 'Light 1' }, { tag: 'Light 2' }, { tag: 'Light 3' }],
})
@IsArray()
@ValidateNested({ each: true })
@Type(() => CreateSpaceProductItemDto)
items: CreateSpaceProductItemDto[];
}
export class AddSpaceDto { export class AddSpaceDto {
@ApiProperty({ @ApiProperty({
@ -29,9 +65,14 @@ export class AddSpaceDto {
@IsOptional() @IsOptional()
parentUuid?: string; parentUuid?: string;
@ApiProperty({
description: 'Icon identifier for the space',
example: 'assets/location',
required: false,
})
@IsString() @IsString()
@IsOptional() @IsOptional()
public icon: string; public icon?: string;
@ApiProperty({ @ApiProperty({
description: 'Indicates whether the space is private or public', description: 'Indicates whether the space is private or public',
@ -49,16 +90,39 @@ export class AddSpaceDto {
@IsNumber() @IsNumber()
y: number; y: number;
@ApiProperty({
description: 'UUID of the Space',
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
})
@IsString()
@IsOptional()
spaceModelUuid?: string;
@ApiProperty({ description: 'Y position on canvas', example: 200 }) @ApiProperty({ description: 'Y position on canvas', example: 200 })
@IsString() @IsString()
@IsOptional() @IsOptional()
direction: string; direction?: string;
@ApiProperty({
description: 'List of products assigned to this space',
type: [ProductAssignmentDto],
required: false,
})
@IsArray() @IsArray()
@ValidateNested({ each: true }) @ValidateNested({ each: true })
@IsOptional() @IsOptional()
@Type(() => ProductAssignmentDto) @Type(() => ProductAssignmentDto)
products: ProductAssignmentDto[]; products?: ProductAssignmentDto[];
@ApiProperty({
description: 'List of subspaces included in the model',
type: [AddSubspaceDto],
})
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => AddSubspaceDto)
subspaces?: AddSubspaceDto[];
} }
export class AddUserSpaceDto { export class AddUserSpaceDto {
@ -101,11 +165,3 @@ export class AddUserSpaceUsingCodeDto {
Object.assign(this, dto); Object.assign(this, dto);
} }
} }
class ProductAssignmentDto {
@IsNotEmpty()
productId: string;
@IsNotEmpty()
count: number;
}

View File

@ -1,5 +1,13 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator'; import { Type } from 'class-transformer';
import {
IsArray,
IsNotEmpty,
IsOptional,
IsString,
ValidateNested,
} from 'class-validator';
import { ProductAssignmentDto } from '../add.space.dto';
export class AddSubspaceDto { export class AddSubspaceDto {
@ApiProperty({ @ApiProperty({
@ -9,4 +17,15 @@ export class AddSubspaceDto {
@IsNotEmpty() @IsNotEmpty()
@IsString() @IsString()
subspaceName: string; subspaceName: string;
@ApiProperty({
description: 'List of products assigned to this space',
type: [ProductAssignmentDto],
required: false,
})
@IsArray()
@ValidateNested({ each: true })
@IsOptional()
@Type(() => ProductAssignmentDto)
products?: ProductAssignmentDto[];
} }

View File

@ -5,3 +5,5 @@ export * from './subspace';
export * from './space-link'; export * from './space-link';
export * from './space-scene.service'; export * from './space-scene.service';
export * from './space-products'; export * from './space-products';
export * from './space-product-items';
export * from './space-validation.service';

View File

@ -6,23 +6,25 @@ import { GetSpaceParam } from '../dtos';
import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
import { SpaceService } from './space.service';
import { ValidationService } from './space-validation.service';
@Injectable() @Injectable()
export class SpaceDeviceService { export class SpaceDeviceService {
constructor( constructor(
private readonly tuyaService: TuyaService, private readonly tuyaService: TuyaService,
private readonly spaceService: SpaceService, private readonly validationService: ValidationService,
) {} ) {}
async listDevicesInSpace(params: GetSpaceParam): Promise<BaseResponseDto> { async listDevicesInSpace(params: GetSpaceParam): Promise<BaseResponseDto> {
const { spaceUuid, communityUuid, projectUuid } = params; const { spaceUuid, communityUuid, projectUuid } = params;
try { try {
const space = await this.spaceService.validateCommunityAndSpace( const space =
communityUuid, await this.validationService.validateSpaceWithinCommunityAndProject(
spaceUuid, communityUuid,
projectUuid, projectUuid,
); spaceUuid,
);
if (!Array.isArray(space.devices)) { if (!Array.isArray(space.devices)) {
throw new HttpException( throw new HttpException(

View File

@ -0,0 +1 @@
export * from './space-product-items.service';

View File

@ -0,0 +1,87 @@
import {
SpaceEntity,
SpaceProductEntity,
SpaceProductItemRepository,
} from '@app/common/modules/space';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateSpaceProductItemDto } from '../../dtos';
import { QueryRunner } from 'typeorm';
import { SpaceProductModelEntity } from '@app/common/modules/space-model';
import { BaseProductItemService } from '../../common';
@Injectable()
export class SpaceProductItemService extends BaseProductItemService {
constructor(
private readonly spaceProductItemRepository: SpaceProductItemRepository,
) {
super();
}
async createProductItem(
itemModelDtos: CreateSpaceProductItemDto[],
spaceProduct: SpaceProductEntity,
space: SpaceEntity,
queryRunner: QueryRunner,
) {
if (!itemModelDtos?.length) return;
const incomingTags = itemModelDtos.map((item) => item.tag);
await this.validateTags(incomingTags, queryRunner, space.uuid);
try {
const productItems = itemModelDtos.map((dto) =>
queryRunner.manager.create(this.spaceProductItemRepository.target, {
tag: dto.tag,
spaceProduct,
}),
);
await this.saveProductItems(
productItems,
this.spaceProductItemRepository.target,
queryRunner,
);
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(
error.message ||
'An unexpected error occurred while creating product items.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async createSpaceProductItemFromModel(
spaceProduct: SpaceProductEntity,
spaceProductModel: SpaceProductModelEntity,
queryRunner: QueryRunner,
) {
const spaceProductItemModels = spaceProductModel.items;
if (!spaceProductItemModels?.length) return;
try {
const productItems = spaceProductItemModels.map((model) =>
queryRunner.manager.create(this.spaceProductItemRepository.target, {
tag: model.tag,
spaceProduct,
}),
);
await queryRunner.manager.save(productItems);
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(
error.message ||
'An unexpected error occurred while creating product items.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}

View File

@ -2,96 +2,101 @@ import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { ProductRepository } from '@app/common/modules/product/repositories'; import { ProductRepository } from '@app/common/modules/product/repositories';
import { SpaceEntity } from '@app/common/modules/space/entities'; import { SpaceEntity } from '@app/common/modules/space/entities';
import { SpaceProductEntity } from '@app/common/modules/space/entities/space-product.entity'; import { SpaceProductEntity } from '@app/common/modules/space/entities/space-product.entity';
import { SpaceProductRepository } from '@app/common/modules/space/repositories'; import { In, QueryRunner } from 'typeorm';
import { In } from 'typeorm'; import { ProductAssignmentDto } from '../../dtos';
import { SpaceProductItemService } from '../space-product-items';
import { SpaceModelEntity } from '@app/common/modules/space-model';
import { ProductEntity } from '@app/common/modules/product/entities';
import { ProductService } from 'src/product/services';
@Injectable() @Injectable()
export class SpaceProductService { export class SpaceProductService {
constructor( constructor(
private readonly productRepository: ProductRepository, private readonly productRepository: ProductRepository,
private readonly spaceProductRepository: SpaceProductRepository, private readonly spaceProductItemService: SpaceProductItemService,
private readonly productService: ProductService,
) {} ) {}
async createFromModel(
spaceModel: SpaceModelEntity,
space: SpaceEntity,
queryRunner: QueryRunner,
) {
const spaceProductModels = spaceModel.spaceProductModels;
if (!spaceProductModels?.length) return;
const newSpaceProducts = [];
spaceProductModels.map((spaceProductModel) => {
newSpaceProducts.push(
queryRunner.manager.create(SpaceProductEntity, {
space: space,
product: spaceProductModel.product,
productCount: spaceProductModel.productCount,
spaceProductModel: spaceProductModel,
}),
);
});
if (newSpaceProducts.length > 0) {
await queryRunner.manager.save(SpaceProductEntity, newSpaceProducts);
await Promise.all(
newSpaceProducts.map((spaceProduct, index) => {
const spaceProductModel = spaceProductModels[index];
return this.spaceProductItemService.createSpaceProductItemFromModel(
spaceProduct,
spaceProductModel,
queryRunner,
);
}),
);
}
}
async assignProductsToSpace( async assignProductsToSpace(
space: SpaceEntity, space: SpaceEntity,
products: { productId: string; count: number }[], products: ProductAssignmentDto[],
queryRunner: QueryRunner,
): Promise<SpaceProductEntity[]> { ): Promise<SpaceProductEntity[]> {
let updatedProducts: SpaceProductEntity[] = [];
try { try {
const uniqueProducts = this.validateUniqueProducts(products); const uniqueProducts = this.validateUniqueProducts(products);
const productEntities = await this.getProductEntities(uniqueProducts); const productEntities = await this.getProductEntities(uniqueProducts);
const existingSpaceProducts = await this.getExistingSpaceProducts(
space,
queryRunner,
);
// Fetch existing space products if (existingSpaceProducts) {
const existingSpaceProducts = await this.spaceProductRepository.find({ updatedProducts = await this.updateExistingProducts(
where: { existingSpaceProducts,
space: { uniqueProducts,
uuid: space.uuid, productEntities,
}, queryRunner,
},
relations: ['product'],
});
const updatedProducts = [];
const newProducts = [];
for (const { productId, count } of uniqueProducts) {
const product = productEntities.get(productId);
if (!product) {
throw new HttpException(
`Product with ID ${productId} not found`,
HttpStatus.NOT_FOUND,
);
}
// Check if product already exists in the space
const existingProduct = existingSpaceProducts.find(
(spaceProduct) => spaceProduct.product.uuid === productId,
); );
if (existingProduct) {
// If count is different, update the existing record
if (existingProduct.productCount !== count) {
existingProduct.productCount = count;
updatedProducts.push(existingProduct);
}
} else {
// Add new product if it doesn't exist
newProducts.push(
this.spaceProductRepository.create({
space,
product,
productCount: count,
}),
);
}
} }
// Save updates and new records const newProducts = await this.createNewProducts(
if (updatedProducts.length > 0) { uniqueProducts,
await this.spaceProductRepository.save(updatedProducts); productEntities,
} space,
queryRunner,
if (newProducts.length > 0) { );
await this.spaceProductRepository.save(newProducts);
}
return [...updatedProducts, ...newProducts]; return [...updatedProducts, ...newProducts];
} catch (error) { } catch (error) {
console.error('Error assigning products to space:', error);
if (!(error instanceof HttpException)) { if (!(error instanceof HttpException)) {
throw new HttpException( throw new HttpException(
'An error occurred while assigning products to the space', `An error occurred while assigning products to the space ${error}`,
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
); );
} }
throw error; throw error;
} }
} }
private validateUniqueProducts( private validateUniqueProducts(
products: { productId: string; count: number }[], products: ProductAssignmentDto[],
): { productId: string; count: number }[] { ): ProductAssignmentDto[] {
const productIds = new Set(); const productIds = new Set();
const uniqueProducts = []; const uniqueProducts = [];
@ -110,15 +115,13 @@ export class SpaceProductService {
} }
private async getProductEntities( private async getProductEntities(
products: { productId: string; count: number }[], products: ProductAssignmentDto[],
): Promise<Map<string, any>> { ): Promise<Map<string, any>> {
try { try {
const productIds = products.map((p) => p.productId); const productIds = products.map((p) => p.productId);
const productEntities = await this.productRepository.find({ const productEntities = await this.productRepository.find({
where: { uuid: In(productIds) }, where: { uuid: In(productIds) },
}); });
return new Map(productEntities.map((p) => [p.uuid, p])); return new Map(productEntities.map((p) => [p.uuid, p]));
} catch (error) { } catch (error) {
console.error('Error fetching product entities:', error); console.error('Error fetching product entities:', error);
@ -128,4 +131,96 @@ export class SpaceProductService {
); );
} }
} }
private async getExistingSpaceProducts(
space: SpaceEntity,
queryRunner: QueryRunner,
): Promise<SpaceProductEntity[]> {
return queryRunner.manager.find(SpaceProductEntity, {
where: { space: { uuid: space.uuid } },
relations: ['product'],
});
}
private async updateExistingProducts(
existingSpaceProducts: SpaceProductEntity[],
uniqueProducts: ProductAssignmentDto[],
productEntities: Map<string, any>,
queryRunner: QueryRunner,
): Promise<SpaceProductEntity[]> {
const updatedProducts = [];
for (const { productId, count } of uniqueProducts) {
productEntities.get(productId);
const existingProduct = existingSpaceProducts.find(
(spaceProduct) => spaceProduct.product.uuid === productId,
);
if (existingProduct && existingProduct.productCount !== count) {
existingProduct.productCount = count;
updatedProducts.push(existingProduct);
}
}
if (updatedProducts.length > 0) {
await queryRunner.manager.save(SpaceProductEntity, updatedProducts);
}
return updatedProducts;
}
private async createNewProducts(
uniqueSpaceProducts: ProductAssignmentDto[],
productEntities: Map<string, any>,
space: SpaceEntity,
queryRunner: QueryRunner,
): Promise<SpaceProductEntity[]> {
const newProducts = [];
for (const uniqueSpaceProduct of uniqueSpaceProducts) {
const product = productEntities.get(uniqueSpaceProduct.productId);
await this.getProduct(uniqueSpaceProduct.productId);
this.validateProductCount(uniqueSpaceProduct);
newProducts.push(
queryRunner.manager.create(SpaceProductEntity, {
space,
product,
productCount: uniqueSpaceProduct.count,
}),
);
}
if (newProducts.length > 0) {
await queryRunner.manager.save(SpaceProductEntity, newProducts);
await Promise.all(
uniqueSpaceProducts.map((dto, index) => {
const spaceProduct = newProducts[index];
return this.spaceProductItemService.createProductItem(
dto.items,
spaceProduct,
space,
queryRunner,
);
}),
);
}
return newProducts;
}
private validateProductCount(dto: ProductAssignmentDto) {
const productItemCount = dto.items.length;
if (dto.count !== productItemCount) {
throw new HttpException(
`Product count (${dto.count}) does not match the number of items (${productItemCount}) for product ID ${dto.productId}.`,
HttpStatus.BAD_REQUEST,
);
}
}
async getProduct(productId: string): Promise<ProductEntity> {
const product = await this.productService.findOne(productId);
return product.data;
}
} }

View File

@ -1,16 +1,16 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { GetSpaceParam } from '../dtos'; import { GetSpaceParam } from '../dtos';
import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { SpaceService } from './space.service';
import { SceneService } from '../../scene/services'; import { SceneService } from '../../scene/services';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import { GetSceneDto } from '../../scene/dtos'; import { GetSceneDto } from '../../scene/dtos';
import { ValidationService } from './space-validation.service';
@Injectable() @Injectable()
export class SpaceSceneService { export class SpaceSceneService {
constructor( constructor(
private readonly spaceSevice: SpaceService,
private readonly sceneSevice: SceneService, private readonly sceneSevice: SceneService,
private readonly validationService: ValidationService,
) {} ) {}
async getScenes( async getScenes(
@ -20,10 +20,10 @@ export class SpaceSceneService {
try { try {
const { spaceUuid, communityUuid, projectUuid } = params; const { spaceUuid, communityUuid, projectUuid } = params;
await this.spaceSevice.validateCommunityAndSpace( await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid, communityUuid,
spaceUuid,
projectUuid, projectUuid,
spaceUuid,
); );
const scenes = await this.sceneSevice.findScenesBySpace( const scenes = await this.sceneSevice.findScenesBySpace(

View File

@ -1,21 +1,19 @@
import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import { SpaceRepository } from '@app/common/modules/space/repositories';
import { import {
UserRepository, UserRepository,
UserSpaceRepository, UserSpaceRepository,
} from '@app/common/modules/user/repositories'; } from '@app/common/modules/user/repositories';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { SpaceService } from './space.service';
import { UserSpaceParam } from '../dtos'; import { UserSpaceParam } from '../dtos';
import { ValidationService } from './space-validation.service';
@Injectable() @Injectable()
export class SpaceUserService { export class SpaceUserService {
constructor( constructor(
private readonly spaceRepository: SpaceRepository, private readonly validationService: ValidationService,
private readonly userRepository: UserRepository, private readonly userRepository: UserRepository,
private readonly userSpaceRepository: UserSpaceRepository, private readonly userSpaceRepository: UserSpaceRepository,
private readonly spaceService: SpaceService,
) {} ) {}
async associateUserToSpace(params: UserSpaceParam): Promise<BaseResponseDto> { async associateUserToSpace(params: UserSpaceParam): Promise<BaseResponseDto> {
const { communityUuid, spaceUuid, userUuid, projectUuid } = params; const { communityUuid, spaceUuid, userUuid, projectUuid } = params;
@ -31,11 +29,12 @@ export class SpaceUserService {
} }
// Find the space by ID // Find the space by ID
const space = await this.spaceService.validateCommunityAndSpace( const space =
communityUuid, await this.validationService.validateSpaceWithinCommunityAndProject(
spaceUuid, communityUuid,
projectUuid, projectUuid,
); spaceUuid,
);
// Check if the association already exists // Check if the association already exists
const existingAssociation = await this.userSpaceRepository.findOne({ const existingAssociation = await this.userSpaceRepository.findOne({
@ -73,10 +72,10 @@ export class SpaceUserService {
} }
// Find the space by ID // Find the space by ID
await this.spaceService.validateCommunityAndSpace( await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid, communityUuid,
spaceUuid,
projectUuid, projectUuid,
spaceUuid,
); );
// Find the existing association // Find the existing association

View File

@ -0,0 +1,81 @@
import { CommunityEntity } from '@app/common/modules/community/entities';
import { SpaceEntity } from '@app/common/modules/space/entities';
import { SpaceRepository } from '@app/common/modules/space/repositories';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CommunityService } from '../../community/services';
import { ProjectService } from '../../project/services';
import {
SpaceModelEntity,
SpaceModelRepository,
} from '@app/common/modules/space-model';
@Injectable()
export class ValidationService {
constructor(
private readonly projectService: ProjectService,
private readonly communityService: CommunityService,
private readonly spaceRepository: SpaceRepository,
private readonly spaceModelRepository: SpaceModelRepository,
) {}
async validateCommunityAndProject(
communityUuid: string,
projectUuid: string,
): Promise<CommunityEntity> {
await this.projectService.findOne(projectUuid);
const community = await this.communityService.getCommunityById({
communityUuid,
projectUuid,
});
return community.data;
}
async validateSpaceWithinCommunityAndProject(
communityUuid: string,
projectUuid: string,
spaceUuid?: string,
): Promise<SpaceEntity> {
await this.validateCommunityAndProject(communityUuid, projectUuid);
const space = await this.validateSpace(spaceUuid);
return space;
}
async validateSpace(spaceUuid: string): Promise<SpaceEntity> {
const space = await this.spaceRepository.findOne({
where: { uuid: spaceUuid },
});
if (!space) {
throw new HttpException(
`Space with UUID ${spaceUuid} not found`,
HttpStatus.NOT_FOUND,
);
}
return space;
}
async validateSpaceModel(spaceModelUuid: string): Promise<SpaceModelEntity> {
const spaceModel = await this.spaceModelRepository.findOne({
where: { uuid: spaceModelUuid },
relations: [
'subspaceModels',
'subspaceModels.productModels.product',
'subspaceModels.productModels',
'spaceProductModels',
'spaceProductModels.product',
'spaceProductModels.items',
],
});
if (!spaceModel) {
throw new HttpException(
`Space model with UUID ${spaceModelUuid} not found`,
HttpStatus.NOT_FOUND,
);
}
return spaceModel;
}
}

View File

@ -9,47 +9,68 @@ import {
AddSpaceDto, AddSpaceDto,
CommunitySpaceParam, CommunitySpaceParam,
GetSpaceParam, GetSpaceParam,
ProductAssignmentDto,
UpdateSpaceDto, UpdateSpaceDto,
} from '../dtos'; } from '../dtos';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { CommunityRepository } from '@app/common/modules/community/repositories';
import { SpaceEntity } from '@app/common/modules/space/entities'; import { SpaceEntity } from '@app/common/modules/space/entities';
import { generateRandomString } from '@app/common/helper/randomString'; import { generateRandomString } from '@app/common/helper/randomString';
import { SpaceLinkService } from './space-link'; import { SpaceLinkService } from './space-link';
import { SpaceProductService } from './space-products'; import { SpaceProductService } from './space-products';
import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { CreateSubspaceModelDto } from 'src/space-model/dtos';
import { SubSpaceService } from './subspace';
import { DataSource } from 'typeorm';
import { ValidationService } from './space-validation.service';
@Injectable() @Injectable()
export class SpaceService { export class SpaceService {
constructor( constructor(
private readonly dataSource: DataSource,
private readonly spaceRepository: SpaceRepository, private readonly spaceRepository: SpaceRepository,
private readonly communityRepository: CommunityRepository,
private readonly spaceLinkService: SpaceLinkService, private readonly spaceLinkService: SpaceLinkService,
private readonly spaceProductService: SpaceProductService, private readonly spaceProductService: SpaceProductService,
private readonly projectRepository: ProjectRepository, private readonly subSpaceService: SubSpaceService,
private readonly validationService: ValidationService,
) {} ) {}
async createSpace( async createSpace(
addSpaceDto: AddSpaceDto, addSpaceDto: AddSpaceDto,
params: CommunitySpaceParam, params: CommunitySpaceParam,
): Promise<BaseResponseDto> { ): Promise<BaseResponseDto> {
const { parentUuid, direction, products } = addSpaceDto; const { parentUuid, direction, products, spaceModelUuid, subspaces } =
addSpaceDto;
const { communityUuid, projectUuid } = params; const { communityUuid, projectUuid } = params;
await this.validateProject(projectUuid); const queryRunner = this.dataSource.createQueryRunner();
const community = await this.validateCommunity(communityUuid); await queryRunner.connect();
await queryRunner.startTransaction();
const community = await this.validationService.validateCommunityAndProject(
communityUuid,
projectUuid,
);
this.validateSpaceCreation(spaceModelUuid, products, subspaces);
const parent = parentUuid
? await this.validationService.validateSpace(parentUuid)
: null;
const spaceModel = spaceModelUuid
? await this.validationService.validateSpaceModel(spaceModelUuid)
: null;
const parent = parentUuid ? await this.validateSpace(parentUuid) : null;
try { try {
const newSpace = this.spaceRepository.create({ const newSpace = queryRunner.manager.create(SpaceEntity, {
...addSpaceDto, ...addSpaceDto,
community, spaceModel,
parent: parentUuid ? parent : null, parent: parentUuid ? parent : null,
community,
}); });
await this.spaceRepository.save(newSpace); await queryRunner.manager.save(newSpace);
if (direction && parent) { if (direction && parent) {
await this.spaceLinkService.saveSpaceLink( await this.spaceLinkService.saveSpaceLink(
@ -59,12 +80,34 @@ export class SpaceService {
); );
} }
if (subspaces?.length) {
await this.subSpaceService.createSubspacesFromDto(
subspaces,
newSpace,
queryRunner,
);
} else if (spaceModel && spaceModel.subspaceModels.length) {
await this.subSpaceService.createSubSpaceFromModel(
spaceModel,
newSpace,
queryRunner,
);
}
if (products && products.length > 0) { if (products && products.length > 0) {
await this.spaceProductService.assignProductsToSpace( await this.spaceProductService.assignProductsToSpace(
newSpace, newSpace,
products, products,
queryRunner,
);
} else if (spaceModel && spaceModel.spaceProductModels.length) {
await this.spaceProductService.createFromModel(
spaceModel,
newSpace,
queryRunner,
); );
} }
await queryRunner.commitTransaction();
return new SuccessResponseDto({ return new SuccessResponseDto({
statusCode: HttpStatus.CREATED, statusCode: HttpStatus.CREATED,
@ -72,7 +115,14 @@ export class SpaceService {
message: 'Space created successfully', message: 'Space created successfully',
}); });
} catch (error) { } catch (error) {
await queryRunner.rollbackTransaction();
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
} finally {
await queryRunner.release();
} }
} }
@ -80,8 +130,10 @@ export class SpaceService {
params: CommunitySpaceParam, params: CommunitySpaceParam,
): Promise<BaseResponseDto> { ): Promise<BaseResponseDto> {
const { communityUuid, projectUuid } = params; const { communityUuid, projectUuid } = params;
await this.validateCommunity(communityUuid); await this.validationService.validateCommunityAndProject(
await this.validateProject(projectUuid); communityUuid,
projectUuid,
);
try { try {
// Get all spaces related to the community, including the parent-child relations // Get all spaces related to the community, including the parent-child relations
const spaces = await this.spaceRepository.find({ const spaces = await this.spaceRepository.find({
@ -114,11 +166,12 @@ export class SpaceService {
async findOne(params: GetSpaceParam): Promise<BaseResponseDto> { async findOne(params: GetSpaceParam): Promise<BaseResponseDto> {
const { communityUuid, spaceUuid, projectUuid } = params; const { communityUuid, spaceUuid, projectUuid } = params;
try { try {
const space = await this.validateCommunityAndSpace( const space =
communityUuid, await this.validationService.validateSpaceWithinCommunityAndProject(
spaceUuid, communityUuid,
projectUuid, projectUuid,
); spaceUuid,
);
return new SuccessResponseDto({ return new SuccessResponseDto({
message: `Space with ID ${spaceUuid} successfully fetched`, message: `Space with ID ${spaceUuid} successfully fetched`,
@ -139,12 +192,13 @@ export class SpaceService {
async delete(params: GetSpaceParam): Promise<BaseResponseDto> { async delete(params: GetSpaceParam): Promise<BaseResponseDto> {
try { try {
const { communityUuid, spaceUuid, projectUuid } = params; const { communityUuid, spaceUuid, projectUuid } = params;
// First, check if the community exists
const space = await this.validateCommunityAndSpace( const space =
communityUuid, await this.validationService.validateSpaceWithinCommunityAndProject(
spaceUuid, communityUuid,
projectUuid, projectUuid,
); spaceUuid,
);
// Delete the space // Delete the space
await this.spaceRepository.remove(space); await this.spaceRepository.remove(space);
@ -169,29 +223,39 @@ export class SpaceService {
updateSpaceDto: UpdateSpaceDto, updateSpaceDto: UpdateSpaceDto,
): Promise<BaseResponseDto> { ): Promise<BaseResponseDto> {
const { communityUuid, spaceUuid, projectUuid } = params; const { communityUuid, spaceUuid, projectUuid } = params;
const queryRunner = this.dataSource.createQueryRunner();
try { try {
const space = await this.validateCommunityAndSpace( await queryRunner.connect();
communityUuid, await queryRunner.startTransaction();
spaceUuid,
projectUuid, const space =
); await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid,
projectUuid,
spaceUuid,
);
// If a parentId is provided, check if the parent exists // If a parentId is provided, check if the parent exists
const { parentUuid, products } = updateSpaceDto; const { parentUuid, products } = updateSpaceDto;
const parent = parentUuid ? await this.validateSpace(parentUuid) : null; const parent = parentUuid
? await this.validationService.validateSpace(parentUuid)
: null;
// Update other space properties from updateSpaceDto // Update other space properties from updateSpaceDto
Object.assign(space, updateSpaceDto, { parent }); Object.assign(space, updateSpaceDto, { parent });
// Save the updated space // Save the updated space
const updatedSpace = await this.spaceRepository.save(space); const updatedSpace = await queryRunner.manager.save(space);
if (products && products.length > 0) { if (products && products.length > 0) {
await this.spaceProductService.assignProductsToSpace( await this.spaceProductService.assignProductsToSpace(
updatedSpace, updatedSpace,
products, products,
queryRunner,
); );
} }
await queryRunner.commitTransaction();
return new SuccessResponseDto({ return new SuccessResponseDto({
message: `Space with ID ${spaceUuid} successfully updated`, message: `Space with ID ${spaceUuid} successfully updated`,
@ -199,6 +263,8 @@ export class SpaceService {
statusCode: HttpStatus.OK, statusCode: HttpStatus.OK,
}); });
} catch (error) { } catch (error) {
await queryRunner.rollbackTransaction();
if (error instanceof HttpException) { if (error instanceof HttpException) {
throw error; throw error;
} }
@ -206,6 +272,8 @@ export class SpaceService {
'An error occurred while updating the space', 'An error occurred while updating the space',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
); );
} finally {
await queryRunner.release();
} }
} }
@ -213,7 +281,11 @@ export class SpaceService {
params: GetSpaceParam, params: GetSpaceParam,
): Promise<BaseResponseDto> { ): Promise<BaseResponseDto> {
const { spaceUuid, communityUuid, projectUuid } = params; const { spaceUuid, communityUuid, projectUuid } = params;
await this.validateCommunityAndSpace(communityUuid, spaceUuid, projectUuid); await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid,
projectUuid,
spaceUuid,
);
try { try {
// Get all spaces that are children of the provided space, including the parent-child relations // Get all spaces that are children of the provided space, including the parent-child relations
@ -243,11 +315,12 @@ export class SpaceService {
try { try {
const invitationCode = generateRandomString(6); const invitationCode = generateRandomString(6);
const space = await this.validateCommunityAndSpace( const space =
communityUuid, await this.validationService.validateSpaceWithinCommunityAndProject(
spaceUuid, communityUuid,
projectUuid, projectUuid,
); spaceUuid,
);
space.invitationCode = invitationCode; space.invitationCode = invitationCode;
await this.spaceRepository.save(space); await this.spaceRepository.save(space);
@ -293,56 +366,16 @@ export class SpaceService {
return rootSpaces; return rootSpaces;
} }
private async validateCommunity(communityId: string) { private validateSpaceCreation(
const community = await this.communityRepository.findOne({ spaceModelUuid?: string,
where: { uuid: communityId }, products?: ProductAssignmentDto[],
}); subSpaces?: CreateSubspaceModelDto[],
if (!community) { ) {
if (spaceModelUuid && (products?.length || subSpaces?.length)) {
throw new HttpException( throw new HttpException(
`Community with ID ${communityId} not found`, 'For space creation choose either space model or products and subspace',
HttpStatus.NOT_FOUND, HttpStatus.CONFLICT,
); );
} }
return community;
}
async validateCommunityAndSpace(
communityUuid: string,
spaceUuid: string,
projectUuid: string,
) {
await this.validateProject(projectUuid);
const community = await this.validateCommunity(communityUuid);
if (!community) {
this.throwNotFound('Community', communityUuid);
}
const space = await this.validateSpace(spaceUuid);
return space;
}
private async validateSpace(spaceUuid: string) {
const space = await this.spaceRepository.findOne({
where: { uuid: spaceUuid },
relations: ['devices', 'devices.productDevice'],
});
if (!space) this.throwNotFound('Space', spaceUuid);
return space;
}
private async validateProject(uuid: string) {
const project = await this.projectRepository.findOne({
where: { uuid },
});
if (!project) this.throwNotFound('Project', uuid);
}
private throwNotFound(entity: string, uuid: string) {
throw new HttpException(
`${entity} with ID ${uuid} not found`,
HttpStatus.NOT_FOUND,
);
} }
} }

View File

@ -1,2 +1,4 @@
export * from './subspace.service'; export * from './subspace.service';
export * from './subspace-device.service'; export * from './subspace-device.service';
export * from './subspace-product-item.service';
export * from './subspace-product.service';

View File

@ -1,29 +1,23 @@
import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import { DeviceRepository } from '@app/common/modules/device/repositories'; import { DeviceRepository } from '@app/common/modules/device/repositories';
import {
SpaceRepository,
SubspaceRepository,
} from '@app/common/modules/space/repositories';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { DeviceSubSpaceParam, GetSubSpaceParam } from '../../dtos'; import { DeviceSubSpaceParam, GetSubSpaceParam } from '../../dtos';
import { CommunityRepository } from '@app/common/modules/community/repositories';
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
import { ProductRepository } from '@app/common/modules/product/repositories'; import { ProductRepository } from '@app/common/modules/product/repositories';
import { GetDeviceDetailsInterface } from '../../../device/interfaces/get.device.interface'; import { GetDeviceDetailsInterface } from '../../../device/interfaces/get.device.interface';
import { SpaceService } from '../space.service'; import { ValidationService } from '../space-validation.service';
import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
@Injectable() @Injectable()
export class SubspaceDeviceService { export class SubspaceDeviceService {
constructor( constructor(
private readonly spaceRepository: SpaceRepository,
private readonly communityRepository: CommunityRepository,
private readonly subspaceRepository: SubspaceRepository, private readonly subspaceRepository: SubspaceRepository,
private readonly deviceRepository: DeviceRepository, private readonly deviceRepository: DeviceRepository,
private readonly tuyaService: TuyaService, private readonly tuyaService: TuyaService,
private readonly productRepository: ProductRepository, private readonly productRepository: ProductRepository,
private readonly spaceService: SpaceService, private readonly validationService: ValidationService,
) {} ) {}
async listDevicesInSubspace( async listDevicesInSubspace(
@ -31,10 +25,10 @@ export class SubspaceDeviceService {
): Promise<BaseResponseDto> { ): Promise<BaseResponseDto> {
const { subSpaceUuid, spaceUuid, communityUuid, projectUuid } = params; const { subSpaceUuid, spaceUuid, communityUuid, projectUuid } = params;
await this.spaceService.validateCommunityAndSpace( await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid, communityUuid,
spaceUuid,
projectUuid, projectUuid,
spaceUuid,
); );
const subspace = await this.findSubspaceWithDevices(subSpaceUuid); const subspace = await this.findSubspaceWithDevices(subSpaceUuid);
@ -76,10 +70,10 @@ export class SubspaceDeviceService {
const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid, projectUuid } = const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid, projectUuid } =
params; params;
try { try {
await this.spaceService.validateCommunityAndSpace( await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid, communityUuid,
spaceUuid,
projectUuid, projectUuid,
spaceUuid,
); );
const subspace = await this.findSubspace(subSpaceUuid); const subspace = await this.findSubspace(subSpaceUuid);
@ -111,10 +105,10 @@ export class SubspaceDeviceService {
const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid, projectUuid } = const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid, projectUuid } =
params; params;
try { try {
await this.spaceService.validateCommunityAndSpace( await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid, communityUuid,
spaceUuid,
projectUuid, projectUuid,
spaceUuid,
); );
const subspace = await this.findSubspace(subSpaceUuid); const subspace = await this.findSubspace(subSpaceUuid);

View File

@ -0,0 +1,92 @@
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { QueryRunner } from 'typeorm';
import {
SpaceEntity,
SubspaceProductEntity,
SubspaceProductItemEntity,
} from '@app/common/modules/space';
import {
SubspaceProductItemModelEntity,
SubspaceProductModelEntity,
} from '@app/common/modules/space-model';
import { SubspaceProductItemRepository } from '@app/common/modules/space/repositories/subspace.repository';
import { CreateSpaceProductItemDto } from '../../dtos';
import { BaseProductItemService } from '../../common';
@Injectable()
export class SubspaceProductItemService extends BaseProductItemService {
constructor(
private readonly productItemRepository: SubspaceProductItemRepository,
) {
super();
}
async createItemFromModel(
product: SubspaceProductEntity,
productModel: SubspaceProductModelEntity,
queryRunner: QueryRunner,
): Promise<void> {
const itemModels = productModel.itemModels;
if (!itemModels?.length) return;
try {
const productItems = itemModels.map((model) =>
this.createProductItem(product, model, queryRunner),
);
await queryRunner.manager.save(
this.productItemRepository.target,
productItems,
);
} catch (error) {
throw new HttpException(
error.message || 'An error occurred while creating product items.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
private createProductItem(
product: SubspaceProductEntity,
model: SubspaceProductItemModelEntity,
queryRunner: QueryRunner,
): Partial<any> {
return queryRunner.manager.create(this.productItemRepository.target, {
tag: model.tag,
product,
model,
});
}
async createItemFromDtos(
product: SubspaceProductEntity,
itemDto: CreateSpaceProductItemDto[],
queryRunner: QueryRunner,
space: SpaceEntity,
) {
if (!itemDto?.length) return;
const incomingTags = itemDto.map((item) => item.tag);
await this.validateTags(incomingTags, queryRunner, space.uuid);
try {
const productItems = itemDto.map((dto) =>
queryRunner.manager.create(SubspaceProductItemEntity, {
tag: dto.tag,
subspaceProduct: product,
}),
);
await queryRunner.manager.save(
this.productItemRepository.target,
productItems,
);
} catch (error) {
throw new HttpException(
error.message || 'An error occurred while creating product items.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}

View File

@ -0,0 +1,128 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { QueryRunner } from 'typeorm';
import {
SpaceEntity,
SubspaceEntity,
SubspaceProductEntity,
} from '@app/common/modules/space';
import {
SubspaceModelEntity,
SubspaceProductModelEntity,
} from '@app/common/modules/space-model';
import { SubspaceProductItemService } from './subspace-product-item.service';
import { ProductAssignmentDto } from 'src/space/dtos';
import { ProductService } from 'src/product/services';
import { ProductEntity } from '@app/common/modules/product/entities';
@Injectable()
export class SubspaceProductService {
constructor(
private readonly subspaceProductItemService: SubspaceProductItemService,
private readonly productService: ProductService,
) {}
async createFromModel(
subspaceModel: SubspaceModelEntity,
subspace: SubspaceEntity,
queryRunner: QueryRunner,
): Promise<void> {
const productModels = subspaceModel.productModels;
if (!productModels?.length) return;
try {
const newSpaceProducts = productModels.map((productModel) =>
this.createSubspaceProductEntity(subspace, productModel),
);
const subspaceProducts = await queryRunner.manager.save(
SubspaceProductEntity,
newSpaceProducts,
);
await Promise.all(
subspaceProducts.map((subspaceProduct, index) =>
this.subspaceProductItemService.createItemFromModel(
subspaceProduct,
productModels[index],
queryRunner,
),
),
);
} catch (error) {
throw new HttpException(
`Transaction failed: Unable to create subspace products ${error}`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
private createSubspaceProductEntity(
subspace: SubspaceEntity,
productModel: SubspaceProductModelEntity,
): Partial<SubspaceProductEntity> {
return {
subspace,
product: productModel.product,
productCount: productModel.productCount,
model: productModel,
};
}
async createFromDto(
productDtos: ProductAssignmentDto[],
subspace: SubspaceEntity,
queryRunner: QueryRunner,
space: SpaceEntity,
): Promise<void> {
try {
const newSpaceProducts = await Promise.all(
productDtos.map(async (dto) => {
this.validateProductCount(dto);
const product = await this.getProduct(dto.productId);
return queryRunner.manager.create(SubspaceProductEntity, {
subspace,
product,
productCount: dto.count,
});
}),
);
const subspaceProducts = await queryRunner.manager.save(
SubspaceProductEntity,
newSpaceProducts,
);
await Promise.all(
productDtos.map((dto, index) =>
this.subspaceProductItemService.createItemFromDtos(
subspaceProducts[index],
dto.items,
queryRunner,
space,
),
),
);
} catch (error) {
throw new HttpException(
`Failed to create subspace products from DTOs. Error: ${error.message}`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async getProduct(productId: string): Promise<ProductEntity> {
const product = await this.productService.findOne(productId);
return product.data;
}
async validateProductCount(dto: ProductAssignmentDto) {
if (dto.count !== dto.items.length) {
throw new HttpException(
'Producy item and count doesnot match',
HttpStatus.BAD_REQUEST,
);
}
}
}

View File

@ -1,5 +1,4 @@
import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { SubspaceRepository } from '@app/common/modules/space/repositories';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { AddSubspaceDto, GetSpaceParam, GetSubSpaceParam } from '../../dtos'; import { AddSubspaceDto, GetSpaceParam, GetSubSpaceParam } from '../../dtos';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
@ -9,25 +8,119 @@ import {
} from '@app/common/models/typeOrmCustom.model'; } from '@app/common/models/typeOrmCustom.model';
import { PageResponse } from '@app/common/dto/pagination.response.dto'; import { PageResponse } from '@app/common/dto/pagination.response.dto';
import { SubspaceDto } from '@app/common/modules/space/dtos'; import { SubspaceDto } from '@app/common/modules/space/dtos';
import { SpaceService } from '../space.service'; import { QueryRunner } from 'typeorm';
import {
SpaceEntity,
SubspaceEntity,
} from '@app/common/modules/space/entities';
import {
SpaceModelEntity,
SubspaceModelEntity,
} from '@app/common/modules/space-model';
import { ValidationService } from '../space-validation.service';
import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
import { SubspaceProductService } from './subspace-product.service';
@Injectable() @Injectable()
export class SubSpaceService { export class SubSpaceService {
constructor( constructor(
private readonly subspaceRepository: SubspaceRepository, private readonly subspaceRepository: SubspaceRepository,
private readonly spaceService: SpaceService, private readonly validationService: ValidationService,
private readonly productService: SubspaceProductService,
) {} ) {}
async createSubspaces(
subspaceData: Array<{
subspaceName: string;
space: SpaceEntity;
subSpaceModel?: SubspaceModelEntity;
}>,
queryRunner: QueryRunner,
): Promise<SubspaceEntity[]> {
try {
const subspaces = subspaceData.map((data) =>
queryRunner.manager.create(this.subspaceRepository.target, data),
);
return await queryRunner.manager.save(subspaces);
} catch (error) {
throw new HttpException(
'An unexpected error occurred while creating subspaces.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async createSubSpaceFromModel(
spaceModel: SpaceModelEntity,
space: SpaceEntity,
queryRunner: QueryRunner,
): Promise<void> {
const subSpaceModels = spaceModel.subspaceModels;
if (!subSpaceModels?.length) return;
const subspaceData = subSpaceModels.map((subSpaceModel) => ({
subspaceName: subSpaceModel.subspaceName,
space,
subSpaceModel,
}));
const subspaces = await this.createSubspaces(subspaceData, queryRunner);
await Promise.all(
subSpaceModels.map((model, index) => {
this.productService.createFromModel(
model,
subspaces[index],
queryRunner,
);
}),
);
}
async createSubspacesFromDto(
addSubspaceDtos: AddSubspaceDto[],
space: SpaceEntity,
queryRunner: QueryRunner,
): Promise<SubspaceEntity[]> {
try {
const subspaceData = addSubspaceDtos.map((dto) => ({
subspaceName: dto.subspaceName,
space,
}));
const subspaces = await this.createSubspaces(subspaceData, queryRunner);
await Promise.all(
addSubspaceDtos.map((dto, index) =>
this.productService.createFromDto(
dto.products,
subspaces[index],
queryRunner,
space,
),
),
);
return subspaces;
} catch (error) {
throw new Error(
`Transaction failed: Unable to create subspaces and products. ${error.message}`,
);
}
}
async createSubspace( async createSubspace(
addSubspaceDto: AddSubspaceDto, addSubspaceDto: AddSubspaceDto,
params: GetSpaceParam, params: GetSpaceParam,
): Promise<BaseResponseDto> { ): Promise<BaseResponseDto> {
const { communityUuid, spaceUuid, projectUuid } = params; const space =
const space = await this.spaceService.validateCommunityAndSpace( await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid, params.projectUuid,
spaceUuid, params.projectUuid,
projectUuid, params.spaceUuid,
); );
try { try {
const newSubspace = this.subspaceRepository.create({ const newSubspace = this.subspaceRepository.create({
@ -52,10 +145,10 @@ export class SubSpaceService {
pageable: Partial<TypeORMCustomModelFindAllQuery>, pageable: Partial<TypeORMCustomModelFindAllQuery>,
): Promise<BaseResponseDto> { ): Promise<BaseResponseDto> {
const { communityUuid, spaceUuid, projectUuid } = params; const { communityUuid, spaceUuid, projectUuid } = params;
await this.spaceService.validateCommunityAndSpace( await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid, communityUuid,
spaceUuid,
projectUuid, projectUuid,
spaceUuid,
); );
try { try {
@ -76,10 +169,10 @@ export class SubSpaceService {
async findOne(params: GetSubSpaceParam): Promise<BaseResponseDto> { async findOne(params: GetSubSpaceParam): Promise<BaseResponseDto> {
const { communityUuid, subSpaceUuid, spaceUuid, projectUuid } = params; const { communityUuid, subSpaceUuid, spaceUuid, projectUuid } = params;
await this.spaceService.validateCommunityAndSpace( await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid, communityUuid,
spaceUuid,
projectUuid, projectUuid,
spaceUuid,
); );
try { try {
const subSpace = await this.subspaceRepository.findOne({ const subSpace = await this.subspaceRepository.findOne({
@ -116,12 +209,11 @@ export class SubSpaceService {
updateSubSpaceDto: AddSubspaceDto, updateSubSpaceDto: AddSubspaceDto,
): Promise<BaseResponseDto> { ): Promise<BaseResponseDto> {
const { spaceUuid, communityUuid, subSpaceUuid, projectUuid } = params; const { spaceUuid, communityUuid, subSpaceUuid, projectUuid } = params;
await this.spaceService.validateCommunityAndSpace( await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid, communityUuid,
spaceUuid,
projectUuid, projectUuid,
spaceUuid,
); );
const subSpace = await this.subspaceRepository.findOne({ const subSpace = await this.subspaceRepository.findOne({
where: { uuid: subSpaceUuid }, where: { uuid: subSpaceUuid },
}); });
@ -156,10 +248,10 @@ export class SubSpaceService {
async delete(params: GetSubSpaceParam): Promise<BaseResponseDto> { async delete(params: GetSubSpaceParam): Promise<BaseResponseDto> {
const { spaceUuid, communityUuid, subSpaceUuid, projectUuid } = params; const { spaceUuid, communityUuid, subSpaceUuid, projectUuid } = params;
await this.spaceService.validateCommunityAndSpace( await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid, communityUuid,
spaceUuid,
projectUuid, projectUuid,
spaceUuid,
); );
const subspace = await this.subspaceRepository.findOne({ const subspace = await this.subspaceRepository.findOne({

View File

@ -12,18 +12,20 @@ import {
import { import {
SpaceDeviceService, SpaceDeviceService,
SpaceLinkService, SpaceLinkService,
SpaceProductItemService,
SpaceProductService, SpaceProductService,
SpaceSceneService, SpaceSceneService,
SpaceService, SpaceService,
SpaceUserService, SpaceUserService,
SubspaceDeviceService, SubspaceDeviceService,
SubspaceProductItemService,
SubSpaceService, SubSpaceService,
} from './services'; } from './services';
import { import {
SpaceProductRepository, SpaceProductRepository,
SpaceRepository, SpaceRepository,
SubspaceRepository,
SpaceLinkRepository, SpaceLinkRepository,
SpaceProductItemRepository,
} from '@app/common/modules/space/repositories'; } from '@app/common/modules/space/repositories';
import { CommunityRepository } from '@app/common/modules/community/repositories'; import { CommunityRepository } from '@app/common/modules/community/repositories';
import { import {
@ -43,9 +45,18 @@ import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status
import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories'; import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories';
import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories';
import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { ProjectRepository } from '@app/common/modules/project/repositiories';
import { SpaceModelRepository } from '@app/common/modules/space-model';
import { CommunityModule } from 'src/community/community.module';
import { ValidationService } from './services';
import {
SubspaceProductItemRepository,
SubspaceProductRepository,
SubspaceRepository,
} from '@app/common/modules/space/repositories/subspace.repository';
import { SubspaceProductService } from './services';
@Module({ @Module({
imports: [ConfigModule, SpaceRepositoryModule], imports: [ConfigModule, SpaceRepositoryModule, CommunityModule],
controllers: [ controllers: [
SpaceController, SpaceController,
SpaceUserController, SpaceUserController,
@ -55,6 +66,7 @@ import { ProjectRepository } from '@app/common/modules/project/repositiories';
SpaceSceneController, SpaceSceneController,
], ],
providers: [ providers: [
ValidationService,
SpaceService, SpaceService,
TuyaService, TuyaService,
ProductRepository, ProductRepository,
@ -76,11 +88,19 @@ import { ProjectRepository } from '@app/common/modules/project/repositiories';
SceneRepository, SceneRepository,
DeviceService, DeviceService,
DeviceStatusFirebaseService, DeviceStatusFirebaseService,
SubspaceProductItemRepository,
DeviceStatusLogRepository, DeviceStatusLogRepository,
SceneDeviceRepository, SceneDeviceRepository,
SpaceProductService, SpaceProductService,
SpaceProductRepository, SpaceProductRepository,
ProjectRepository, ProjectRepository,
SpaceModelRepository,
SubspaceRepository,
SpaceProductItemService,
SpaceProductItemRepository,
SubspaceProductService,
SubspaceProductItemService,
SubspaceProductRepository,
], ],
exports: [SpaceService], exports: [SpaceService],
}) })