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 { TuyaService } from './integrations/tuya/services/tuya.service';
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({
providers: [
CommonService,
@ -16,6 +25,14 @@ import { SceneDeviceRepository } from './modules/scene-device/repositories';
ErrorMessageService,
TuyaService,
SceneDeviceRepository,
SpaceRepository,
SubspaceRepository,
SubspaceModelRepository,
SubspaceProductModelRepository,
SubspaceProductItemModelRepository,
SpaceModelRepository,
SpaceProductModelRepository,
SpaceProductItemRepository,
],
exports: [
CommonService,
@ -25,6 +42,13 @@ import { SceneDeviceRepository } from './modules/scene-device/repositories';
EmailService,
ErrorMessageService,
SceneDeviceRepository,
SpaceRepository,
SubspaceRepository,
SubspaceModelRepository,
SubspaceProductModelRepository,
SubspaceProductItemModelRepository,
SpaceModelRepository,
SpaceProductModelRepository,
],
imports: [
ConfigModule.forRoot({

View File

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

View File

@ -4,6 +4,7 @@ import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { DeviceEntity } from '../../device/entities';
import { SpaceProductEntity } from '../../space/entities/space-product.entity';
import { SpaceProductModelEntity } from '../../space-model/entities';
import { SubspaceProductModelEntity } from '../../space-model/entities/subspace-model/subspace-product-model.entity';
@Entity({ name: 'product' })
export class ProductEntity extends AbstractEntity<ProductDto> {
@ -37,6 +38,12 @@ export class ProductEntity extends AbstractEntity<ProductDto> {
)
spaceProductModels: SpaceProductModelEntity[];
@OneToMany(
() => SubspaceProductModelEntity,
(subspaceProductModel) => subspaceProductModel.product,
)
subpaceProductModels: SubspaceProductModelEntity[];
@OneToMany(
() => DeviceEntity,
(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-product-item.dto';
export * from './space-product-item-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 { IsNotEmpty, IsNumber, IsString } from 'class-validator';
import { SpaceProductItemDto } from './space-product-item.dto';
import { SpaceProductItemModelDto } from './space-product-item-model.dto';
export class SpaceProductModelDto {
@IsString()
@ -17,7 +17,7 @@ export class SpaceProductModelDto {
@ApiProperty({
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-product-item.entity';
export * from './space-product-item-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';
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { SpaceModelDto } from '../dtos';
import { SubspaceModelEntity } from './subspace-model.entity';
import { SubspaceModelEntity } from './subspace-model';
import { SpaceProductModelEntity } from './space-product-model.entity';
import { ProjectEntity } from '../../project/entities';
import { SpaceEntity } from '../../space/entities';
@Entity({ name: 'space-model' })
@Unique(['modelName', 'project'])
@ -48,9 +49,13 @@ export class SpaceModelEntity extends AbstractEntity<SpaceModelDto> {
() => SpaceProductModelEntity,
(productModel) => productModel.spaceModel,
{
cascade: true,
nullable: true,
},
)
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 { SpaceProductItemDto } from '../dtos';
import { SpaceProductItemModelDto } from '../dtos';
import { SpaceProductModelEntity } from './space-product-model.entity';
import { SpaceModelEntity } from './space-model.entity';
import { SpaceProductItemEntity } from '../../space/entities';
@Entity({ name: 'space-product-item-model' })
@Unique(['tag', 'spaceProductModel', 'spaceModel'])
export class SpaceProductItemModelEntity extends AbstractEntity<SpaceProductItemDto> {
export class SpaceProductItemModelEntity extends AbstractEntity<SpaceProductItemModelDto> {
@Column({
nullable: false,
})
@ -21,12 +20,10 @@ export class SpaceProductItemModelEntity extends AbstractEntity<SpaceProductItem
)
public spaceProductModel: SpaceProductModelEntity;
@ManyToOne(
() => SpaceModelEntity,
(spaceModel) => spaceModel.spaceProductModels,
{
nullable: false,
},
@OneToMany(
() => SpaceProductItemEntity,
(spaceProductItem) => spaceProductItem.spaceProductItemModel,
{ cascade: true },
)
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 { ProductEntity } from '../../product/entities';
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 { SpaceProductEntity } from '../../space/entities';
@Entity({ name: 'space-product-model' })
export class SpaceProductModelEntity extends AbstractEntity<SpaceProductModelDto> {
@ -37,4 +38,13 @@ export class SpaceProductModelEntity extends AbstractEntity<SpaceProductModelDto
},
)
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,
SpaceProductModelEntity,
SubspaceModelEntity,
SubspaceProductItemModelEntity,
SubspaceProductModelEntity,
} from '../entities';
@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()
export class SpaceProductModelRepository extends Repository<SpaceProductModelEntity> {
constructor(private dataSource: DataSource) {

View File

@ -1,2 +1,4 @@
export * from './space.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()
@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 './subspace.entity';
export * from './subspace';
export * from './space-product.entity';
export * from './space-product-item.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 { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { ProductEntity } from '../../product/entities';
import { SpaceProductItemEntity } from './space-product-item.entity';
import { SpaceProductModelEntity } from '../../space-model';
@Entity({ name: 'space-product' })
export class SpaceProductEntity extends AbstractEntity<SpaceProductEntity> {
@ -25,6 +27,21 @@ export class SpaceProductEntity extends AbstractEntity<SpaceProductEntity> {
})
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>) {
super();
Object.assign(this, partial);

View File

@ -11,10 +11,11 @@ import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { UserSpaceEntity } from '../../user/entities';
import { DeviceEntity } from '../../device/entities';
import { CommunityEntity } from '../../community/entities';
import { SubspaceEntity } from './subspace.entity';
import { SubspaceEntity } from './subspace';
import { SpaceLinkEntity } from './space-link.entity';
import { SpaceProductEntity } from './space-product.entity';
import { SceneEntity } from '../../scene/entities';
import { SpaceModelEntity } from '../../space-model';
import { InviteUserSpaceEntity } from '../../Invite-user/entities';
@Entity({ name: 'space' })
@ -98,6 +99,11 @@ export class SpaceEntity extends AbstractEntity<SpaceDto> {
@OneToMany(() => SceneEntity, (scene) => scene.space)
scenes: SceneEntity[];
@ManyToOne(() => SpaceModelEntity, { nullable: true })
@JoinColumn({ name: 'space_model_uuid' })
spaceModel?: SpaceModelEntity;
@OneToMany(
() => InviteUserSpaceEntity,
(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 { Injectable } from '@nestjs/common';
import { SpaceProductEntity } from '../entities/space-product.entity';
import { SpaceEntity, SpaceLinkEntity, SubspaceEntity } from '../entities';
import {
SpaceEntity,
SpaceLinkEntity,
SpaceProductItemEntity,
} from '../entities';
@Injectable()
export class SpaceRepository extends Repository<SpaceEntity> {
@ -9,12 +13,6 @@ export class SpaceRepository extends Repository<SpaceEntity> {
super(SpaceEntity, dataSource.createEntityManager());
}
}
@Injectable()
export class SubspaceRepository extends Repository<SubspaceEntity> {
constructor(private dataSource: DataSource) {
super(SubspaceEntity, dataSource.createEntityManager());
}
}
@Injectable()
export class SpaceLinkRepository extends Repository<SpaceLinkEntity> {
@ -28,3 +26,10 @@ export class SpaceProductRepository extends Repository<SpaceProductEntity> {
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 { TypeOrmModule } from '@nestjs/typeorm';
import { SpaceEntity, SubspaceEntity } from './entities';
import { SpaceEntity, SubspaceEntity, SubspaceProductEntity } from './entities';
@Module({
providers: [],
exports: [],
controllers: [],
imports: [TypeOrmModule.forFeature([SpaceEntity, SubspaceEntity])],
imports: [
TypeOrmModule.forFeature([
SpaceEntity,
SubspaceEntity,
SubspaceProductEntity,
]),
],
})
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 { ProductRepository } from '@app/common/modules/product/repositories';
import { ProductController } from './controllers';
@Global()
@Module({
controllers: [ProductController],
providers: [ProductService, ProductRepository],
exports: [ProductService],
})
export class ProductModule {}

View File

@ -21,4 +21,23 @@ export class ProductService {
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 { ProjectService } from './services';
import { ProjectRepository } from '@app/common/modules/project/repositiories';
@Global()
@Module({
imports: [],
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 { IsNotEmpty, IsString } from 'class-validator';
export class CreateSpaceProductItemModelDto {
export class CreateProductItemModelDto {
@ApiProperty({
description: 'Specific name for the product item',
example: 'Light 1',

View File

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

View File

@ -1,5 +1,13 @@
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 {
@ApiProperty({
@ -9,4 +17,14 @@ export class CreateSubspaceModelDto {
@IsNotEmpty()
@IsString()
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-product-item-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 { ProjectParam } from 'src/community/dtos';
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 { DataSource } from 'typeorm';

View File

@ -4,29 +4,30 @@ import {
SpaceProductModelEntity,
} from '@app/common/modules/space-model';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateSpaceProductItemModelDto } from '../dtos';
import { CreateProductItemModelDto } from '../dtos';
import { QueryRunner } from 'typeorm';
import { BaseProductItemService } from '../common';
@Injectable()
export class SpaceProductItemModelService {
export class SpaceProductItemModelService extends BaseProductItemService {
constructor(
private readonly spaceProductItemRepository: SpaceProductItemModelRepository,
) {}
) {
super();
}
async createProdutItemModel(
itemModelDtos: CreateSpaceProductItemModelDto[],
itemModelDtos: CreateProductItemModelDto[],
spaceProductModel: SpaceProductModelEntity,
spaceModel: SpaceModelEntity,
queryRunner: QueryRunner,
) {
await this.validateTags(itemModelDtos, spaceModel, queryRunner);
await this.validateTags(itemModelDtos, queryRunner, spaceModel);
try {
const productItems = itemModelDtos.map((dto) =>
queryRunner.manager.create(this.spaceProductItemRepository.target, {
tag: dto.tag,
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 {
SpaceModelEntity,
SpaceProductModelRepository,
} 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()
export class SpaceProductModelService {
export class SpaceProductModelService extends BaseProductModelService {
constructor(
private readonly spaceProductModelRepository: SpaceProductModelRepository,
private readonly productRepository: ProductRepository,
private readonly spaceProductItemModelService: SpaceProductItemModelService,
) {}
productService: ProductService,
) {
super(productService);
}
async createSpaceProductModels(
spaceProductModelDtos: CreateSpaceProductModelDto[],
@ -25,7 +28,7 @@ export class SpaceProductModelService {
const productModels = await Promise.all(
spaceProductModelDtos.map(async (dto) => {
this.validateProductCount(dto);
const product = await this.getProduct(dto.productId);
const product = await this.getProduct(dto.productUuid);
return queryRunner.manager.create(
this.spaceProductModelRepository.target,
{
@ -40,14 +43,15 @@ export class SpaceProductModelService {
const savedProductModels = await queryRunner.manager.save(productModels);
await Promise.all(
spaceProductModelDtos.map((dto, index) =>
this.spaceProductItemModelService.createProdutItemModel(
spaceProductModelDtos.map((dto, index) => {
const savedModel = savedProductModels[index];
return this.spaceProductItemModelService.createProdutItemModel(
dto.items,
savedProductModels[index],
savedModel, // Pass the saved model
spaceModel,
queryRunner,
),
),
);
}),
);
} catch (error) {
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,
} from '@app/common/modules/space-model';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateSubspaceModelDto } from '../dtos';
import { CreateSubspaceModelDto } from '../../dtos';
import { QueryRunner } from 'typeorm';
import { SubspaceProductModelService } from './subspace-product-model.service';
@Injectable()
export class SubSpaceModelService {
constructor(
private readonly subspaceModelRepository: SubspaceModelRepository,
private readonly subSpaceProducetModelService: SubspaceProductModelService,
) {}
async createSubSpaceModels(
@ -28,6 +30,18 @@ export class SubSpaceModelService {
);
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) {
if (error instanceof HttpException) {
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,
SpaceProductModelService,
SubSpaceModelService,
SubspaceProductItemModelService,
} from './services';
import {
SpaceModelRepository,
SpaceProductItemModelRepository,
SpaceProductModelRepository,
SubspaceModelRepository,
SubspaceProductItemModelRepository,
SubspaceProductModelRepository,
} from '@app/common/modules/space-model';
import { ProjectRepository } from '@app/common/modules/project/repositiories';
import { ProductRepository } from '@app/common/modules/product/repositories';
import { SubspaceProductModelService } from './services/subspace/subspace-product-model.service';
@Module({
imports: [ConfigModule, SpaceRepositoryModule],
@ -31,6 +35,10 @@ import { ProductRepository } from '@app/common/modules/product/repositories';
ProductRepository,
SpaceProductItemModelService,
SpaceProductItemModelRepository,
SubspaceProductItemModelService,
SubspaceProductItemModelRepository,
SubspaceProductModelService,
SubspaceProductModelRepository,
],
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,
ValidateNested,
} 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 {
@ApiProperty({
@ -29,9 +65,14 @@ export class AddSpaceDto {
@IsOptional()
parentUuid?: string;
@ApiProperty({
description: 'Icon identifier for the space',
example: 'assets/location',
required: false,
})
@IsString()
@IsOptional()
public icon: string;
public icon?: string;
@ApiProperty({
description: 'Indicates whether the space is private or public',
@ -49,16 +90,39 @@ export class AddSpaceDto {
@IsNumber()
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 })
@IsString()
@IsOptional()
direction: string;
direction?: string;
@ApiProperty({
description: 'List of products assigned to this space',
type: [ProductAssignmentDto],
required: false,
})
@IsArray()
@ValidateNested({ each: true })
@IsOptional()
@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 {
@ -101,11 +165,3 @@ export class AddUserSpaceUsingCodeDto {
Object.assign(this, dto);
}
}
class ProductAssignmentDto {
@IsNotEmpty()
productId: string;
@IsNotEmpty()
count: number;
}

View File

@ -1,5 +1,13 @@
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 {
@ApiProperty({
@ -9,4 +17,15 @@ export class AddSubspaceDto {
@IsNotEmpty()
@IsString()
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-scene.service';
export * from './space-products';
export * from './space-product-items';
export * from './space-validation.service';

View File

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

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 { SpaceEntity } from '@app/common/modules/space/entities';
import { SpaceProductEntity } from '@app/common/modules/space/entities/space-product.entity';
import { SpaceProductRepository } from '@app/common/modules/space/repositories';
import { In } from 'typeorm';
import { In, QueryRunner } 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()
export class SpaceProductService {
constructor(
private readonly productRepository: ProductRepository,
private readonly spaceProductRepository: SpaceProductRepository,
private readonly spaceProductItemService: SpaceProductItemService,
private readonly productService: ProductService,
) {}
async assignProductsToSpace(
async createFromModel(
spaceModel: SpaceModelEntity,
space: SpaceEntity,
products: { productId: string; count: number }[],
): Promise<SpaceProductEntity[]> {
try {
const uniqueProducts = this.validateUniqueProducts(products);
const productEntities = await this.getProductEntities(uniqueProducts);
queryRunner: QueryRunner,
) {
const spaceProductModels = spaceModel.spaceProductModels;
if (!spaceProductModels?.length) return;
const newSpaceProducts = [];
// Fetch existing space products
const existingSpaceProducts = await this.spaceProductRepository.find({
where: {
space: {
uuid: space.uuid,
},
},
relations: ['product'],
spaceProductModels.map((spaceProductModel) => {
newSpaceProducts.push(
queryRunner.manager.create(SpaceProductEntity, {
space: space,
product: spaceProductModel.product,
productCount: spaceProductModel.productCount,
spaceProductModel: spaceProductModel,
}),
);
});
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,
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,
);
}
// 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
if (updatedProducts.length > 0) {
await this.spaceProductRepository.save(updatedProducts);
}
async assignProductsToSpace(
space: SpaceEntity,
products: ProductAssignmentDto[],
queryRunner: QueryRunner,
): Promise<SpaceProductEntity[]> {
let updatedProducts: SpaceProductEntity[] = [];
if (newProducts.length > 0) {
await this.spaceProductRepository.save(newProducts);
}
try {
const uniqueProducts = this.validateUniqueProducts(products);
const productEntities = await this.getProductEntities(uniqueProducts);
const existingSpaceProducts = await this.getExistingSpaceProducts(
space,
queryRunner,
);
return [...updatedProducts, ...newProducts];
} catch (error) {
console.error('Error assigning products to space:', error);
if (!(error instanceof HttpException)) {
throw new HttpException(
'An error occurred while assigning products to the space',
HttpStatus.INTERNAL_SERVER_ERROR,
if (existingSpaceProducts) {
updatedProducts = await this.updateExistingProducts(
existingSpaceProducts,
uniqueProducts,
productEntities,
queryRunner,
);
}
const newProducts = await this.createNewProducts(
uniqueProducts,
productEntities,
space,
queryRunner,
);
return [...updatedProducts, ...newProducts];
} catch (error) {
if (!(error instanceof HttpException)) {
throw new HttpException(
`An error occurred while assigning products to the space ${error}`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
throw error;
}
}
private validateUniqueProducts(
products: { productId: string; count: number }[],
): { productId: string; count: number }[] {
products: ProductAssignmentDto[],
): ProductAssignmentDto[] {
const productIds = new Set();
const uniqueProducts = [];
@ -110,15 +115,13 @@ export class SpaceProductService {
}
private async getProductEntities(
products: { productId: string; count: number }[],
products: ProductAssignmentDto[],
): Promise<Map<string, any>> {
try {
const productIds = products.map((p) => p.productId);
const productEntities = await this.productRepository.find({
where: { uuid: In(productIds) },
});
return new Map(productEntities.map((p) => [p.uuid, p]));
} catch (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 { GetSpaceParam } from '../dtos';
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { SpaceService } from './space.service';
import { SceneService } from '../../scene/services';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import { GetSceneDto } from '../../scene/dtos';
import { ValidationService } from './space-validation.service';
@Injectable()
export class SpaceSceneService {
constructor(
private readonly spaceSevice: SpaceService,
private readonly sceneSevice: SceneService,
private readonly validationService: ValidationService,
) {}
async getScenes(
@ -20,10 +20,10 @@ export class SpaceSceneService {
try {
const { spaceUuid, communityUuid, projectUuid } = params;
await this.spaceSevice.validateCommunityAndSpace(
await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid,
spaceUuid,
projectUuid,
spaceUuid,
);
const scenes = await this.sceneSevice.findScenesBySpace(

View File

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

View File

@ -1,2 +1,4 @@
export * from './subspace.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 { SuccessResponseDto } from '@app/common/dto/success.response.dto';
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 { DeviceSubSpaceParam, GetSubSpaceParam } from '../../dtos';
import { CommunityRepository } from '@app/common/modules/community/repositories';
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
import { ProductRepository } from '@app/common/modules/product/repositories';
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()
export class SubspaceDeviceService {
constructor(
private readonly spaceRepository: SpaceRepository,
private readonly communityRepository: CommunityRepository,
private readonly subspaceRepository: SubspaceRepository,
private readonly deviceRepository: DeviceRepository,
private readonly tuyaService: TuyaService,
private readonly productRepository: ProductRepository,
private readonly spaceService: SpaceService,
private readonly validationService: ValidationService,
) {}
async listDevicesInSubspace(
@ -31,10 +25,10 @@ export class SubspaceDeviceService {
): Promise<BaseResponseDto> {
const { subSpaceUuid, spaceUuid, communityUuid, projectUuid } = params;
await this.spaceService.validateCommunityAndSpace(
await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid,
spaceUuid,
projectUuid,
spaceUuid,
);
const subspace = await this.findSubspaceWithDevices(subSpaceUuid);
@ -76,10 +70,10 @@ export class SubspaceDeviceService {
const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid, projectUuid } =
params;
try {
await this.spaceService.validateCommunityAndSpace(
await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid,
spaceUuid,
projectUuid,
spaceUuid,
);
const subspace = await this.findSubspace(subSpaceUuid);
@ -111,10 +105,10 @@ export class SubspaceDeviceService {
const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid, projectUuid } =
params;
try {
await this.spaceService.validateCommunityAndSpace(
await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid,
spaceUuid,
projectUuid,
spaceUuid,
);
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 { SubspaceRepository } from '@app/common/modules/space/repositories';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { AddSubspaceDto, GetSpaceParam, GetSubSpaceParam } from '../../dtos';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
@ -9,24 +8,118 @@ import {
} from '@app/common/models/typeOrmCustom.model';
import { PageResponse } from '@app/common/dto/pagination.response.dto';
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()
export class SubSpaceService {
constructor(
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(
addSubspaceDto: AddSubspaceDto,
params: GetSpaceParam,
): Promise<BaseResponseDto> {
const { communityUuid, spaceUuid, projectUuid } = params;
const space = await this.spaceService.validateCommunityAndSpace(
communityUuid,
spaceUuid,
projectUuid,
const space =
await this.validationService.validateSpaceWithinCommunityAndProject(
params.projectUuid,
params.projectUuid,
params.spaceUuid,
);
try {
@ -52,10 +145,10 @@ export class SubSpaceService {
pageable: Partial<TypeORMCustomModelFindAllQuery>,
): Promise<BaseResponseDto> {
const { communityUuid, spaceUuid, projectUuid } = params;
await this.spaceService.validateCommunityAndSpace(
await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid,
spaceUuid,
projectUuid,
spaceUuid,
);
try {
@ -76,10 +169,10 @@ export class SubSpaceService {
async findOne(params: GetSubSpaceParam): Promise<BaseResponseDto> {
const { communityUuid, subSpaceUuid, spaceUuid, projectUuid } = params;
await this.spaceService.validateCommunityAndSpace(
await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid,
spaceUuid,
projectUuid,
spaceUuid,
);
try {
const subSpace = await this.subspaceRepository.findOne({
@ -116,12 +209,11 @@ export class SubSpaceService {
updateSubSpaceDto: AddSubspaceDto,
): Promise<BaseResponseDto> {
const { spaceUuid, communityUuid, subSpaceUuid, projectUuid } = params;
await this.spaceService.validateCommunityAndSpace(
await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid,
spaceUuid,
projectUuid,
spaceUuid,
);
const subSpace = await this.subspaceRepository.findOne({
where: { uuid: subSpaceUuid },
});
@ -156,10 +248,10 @@ export class SubSpaceService {
async delete(params: GetSubSpaceParam): Promise<BaseResponseDto> {
const { spaceUuid, communityUuid, subSpaceUuid, projectUuid } = params;
await this.spaceService.validateCommunityAndSpace(
await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid,
spaceUuid,
projectUuid,
spaceUuid,
);
const subspace = await this.subspaceRepository.findOne({

View File

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