diff --git a/src/project/project.module.ts b/src/project/project.module.ts index 9d82ba9..3cd906e 100644 --- a/src/project/project.module.ts +++ b/src/project/project.module.ts @@ -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], diff --git a/src/space/dtos/add.space.dto.ts b/src/space/dtos/add.space.dto.ts index d66fa87..402d37a 100644 --- a/src/space/dtos/add.space.dto.ts +++ b/src/space/dtos/add.space.dto.ts @@ -11,6 +11,7 @@ import { IsUUID, ValidateNested, } from 'class-validator'; +import { AddSubspaceDto } from './subspace'; export class CreateSpaceProductItemDto { @ApiProperty({ @@ -22,16 +23,6 @@ export class CreateSpaceProductItemDto { tag: string; } -export class CreateSubspaceDto { - @ApiProperty({ - description: 'Name of the subspace', - example: 'Living Room', - }) - @IsNotEmpty() - @IsString() - subspaceName: string; -} - export class ProductAssignmentDto { @ApiProperty({ description: 'UUID of the product to be assigned', @@ -127,13 +118,13 @@ export class AddSpaceDto { @ApiProperty({ description: 'List of subspaces included in the model', - type: [CreateSubspaceDto], + type: [AddSubspaceDto], }) @IsOptional() @IsArray() @ValidateNested({ each: true }) - @Type(() => CreateSubspaceDto) - subspaces?: CreateSubspaceDto[]; + @Type(() => AddSubspaceDto) + subspaces?: AddSubspaceDto[]; } export class AddUserSpaceDto { diff --git a/src/space/services/space-validation.service.ts b/src/space/services/space-validation.service.ts new file mode 100644 index 0000000..56e6532 --- /dev/null +++ b/src/space/services/space-validation.service.ts @@ -0,0 +1,74 @@ +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 { + 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 { + await this.validateCommunityAndProject(communityUuid, projectUuid); + const space = await this.validateSpace(spaceUuid); + return space; + } + + async validateSpace(spaceUuid: string): Promise { + 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 { + const spaceModel = await this.spaceModelRepository.findOne({ + where: { uuid: spaceModelUuid }, + relations: ['subspaceModels'], + }); + + if (!spaceModel) { + throw new HttpException( + `Space model with UUID ${spaceModelUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + return spaceModel; + } +} diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 6fc35eb..135f2e5 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -14,22 +14,24 @@ import { } 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( @@ -40,21 +42,35 @@ export class SpaceService { addSpaceDto; const { communityUuid, projectUuid } = params; - await this.validateProject(projectUuid); + const queryRunner = this.dataSource.createQueryRunner(); + + await queryRunner.connect(); + await queryRunner.startTransaction(); + + const community = await this.validationService.validateCommunityAndProject( + communityUuid, + projectUuid, + ); this.validateSpaceCreation(spaceModelUuid, products, subspaces); - const community = await this.validateCommunity(communityUuid); + 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({ ...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( @@ -64,12 +80,27 @@ export class SpaceService { ); } + if (subspaces) { + await this.subSpaceService.createSubspacesFromNames( + subspaces, + newSpace, + queryRunner, + ); + } else { + await this.subSpaceService.createSubSpaceFromModel( + spaceModel, + newSpace, + queryRunner, + ); + } + if (products && products.length > 0) { await this.spaceProductService.assignProductsToSpace( newSpace, products, ); } + await queryRunner.commitTransaction(); return new SuccessResponseDto({ statusCode: HttpStatus.CREATED, @@ -77,7 +108,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(); } } @@ -85,8 +123,10 @@ export class SpaceService { params: CommunitySpaceParam, ): Promise { 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({ @@ -119,11 +159,12 @@ export class SpaceService { async findOne(params: GetSpaceParam): Promise { const { communityUuid, spaceUuid, projectUuid } = params; try { - const space = await this.validateCommunityAndSpace( - communityUuid, - spaceUuid, - projectUuid, - ); + const space = + await this.validationService.validateSpaceWithinCommunityAndProject( + communityUuid, + projectUuid, + spaceUuid, + ); return new SuccessResponseDto({ message: `Space with ID ${spaceUuid} successfully fetched`, @@ -144,12 +185,13 @@ export class SpaceService { async delete(params: GetSpaceParam): Promise { try { const { communityUuid, spaceUuid, projectUuid } = params; - // First, check if the community exists - const space = await this.validateCommunityAndSpace( - communityUuid, - spaceUuid, - projectUuid, - ); + + const space = + await this.validationService.validateSpaceWithinCommunityAndProject( + communityUuid, + projectUuid, + spaceUuid, + ); // Delete the space await this.spaceRepository.remove(space); @@ -175,15 +217,18 @@ export class SpaceService { ): Promise { const { communityUuid, spaceUuid, projectUuid } = params; try { - const space = await this.validateCommunityAndSpace( - communityUuid, - spaceUuid, - projectUuid, - ); + const space = + await this.validationService.validateSpaceWithinCommunityAndProject( + communityUuid, + 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 }); @@ -218,7 +263,11 @@ export class SpaceService { params: GetSpaceParam, ): Promise { 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 @@ -248,11 +297,12 @@ export class SpaceService { try { const invitationCode = generateRandomString(6); - const space = await this.validateCommunityAndSpace( - communityUuid, - spaceUuid, - projectUuid, - ); + const space = + await this.validationService.validateSpaceWithinCommunityAndProject( + communityUuid, + projectUuid, + spaceUuid, + ); space.invitationCode = invitationCode; await this.spaceRepository.save(space); @@ -298,51 +348,6 @@ 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, - ) { - 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 }, - }); - if (!space) this.throwNotFound('Space', spaceUuid); - return space; - } - - private async validateProject(uuid: string) { - const project = await this.projectRepository.findOne({ - where: { uuid }, - }); - - if (!project) this.throwNotFound('Project', uuid); - } - private throwNotFound(entity: string, uuid: string) { throw new HttpException( `${entity} with ID ${uuid} not found`, diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index c343512..dae280b 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -9,25 +9,83 @@ 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 } from '@app/common/modules/space-model'; +import { ValidationService } from '../space-validation.service'; @Injectable() export class SubSpaceService { constructor( private readonly subspaceRepository: SubspaceRepository, - private readonly spaceService: SpaceService, + private readonly validationService: ValidationService, ) {} + async createSubspaces( + subspaceData: Array<{ subspaceName: string; space: SpaceEntity }>, + queryRunner: QueryRunner, + ): Promise { + 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 { + const subSpaces = spaceModel.subspaceModels; + + if (!subSpaces || subSpaces.length === 0) { + return; + } + + const subspaceData = subSpaces.map((subSpaceModel) => ({ + subspaceName: subSpaceModel.subspaceName, + space, + subSpaceModel, + })); + + await this.createSubspaces(subspaceData, queryRunner); + } + + async createSubspacesFromNames( + addSubspaceDtos: AddSubspaceDto[], + space: SpaceEntity, + queryRunner: QueryRunner, + ): Promise { + const subspaceData = addSubspaceDtos.map((dto) => ({ + subspaceName: dto.subspaceName, + space, + })); + + return await this.createSubspaces(subspaceData, queryRunner); + } + async createSubspace( addSubspaceDto: AddSubspaceDto, params: GetSpaceParam, ): Promise { const { communityUuid, spaceUuid, projectUuid } = params; - const space = await this.spaceService.validateCommunityAndSpace( - communityUuid, - spaceUuid, - projectUuid, - ); + const space = + await this.validationService.validateSpaceWithinCommunityAndProject( + communityUuid, + projectUuid, + spaceUuid, + ); try { const newSubspace = this.subspaceRepository.create({ @@ -52,10 +110,10 @@ export class SubSpaceService { pageable: Partial, ): Promise { const { communityUuid, spaceUuid, projectUuid } = params; - await this.spaceService.validateCommunityAndSpace( + await this.validationService.validateSpaceWithinCommunityAndProject( communityUuid, - spaceUuid, projectUuid, + spaceUuid, ); try { @@ -76,10 +134,10 @@ export class SubSpaceService { async findOne(params: GetSubSpaceParam): Promise { 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 +174,11 @@ export class SubSpaceService { updateSubSpaceDto: AddSubspaceDto, ): Promise { 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 +213,10 @@ export class SubSpaceService { async delete(params: GetSubSpaceParam): Promise { const { spaceUuid, communityUuid, subSpaceUuid, projectUuid } = params; - await this.spaceService.validateCommunityAndSpace( + await this.validationService.validateSpaceWithinCommunityAndProject( communityUuid, - spaceUuid, projectUuid, + spaceUuid, ); const subspace = await this.subspaceRepository.findOne({ diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 002c2df..3a52f7b 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -43,9 +43,12 @@ 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/space-validation.service'; @Module({ - imports: [ConfigModule, SpaceRepositoryModule], + imports: [ConfigModule, SpaceRepositoryModule, CommunityModule], controllers: [ SpaceController, SpaceUserController, @@ -55,6 +58,7 @@ import { ProjectRepository } from '@app/common/modules/project/repositiories'; SpaceSceneController, ], providers: [ + ValidationService, SpaceService, TuyaService, ProductRepository, @@ -81,6 +85,8 @@ import { ProjectRepository } from '@app/common/modules/project/repositiories'; SpaceProductService, SpaceProductRepository, ProjectRepository, + SpaceModelRepository, + SubspaceRepository, ], exports: [SpaceService], })