reduced code duplication

This commit is contained in:
hannathkadher
2024-12-12 09:21:56 +04:00
parent 8af29cddfd
commit ba002ae474
6 changed files with 244 additions and 110 deletions

View File

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

View File

@ -11,6 +11,7 @@ import {
IsUUID, IsUUID,
ValidateNested, ValidateNested,
} from 'class-validator'; } from 'class-validator';
import { AddSubspaceDto } from './subspace';
export class CreateSpaceProductItemDto { export class CreateSpaceProductItemDto {
@ApiProperty({ @ApiProperty({
@ -22,16 +23,6 @@ export class CreateSpaceProductItemDto {
tag: string; tag: string;
} }
export class CreateSubspaceDto {
@ApiProperty({
description: 'Name of the subspace',
example: 'Living Room',
})
@IsNotEmpty()
@IsString()
subspaceName: string;
}
export class ProductAssignmentDto { export class ProductAssignmentDto {
@ApiProperty({ @ApiProperty({
description: 'UUID of the product to be assigned', description: 'UUID of the product to be assigned',
@ -127,13 +118,13 @@ export class AddSpaceDto {
@ApiProperty({ @ApiProperty({
description: 'List of subspaces included in the model', description: 'List of subspaces included in the model',
type: [CreateSubspaceDto], type: [AddSubspaceDto],
}) })
@IsOptional() @IsOptional()
@IsArray() @IsArray()
@ValidateNested({ each: true }) @ValidateNested({ each: true })
@Type(() => CreateSubspaceDto) @Type(() => AddSubspaceDto)
subspaces?: CreateSubspaceDto[]; subspaces?: AddSubspaceDto[];
} }
export class AddUserSpaceDto { export class AddUserSpaceDto {

View File

@ -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<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'],
});
if (!spaceModel) {
throw new HttpException(
`Space model with UUID ${spaceModelUuid} not found`,
HttpStatus.NOT_FOUND,
);
}
return spaceModel;
}
}

View File

@ -14,22 +14,24 @@ import {
} from '../dtos'; } from '../dtos';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { CommunityRepository } from '@app/common/modules/community/repositories';
import { SpaceEntity } from '@app/common/modules/space/entities'; import { SpaceEntity } from '@app/common/modules/space/entities';
import { generateRandomString } from '@app/common/helper/randomString'; import { generateRandomString } from '@app/common/helper/randomString';
import { SpaceLinkService } from './space-link'; import { SpaceLinkService } from './space-link';
import { SpaceProductService } from './space-products'; import { SpaceProductService } from './space-products';
import { ProjectRepository } from '@app/common/modules/project/repositiories';
import { CreateSubspaceModelDto } from 'src/space-model/dtos'; import { CreateSubspaceModelDto } from 'src/space-model/dtos';
import { SubSpaceService } from './subspace';
import { DataSource } from 'typeorm';
import { ValidationService } from './space-validation.service';
@Injectable() @Injectable()
export class SpaceService { export class SpaceService {
constructor( constructor(
private readonly dataSource: DataSource,
private readonly spaceRepository: SpaceRepository, private readonly spaceRepository: SpaceRepository,
private readonly communityRepository: CommunityRepository,
private readonly spaceLinkService: SpaceLinkService, private readonly spaceLinkService: SpaceLinkService,
private readonly spaceProductService: SpaceProductService, private readonly spaceProductService: SpaceProductService,
private readonly projectRepository: ProjectRepository, private readonly subSpaceService: SubSpaceService,
private readonly validationService: ValidationService,
) {} ) {}
async createSpace( async createSpace(
@ -40,21 +42,35 @@ export class SpaceService {
addSpaceDto; addSpaceDto;
const { communityUuid, projectUuid } = params; 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); 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 { try {
const newSpace = this.spaceRepository.create({ const newSpace = this.spaceRepository.create({
...addSpaceDto, ...addSpaceDto,
community, spaceModel,
parent: parentUuid ? parent : null, parent: parentUuid ? parent : null,
community,
}); });
await this.spaceRepository.save(newSpace); await queryRunner.manager.save(newSpace);
if (direction && parent) { if (direction && parent) {
await this.spaceLinkService.saveSpaceLink( await this.spaceLinkService.saveSpaceLink(
@ -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) { if (products && products.length > 0) {
await this.spaceProductService.assignProductsToSpace( await this.spaceProductService.assignProductsToSpace(
newSpace, newSpace,
products, products,
); );
} }
await queryRunner.commitTransaction();
return new SuccessResponseDto({ return new SuccessResponseDto({
statusCode: HttpStatus.CREATED, statusCode: HttpStatus.CREATED,
@ -77,7 +108,14 @@ export class SpaceService {
message: 'Space created successfully', message: 'Space created successfully',
}); });
} catch (error) { } catch (error) {
await queryRunner.rollbackTransaction();
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
} finally {
await queryRunner.release();
} }
} }
@ -85,8 +123,10 @@ export class SpaceService {
params: CommunitySpaceParam, params: CommunitySpaceParam,
): Promise<BaseResponseDto> { ): Promise<BaseResponseDto> {
const { communityUuid, projectUuid } = params; const { communityUuid, projectUuid } = params;
await this.validateCommunity(communityUuid); await this.validationService.validateCommunityAndProject(
await this.validateProject(projectUuid); communityUuid,
projectUuid,
);
try { try {
// Get all spaces related to the community, including the parent-child relations // Get all spaces related to the community, including the parent-child relations
const spaces = await this.spaceRepository.find({ const spaces = await this.spaceRepository.find({
@ -119,11 +159,12 @@ export class SpaceService {
async findOne(params: GetSpaceParam): Promise<BaseResponseDto> { async findOne(params: GetSpaceParam): Promise<BaseResponseDto> {
const { communityUuid, spaceUuid, projectUuid } = params; const { communityUuid, spaceUuid, projectUuid } = params;
try { try {
const space = await this.validateCommunityAndSpace( const space =
communityUuid, await this.validationService.validateSpaceWithinCommunityAndProject(
spaceUuid, communityUuid,
projectUuid, projectUuid,
); spaceUuid,
);
return new SuccessResponseDto({ return new SuccessResponseDto({
message: `Space with ID ${spaceUuid} successfully fetched`, message: `Space with ID ${spaceUuid} successfully fetched`,
@ -144,12 +185,13 @@ export class SpaceService {
async delete(params: GetSpaceParam): Promise<BaseResponseDto> { async delete(params: GetSpaceParam): Promise<BaseResponseDto> {
try { try {
const { communityUuid, spaceUuid, projectUuid } = params; const { communityUuid, spaceUuid, projectUuid } = params;
// First, check if the community exists
const space = await this.validateCommunityAndSpace( const space =
communityUuid, await this.validationService.validateSpaceWithinCommunityAndProject(
spaceUuid, communityUuid,
projectUuid, projectUuid,
); spaceUuid,
);
// Delete the space // Delete the space
await this.spaceRepository.remove(space); await this.spaceRepository.remove(space);
@ -175,15 +217,18 @@ export class SpaceService {
): Promise<BaseResponseDto> { ): Promise<BaseResponseDto> {
const { communityUuid, spaceUuid, projectUuid } = params; const { communityUuid, spaceUuid, projectUuid } = params;
try { try {
const space = await this.validateCommunityAndSpace( const space =
communityUuid, await this.validationService.validateSpaceWithinCommunityAndProject(
spaceUuid, communityUuid,
projectUuid, projectUuid,
); spaceUuid,
);
// If a parentId is provided, check if the parent exists // If a parentId is provided, check if the parent exists
const { parentUuid, products } = updateSpaceDto; const { parentUuid, products } = updateSpaceDto;
const parent = parentUuid ? await this.validateSpace(parentUuid) : null; const parent = parentUuid
? await this.validationService.validateSpace(parentUuid)
: null;
// Update other space properties from updateSpaceDto // Update other space properties from updateSpaceDto
Object.assign(space, updateSpaceDto, { parent }); Object.assign(space, updateSpaceDto, { parent });
@ -218,7 +263,11 @@ export class SpaceService {
params: GetSpaceParam, params: GetSpaceParam,
): Promise<BaseResponseDto> { ): Promise<BaseResponseDto> {
const { spaceUuid, communityUuid, projectUuid } = params; const { spaceUuid, communityUuid, projectUuid } = params;
await this.validateCommunityAndSpace(communityUuid, spaceUuid, projectUuid); await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid,
projectUuid,
spaceUuid,
);
try { try {
// Get all spaces that are children of the provided space, including the parent-child relations // Get all spaces that are children of the provided space, including the parent-child relations
@ -248,11 +297,12 @@ export class SpaceService {
try { try {
const invitationCode = generateRandomString(6); const invitationCode = generateRandomString(6);
const space = await this.validateCommunityAndSpace( const space =
communityUuid, await this.validationService.validateSpaceWithinCommunityAndProject(
spaceUuid, communityUuid,
projectUuid, projectUuid,
); spaceUuid,
);
space.invitationCode = invitationCode; space.invitationCode = invitationCode;
await this.spaceRepository.save(space); await this.spaceRepository.save(space);
@ -298,51 +348,6 @@ export class SpaceService {
return rootSpaces; 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) { private throwNotFound(entity: string, uuid: string) {
throw new HttpException( throw new HttpException(
`${entity} with ID ${uuid} not found`, `${entity} with ID ${uuid} not found`,

View File

@ -9,25 +9,83 @@ import {
} from '@app/common/models/typeOrmCustom.model'; } from '@app/common/models/typeOrmCustom.model';
import { PageResponse } from '@app/common/dto/pagination.response.dto'; import { PageResponse } from '@app/common/dto/pagination.response.dto';
import { SubspaceDto } from '@app/common/modules/space/dtos'; import { SubspaceDto } from '@app/common/modules/space/dtos';
import { SpaceService } from '../space.service'; import { QueryRunner } from 'typeorm';
import {
SpaceEntity,
SubspaceEntity,
} from '@app/common/modules/space/entities';
import { SpaceModelEntity } from '@app/common/modules/space-model';
import { ValidationService } from '../space-validation.service';
@Injectable() @Injectable()
export class SubSpaceService { export class SubSpaceService {
constructor( constructor(
private readonly subspaceRepository: SubspaceRepository, private readonly subspaceRepository: SubspaceRepository,
private readonly spaceService: SpaceService, private readonly validationService: ValidationService,
) {} ) {}
async createSubspaces(
subspaceData: Array<{ subspaceName: string; space: SpaceEntity }>,
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 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<SubspaceEntity[]> {
const subspaceData = addSubspaceDtos.map((dto) => ({
subspaceName: dto.subspaceName,
space,
}));
return await this.createSubspaces(subspaceData, queryRunner);
}
async createSubspace( async createSubspace(
addSubspaceDto: AddSubspaceDto, addSubspaceDto: AddSubspaceDto,
params: GetSpaceParam, params: GetSpaceParam,
): Promise<BaseResponseDto> { ): Promise<BaseResponseDto> {
const { communityUuid, spaceUuid, projectUuid } = params; const { communityUuid, spaceUuid, projectUuid } = params;
const space = await this.spaceService.validateCommunityAndSpace( const space =
communityUuid, await this.validationService.validateSpaceWithinCommunityAndProject(
spaceUuid, communityUuid,
projectUuid, projectUuid,
); spaceUuid,
);
try { try {
const newSubspace = this.subspaceRepository.create({ const newSubspace = this.subspaceRepository.create({
@ -52,10 +110,10 @@ export class SubSpaceService {
pageable: Partial<TypeORMCustomModelFindAllQuery>, pageable: Partial<TypeORMCustomModelFindAllQuery>,
): Promise<BaseResponseDto> { ): Promise<BaseResponseDto> {
const { communityUuid, spaceUuid, projectUuid } = params; const { communityUuid, spaceUuid, projectUuid } = params;
await this.spaceService.validateCommunityAndSpace( await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid, communityUuid,
spaceUuid,
projectUuid, projectUuid,
spaceUuid,
); );
try { try {
@ -76,10 +134,10 @@ export class SubSpaceService {
async findOne(params: GetSubSpaceParam): Promise<BaseResponseDto> { async findOne(params: GetSubSpaceParam): Promise<BaseResponseDto> {
const { communityUuid, subSpaceUuid, spaceUuid, projectUuid } = params; const { communityUuid, subSpaceUuid, spaceUuid, projectUuid } = params;
await this.spaceService.validateCommunityAndSpace( await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid, communityUuid,
spaceUuid,
projectUuid, projectUuid,
spaceUuid,
); );
try { try {
const subSpace = await this.subspaceRepository.findOne({ const subSpace = await this.subspaceRepository.findOne({
@ -116,12 +174,11 @@ export class SubSpaceService {
updateSubSpaceDto: AddSubspaceDto, updateSubSpaceDto: AddSubspaceDto,
): Promise<BaseResponseDto> { ): Promise<BaseResponseDto> {
const { spaceUuid, communityUuid, subSpaceUuid, projectUuid } = params; const { spaceUuid, communityUuid, subSpaceUuid, projectUuid } = params;
await this.spaceService.validateCommunityAndSpace( await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid, communityUuid,
spaceUuid,
projectUuid, projectUuid,
spaceUuid,
); );
const subSpace = await this.subspaceRepository.findOne({ const subSpace = await this.subspaceRepository.findOne({
where: { uuid: subSpaceUuid }, where: { uuid: subSpaceUuid },
}); });
@ -156,10 +213,10 @@ export class SubSpaceService {
async delete(params: GetSubSpaceParam): Promise<BaseResponseDto> { async delete(params: GetSubSpaceParam): Promise<BaseResponseDto> {
const { spaceUuid, communityUuid, subSpaceUuid, projectUuid } = params; const { spaceUuid, communityUuid, subSpaceUuid, projectUuid } = params;
await this.spaceService.validateCommunityAndSpace( await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid, communityUuid,
spaceUuid,
projectUuid, projectUuid,
spaceUuid,
); );
const subspace = await this.subspaceRepository.findOne({ const subspace = await this.subspaceRepository.findOne({

View File

@ -43,9 +43,12 @@ import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status
import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories'; import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories';
import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories';
import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { ProjectRepository } from '@app/common/modules/project/repositiories';
import { SpaceModelRepository } from '@app/common/modules/space-model';
import { CommunityModule } from 'src/community/community.module';
import { ValidationService } from './services/space-validation.service';
@Module({ @Module({
imports: [ConfigModule, SpaceRepositoryModule], imports: [ConfigModule, SpaceRepositoryModule, CommunityModule],
controllers: [ controllers: [
SpaceController, SpaceController,
SpaceUserController, SpaceUserController,
@ -55,6 +58,7 @@ import { ProjectRepository } from '@app/common/modules/project/repositiories';
SpaceSceneController, SpaceSceneController,
], ],
providers: [ providers: [
ValidationService,
SpaceService, SpaceService,
TuyaService, TuyaService,
ProductRepository, ProductRepository,
@ -81,6 +85,8 @@ import { ProjectRepository } from '@app/common/modules/project/repositiories';
SpaceProductService, SpaceProductService,
SpaceProductRepository, SpaceProductRepository,
ProjectRepository, ProjectRepository,
SpaceModelRepository,
SubspaceRepository,
], ],
exports: [SpaceService], exports: [SpaceService],
}) })