Merge pull request #170 from SyncrowIOT/feat/create-space-model

Feat/create space model
This commit is contained in:
hannathkadher
2024-12-11 13:56:43 +04:00
committed by GitHub
35 changed files with 878 additions and 3 deletions

View File

@ -266,6 +266,16 @@ export class ControllerRoute {
};
};
static SPACE_MODEL = class {
public static readonly ROUTE = '/projects/:projectUuid/space-models';
static ACTIONS = class {
public static readonly CREATE_SPACE_MODEL_SUMMARY =
'Create a New Space Model';
public static readonly CREATE_SPACE_MODEL_DESCRIPTION =
'This endpoint allows you to create a new space model within a specified project. A space model defines the structure of spaces, including subspaces, products, and product items, and is uniquely identifiable within the project.';
};
};
static PRODUCT = class {
public static readonly ROUTE = 'products';
static ACTIONS = class {

View File

@ -28,6 +28,12 @@ import { SceneEntity, SceneIconEntity } from '../modules/scene/entities';
import { SceneDeviceEntity } from '../modules/scene-device/entities';
import { SpaceProductEntity } from '../modules/space/entities/space-product.entity';
import { ProjectEntity } from '../modules/project/entities';
import {
SpaceModelEntity,
SpaceProductItemModelEntity,
SpaceProductModelEntity,
SubspaceModelEntity,
} from '../modules/space-model/entities';
@Module({
imports: [
TypeOrmModule.forRootAsync({
@ -68,6 +74,10 @@ import { ProjectEntity } from '../modules/project/entities';
SceneEntity,
SceneIconEntity,
SceneDeviceEntity,
SpaceModelEntity,
SpaceProductModelEntity,
SpaceProductItemModelEntity,
SubspaceModelEntity,
],
namingStrategy: new SnakeNamingStrategy(),
synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))),

View File

@ -3,6 +3,7 @@ import { ProductDto } from '../dtos';
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';
@Entity({ name: 'product' })
export class ProductEntity extends AbstractEntity<ProductDto> {
@ -30,6 +31,12 @@ export class ProductEntity extends AbstractEntity<ProductDto> {
@OneToMany(() => SpaceProductEntity, (spaceProduct) => spaceProduct.product)
spaceProducts: SpaceProductEntity[];
@OneToMany(
() => SpaceProductModelEntity,
(spaceProductModel) => spaceProductModel.product,
)
spaceProductModels: SpaceProductModelEntity[];
@OneToMany(
() => DeviceEntity,
(devicesProductEntity) => devicesProductEntity.productDevice,

View File

@ -2,6 +2,7 @@ import { Entity, Column, Unique, OneToMany } from 'typeorm';
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { ProjectDto } from '../dtos';
import { CommunityEntity } from '../../community/entities';
import { SpaceModelEntity } from '../../space-model';
@Entity({ name: 'project' })
@Unique(['name'])
@ -21,6 +22,9 @@ export class ProjectEntity extends AbstractEntity<ProjectDto> {
@Column({ length: 255, nullable: true })
description: string;
@OneToMany(() => SpaceModelEntity, (spaceModel) => spaceModel.project)
public spaceModels: SpaceModelEntity[];
@OneToMany(() => CommunityEntity, (community) => community.project)
communities: CommunityEntity[];

View File

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

View File

@ -0,0 +1,15 @@
import { IsString, IsNotEmpty } from 'class-validator';
export class SpaceModelDto {
@IsString()
@IsNotEmpty()
public uuid: string;
@IsString()
@IsNotEmpty()
public spaceModelName: string;
@IsString()
@IsNotEmpty()
projectUuid: string;
}

View File

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

View File

@ -0,0 +1,23 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsNumber, IsString } 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

@ -0,0 +1,15 @@
import { IsString, IsNotEmpty } from 'class-validator';
export class SubSpaceModelDto {
@IsString()
@IsNotEmpty()
public uuid: string;
@IsString()
@IsNotEmpty()
public subSpaceModelName: string;
@IsString()
@IsNotEmpty()
spaceModelUuid: string;
}

View File

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

View File

@ -0,0 +1,56 @@
import {
Entity,
Column,
OneToMany,
ManyToOne,
JoinColumn,
Unique,
} from 'typeorm';
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { SpaceModelDto } from '../dtos';
import { SubspaceModelEntity } from './subspace-model.entity';
import { SpaceProductModelEntity } from './space-product-model.entity';
import { ProjectEntity } from '../../project/entities';
@Entity({ name: 'space-model' })
@Unique(['modelName', 'project'])
export class SpaceModelEntity extends AbstractEntity<SpaceModelDto> {
@Column({
type: 'uuid',
default: () => 'gen_random_uuid()',
nullable: false,
})
public uuid: string;
@Column({
nullable: false,
})
public modelName: string;
@ManyToOne(() => ProjectEntity, (project) => project.spaceModels, {
nullable: false,
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'project_uuid' })
public project: ProjectEntity;
@OneToMany(
() => SubspaceModelEntity,
(subspaceModel) => subspaceModel.spaceModel,
{
cascade: true,
nullable: true,
},
)
public subspaceModels: SubspaceModelEntity[];
@OneToMany(
() => SpaceProductModelEntity,
(productModel) => productModel.spaceModel,
{
cascade: true,
nullable: true,
},
)
public spaceProductModels: SpaceProductModelEntity[];
}

View File

@ -0,0 +1,32 @@
import { Entity, Column, ManyToOne, Unique } from 'typeorm';
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { SpaceProductItemDto } from '../dtos';
import { SpaceProductModelEntity } from './space-product-model.entity';
import { SpaceModelEntity } from './space-model.entity';
@Entity({ name: 'space-product-item-model' })
@Unique(['tag', 'spaceProductModel', 'spaceModel'])
export class SpaceProductItemModelEntity extends AbstractEntity<SpaceProductItemDto> {
@Column({
nullable: false,
})
public tag: string;
@ManyToOne(
() => SpaceProductModelEntity,
(spaceProductModel) => spaceProductModel.items,
{
nullable: false,
},
)
public spaceProductModel: SpaceProductModelEntity;
@ManyToOne(
() => SpaceModelEntity,
(spaceModel) => spaceModel.spaceProductModels,
{
nullable: false,
},
)
public spaceModel: SpaceModelEntity;
}

View File

@ -0,0 +1,40 @@
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 { SpaceProductModelDto } from '../dtos';
@Entity({ name: 'space-product-model' })
export class SpaceProductModelEntity extends AbstractEntity<SpaceProductModelDto> {
@Column({
nullable: false,
type: 'int',
})
productCount: number;
@ManyToOne(
() => SpaceModelEntity,
(spaceModel) => spaceModel.spaceProductModels,
{
nullable: false,
onDelete: 'CASCADE',
},
)
public spaceModel: SpaceModelEntity;
@ManyToOne(() => ProductEntity, (product) => product.spaceProductModels, {
nullable: false,
onDelete: 'CASCADE',
})
public product: ProductEntity;
@OneToMany(
() => SpaceProductItemModelEntity,
(item) => item.spaceProductModel,
{
cascade: true,
},
)
public items: SpaceProductItemModelEntity[];
}

View File

@ -0,0 +1,30 @@
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 './space-model.repository.module';
export * from './entities';
export * from './repositories';

View File

@ -0,0 +1 @@
export * from './space-model.repository';

View File

@ -0,0 +1,34 @@
import { DataSource, Repository } from 'typeorm';
import { Injectable } from '@nestjs/common';
import {
SpaceModelEntity,
SpaceProductItemModelEntity,
SpaceProductModelEntity,
SubspaceModelEntity,
} from '../entities';
@Injectable()
export class SpaceModelRepository extends Repository<SpaceModelEntity> {
constructor(private dataSource: DataSource) {
super(SpaceModelEntity, dataSource.createEntityManager());
}
}
@Injectable()
export class SubspaceModelRepository extends Repository<SubspaceModelEntity> {
constructor(private dataSource: DataSource) {
super(SubspaceModelEntity, dataSource.createEntityManager());
}
}
@Injectable()
export class SpaceProductModelRepository extends Repository<SpaceProductModelEntity> {
constructor(private dataSource: DataSource) {
super(SpaceProductModelEntity, dataSource.createEntityManager());
}
}
@Injectable()
export class SpaceProductItemModelRepository extends Repository<SpaceProductItemModelEntity> {
constructor(private dataSource: DataSource) {
super(SpaceProductItemModelEntity, dataSource.createEntityManager());
}
}

View File

@ -0,0 +1,23 @@
import { TypeOrmModule } from '@nestjs/typeorm';
import {
SpaceModelEntity,
SpaceProductItemModelEntity,
SpaceProductModelEntity,
SubspaceModelEntity,
} from './entities';
import { Module } from '@nestjs/common';
@Module({
providers: [],
exports: [],
controllers: [],
imports: [
TypeOrmModule.forFeature([
SpaceModelEntity,
SubspaceModelEntity,
SpaceProductModelEntity,
SpaceProductItemModelEntity,
]),
],
})
export class SpaceModelRepositoryModule {}

View File

@ -23,6 +23,7 @@ import { ScheduleModule } from './schedule/schedule.module';
import { SpaceModule } from './space/space.module';
import { ProductModule } from './product';
import { ProjectModule } from './project';
import { SpaceModelModule } from './space-model';
@Module({
imports: [
ConfigModule.forRoot({
@ -34,7 +35,7 @@ import { ProjectModule } from './project';
CommunityModule,
SpaceModule,
SpaceModelModule,
GroupModule,
DeviceModule,
DeviceMessagesSubscriptionModule,

View File

@ -0,0 +1 @@
export * from './space-model.controller';

View File

@ -0,0 +1,35 @@
import { ControllerRoute } from '@app/common/constants/controller-route';
import { Body, Controller, Param, Post, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { SpaceModelService } from '../services';
import { CreateSpaceModelDto } from '../dtos';
import { ProjectParam } from 'src/community/dtos';
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
@ApiTags('Space Model Module')
@Controller({
version: '1',
path: ControllerRoute.SPACE_MODEL.ROUTE,
})
export class SpaceModelController {
constructor(private readonly spaceModelService: SpaceModelService) {}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@ApiOperation({
summary: ControllerRoute.SPACE_MODEL.ACTIONS.CREATE_SPACE_MODEL_SUMMARY,
description:
ControllerRoute.SPACE_MODEL.ACTIONS.CREATE_SPACE_MODEL_DESCRIPTION,
})
@Post()
async createSpaceModel(
@Body() createSpaceModelDto: CreateSpaceModelDto,
@Param() projectParam: ProjectParam,
): Promise<BaseResponseDto> {
return await this.spaceModelService.createSpaceModel(
createSpaceModelDto,
projectParam,
);
}
}

View File

@ -0,0 +1,33 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString, IsArray, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { CreateSubspaceModelDto } from './create-subspace-model.dto';
import { CreateSpaceProductModelDto } from './create-space-product-model.dto';
export class CreateSpaceModelDto {
@ApiProperty({
description: 'Name of the space model',
example: 'Apartment Model',
})
@IsNotEmpty()
@IsString()
modelName: string;
@ApiProperty({
description: 'List of subspaces included in the model',
type: [CreateSubspaceModelDto],
})
@IsArray()
@ValidateNested({ each: true })
@Type(() => CreateSubspaceModelDto)
subspaceModels?: CreateSubspaceModelDto[];
@ApiProperty({
description: 'List of products included in the model',
type: [CreateSpaceProductModelDto],
})
@IsArray()
@ValidateNested({ each: true })
@Type(() => CreateSpaceProductModelDto)
spaceProductModels?: CreateSpaceProductModelDto[];
}

View File

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

View File

@ -0,0 +1,39 @@
import { ApiProperty } from '@nestjs/swagger';
import {
IsNotEmpty,
IsString,
IsArray,
ValidateNested,
IsInt,
ArrayNotEmpty,
} from 'class-validator';
import { Type } from 'class-transformer';
import { CreateSpaceProductItemModelDto } from './create-space-product-item-model.dto';
export class CreateSpaceProductModelDto {
@ApiProperty({
description: 'ID of the product associated with the model',
example: 'product-uuid',
})
@IsNotEmpty()
@IsString()
productId: string;
@ApiProperty({
description: 'Number of products in the model',
example: 3,
})
@IsNotEmpty()
@IsInt()
productCount: number;
@ApiProperty({
description: 'Specific names for each product item',
type: [CreateSpaceProductItemModelDto],
})
@IsArray()
@ArrayNotEmpty()
@ValidateNested({ each: true })
@Type(() => CreateSpaceProductItemModelDto)
items: CreateSpaceProductItemModelDto[];
}

View File

@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';
export class CreateSubspaceModelDto {
@ApiProperty({
description: 'Name of the subspace',
example: 'Living Room',
})
@IsNotEmpty()
@IsString()
subspaceName: string;
}

View File

@ -0,0 +1,5 @@
export * from './create-space-model.dto';
export * from './create-space-product-item-model.dto';
export * from './create-space-product-model.dto';
export * from './create-subspace-model.dto';
export * from './project-param.dto';

View File

@ -0,0 +1,11 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsUUID } from 'class-validator';
export class projectParam {
@ApiProperty({
description: 'UUID of the Project',
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
})
@IsUUID()
projectUuid: string;
}

1
src/space-model/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './space-model.module';

View File

@ -0,0 +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';

View File

@ -0,0 +1,112 @@
import { SpaceModelRepository } from '@app/common/modules/space-model';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
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 { SpaceProductModelService } from './space-product-model.service';
import { DataSource } from 'typeorm';
@Injectable()
export class SpaceModelService {
constructor(
private readonly dataSource: DataSource,
private readonly spaceModelRepository: SpaceModelRepository,
private readonly projectRepository: ProjectRepository,
private readonly subSpaceModelService: SubSpaceModelService,
private readonly spaceProductModelService: SpaceProductModelService,
) {}
async createSpaceModel(
createSpaceModelDto: CreateSpaceModelDto,
params: ProjectParam,
) {
const { modelName, subspaceModels, spaceProductModels } =
createSpaceModelDto;
const project = await this.validateProject(params.projectUuid);
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const isModelExist = await this.validateName(
modelName,
params.projectUuid,
);
if (isModelExist) {
throw new HttpException(
`Model name "${modelName}" already exists in this project ${project.name}.`,
HttpStatus.CONFLICT,
);
}
const spaceModel = this.spaceModelRepository.create({
modelName,
project,
});
const savedSpaceModel = await queryRunner.manager.save(spaceModel);
if (subspaceModels) {
await this.subSpaceModelService.createSubSpaceModels(
subspaceModels,
savedSpaceModel,
queryRunner,
);
}
if (spaceProductModels) {
await this.spaceProductModelService.createSpaceProductModels(
spaceProductModels,
savedSpaceModel,
queryRunner,
);
}
await queryRunner.commitTransaction();
return new SuccessResponseDto({
message: `Successfully created new space model with uuid ${savedSpaceModel.uuid}`,
data: savedSpaceModel,
statusCode: HttpStatus.CREATED,
});
} catch (error) {
await queryRunner.rollbackTransaction();
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(
error.message || `An unexpected error occurred`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
await queryRunner.release();
}
}
async validateProject(projectUuid: string) {
const project = await this.projectRepository.findOne({
where: {
uuid: projectUuid,
},
});
if (!project) {
throw new HttpException(
`Project with uuid ${projectUuid} not found`,
HttpStatus.NOT_FOUND,
);
}
return project;
}
async validateName(modelName: string, projectUuid: string): Promise<boolean> {
const isModelExist = await this.spaceModelRepository.exists({
where: { modelName, project: { uuid: projectUuid } },
});
return isModelExist;
}
}

View File

@ -0,0 +1,83 @@
import {
SpaceModelEntity,
SpaceProductItemModelRepository,
SpaceProductModelEntity,
} from '@app/common/modules/space-model';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateSpaceProductItemModelDto } from '../dtos';
import { QueryRunner } from 'typeorm';
@Injectable()
export class SpaceProductItemModelService {
constructor(
private readonly spaceProductItemRepository: SpaceProductItemModelRepository,
) {}
async createProdutItemModel(
itemModelDtos: CreateSpaceProductItemModelDto[],
spaceProductModel: SpaceProductModelEntity,
spaceModel: SpaceModelEntity,
queryRunner: QueryRunner,
) {
await this.validateTags(itemModelDtos, spaceModel, queryRunner);
try {
const productItems = itemModelDtos.map((dto) =>
queryRunner.manager.create(this.spaceProductItemRepository.target, {
tag: dto.tag,
spaceProductModel,
spaceModel,
}),
);
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,
);
}
}
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

@ -0,0 +1,84 @@
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 {
constructor(
private readonly spaceProductModelRepository: SpaceProductModelRepository,
private readonly productRepository: ProductRepository,
private readonly spaceProductItemModelService: SpaceProductItemModelService,
) {}
async createSpaceProductModels(
spaceProductModelDtos: CreateSpaceProductModelDto[],
spaceModel: SpaceModelEntity,
queryRunner: QueryRunner,
) {
try {
const productModels = await Promise.all(
spaceProductModelDtos.map(async (dto) => {
this.validateProductCount(dto);
const product = await this.getProduct(dto.productId);
return queryRunner.manager.create(
this.spaceProductModelRepository.target,
{
product,
productCount: dto.productCount,
spaceModel,
},
);
}),
);
const savedProductModels = await queryRunner.manager.save(productModels);
await Promise.all(
spaceProductModelDtos.map((dto, index) =>
this.spaceProductItemModelService.createProdutItemModel(
dto.items,
savedProductModels[index],
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,
);
}
}
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,74 @@
import {
SpaceModelEntity,
SubspaceModelRepository,
} from '@app/common/modules/space-model';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateSubspaceModelDto } from '../dtos';
import { QueryRunner } from 'typeorm';
@Injectable()
export class SubSpaceModelService {
constructor(
private readonly subspaceModelRepository: SubspaceModelRepository,
) {}
async createSubSpaceModels(
subSpaceModelDtos: CreateSubspaceModelDto[],
spaceModel: SpaceModelEntity,
queryRunner: QueryRunner,
) {
this.validateInputDtos(subSpaceModelDtos);
try {
const subspaces = subSpaceModelDtos.map((subspaceDto) =>
queryRunner.manager.create(this.subspaceModelRepository.target, {
subspaceName: subspaceDto.subspaceName,
spaceModel: spaceModel,
}),
);
await queryRunner.manager.save(subspaces);
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(
error.message || `An unexpected error occurred`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
private validateInputDtos(subSpaceModelDtos: CreateSubspaceModelDto[]) {
if (subSpaceModelDtos.length === 0) {
throw new HttpException(
'Subspace models cannot be empty.',
HttpStatus.BAD_REQUEST,
);
}
const incomingNames = subSpaceModelDtos.map((dto) => dto.subspaceName);
this.validateName(incomingNames);
}
private validateName(names: string[]) {
const seenNames = new Set<string>();
const duplicateNames = new Set<string>();
for (const name of names) {
if (seenNames.has(name)) {
duplicateNames.add(name);
} else {
seenNames.add(name);
}
}
if (duplicateNames.size > 0) {
throw new HttpException(
`Duplicate subspace names found in request: ${[...duplicateNames].join(', ')}`,
HttpStatus.CONFLICT,
);
}
}
}

View File

@ -0,0 +1,37 @@
import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module';
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { SpaceModelController } from './controllers';
import {
SpaceModelService,
SpaceProductItemModelService,
SpaceProductModelService,
SubSpaceModelService,
} from './services';
import {
SpaceModelRepository,
SpaceProductItemModelRepository,
SpaceProductModelRepository,
SubspaceModelRepository,
} from '@app/common/modules/space-model';
import { ProjectRepository } from '@app/common/modules/project/repositiories';
import { ProductRepository } from '@app/common/modules/product/repositories';
@Module({
imports: [ConfigModule, SpaceRepositoryModule],
controllers: [SpaceModelController],
providers: [
SpaceModelService,
SpaceModelRepository,
ProjectRepository,
SubSpaceModelService,
SpaceProductModelService,
SubspaceModelRepository,
SpaceProductModelRepository,
ProductRepository,
SpaceProductItemModelService,
SpaceProductItemModelRepository,
],
exports: [],
})
export class SpaceModelModule {}

View File

@ -5,7 +5,12 @@ import {
HttpStatus,
Injectable,
} from '@nestjs/common';
import { AddSpaceDto, CommunitySpaceParam, GetSpaceParam, UpdateSpaceDto } from '../dtos';
import {
AddSpaceDto,
CommunitySpaceParam,
GetSpaceParam,
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';