From 93e8aabc4d15263ed6171ab266e390f7e6a75775 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 31 Mar 2024 13:35:49 +0300 Subject: [PATCH 01/75] Add space DTO, entity, repository, and module --- libs/common/src/modules/space/dtos/index.ts | 1 + .../src/modules/space/dtos/space.dto.ts | 19 ++++++++++ .../src/modules/space/entities/index.ts | 1 + .../modules/space/entities/space.entity.ts | 36 +++++++++++++++++++ .../src/modules/space/repositories/index.ts | 1 + .../space/repositories/space.repository.ts | 10 ++++++ .../modules/space/space.repository.module.ts | 11 ++++++ 7 files changed, 79 insertions(+) create mode 100644 libs/common/src/modules/space/dtos/index.ts create mode 100644 libs/common/src/modules/space/dtos/space.dto.ts create mode 100644 libs/common/src/modules/space/entities/index.ts create mode 100644 libs/common/src/modules/space/entities/space.entity.ts create mode 100644 libs/common/src/modules/space/repositories/index.ts create mode 100644 libs/common/src/modules/space/repositories/space.repository.ts create mode 100644 libs/common/src/modules/space/space.repository.module.ts diff --git a/libs/common/src/modules/space/dtos/index.ts b/libs/common/src/modules/space/dtos/index.ts new file mode 100644 index 0000000..9144208 --- /dev/null +++ b/libs/common/src/modules/space/dtos/index.ts @@ -0,0 +1 @@ +export * from './space.dto'; diff --git a/libs/common/src/modules/space/dtos/space.dto.ts b/libs/common/src/modules/space/dtos/space.dto.ts new file mode 100644 index 0000000..cc5ad83 --- /dev/null +++ b/libs/common/src/modules/space/dtos/space.dto.ts @@ -0,0 +1,19 @@ +import { IsNotEmpty, IsString } from 'class-validator'; + +export class SpaceDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public parentUuid: string; + + @IsString() + @IsNotEmpty() + public spaceName: string; + + @IsString() + @IsNotEmpty() + public spaceTypeUuid: string; +} diff --git a/libs/common/src/modules/space/entities/index.ts b/libs/common/src/modules/space/entities/index.ts new file mode 100644 index 0000000..bce8032 --- /dev/null +++ b/libs/common/src/modules/space/entities/index.ts @@ -0,0 +1 @@ +export * from './space.entity'; diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts new file mode 100644 index 0000000..f546b62 --- /dev/null +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -0,0 +1,36 @@ +import { Column, Entity, ManyToOne, OneToMany } from 'typeorm'; +import { SpaceDto } from '../dtos'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { SpaceTypeEntity } from '../../space-type/entities'; + +@Entity({ name: 'space' }) +export class SpaceEntity extends AbstractEntity { + @Column({ + type: 'uuid', + default: () => 'gen_random_uuid()', // Use gen_random_uuid() for default value + nullable: false, + }) + public uuid: string; + + @Column({ + nullable: false, + }) + parentUuid: string; + + @Column({ + nullable: false, + }) + public spaceName: string; + @ManyToOne(() => SpaceEntity, (space) => space.children, { nullable: true }) + parent: SpaceEntity; + + @OneToMany(() => SpaceEntity, (space) => space.parent) + children: SpaceEntity[]; + @ManyToOne(() => SpaceTypeEntity, (spaceType) => spaceType.spaces) + spaceType: SpaceTypeEntity; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/space/repositories/index.ts b/libs/common/src/modules/space/repositories/index.ts new file mode 100644 index 0000000..1e390d3 --- /dev/null +++ b/libs/common/src/modules/space/repositories/index.ts @@ -0,0 +1 @@ +export * from './space.repository'; diff --git a/libs/common/src/modules/space/repositories/space.repository.ts b/libs/common/src/modules/space/repositories/space.repository.ts new file mode 100644 index 0000000..c939761 --- /dev/null +++ b/libs/common/src/modules/space/repositories/space.repository.ts @@ -0,0 +1,10 @@ +import { DataSource, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common'; +import { SpaceEntity } from '../entities/space.entity'; + +@Injectable() +export class SpaceRepository extends Repository { + constructor(private dataSource: DataSource) { + super(SpaceEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/modules/space/space.repository.module.ts b/libs/common/src/modules/space/space.repository.module.ts new file mode 100644 index 0000000..58a8ad0 --- /dev/null +++ b/libs/common/src/modules/space/space.repository.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { SpaceEntity } from './entities/space.entity'; + +@Module({ + providers: [], + exports: [], + controllers: [], + imports: [TypeOrmModule.forFeature([SpaceEntity])], +}) +export class SpaceRepositoryModule {} From 861aa788ae95163edbc6a2c7360bdb6a1229c103 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 31 Mar 2024 13:36:19 +0300 Subject: [PATCH 02/75] Add space type DTO, entity, repository, and module --- .../src/modules/space-type/dtos/index.ts | 1 + .../modules/space-type/dtos/space.type.dto.ts | 11 ++++++++ .../src/modules/space-type/entities/index.ts | 1 + .../space-type/entities/space.type.entity.ts | 26 +++++++++++++++++++ .../modules/space-type/repositories/index.ts | 1 + .../repositories/space.type.repository.ts | 10 +++++++ .../space.type.repository.module.ts | 11 ++++++++ 7 files changed, 61 insertions(+) create mode 100644 libs/common/src/modules/space-type/dtos/index.ts create mode 100644 libs/common/src/modules/space-type/dtos/space.type.dto.ts create mode 100644 libs/common/src/modules/space-type/entities/index.ts create mode 100644 libs/common/src/modules/space-type/entities/space.type.entity.ts create mode 100644 libs/common/src/modules/space-type/repositories/index.ts create mode 100644 libs/common/src/modules/space-type/repositories/space.type.repository.ts create mode 100644 libs/common/src/modules/space-type/space.type.repository.module.ts diff --git a/libs/common/src/modules/space-type/dtos/index.ts b/libs/common/src/modules/space-type/dtos/index.ts new file mode 100644 index 0000000..e9824e7 --- /dev/null +++ b/libs/common/src/modules/space-type/dtos/index.ts @@ -0,0 +1 @@ +export * from './space.type.dto'; diff --git a/libs/common/src/modules/space-type/dtos/space.type.dto.ts b/libs/common/src/modules/space-type/dtos/space.type.dto.ts new file mode 100644 index 0000000..2f3d807 --- /dev/null +++ b/libs/common/src/modules/space-type/dtos/space.type.dto.ts @@ -0,0 +1,11 @@ +import { IsNotEmpty, IsString } from 'class-validator'; + +export class SpaceTypeDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public type: string; +} diff --git a/libs/common/src/modules/space-type/entities/index.ts b/libs/common/src/modules/space-type/entities/index.ts new file mode 100644 index 0000000..71944ff --- /dev/null +++ b/libs/common/src/modules/space-type/entities/index.ts @@ -0,0 +1 @@ +export * from './space.type.entity'; diff --git a/libs/common/src/modules/space-type/entities/space.type.entity.ts b/libs/common/src/modules/space-type/entities/space.type.entity.ts new file mode 100644 index 0000000..f66ee86 --- /dev/null +++ b/libs/common/src/modules/space-type/entities/space.type.entity.ts @@ -0,0 +1,26 @@ +import { Column, Entity, OneToMany } from 'typeorm'; +import { SpaceTypeDto } from '../dtos'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { SpaceEntity } from '../../space/entities'; + +@Entity({ name: 'space-type' }) +export class SpaceTypeEntity extends AbstractEntity { + @Column({ + type: 'uuid', + default: () => 'gen_random_uuid()', // Use gen_random_uuid() for default value + nullable: false, + }) + public uuid: string; + + @Column({ + nullable: false, + }) + type: string; + + @OneToMany(() => SpaceEntity, (space) => space.spaceType) + spaces: SpaceEntity[]; + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/space-type/repositories/index.ts b/libs/common/src/modules/space-type/repositories/index.ts new file mode 100644 index 0000000..26f69ae --- /dev/null +++ b/libs/common/src/modules/space-type/repositories/index.ts @@ -0,0 +1 @@ +export * from './space.type.repository'; diff --git a/libs/common/src/modules/space-type/repositories/space.type.repository.ts b/libs/common/src/modules/space-type/repositories/space.type.repository.ts new file mode 100644 index 0000000..0ea5b13 --- /dev/null +++ b/libs/common/src/modules/space-type/repositories/space.type.repository.ts @@ -0,0 +1,10 @@ +import { DataSource, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common'; +import { SpaceTypeEntity } from '../entities/space.type.entity'; + +@Injectable() +export class SpaceTypeRepository extends Repository { + constructor(private dataSource: DataSource) { + super(SpaceTypeEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/modules/space-type/space.type.repository.module.ts b/libs/common/src/modules/space-type/space.type.repository.module.ts new file mode 100644 index 0000000..6787b67 --- /dev/null +++ b/libs/common/src/modules/space-type/space.type.repository.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { SpaceTypeEntity } from './entities/space.type.entity'; + +@Module({ + providers: [], + exports: [], + controllers: [], + imports: [TypeOrmModule.forFeature([SpaceTypeEntity])], +}) +export class SpaceTypeRepositoryModule {} From 4092c67f892a65268dd14c8e8ea24d110b042661 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 31 Mar 2024 22:28:51 +0300 Subject: [PATCH 03/75] make spaceType nullable false --- libs/common/src/modules/space/entities/space.entity.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index f546b62..1f523f3 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -12,11 +12,6 @@ export class SpaceEntity extends AbstractEntity { }) public uuid: string; - @Column({ - nullable: false, - }) - parentUuid: string; - @Column({ nullable: false, }) @@ -26,7 +21,9 @@ export class SpaceEntity extends AbstractEntity { @OneToMany(() => SpaceEntity, (space) => space.parent) children: SpaceEntity[]; - @ManyToOne(() => SpaceTypeEntity, (spaceType) => spaceType.spaces) + @ManyToOne(() => SpaceTypeEntity, (spaceType) => spaceType.spaces, { + nullable: false, + }) spaceType: SpaceTypeEntity; constructor(partial: Partial) { From 75197664fdca418014de00452611d27db3df06c1 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 31 Mar 2024 22:29:33 +0300 Subject: [PATCH 04/75] Add CommunityController --- .../controllers/community.controller.ts | 36 +++++++++++++++++++ src/community/controllers/index.ts | 1 + 2 files changed, 37 insertions(+) create mode 100644 src/community/controllers/community.controller.ts create mode 100644 src/community/controllers/index.ts diff --git a/src/community/controllers/community.controller.ts b/src/community/controllers/community.controller.ts new file mode 100644 index 0000000..3fb10cd --- /dev/null +++ b/src/community/controllers/community.controller.ts @@ -0,0 +1,36 @@ +import { CommunityService } from '../services/community.service'; +import { + Body, + Controller, + HttpException, + HttpStatus, + Post, + UseGuards, +} from '@nestjs/common'; +import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; +import { AddCommunityDto } from '../dtos/add.community.dto'; + +@ApiTags('Community Module') +@Controller({ + version: '1', + path: 'community', +}) +export class CommunityController { + constructor(private readonly communityService: CommunityService) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post() + async addCommunity(@Body() addCommunityDto: AddCommunityDto) { + try { + await this.communityService.addCommunity(addCommunityDto); + return { message: 'Community added successfully' }; + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/community/controllers/index.ts b/src/community/controllers/index.ts new file mode 100644 index 0000000..b78bbea --- /dev/null +++ b/src/community/controllers/index.ts @@ -0,0 +1 @@ +export * from './community.controller'; From 9654d58ea0eb99549b82a53586c1e628e266e04f Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 31 Mar 2024 22:30:05 +0300 Subject: [PATCH 05/75] Add AddCommunityDto class with properties --- src/community/dtos/add.community.dto.ts | 32 +++++++++++++++++++++++++ src/community/dtos/index.ts | 1 + 2 files changed, 33 insertions(+) create mode 100644 src/community/dtos/add.community.dto.ts create mode 100644 src/community/dtos/index.ts diff --git a/src/community/dtos/add.community.dto.ts b/src/community/dtos/add.community.dto.ts new file mode 100644 index 0000000..d28af76 --- /dev/null +++ b/src/community/dtos/add.community.dto.ts @@ -0,0 +1,32 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString, IsOptional } from 'class-validator'; + +export class AddCommunityDto { + @ApiProperty({ + description: 'spaceName', + required: true, + }) + @IsString() + @IsNotEmpty() + public spaceName: string; + + @ApiProperty({ + description: 'parentUuid', + required: false, + }) + @IsString() + @IsOptional() + public parentUuid?: string; + + @ApiProperty({ + description: 'spaceTypeUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public spaceTypeUuid: string; + + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/community/dtos/index.ts b/src/community/dtos/index.ts new file mode 100644 index 0000000..7119b23 --- /dev/null +++ b/src/community/dtos/index.ts @@ -0,0 +1 @@ +export * from './add.community.dto'; From 85f6dce85fa33a4fc488fb4ee631d25f6eaabe70 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 31 Mar 2024 22:30:51 +0300 Subject: [PATCH 06/75] Add CommunityService implementation --- src/community/services/community.service.ts | 35 +++++++++++++++++++++ src/community/services/index.ts | 1 + 2 files changed, 36 insertions(+) create mode 100644 src/community/services/community.service.ts create mode 100644 src/community/services/index.ts diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts new file mode 100644 index 0000000..af76894 --- /dev/null +++ b/src/community/services/community.service.ts @@ -0,0 +1,35 @@ +import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { TuyaContext } from '@tuya/tuya-connector-nodejs'; +import { ConfigService } from '@nestjs/config'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { AddCommunityDto } from '../dtos'; + +@Injectable() +export class CommunityService { + private tuya: TuyaContext; + constructor( + private readonly configService: ConfigService, + private readonly spaceRepository: SpaceRepository, + ) { + const accessKey = this.configService.get('auth-config.ACCESS_KEY'); + const secretKey = this.configService.get('auth-config.SECRET_KEY'); + // const clientId = this.configService.get('auth-config.CLIENT_ID'); + this.tuya = new TuyaContext({ + baseUrl: 'https://openapi.tuyaeu.com', + accessKey, + secretKey, + }); + } + + async addCommunity(addCommunityDto: AddCommunityDto) { + try { + await this.spaceRepository.save({ + spaceName: addCommunityDto.spaceName, + parent: { uuid: addCommunityDto.parentUuid }, + spaceType: { uuid: addCommunityDto.spaceTypeUuid }, + }); + } catch (err) { + throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/community/services/index.ts b/src/community/services/index.ts new file mode 100644 index 0000000..2408c7c --- /dev/null +++ b/src/community/services/index.ts @@ -0,0 +1 @@ +export * from './community.service'; From be880e47e79f304c2ee7d302c723cf515ac69bca Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 31 Mar 2024 22:31:49 +0300 Subject: [PATCH 07/75] Add CommunityModule to app.module.ts --- src/app.module.ts | 2 ++ src/community/community.module.ts | 14 ++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 src/community/community.module.ts diff --git a/src/app.module.ts b/src/app.module.ts index b3c0106..1ff3985 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -8,6 +8,7 @@ import { HomeModule } from './home/home.module'; import { RoomModule } from './room/room.module'; import { GroupModule } from './group/group.module'; import { DeviceModule } from './device/device.module'; +import { CommunityModule } from './community/community.module'; @Module({ imports: [ ConfigModule.forRoot({ @@ -15,6 +16,7 @@ import { DeviceModule } from './device/device.module'; }), AuthenticationModule, UserModule, + CommunityModule, HomeModule, RoomModule, GroupModule, diff --git a/src/community/community.module.ts b/src/community/community.module.ts new file mode 100644 index 0000000..cb83b4d --- /dev/null +++ b/src/community/community.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { CommunityService } from './services/community.service'; +import { CommunityController } from './controllers/community.controller'; +import { ConfigModule } from '@nestjs/config'; +import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; + +@Module({ + imports: [ConfigModule, SpaceRepositoryModule], + controllers: [CommunityController], + providers: [CommunityService, SpaceRepository], + exports: [CommunityService], +}) +export class CommunityModule {} From d765d090eaf57a2563114279f2e970c1c6936421 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 31 Mar 2024 22:39:26 +0300 Subject: [PATCH 08/75] Refactor CommunityService constructor for simplicity and readability --- src/community/services/community.service.ts | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index af76894..e3d1560 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -1,25 +1,10 @@ import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; -import { TuyaContext } from '@tuya/tuya-connector-nodejs'; -import { ConfigService } from '@nestjs/config'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { AddCommunityDto } from '../dtos'; @Injectable() export class CommunityService { - private tuya: TuyaContext; - constructor( - private readonly configService: ConfigService, - private readonly spaceRepository: SpaceRepository, - ) { - const accessKey = this.configService.get('auth-config.ACCESS_KEY'); - const secretKey = this.configService.get('auth-config.SECRET_KEY'); - // const clientId = this.configService.get('auth-config.CLIENT_ID'); - this.tuya = new TuyaContext({ - baseUrl: 'https://openapi.tuyaeu.com', - accessKey, - secretKey, - }); - } + constructor(private readonly spaceRepository: SpaceRepository) {} async addCommunity(addCommunityDto: AddCommunityDto) { try { From ae8d909268e0704ab3b3bc78c53bf4c76ea3220c Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 1 Apr 2024 00:04:38 +0300 Subject: [PATCH 09/75] Add endpoint to get community by UUID --- .../controllers/community.controller.ts | 23 ++++++++++++++++++ src/community/dtos/get.community.dto.ts | 12 ++++++++++ .../interface/community.interface.ts | 12 ++++++++++ src/community/services/community.service.ts | 24 +++++++++++++++++++ 4 files changed, 71 insertions(+) create mode 100644 src/community/dtos/get.community.dto.ts create mode 100644 src/community/interface/community.interface.ts diff --git a/src/community/controllers/community.controller.ts b/src/community/controllers/community.controller.ts index 3fb10cd..75b41fb 100644 --- a/src/community/controllers/community.controller.ts +++ b/src/community/controllers/community.controller.ts @@ -2,8 +2,10 @@ import { CommunityService } from '../services/community.service'; import { Body, Controller, + Get, HttpException, HttpStatus, + Param, Post, UseGuards, } from '@nestjs/common'; @@ -33,4 +35,25 @@ export class CommunityController { ); } } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get(':communityUuid') + async getCommunityByUuid(@Param('communityUuid') communityUuid: string) { + try { + const community = + await this.communityService.getCommunityByUuid(communityUuid); + + return community; + } catch (error) { + if (error.status === 404) { + throw new HttpException('Community not found', HttpStatus.NOT_FOUND); + } else { + throw new HttpException( + error.message || 'Internal server error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } } diff --git a/src/community/dtos/get.community.dto.ts b/src/community/dtos/get.community.dto.ts new file mode 100644 index 0000000..e313125 --- /dev/null +++ b/src/community/dtos/get.community.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class GetCommunityDto { + @ApiProperty({ + description: 'communityUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public communityUuid: string; +} diff --git a/src/community/interface/community.interface.ts b/src/community/interface/community.interface.ts new file mode 100644 index 0000000..50fe78a --- /dev/null +++ b/src/community/interface/community.interface.ts @@ -0,0 +1,12 @@ +export interface GetCommunityByUuidInterface { + uuid: string; + createdAt: Date; + updatedAt: Date; + spaceName: string; + spaceType: { + uuid: string; + createdAt: Date; + updatedAt: Date; + type: string; + }; +} diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index e3d1560..1bfd12b 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -1,6 +1,7 @@ import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { AddCommunityDto } from '../dtos'; +import { GetCommunityByUuidInterface } from '../interface/community.interface'; @Injectable() export class CommunityService { @@ -17,4 +18,27 @@ export class CommunityService { throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR); } } + + async getCommunityByUuid( + communityUuid: string, + ): Promise { + try { + const community: GetCommunityByUuidInterface = + await this.spaceRepository.findOne({ + where: { + uuid: communityUuid, + }, + relations: ['spaceType'], + }); + if (!community) { + throw new HttpException('Community not found', HttpStatus.NOT_FOUND); + } + return community; + } catch (err) { + throw new HttpException( + err.message, + err.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } From badda01a05871f4784219d23a97a9d746d38cd9e Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 2 Apr 2024 11:30:44 +0300 Subject: [PATCH 10/75] Add SpaceTypeRepository to CommunityModule imports --- src/community/community.module.ts | 6 ++++-- .../controllers/community.controller.ts | 1 - src/community/dtos/add.community.dto.ts | 8 -------- src/community/services/community.service.ts | 17 +++++++++++++++-- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/community/community.module.ts b/src/community/community.module.ts index cb83b4d..36b9da2 100644 --- a/src/community/community.module.ts +++ b/src/community/community.module.ts @@ -4,11 +4,13 @@ import { CommunityController } from './controllers/community.controller'; import { ConfigModule } from '@nestjs/config'; import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module'; import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space.type.repository.module'; +import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; @Module({ - imports: [ConfigModule, SpaceRepositoryModule], + imports: [ConfigModule, SpaceRepositoryModule, SpaceTypeRepositoryModule], controllers: [CommunityController], - providers: [CommunityService, SpaceRepository], + providers: [CommunityService, SpaceRepository, SpaceTypeRepository], exports: [CommunityService], }) export class CommunityModule {} diff --git a/src/community/controllers/community.controller.ts b/src/community/controllers/community.controller.ts index 75b41fb..127ea72 100644 --- a/src/community/controllers/community.controller.ts +++ b/src/community/controllers/community.controller.ts @@ -43,7 +43,6 @@ export class CommunityController { try { const community = await this.communityService.getCommunityByUuid(communityUuid); - return community; } catch (error) { if (error.status === 404) { diff --git a/src/community/dtos/add.community.dto.ts b/src/community/dtos/add.community.dto.ts index d28af76..0409b3e 100644 --- a/src/community/dtos/add.community.dto.ts +++ b/src/community/dtos/add.community.dto.ts @@ -18,14 +18,6 @@ export class AddCommunityDto { @IsOptional() public parentUuid?: string; - @ApiProperty({ - description: 'spaceTypeUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public spaceTypeUuid: string; - constructor(dto: Partial) { Object.assign(this, dto); } diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index 1bfd12b..ec964a9 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -1,3 +1,4 @@ +import { SpaceTypeRepository } from './../../../libs/common/src/modules/space-type/repositories/space.type.repository'; import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { AddCommunityDto } from '../dtos'; @@ -5,14 +6,23 @@ import { GetCommunityByUuidInterface } from '../interface/community.interface'; @Injectable() export class CommunityService { - constructor(private readonly spaceRepository: SpaceRepository) {} + constructor( + private readonly spaceRepository: SpaceRepository, + private readonly spaceTypeRepository: SpaceTypeRepository, + ) {} async addCommunity(addCommunityDto: AddCommunityDto) { try { + const spaceType = await this.spaceTypeRepository.findOne({ + where: { + type: 'community', + }, + }); + await this.spaceRepository.save({ spaceName: addCommunityDto.spaceName, parent: { uuid: addCommunityDto.parentUuid }, - spaceType: { uuid: addCommunityDto.spaceTypeUuid }, + spaceType: { uuid: spaceType.uuid }, }); } catch (err) { throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR); @@ -27,6 +37,9 @@ export class CommunityService { await this.spaceRepository.findOne({ where: { uuid: communityUuid, + spaceType: { + type: 'community', + }, }, relations: ['spaceType'], }); From 4a033107da887ce41b44b7bb246b214828c64f34 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 2 Apr 2024 17:29:33 +0300 Subject: [PATCH 11/75] Add SpaceEntity and SpaceTypeEntity to database module --- libs/common/src/database/database.module.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index dd29899..c604089 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -7,6 +7,8 @@ import { UserSessionEntity } from '../modules/session/entities/session.entity'; import { UserOtpEntity } from '../modules/user-otp/entities'; import { HomeEntity } from '../modules/home/entities'; import { ProductEntity } from '../modules/product/entities'; +import { SpaceEntity } from '../modules/space/entities'; +import { SpaceTypeEntity } from '../modules/space-type/entities'; @Module({ imports: [ @@ -27,10 +29,12 @@ import { ProductEntity } from '../modules/product/entities'; UserOtpEntity, HomeEntity, ProductEntity, + SpaceEntity, + SpaceTypeEntity, ], namingStrategy: new SnakeNamingStrategy(), synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))), - logging: true, + logging: false, extra: { charset: 'utf8mb4', max: 20, // set pool max size From dcbeb92ce6773b74572285959afad5e9fd70e5b7 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 2 Apr 2024 17:30:11 +0300 Subject: [PATCH 12/75] Refactor class-validator imports in community DTOs --- src/community/dtos/add.community.dto.ts | 10 +----- src/community/dtos/get.community.dto.ts | 41 ++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/community/dtos/add.community.dto.ts b/src/community/dtos/add.community.dto.ts index 0409b3e..f335d3d 100644 --- a/src/community/dtos/add.community.dto.ts +++ b/src/community/dtos/add.community.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString, IsOptional } from 'class-validator'; +import { IsNotEmpty, IsString } from 'class-validator'; export class AddCommunityDto { @ApiProperty({ @@ -10,14 +10,6 @@ export class AddCommunityDto { @IsNotEmpty() public spaceName: string; - @ApiProperty({ - description: 'parentUuid', - required: false, - }) - @IsString() - @IsOptional() - public parentUuid?: string; - constructor(dto: Partial) { Object.assign(this, dto); } diff --git a/src/community/dtos/get.community.dto.ts b/src/community/dtos/get.community.dto.ts index e313125..be614e5 100644 --- a/src/community/dtos/get.community.dto.ts +++ b/src/community/dtos/get.community.dto.ts @@ -1,5 +1,13 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; +import { Transform } from 'class-transformer'; +import { + IsBoolean, + IsInt, + IsNotEmpty, + IsOptional, + IsString, + Min, +} from 'class-validator'; export class GetCommunityDto { @ApiProperty({ @@ -10,3 +18,34 @@ export class GetCommunityDto { @IsNotEmpty() public communityUuid: string; } + +export class GetCommunityChildDto { + @ApiProperty({ example: 1, description: 'Page number', required: true }) + @IsInt({ message: 'Page must be a number' }) + @Min(1, { message: 'Page must not be less than 1' }) + @IsNotEmpty() + public page: number; + + @ApiProperty({ + example: 10, + description: 'Number of items per page', + required: true, + }) + @IsInt({ message: 'Page size must be a number' }) + @Min(1, { message: 'Page size must not be less than 1' }) + @IsNotEmpty() + public pageSize: number; + + @ApiProperty({ + example: true, + description: 'Flag to determine whether to fetch full hierarchy', + required: false, + default: false, + }) + @IsOptional() + @IsBoolean() + @Transform((value) => { + return value.obj.includeSubSpaces === 'true'; + }) + public includeSubSpaces: boolean = false; +} From 8a759aca354add9b887cbe0b4ac926bbee18d794 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 2 Apr 2024 17:30:31 +0300 Subject: [PATCH 13/75] Refactor GetCommunityByUuidInterface --- src/community/interface/community.interface.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/community/interface/community.interface.ts b/src/community/interface/community.interface.ts index 50fe78a..b0f491e 100644 --- a/src/community/interface/community.interface.ts +++ b/src/community/interface/community.interface.ts @@ -2,11 +2,13 @@ export interface GetCommunityByUuidInterface { uuid: string; createdAt: Date; updatedAt: Date; - spaceName: string; - spaceType: { - uuid: string; - createdAt: Date; - updatedAt: Date; - type: string; - }; + name: string; + type: string; +} + +export interface CommunityChildInterface { + uuid: string; + name: string; + type: string; + children?: CommunityChildInterface[]; } From 893667c1b502c6d12d6e1707ef808134340bd9fa Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 2 Apr 2024 17:30:40 +0300 Subject: [PATCH 14/75] Update global validation pipe configuration --- src/main.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main.ts b/src/main.ts index 95f8ed1..129f319 100644 --- a/src/main.ts +++ b/src/main.ts @@ -25,7 +25,14 @@ async function bootstrap() { setupSwaggerAuthentication(app); - app.useGlobalPipes(new ValidationPipe()); + app.useGlobalPipes( + new ValidationPipe({ + transform: true, // Auto-transform payloads to their DTO instances. + transformOptions: { + enableImplicitConversion: true, // Convert incoming payloads to their DTO instances if possible. + }, + }), + ); await app.listen(process.env.PORT || 4000); } From ca5d67e291708ac837abce4591b1af28ef187549 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 2 Apr 2024 17:30:54 +0300 Subject: [PATCH 15/75] Refactor community service to include child hierarchy retrieval --- src/community/services/community.service.ts | 93 ++++++++++++++++++--- 1 file changed, 81 insertions(+), 12 deletions(-) diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index ec964a9..81aab77 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -1,8 +1,13 @@ +import { GetCommunityChildDto } from './../dtos/get.community.dto'; import { SpaceTypeRepository } from './../../../libs/common/src/modules/space-type/repositories/space.type.repository'; import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { AddCommunityDto } from '../dtos'; -import { GetCommunityByUuidInterface } from '../interface/community.interface'; +import { + CommunityChildInterface, + GetCommunityByUuidInterface, +} from '../interface/community.interface'; +import { SpaceEntity } from '@app/common/modules/space/entities'; @Injectable() export class CommunityService { @@ -21,7 +26,6 @@ export class CommunityService { await this.spaceRepository.save({ spaceName: addCommunityDto.spaceName, - parent: { uuid: addCommunityDto.parentUuid }, spaceType: { uuid: spaceType.uuid }, }); } catch (err) { @@ -33,20 +37,25 @@ export class CommunityService { communityUuid: string, ): Promise { try { - const community: GetCommunityByUuidInterface = - await this.spaceRepository.findOne({ - where: { - uuid: communityUuid, - spaceType: { - type: 'community', - }, + const community = await this.spaceRepository.findOne({ + where: { + uuid: communityUuid, + spaceType: { + type: 'community', }, - relations: ['spaceType'], - }); + }, + relations: ['spaceType'], + }); if (!community) { throw new HttpException('Community not found', HttpStatus.NOT_FOUND); } - return community; + return { + uuid: community.uuid, + createdAt: community.createdAt, + updatedAt: community.updatedAt, + name: community.spaceName, + type: community.spaceType.type, + }; } catch (err) { throw new HttpException( err.message, @@ -54,4 +63,64 @@ export class CommunityService { ); } } + async getCommunityChildByUuid( + communityUuid: string, + getCommunityChildDto: GetCommunityChildDto, + ): Promise { + const { includeSubSpaces, page, pageSize } = getCommunityChildDto; + + const space = await this.spaceRepository.findOneOrFail({ + where: { uuid: communityUuid }, + relations: ['children', 'spaceType'], + }); + + if (space.spaceType.type !== 'community') { + throw new HttpException('Community not found', HttpStatus.NOT_FOUND); + } + + return { + uuid: space.uuid, + name: space.spaceName, + type: space.spaceType.type, + children: await this.buildHierarchy( + space, + includeSubSpaces, + page, + pageSize, + ), + }; + } + + private async buildHierarchy( + space: SpaceEntity, + includeSubSpaces: boolean, + page: number, + pageSize: number, + ): Promise { + const children = await this.spaceRepository.find({ + where: { parent: { uuid: space.uuid } }, + relations: ['spaceType'], + skip: (page - 1) * pageSize, + take: pageSize, + }); + + if (!children || children.length === 0 || !includeSubSpaces) { + return children.map((child) => ({ + uuid: child.uuid, + name: child.spaceName, + type: child.spaceType.type, + })); + } + + const childHierarchies = await Promise.all( + children.map(async (child) => ({ + uuid: child.uuid, + name: child.spaceName, + type: child.spaceType.type, + children: await this.buildHierarchy(child, true, 1, pageSize), + })), + ); + + return childHierarchies; + } } From 72756e4e0801f34a2f499cdc64175110700850ab Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 2 Apr 2024 17:31:20 +0300 Subject: [PATCH 16/75] Add endpoint to get community child by UUID --- .../controllers/community.controller.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/community/controllers/community.controller.ts b/src/community/controllers/community.controller.ts index 127ea72..6b1a5a7 100644 --- a/src/community/controllers/community.controller.ts +++ b/src/community/controllers/community.controller.ts @@ -7,11 +7,13 @@ import { HttpStatus, Param, Post, + Query, UseGuards, } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { AddCommunityDto } from '../dtos/add.community.dto'; +import { GetCommunityChildDto } from '../dtos/get.community.dto'; @ApiTags('Community Module') @Controller({ @@ -55,4 +57,29 @@ export class CommunityController { } } } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('child/:communityUuid') + async getCommunityChildByUuid( + @Param('communityUuid') communityUuid: string, + @Query() query: GetCommunityChildDto, + ) { + try { + const community = await this.communityService.getCommunityChildByUuid( + communityUuid, + query, + ); + return community; + } catch (error) { + if (error.status === 404) { + throw new HttpException('Community not found', HttpStatus.NOT_FOUND); + } else { + throw new HttpException( + error.message || 'Internal server error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } } From 7cf26f3146ae98f8eeff95912e4158e2e0acd560 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 2 Apr 2024 18:12:48 +0300 Subject: [PATCH 17/75] Update property name to communityName --- src/community/dtos/add.community.dto.ts | 4 ++-- src/community/services/community.service.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/community/dtos/add.community.dto.ts b/src/community/dtos/add.community.dto.ts index f335d3d..96dce06 100644 --- a/src/community/dtos/add.community.dto.ts +++ b/src/community/dtos/add.community.dto.ts @@ -3,12 +3,12 @@ import { IsNotEmpty, IsString } from 'class-validator'; export class AddCommunityDto { @ApiProperty({ - description: 'spaceName', + description: 'communityName', required: true, }) @IsString() @IsNotEmpty() - public spaceName: string; + public communityName: string; constructor(dto: Partial) { Object.assign(this, dto); diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index 81aab77..2861fe2 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -25,7 +25,7 @@ export class CommunityService { }); await this.spaceRepository.save({ - spaceName: addCommunityDto.spaceName, + spaceName: addCommunityDto.communityName, spaceType: { uuid: spaceType.uuid }, }); } catch (err) { From 499603f3c0bc7f8e7e71fa33e8cbff2a02960701 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 3 Apr 2024 14:51:40 +0300 Subject: [PATCH 18/75] Add BuildingController with CRUD operations --- .../controllers/building.controller.ts | 104 ++++++++++++++++++ src/building/controllers/index.ts | 1 + 2 files changed, 105 insertions(+) create mode 100644 src/building/controllers/building.controller.ts create mode 100644 src/building/controllers/index.ts diff --git a/src/building/controllers/building.controller.ts b/src/building/controllers/building.controller.ts new file mode 100644 index 0000000..17e01d4 --- /dev/null +++ b/src/building/controllers/building.controller.ts @@ -0,0 +1,104 @@ +import { BuildingService } from '../services/building.service'; +import { + Body, + Controller, + Get, + HttpException, + HttpStatus, + Param, + Post, + Query, + UseGuards, +} from '@nestjs/common'; +import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; +import { AddBuildingDto } from '../dtos/add.building.dto'; +import { GetBuildingChildDto } from '../dtos/get.building.dto'; + +@ApiTags('Building Module') +@Controller({ + version: '1', + path: 'building', +}) +export class BuildingController { + constructor(private readonly buildingService: BuildingService) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post() + async addBuilding(@Body() addBuildingDto: AddBuildingDto) { + try { + await this.buildingService.addBuilding(addBuildingDto); + return { message: 'Building added successfully' }; + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get(':buildingUuid') + async getBuildingByUuid(@Param('buildingUuid') buildingUuid: string) { + try { + const building = + await this.buildingService.getBuildingByUuid(buildingUuid); + return building; + } catch (error) { + if (error.status === 404) { + throw new HttpException('Building not found', HttpStatus.NOT_FOUND); + } else { + throw new HttpException( + error.message || 'Internal server error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('child/:buildingUuid') + async getBuildingChildByUuid( + @Param('buildingUuid') buildingUuid: string, + @Query() query: GetBuildingChildDto, + ) { + try { + const building = await this.buildingService.getBuildingChildByUuid( + buildingUuid, + query, + ); + return building; + } catch (error) { + if (error.status === 404) { + throw new HttpException('Building not found', HttpStatus.NOT_FOUND); + } else { + throw new HttpException( + error.message || 'Internal server error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('parent/:buildingUuid') + async getBuildingParentByUuid(@Param('buildingUuid') buildingUuid: string) { + try { + const building = + await this.buildingService.getBuildingParentByUuid(buildingUuid); + return building; + } catch (error) { + if (error.status === 404) { + throw new HttpException('Building not found', HttpStatus.NOT_FOUND); + } else { + throw new HttpException( + error.message || 'Internal server error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } +} diff --git a/src/building/controllers/index.ts b/src/building/controllers/index.ts new file mode 100644 index 0000000..b5ec3c2 --- /dev/null +++ b/src/building/controllers/index.ts @@ -0,0 +1 @@ +export * from './building.controller'; From 6fedcb6c3bec235c7adf1510210c257822691a99 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 3 Apr 2024 14:51:53 +0300 Subject: [PATCH 19/75] Add building and get building DTOs --- src/building/dtos/add.building.dto.ts | 23 ++++++++++++ src/building/dtos/get.building.dto.ts | 51 +++++++++++++++++++++++++++ src/building/dtos/index.ts | 1 + 3 files changed, 75 insertions(+) create mode 100644 src/building/dtos/add.building.dto.ts create mode 100644 src/building/dtos/get.building.dto.ts create mode 100644 src/building/dtos/index.ts diff --git a/src/building/dtos/add.building.dto.ts b/src/building/dtos/add.building.dto.ts new file mode 100644 index 0000000..e9268c0 --- /dev/null +++ b/src/building/dtos/add.building.dto.ts @@ -0,0 +1,23 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class AddBuildingDto { + @ApiProperty({ + description: 'buildingName', + required: true, + }) + @IsString() + @IsNotEmpty() + public buildingName: string; + + @ApiProperty({ + description: 'communityUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public communityUuid: string; + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/building/dtos/get.building.dto.ts b/src/building/dtos/get.building.dto.ts new file mode 100644 index 0000000..f762469 --- /dev/null +++ b/src/building/dtos/get.building.dto.ts @@ -0,0 +1,51 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { + IsBoolean, + IsInt, + IsNotEmpty, + IsOptional, + IsString, + Min, +} from 'class-validator'; + +export class GetBuildingDto { + @ApiProperty({ + description: 'buildingUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public buildingUuid: string; +} + +export class GetBuildingChildDto { + @ApiProperty({ example: 1, description: 'Page number', required: true }) + @IsInt({ message: 'Page must be a number' }) + @Min(1, { message: 'Page must not be less than 1' }) + @IsNotEmpty() + public page: number; + + @ApiProperty({ + example: 10, + description: 'Number of items per page', + required: true, + }) + @IsInt({ message: 'Page size must be a number' }) + @Min(1, { message: 'Page size must not be less than 1' }) + @IsNotEmpty() + public pageSize: number; + + @ApiProperty({ + example: true, + description: 'Flag to determine whether to fetch full hierarchy', + required: false, + default: false, + }) + @IsOptional() + @IsBoolean() + @Transform((value) => { + return value.obj.includeSubSpaces === 'true'; + }) + public includeSubSpaces: boolean = false; +} diff --git a/src/building/dtos/index.ts b/src/building/dtos/index.ts new file mode 100644 index 0000000..93e7c6f --- /dev/null +++ b/src/building/dtos/index.ts @@ -0,0 +1 @@ +export * from './add.building.dto'; From 296457310ab859d73f5248accf8291c637e81ba7 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 3 Apr 2024 14:52:07 +0300 Subject: [PATCH 20/75] Add building interfaces for parent and child relationships --- src/building/interface/building.interface.ts | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/building/interface/building.interface.ts diff --git a/src/building/interface/building.interface.ts b/src/building/interface/building.interface.ts new file mode 100644 index 0000000..92852a7 --- /dev/null +++ b/src/building/interface/building.interface.ts @@ -0,0 +1,21 @@ +export interface GetBuildingByUuidInterface { + uuid: string; + createdAt: Date; + updatedAt: Date; + name: string; + type: string; +} + +export interface BuildingChildInterface { + uuid: string; + name: string; + type: string; + totalCount?: number; + children?: BuildingChildInterface[]; +} +export interface BuildingParentInterface { + uuid: string; + name: string; + type: string; + parent?: BuildingParentInterface; +} From 526bf4b6618bd7763e4665e25e50ffdd576a4caf Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 3 Apr 2024 14:52:30 +0300 Subject: [PATCH 21/75] Add building service and check building middleware --- src/building/services/building.service.ts | 182 ++++++++++++++++++++++ src/building/services/index.ts | 1 + src/middleware/CheckBuildingMiddleware.ts | 74 +++++++++ 3 files changed, 257 insertions(+) create mode 100644 src/building/services/building.service.ts create mode 100644 src/building/services/index.ts create mode 100644 src/middleware/CheckBuildingMiddleware.ts diff --git a/src/building/services/building.service.ts b/src/building/services/building.service.ts new file mode 100644 index 0000000..fbaf8cb --- /dev/null +++ b/src/building/services/building.service.ts @@ -0,0 +1,182 @@ +import { GetBuildingChildDto } from '../dtos/get.building.dto'; +import { SpaceTypeRepository } from '../../../libs/common/src/modules/space-type/repositories/space.type.repository'; +import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { AddBuildingDto } from '../dtos'; +import { + BuildingChildInterface, + BuildingParentInterface, + GetBuildingByUuidInterface, +} from '../interface/building.interface'; +import { SpaceEntity } from '@app/common/modules/space/entities'; + +@Injectable() +export class BuildingService { + constructor( + private readonly spaceRepository: SpaceRepository, + private readonly spaceTypeRepository: SpaceTypeRepository, + ) {} + + async addBuilding(addBuildingDto: AddBuildingDto) { + try { + const spaceType = await this.spaceTypeRepository.findOne({ + where: { + type: 'building', + }, + }); + + await this.spaceRepository.save({ + spaceName: addBuildingDto.buildingName, + parent: { uuid: addBuildingDto.communityUuid }, + spaceType: { uuid: spaceType.uuid }, + }); + } catch (err) { + throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + async getBuildingByUuid( + buildingUuid: string, + ): Promise { + try { + const building = await this.spaceRepository.findOne({ + where: { + uuid: buildingUuid, + spaceType: { + type: 'building', + }, + }, + relations: ['spaceType'], + }); + if (!building) { + throw new HttpException('Building not found', HttpStatus.NOT_FOUND); + } + return { + uuid: building.uuid, + createdAt: building.createdAt, + updatedAt: building.updatedAt, + name: building.spaceName, + type: building.spaceType.type, + }; + } catch (err) { + throw new HttpException( + err.message, + err.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async getBuildingChildByUuid( + buildingUuid: string, + getBuildingChildDto: GetBuildingChildDto, + ): Promise { + const { includeSubSpaces, page, pageSize } = getBuildingChildDto; + + const space = await this.spaceRepository.findOneOrFail({ + where: { uuid: buildingUuid }, + relations: ['children', 'spaceType'], + }); + + if (space.spaceType.type !== 'building') { + throw new HttpException('Building not found', HttpStatus.NOT_FOUND); + } + + const totalCount = await this.spaceRepository.count({ + where: { parent: { uuid: space.uuid } }, + }); + + const children = await this.buildHierarchy( + space, + includeSubSpaces, + page, + pageSize, + ); + + return { + uuid: space.uuid, + name: space.spaceName, + type: space.spaceType.type, + totalCount, + children, + }; + } + + private async buildHierarchy( + space: SpaceEntity, + includeSubSpaces: boolean, + page: number, + pageSize: number, + ): Promise { + const children = await this.spaceRepository.find({ + where: { parent: { uuid: space.uuid } }, + relations: ['spaceType'], + skip: (page - 1) * pageSize, + take: pageSize, + }); + + if (!children || children.length === 0 || !includeSubSpaces) { + return children + .filter( + (child) => + child.spaceType.type !== 'building' && + child.spaceType.type !== 'community', + ) // Filter remaining building and community types + .map((child) => ({ + uuid: child.uuid, + name: child.spaceName, + type: child.spaceType.type, + })); + } + + const childHierarchies = await Promise.all( + children + .filter( + (child) => + child.spaceType.type !== 'building' && + child.spaceType.type !== 'community', + ) // Filter remaining building and community types + .map(async (child) => ({ + uuid: child.uuid, + name: child.spaceName, + type: child.spaceType.type, + children: await this.buildHierarchy(child, true, 1, pageSize), + })), + ); + + return childHierarchies; + } + + async getBuildingParentByUuid( + buildingUuid: string, + ): Promise { + try { + const building = await this.spaceRepository.findOne({ + where: { + uuid: buildingUuid, + spaceType: { + type: 'building', + }, + }, + relations: ['spaceType', 'parent', 'parent.spaceType'], + }); + if (!building) { + throw new HttpException('Building not found', HttpStatus.NOT_FOUND); + } + + return { + uuid: building.uuid, + name: building.spaceName, + type: building.spaceType.type, + parent: { + uuid: building.parent.uuid, + name: building.parent.spaceName, + type: building.parent.spaceType.type, + }, + }; + } catch (err) { + throw new HttpException( + err.message, + err.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/building/services/index.ts b/src/building/services/index.ts new file mode 100644 index 0000000..7b260d2 --- /dev/null +++ b/src/building/services/index.ts @@ -0,0 +1 @@ +export * from './building.service'; diff --git a/src/middleware/CheckBuildingMiddleware.ts b/src/middleware/CheckBuildingMiddleware.ts new file mode 100644 index 0000000..92a5aba --- /dev/null +++ b/src/middleware/CheckBuildingMiddleware.ts @@ -0,0 +1,74 @@ +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { + Injectable, + NestMiddleware, + HttpStatus, + HttpException, +} from '@nestjs/common'; +import { Request, Response, NextFunction } from 'express'; + +@Injectable() +export class CheckBuildingMiddleware implements NestMiddleware { + constructor(private readonly spaceRepository: SpaceRepository) {} + + async use(req: Request, res: Response, next: NextFunction) { + try { + // Destructure request body for cleaner code + const { buildingName, communityUuid } = req.body; + + // Guard clauses for early return + if (!buildingName) { + return res.status(HttpStatus.BAD_REQUEST).json({ + statusCode: HttpStatus.BAD_REQUEST, + message: 'buildingName is required', + }); + } + + if (!communityUuid) { + return res.status(HttpStatus.BAD_REQUEST).json({ + statusCode: HttpStatus.BAD_REQUEST, + message: 'communityUuid is required', + }); + } + + // Call function to check if community is a building + await this.checkCommunityIsBuilding(communityUuid); + + // Call next middleware + next(); + } catch (error) { + // Handle errors + this.handleMiddlewareError(error, res); + } + } + + async checkCommunityIsBuilding(communityUuid: string) { + const communityData = await this.spaceRepository.findOne({ + where: { uuid: communityUuid }, + relations: ['spaceType'], + }); + + // Throw error if community not found + if (!communityData) { + throw new HttpException('Community not found', HttpStatus.NOT_FOUND); + } + + // Throw error if community is not of type 'community' + if (communityData.spaceType.type !== 'community') { + throw new HttpException( + "communityUuid is not of type 'community'", + HttpStatus.BAD_REQUEST, + ); + } + } + + // Function to handle middleware errors + private handleMiddlewareError(error: Error, res: Response) { + const status = + error instanceof HttpException + ? error.getStatus() + : HttpStatus.INTERNAL_SERVER_ERROR; + const message = error.message || 'Internal server error'; + res.status(status).json({ statusCode: status, message }); + } +} From 033e09a44e973e72138a751d2f9c53b28a01f7dd Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 3 Apr 2024 14:55:08 +0300 Subject: [PATCH 22/75] Add BuildingModule and CheckCommunityTypeMiddleware --- src/building/building.module.ts | 29 +++++++++++++++++++ ...are.ts => CheckCommunityTypeMiddleware.ts} | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/building/building.module.ts rename src/middleware/{CheckBuildingMiddleware.ts => CheckCommunityTypeMiddleware.ts} (96%) diff --git a/src/building/building.module.ts b/src/building/building.module.ts new file mode 100644 index 0000000..2bdeb7e --- /dev/null +++ b/src/building/building.module.ts @@ -0,0 +1,29 @@ +import { + MiddlewareConsumer, + Module, + NestModule, + RequestMethod, +} from '@nestjs/common'; +import { BuildingService } from './services/building.service'; +import { BuildingController } from './controllers/building.controller'; +import { ConfigModule } from '@nestjs/config'; +import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space.type.repository.module'; +import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; +import { CheckCommunityTypeMiddleware } from 'src/middleware/CheckCommunityTypeMiddleware'; + +@Module({ + imports: [ConfigModule, SpaceRepositoryModule, SpaceTypeRepositoryModule], + controllers: [BuildingController], + providers: [BuildingService, SpaceRepository, SpaceTypeRepository], + exports: [BuildingService], +}) +export class BuildingModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + consumer.apply(CheckCommunityTypeMiddleware).forRoutes({ + path: '/building', + method: RequestMethod.POST, + }); + } +} diff --git a/src/middleware/CheckBuildingMiddleware.ts b/src/middleware/CheckCommunityTypeMiddleware.ts similarity index 96% rename from src/middleware/CheckBuildingMiddleware.ts rename to src/middleware/CheckCommunityTypeMiddleware.ts index 92a5aba..314b954 100644 --- a/src/middleware/CheckBuildingMiddleware.ts +++ b/src/middleware/CheckCommunityTypeMiddleware.ts @@ -8,7 +8,7 @@ import { import { Request, Response, NextFunction } from 'express'; @Injectable() -export class CheckBuildingMiddleware implements NestMiddleware { +export class CheckCommunityTypeMiddleware implements NestMiddleware { constructor(private readonly spaceRepository: SpaceRepository) {} async use(req: Request, res: Response, next: NextFunction) { From cb6ec0e278e14ee27d401e03a4e3c618b0cfde4a Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 3 Apr 2024 14:55:50 +0300 Subject: [PATCH 23/75] Add BuildingModule to imports in app.module.ts --- src/app.module.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app.module.ts b/src/app.module.ts index 1ff3985..6fcf2c1 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -9,6 +9,7 @@ import { RoomModule } from './room/room.module'; import { GroupModule } from './group/group.module'; import { DeviceModule } from './device/device.module'; import { CommunityModule } from './community/community.module'; +import { BuildingModule } from './building/building.module'; @Module({ imports: [ ConfigModule.forRoot({ @@ -17,6 +18,7 @@ import { CommunityModule } from './community/community.module'; AuthenticationModule, UserModule, CommunityModule, + BuildingModule, HomeModule, RoomModule, GroupModule, From 249dd675918a2b59e68dfc4588ddf1325dd4a57f Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:07:00 +0300 Subject: [PATCH 24/75] Add totalCount field to CommunityChildInterface --- src/community/interface/community.interface.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/community/interface/community.interface.ts b/src/community/interface/community.interface.ts index b0f491e..7f769d3 100644 --- a/src/community/interface/community.interface.ts +++ b/src/community/interface/community.interface.ts @@ -10,5 +10,6 @@ export interface CommunityChildInterface { uuid: string; name: string; type: string; + totalCount?: number; children?: CommunityChildInterface[]; } From 9b837bd15a6e20c91329bd8569b88b2079b21540 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:07:11 +0300 Subject: [PATCH 25/75] Refactor buildHierarchy method for improved performance --- src/community/services/community.service.ts | 44 ++++++++++++--------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index 2861fe2..a917d94 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -77,17 +77,21 @@ export class CommunityService { if (space.spaceType.type !== 'community') { throw new HttpException('Community not found', HttpStatus.NOT_FOUND); } - + const totalCount = await this.spaceRepository.count({ + where: { parent: { uuid: space.uuid } }, + }); + const children = await this.buildHierarchy( + space, + includeSubSpaces, + page, + pageSize, + ); return { uuid: space.uuid, name: space.spaceName, type: space.spaceType.type, - children: await this.buildHierarchy( - space, - includeSubSpaces, - page, - pageSize, - ), + totalCount, + children, }; } @@ -105,20 +109,24 @@ export class CommunityService { }); if (!children || children.length === 0 || !includeSubSpaces) { - return children.map((child) => ({ - uuid: child.uuid, - name: child.spaceName, - type: child.spaceType.type, - })); + return children + .filter((child) => child.spaceType.type !== 'community') // Filter remaining community type + .map((child) => ({ + uuid: child.uuid, + name: child.spaceName, + type: child.spaceType.type, + })); } const childHierarchies = await Promise.all( - children.map(async (child) => ({ - uuid: child.uuid, - name: child.spaceName, - type: child.spaceType.type, - children: await this.buildHierarchy(child, true, 1, pageSize), - })), + children + .filter((child) => child.spaceType.type !== 'community') // Filter remaining community type + .map(async (child) => ({ + uuid: child.uuid, + name: child.spaceName, + type: child.spaceType.type, + children: await this.buildHierarchy(child, true, 1, pageSize), + })), ); return childHierarchies; From d5f396e4f52cda01a53f6dfae27b7715a008f908 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 13 Apr 2024 17:20:20 +0300 Subject: [PATCH 26/75] Add floor module with controller, service, DTOs, and interfaces --- src/app.module.ts | 2 + src/floor/controllers/floor.controller.ts | 102 ++++++++++ src/floor/controllers/index.ts | 1 + src/floor/dtos/add.floor.dto.ts | 23 +++ src/floor/dtos/get.floor.dto.ts | 51 +++++ src/floor/dtos/index.ts | 1 + src/floor/floor.module.ts | 29 +++ src/floor/interface/floor.interface.ts | 21 +++ src/floor/services/floor.service.ts | 178 ++++++++++++++++++ src/floor/services/index.ts | 1 + src/middleware/CheckBuildingTypeMiddleware.ts | 74 ++++++++ 11 files changed, 483 insertions(+) create mode 100644 src/floor/controllers/floor.controller.ts create mode 100644 src/floor/controllers/index.ts create mode 100644 src/floor/dtos/add.floor.dto.ts create mode 100644 src/floor/dtos/get.floor.dto.ts create mode 100644 src/floor/dtos/index.ts create mode 100644 src/floor/floor.module.ts create mode 100644 src/floor/interface/floor.interface.ts create mode 100644 src/floor/services/floor.service.ts create mode 100644 src/floor/services/index.ts create mode 100644 src/middleware/CheckBuildingTypeMiddleware.ts diff --git a/src/app.module.ts b/src/app.module.ts index 6fcf2c1..ff3bc78 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -10,6 +10,7 @@ import { GroupModule } from './group/group.module'; import { DeviceModule } from './device/device.module'; import { CommunityModule } from './community/community.module'; import { BuildingModule } from './building/building.module'; +import { FloorModule } from './floor/floor.module'; @Module({ imports: [ ConfigModule.forRoot({ @@ -19,6 +20,7 @@ import { BuildingModule } from './building/building.module'; UserModule, CommunityModule, BuildingModule, + FloorModule, HomeModule, RoomModule, GroupModule, diff --git a/src/floor/controllers/floor.controller.ts b/src/floor/controllers/floor.controller.ts new file mode 100644 index 0000000..6e3de22 --- /dev/null +++ b/src/floor/controllers/floor.controller.ts @@ -0,0 +1,102 @@ +import { FloorService } from '../services/floor.service'; +import { + Body, + Controller, + Get, + HttpException, + HttpStatus, + Param, + Post, + Query, + UseGuards, +} from '@nestjs/common'; +import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; +import { AddFloorDto } from '../dtos/add.floor.dto'; +import { GetFloorChildDto } from '../dtos/get.floor.dto'; + +@ApiTags('Floor Module') +@Controller({ + version: '1', + path: 'floor', +}) +export class FloorController { + constructor(private readonly floorService: FloorService) {} + + // @ApiBearerAuth() + // @UseGuards(JwtAuthGuard) + @Post() + async addFloor(@Body() addFloorDto: AddFloorDto) { + try { + await this.floorService.addFloor(addFloorDto); + return { message: 'Floor added successfully' }; + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get(':floorUuid') + async getFloorByUuid(@Param('floorUuid') floorUuid: string) { + try { + const floor = await this.floorService.getFloorByUuid(floorUuid); + return floor; + } catch (error) { + if (error.status === 404) { + throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); + } else { + throw new HttpException( + error.message || 'Internal server error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('child/:floorUuid') + async getFloorChildByUuid( + @Param('floorUuid') floorUuid: string, + @Query() query: GetFloorChildDto, + ) { + try { + const floor = await this.floorService.getFloorChildByUuid( + floorUuid, + query, + ); + return floor; + } catch (error) { + if (error.status === 404) { + throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); + } else { + throw new HttpException( + error.message || 'Internal server error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('parent/:floorUuid') + async getFloorParentByUuid(@Param('floorUuid') floorUuid: string) { + try { + const floor = await this.floorService.getFloorParentByUuid(floorUuid); + return floor; + } catch (error) { + if (error.status === 404) { + throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); + } else { + throw new HttpException( + error.message || 'Internal server error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } +} diff --git a/src/floor/controllers/index.ts b/src/floor/controllers/index.ts new file mode 100644 index 0000000..99eb600 --- /dev/null +++ b/src/floor/controllers/index.ts @@ -0,0 +1 @@ +export * from './floor.controller'; diff --git a/src/floor/dtos/add.floor.dto.ts b/src/floor/dtos/add.floor.dto.ts new file mode 100644 index 0000000..9f2de58 --- /dev/null +++ b/src/floor/dtos/add.floor.dto.ts @@ -0,0 +1,23 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class AddFloorDto { + @ApiProperty({ + description: 'floorName', + required: true, + }) + @IsString() + @IsNotEmpty() + public floorName: string; + + @ApiProperty({ + description: 'buildingUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public buildingUuid: string; + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/floor/dtos/get.floor.dto.ts b/src/floor/dtos/get.floor.dto.ts new file mode 100644 index 0000000..23a8e56 --- /dev/null +++ b/src/floor/dtos/get.floor.dto.ts @@ -0,0 +1,51 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { + IsBoolean, + IsInt, + IsNotEmpty, + IsOptional, + IsString, + Min, +} from 'class-validator'; + +export class GetFloorDto { + @ApiProperty({ + description: 'floorUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public floorUuid: string; +} + +export class GetFloorChildDto { + @ApiProperty({ example: 1, description: 'Page number', required: true }) + @IsInt({ message: 'Page must be a number' }) + @Min(1, { message: 'Page must not be less than 1' }) + @IsNotEmpty() + public page: number; + + @ApiProperty({ + example: 10, + description: 'Number of items per page', + required: true, + }) + @IsInt({ message: 'Page size must be a number' }) + @Min(1, { message: 'Page size must not be less than 1' }) + @IsNotEmpty() + public pageSize: number; + + @ApiProperty({ + example: true, + description: 'Flag to determine whether to fetch full hierarchy', + required: false, + default: false, + }) + @IsOptional() + @IsBoolean() + @Transform((value) => { + return value.obj.includeSubSpaces === 'true'; + }) + public includeSubSpaces: boolean = false; +} diff --git a/src/floor/dtos/index.ts b/src/floor/dtos/index.ts new file mode 100644 index 0000000..9c08a9f --- /dev/null +++ b/src/floor/dtos/index.ts @@ -0,0 +1 @@ +export * from './add.floor.dto'; diff --git a/src/floor/floor.module.ts b/src/floor/floor.module.ts new file mode 100644 index 0000000..199f876 --- /dev/null +++ b/src/floor/floor.module.ts @@ -0,0 +1,29 @@ +import { + MiddlewareConsumer, + Module, + NestModule, + RequestMethod, +} from '@nestjs/common'; +import { FloorService } from './services/floor.service'; +import { FloorController } from './controllers/floor.controller'; +import { ConfigModule } from '@nestjs/config'; +import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space.type.repository.module'; +import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; +import { CheckBuildingTypeMiddleware } from 'src/middleware/CheckBuildingTypeMiddleware'; + +@Module({ + imports: [ConfigModule, SpaceRepositoryModule, SpaceTypeRepositoryModule], + controllers: [FloorController], + providers: [FloorService, SpaceRepository, SpaceTypeRepository], + exports: [FloorService], +}) +export class FloorModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + consumer.apply(CheckBuildingTypeMiddleware).forRoutes({ + path: '/floor', + method: RequestMethod.POST, + }); + } +} diff --git a/src/floor/interface/floor.interface.ts b/src/floor/interface/floor.interface.ts new file mode 100644 index 0000000..b70d504 --- /dev/null +++ b/src/floor/interface/floor.interface.ts @@ -0,0 +1,21 @@ +export interface GetFloorByUuidInterface { + uuid: string; + createdAt: Date; + updatedAt: Date; + name: string; + type: string; +} + +export interface FloorChildInterface { + uuid: string; + name: string; + type: string; + totalCount?: number; + children?: FloorChildInterface[]; +} +export interface FloorParentInterface { + uuid: string; + name: string; + type: string; + parent?: FloorParentInterface; +} diff --git a/src/floor/services/floor.service.ts b/src/floor/services/floor.service.ts new file mode 100644 index 0000000..089f17e --- /dev/null +++ b/src/floor/services/floor.service.ts @@ -0,0 +1,178 @@ +import { GetFloorChildDto } from '../dtos/get.floor.dto'; +import { SpaceTypeRepository } from '../../../libs/common/src/modules/space-type/repositories/space.type.repository'; +import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { AddFloorDto } from '../dtos'; +import { + FloorChildInterface, + FloorParentInterface, + GetFloorByUuidInterface, +} from '../interface/floor.interface'; +import { SpaceEntity } from '@app/common/modules/space/entities'; + +@Injectable() +export class FloorService { + constructor( + private readonly spaceRepository: SpaceRepository, + private readonly spaceTypeRepository: SpaceTypeRepository, + ) {} + + async addFloor(addFloorDto: AddFloorDto) { + try { + const spaceType = await this.spaceTypeRepository.findOne({ + where: { + type: 'floor', + }, + }); + + await this.spaceRepository.save({ + spaceName: addFloorDto.floorName, + parent: { uuid: addFloorDto.buildingUuid }, + spaceType: { uuid: spaceType.uuid }, + }); + } catch (err) { + throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + async getFloorByUuid(floorUuid: string): Promise { + try { + const floor = await this.spaceRepository.findOne({ + where: { + uuid: floorUuid, + spaceType: { + type: 'floor', + }, + }, + relations: ['spaceType'], + }); + if (!floor) { + throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); + } + return { + uuid: floor.uuid, + createdAt: floor.createdAt, + updatedAt: floor.updatedAt, + name: floor.spaceName, + type: floor.spaceType.type, + }; + } catch (err) { + throw new HttpException( + err.message, + err.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async getFloorChildByUuid( + floorUuid: string, + getFloorChildDto: GetFloorChildDto, + ): Promise { + const { includeSubSpaces, page, pageSize } = getFloorChildDto; + + const space = await this.spaceRepository.findOneOrFail({ + where: { uuid: floorUuid }, + relations: ['children', 'spaceType'], + }); + + if (space.spaceType.type !== 'floor') { + throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); + } + + const totalCount = await this.spaceRepository.count({ + where: { parent: { uuid: space.uuid } }, + }); + + const children = await this.buildHierarchy( + space, + includeSubSpaces, + page, + pageSize, + ); + + return { + uuid: space.uuid, + name: space.spaceName, + type: space.spaceType.type, + totalCount, + children, + }; + } + + private async buildHierarchy( + space: SpaceEntity, + includeSubSpaces: boolean, + page: number, + pageSize: number, + ): Promise { + const children = await this.spaceRepository.find({ + where: { parent: { uuid: space.uuid } }, + relations: ['spaceType'], + skip: (page - 1) * pageSize, + take: pageSize, + }); + + if (!children || children.length === 0 || !includeSubSpaces) { + return children + .filter( + (child) => + child.spaceType.type !== 'floor' && + child.spaceType.type !== 'building', + ) // Filter remaining floor and building types + .map((child) => ({ + uuid: child.uuid, + name: child.spaceName, + type: child.spaceType.type, + })); + } + + const childHierarchies = await Promise.all( + children + .filter( + (child) => + child.spaceType.type !== 'floor' && + child.spaceType.type !== 'building', + ) // Filter remaining floor and building types + .map(async (child) => ({ + uuid: child.uuid, + name: child.spaceName, + type: child.spaceType.type, + children: await this.buildHierarchy(child, true, 1, pageSize), + })), + ); + + return childHierarchies; + } + + async getFloorParentByUuid(floorUuid: string): Promise { + try { + const floor = await this.spaceRepository.findOne({ + where: { + uuid: floorUuid, + spaceType: { + type: 'floor', + }, + }, + relations: ['spaceType', 'parent', 'parent.spaceType'], + }); + if (!floor) { + throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); + } + + return { + uuid: floor.uuid, + name: floor.spaceName, + type: floor.spaceType.type, + parent: { + uuid: floor.parent.uuid, + name: floor.parent.spaceName, + type: floor.parent.spaceType.type, + }, + }; + } catch (err) { + throw new HttpException( + err.message, + err.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/floor/services/index.ts b/src/floor/services/index.ts new file mode 100644 index 0000000..e6f7946 --- /dev/null +++ b/src/floor/services/index.ts @@ -0,0 +1 @@ +export * from './floor.service'; diff --git a/src/middleware/CheckBuildingTypeMiddleware.ts b/src/middleware/CheckBuildingTypeMiddleware.ts new file mode 100644 index 0000000..dab04c1 --- /dev/null +++ b/src/middleware/CheckBuildingTypeMiddleware.ts @@ -0,0 +1,74 @@ +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { + Injectable, + NestMiddleware, + HttpStatus, + HttpException, +} from '@nestjs/common'; +import { Request, Response, NextFunction } from 'express'; + +@Injectable() +export class CheckBuildingTypeMiddleware implements NestMiddleware { + constructor(private readonly spaceRepository: SpaceRepository) {} + + async use(req: Request, res: Response, next: NextFunction) { + try { + // Destructure request body for cleaner code + const { floorName, buildingUuid } = req.body; + + // Guard clauses for early return + if (!floorName) { + return res.status(HttpStatus.BAD_REQUEST).json({ + statusCode: HttpStatus.BAD_REQUEST, + message: 'floorName is required', + }); + } + + if (!buildingUuid) { + return res.status(HttpStatus.BAD_REQUEST).json({ + statusCode: HttpStatus.BAD_REQUEST, + message: 'buildingUuid is required', + }); + } + + // Call function to check if building is a building + await this.checkBuildingIsBuilding(buildingUuid); + + // Call next middleware + next(); + } catch (error) { + // Handle errors + this.handleMiddlewareError(error, res); + } + } + + async checkBuildingIsBuilding(buildingUuid: string) { + const buildingData = await this.spaceRepository.findOne({ + where: { uuid: buildingUuid }, + relations: ['spaceType'], + }); + + // Throw error if building not found + if (!buildingData) { + throw new HttpException('Building not found', HttpStatus.NOT_FOUND); + } + + // Throw error if building is not of type 'building' + if (buildingData.spaceType.type !== 'building') { + throw new HttpException( + "buildingUuid is not of type 'building'", + HttpStatus.BAD_REQUEST, + ); + } + } + + // Function to handle middleware errors + private handleMiddlewareError(error: Error, res: Response) { + const status = + error instanceof HttpException + ? error.getStatus() + : HttpStatus.INTERNAL_SERVER_ERROR; + const message = error.message || 'Internal server error'; + res.status(status).json({ statusCode: status, message }); + } +} From 9e3544756e2c531c740ec531e7c77966b8e55e6b Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 13 Apr 2024 17:23:56 +0300 Subject: [PATCH 27/75] Refactor filtering logic in FloorService --- src/floor/services/floor.service.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/floor/services/floor.service.ts b/src/floor/services/floor.service.ts index 089f17e..3485b88 100644 --- a/src/floor/services/floor.service.ts +++ b/src/floor/services/floor.service.ts @@ -116,8 +116,9 @@ export class FloorService { .filter( (child) => child.spaceType.type !== 'floor' && - child.spaceType.type !== 'building', - ) // Filter remaining floor and building types + child.spaceType.type !== 'building' && + child.spaceType.type !== 'community', + ) // Filter remaining floor and building and community types .map((child) => ({ uuid: child.uuid, name: child.spaceName, @@ -130,8 +131,9 @@ export class FloorService { .filter( (child) => child.spaceType.type !== 'floor' && - child.spaceType.type !== 'building', - ) // Filter remaining floor and building types + child.spaceType.type !== 'building' && + child.spaceType.type !== 'community', + ) // Filter remaining floor and building and community types .map(async (child) => ({ uuid: child.uuid, name: child.spaceName, From 120939b4701722fc3f962d65b6dc0fb5c205408d Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 13 Apr 2024 17:25:48 +0300 Subject: [PATCH 28/75] Refactor middleware function names for clarity --- src/middleware/CheckBuildingTypeMiddleware.ts | 4 ++-- src/middleware/CheckCommunityTypeMiddleware.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/middleware/CheckBuildingTypeMiddleware.ts b/src/middleware/CheckBuildingTypeMiddleware.ts index dab04c1..170cb9b 100644 --- a/src/middleware/CheckBuildingTypeMiddleware.ts +++ b/src/middleware/CheckBuildingTypeMiddleware.ts @@ -32,7 +32,7 @@ export class CheckBuildingTypeMiddleware implements NestMiddleware { } // Call function to check if building is a building - await this.checkBuildingIsBuilding(buildingUuid); + await this.checkBuildingIsBuildingType(buildingUuid); // Call next middleware next(); @@ -42,7 +42,7 @@ export class CheckBuildingTypeMiddleware implements NestMiddleware { } } - async checkBuildingIsBuilding(buildingUuid: string) { + async checkBuildingIsBuildingType(buildingUuid: string) { const buildingData = await this.spaceRepository.findOne({ where: { uuid: buildingUuid }, relations: ['spaceType'], diff --git a/src/middleware/CheckCommunityTypeMiddleware.ts b/src/middleware/CheckCommunityTypeMiddleware.ts index 314b954..3eef679 100644 --- a/src/middleware/CheckCommunityTypeMiddleware.ts +++ b/src/middleware/CheckCommunityTypeMiddleware.ts @@ -32,7 +32,7 @@ export class CheckCommunityTypeMiddleware implements NestMiddleware { } // Call function to check if community is a building - await this.checkCommunityIsBuilding(communityUuid); + await this.checkCommunityIsCommunityType(communityUuid); // Call next middleware next(); @@ -42,7 +42,7 @@ export class CheckCommunityTypeMiddleware implements NestMiddleware { } } - async checkCommunityIsBuilding(communityUuid: string) { + async checkCommunityIsCommunityType(communityUuid: string) { const communityData = await this.spaceRepository.findOne({ where: { uuid: communityUuid }, relations: ['spaceType'], From c0e84d80d437c3a38dea54a6c9a480abb485d913 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 13 Apr 2024 17:55:28 +0300 Subject: [PATCH 29/75] Add unit module with controller, service, dtos, and middleware --- src/app.module.ts | 2 + src/middleware/CheckFloorTypeMiddleware.ts | 74 +++++++++ src/unit/controllers/index.ts | 1 + src/unit/controllers/unit.controller.ts | 99 ++++++++++++ src/unit/dtos/add.unit.dto.ts | 23 +++ src/unit/dtos/get.unit.dto.ts | 51 ++++++ src/unit/dtos/index.ts | 1 + src/unit/interface/unit.interface.ts | 21 +++ src/unit/services/index.ts | 1 + src/unit/services/unit.service.ts | 180 +++++++++++++++++++++ src/unit/unit.module.ts | 29 ++++ 11 files changed, 482 insertions(+) create mode 100644 src/middleware/CheckFloorTypeMiddleware.ts create mode 100644 src/unit/controllers/index.ts create mode 100644 src/unit/controllers/unit.controller.ts create mode 100644 src/unit/dtos/add.unit.dto.ts create mode 100644 src/unit/dtos/get.unit.dto.ts create mode 100644 src/unit/dtos/index.ts create mode 100644 src/unit/interface/unit.interface.ts create mode 100644 src/unit/services/index.ts create mode 100644 src/unit/services/unit.service.ts create mode 100644 src/unit/unit.module.ts diff --git a/src/app.module.ts b/src/app.module.ts index ff3bc78..1322e04 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -11,6 +11,7 @@ import { DeviceModule } from './device/device.module'; import { CommunityModule } from './community/community.module'; import { BuildingModule } from './building/building.module'; import { FloorModule } from './floor/floor.module'; +import { UnitModule } from './unit/unit.module'; @Module({ imports: [ ConfigModule.forRoot({ @@ -21,6 +22,7 @@ import { FloorModule } from './floor/floor.module'; CommunityModule, BuildingModule, FloorModule, + UnitModule, HomeModule, RoomModule, GroupModule, diff --git a/src/middleware/CheckFloorTypeMiddleware.ts b/src/middleware/CheckFloorTypeMiddleware.ts new file mode 100644 index 0000000..e096750 --- /dev/null +++ b/src/middleware/CheckFloorTypeMiddleware.ts @@ -0,0 +1,74 @@ +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { + Injectable, + NestMiddleware, + HttpStatus, + HttpException, +} from '@nestjs/common'; +import { Request, Response, NextFunction } from 'express'; + +@Injectable() +export class CheckFloorTypeMiddleware implements NestMiddleware { + constructor(private readonly spaceRepository: SpaceRepository) {} + + async use(req: Request, res: Response, next: NextFunction) { + try { + // Destructure request body for cleaner code + const { unitName, floorUuid } = req.body; + + // Guard clauses for early return + if (!unitName) { + return res.status(HttpStatus.BAD_REQUEST).json({ + statusCode: HttpStatus.BAD_REQUEST, + message: 'unitName is required', + }); + } + + if (!floorUuid) { + return res.status(HttpStatus.BAD_REQUEST).json({ + statusCode: HttpStatus.BAD_REQUEST, + message: 'floorUuid is required', + }); + } + + // Call function to check if floor is a floor + await this.checkFloorIsFloorType(floorUuid); + + // Call next middleware + next(); + } catch (error) { + // Handle errors + this.handleMiddlewareError(error, res); + } + } + + async checkFloorIsFloorType(floorUuid: string) { + const floorData = await this.spaceRepository.findOne({ + where: { uuid: floorUuid }, + relations: ['spaceType'], + }); + + // Throw error if floor not found + if (!floorData) { + throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); + } + + // Throw error if floor is not of type 'floor' + if (floorData.spaceType.type !== 'floor') { + throw new HttpException( + "floorUuid is not of type 'floor'", + HttpStatus.BAD_REQUEST, + ); + } + } + + // Function to handle middleware errors + private handleMiddlewareError(error: Error, res: Response) { + const status = + error instanceof HttpException + ? error.getStatus() + : HttpStatus.INTERNAL_SERVER_ERROR; + const message = error.message || 'Internal server error'; + res.status(status).json({ statusCode: status, message }); + } +} diff --git a/src/unit/controllers/index.ts b/src/unit/controllers/index.ts new file mode 100644 index 0000000..c8d7271 --- /dev/null +++ b/src/unit/controllers/index.ts @@ -0,0 +1 @@ +export * from './unit.controller'; diff --git a/src/unit/controllers/unit.controller.ts b/src/unit/controllers/unit.controller.ts new file mode 100644 index 0000000..40f35a1 --- /dev/null +++ b/src/unit/controllers/unit.controller.ts @@ -0,0 +1,99 @@ +import { UnitService } from '../services/unit.service'; +import { + Body, + Controller, + Get, + HttpException, + HttpStatus, + Param, + Post, + Query, + UseGuards, +} from '@nestjs/common'; +import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; +import { AddUnitDto } from '../dtos/add.unit.dto'; +import { GetUnitChildDto } from '../dtos/get.unit.dto'; + +@ApiTags('Unit Module') +@Controller({ + version: '1', + path: 'unit', +}) +export class UnitController { + constructor(private readonly unitService: UnitService) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post() + async addUnit(@Body() addUnitDto: AddUnitDto) { + try { + await this.unitService.addUnit(addUnitDto); + return { message: 'Unit added successfully' }; + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get(':unitUuid') + async getUnitByUuid(@Param('unitUuid') unitUuid: string) { + try { + const unit = await this.unitService.getUnitByUuid(unitUuid); + return unit; + } catch (error) { + if (error.status === 404) { + throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); + } else { + throw new HttpException( + error.message || 'Internal server error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('child/:unitUuid') + async getUnitChildByUuid( + @Param('unitUuid') unitUuid: string, + @Query() query: GetUnitChildDto, + ) { + try { + const unit = await this.unitService.getUnitChildByUuid(unitUuid, query); + return unit; + } catch (error) { + if (error.status === 404) { + throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); + } else { + throw new HttpException( + error.message || 'Internal server error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('parent/:unitUuid') + async getUnitParentByUuid(@Param('unitUuid') unitUuid: string) { + try { + const unit = await this.unitService.getUnitParentByUuid(unitUuid); + return unit; + } catch (error) { + if (error.status === 404) { + throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); + } else { + throw new HttpException( + error.message || 'Internal server error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } +} diff --git a/src/unit/dtos/add.unit.dto.ts b/src/unit/dtos/add.unit.dto.ts new file mode 100644 index 0000000..40f164a --- /dev/null +++ b/src/unit/dtos/add.unit.dto.ts @@ -0,0 +1,23 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class AddUnitDto { + @ApiProperty({ + description: 'unitName', + required: true, + }) + @IsString() + @IsNotEmpty() + public unitName: string; + + @ApiProperty({ + description: 'floorUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public floorUuid: string; + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/unit/dtos/get.unit.dto.ts b/src/unit/dtos/get.unit.dto.ts new file mode 100644 index 0000000..b96147b --- /dev/null +++ b/src/unit/dtos/get.unit.dto.ts @@ -0,0 +1,51 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { + IsBoolean, + IsInt, + IsNotEmpty, + IsOptional, + IsString, + Min, +} from 'class-validator'; + +export class GetUnitDto { + @ApiProperty({ + description: 'unitUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public unitUuid: string; +} + +export class GetUnitChildDto { + @ApiProperty({ example: 1, description: 'Page number', required: true }) + @IsInt({ message: 'Page must be a number' }) + @Min(1, { message: 'Page must not be less than 1' }) + @IsNotEmpty() + public page: number; + + @ApiProperty({ + example: 10, + description: 'Number of items per page', + required: true, + }) + @IsInt({ message: 'Page size must be a number' }) + @Min(1, { message: 'Page size must not be less than 1' }) + @IsNotEmpty() + public pageSize: number; + + @ApiProperty({ + example: true, + description: 'Flag to determine whether to fetch full hierarchy', + required: false, + default: false, + }) + @IsOptional() + @IsBoolean() + @Transform((value) => { + return value.obj.includeSubSpaces === 'true'; + }) + public includeSubSpaces: boolean = false; +} diff --git a/src/unit/dtos/index.ts b/src/unit/dtos/index.ts new file mode 100644 index 0000000..970d13d --- /dev/null +++ b/src/unit/dtos/index.ts @@ -0,0 +1 @@ +export * from './add.unit.dto'; diff --git a/src/unit/interface/unit.interface.ts b/src/unit/interface/unit.interface.ts new file mode 100644 index 0000000..8db6bfa --- /dev/null +++ b/src/unit/interface/unit.interface.ts @@ -0,0 +1,21 @@ +export interface GetUnitByUuidInterface { + uuid: string; + createdAt: Date; + updatedAt: Date; + name: string; + type: string; +} + +export interface UnitChildInterface { + uuid: string; + name: string; + type: string; + totalCount?: number; + children?: UnitChildInterface[]; +} +export interface UnitParentInterface { + uuid: string; + name: string; + type: string; + parent?: UnitParentInterface; +} diff --git a/src/unit/services/index.ts b/src/unit/services/index.ts new file mode 100644 index 0000000..0540c40 --- /dev/null +++ b/src/unit/services/index.ts @@ -0,0 +1 @@ +export * from './unit.service'; diff --git a/src/unit/services/unit.service.ts b/src/unit/services/unit.service.ts new file mode 100644 index 0000000..4906f7d --- /dev/null +++ b/src/unit/services/unit.service.ts @@ -0,0 +1,180 @@ +import { GetUnitChildDto } from '../dtos/get.unit.dto'; +import { SpaceTypeRepository } from '../../../libs/common/src/modules/space-type/repositories/space.type.repository'; +import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { AddUnitDto } from '../dtos'; +import { + UnitChildInterface, + UnitParentInterface, + GetUnitByUuidInterface, +} from '../interface/unit.interface'; +import { SpaceEntity } from '@app/common/modules/space/entities'; + +@Injectable() +export class UnitService { + constructor( + private readonly spaceRepository: SpaceRepository, + private readonly spaceTypeRepository: SpaceTypeRepository, + ) {} + + async addUnit(addUnitDto: AddUnitDto) { + try { + const spaceType = await this.spaceTypeRepository.findOne({ + where: { + type: 'unit', + }, + }); + + await this.spaceRepository.save({ + spaceName: addUnitDto.unitName, + parent: { uuid: addUnitDto.floorUuid }, + spaceType: { uuid: spaceType.uuid }, + }); + } catch (err) { + throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + async getUnitByUuid(unitUuid: string): Promise { + try { + const unit = await this.spaceRepository.findOne({ + where: { + uuid: unitUuid, + spaceType: { + type: 'unit', + }, + }, + relations: ['spaceType'], + }); + if (!unit) { + throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); + } + return { + uuid: unit.uuid, + createdAt: unit.createdAt, + updatedAt: unit.updatedAt, + name: unit.spaceName, + type: unit.spaceType.type, + }; + } catch (err) { + throw new HttpException( + err.message, + err.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async getUnitChildByUuid( + unitUuid: string, + getUnitChildDto: GetUnitChildDto, + ): Promise { + const { includeSubSpaces, page, pageSize } = getUnitChildDto; + + const space = await this.spaceRepository.findOneOrFail({ + where: { uuid: unitUuid }, + relations: ['children', 'spaceType'], + }); + + if (space.spaceType.type !== 'unit') { + throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); + } + + const totalCount = await this.spaceRepository.count({ + where: { parent: { uuid: space.uuid } }, + }); + + const children = await this.buildHierarchy( + space, + includeSubSpaces, + page, + pageSize, + ); + + return { + uuid: space.uuid, + name: space.spaceName, + type: space.spaceType.type, + totalCount, + children, + }; + } + + private async buildHierarchy( + space: SpaceEntity, + includeSubSpaces: boolean, + page: number, + pageSize: number, + ): Promise { + const children = await this.spaceRepository.find({ + where: { parent: { uuid: space.uuid } }, + relations: ['spaceType'], + skip: (page - 1) * pageSize, + take: pageSize, + }); + + if (!children || children.length === 0 || !includeSubSpaces) { + return children + .filter( + (child) => + child.spaceType.type !== 'unit' && + child.spaceType.type !== 'floor' && + child.spaceType.type !== 'community', + ) // Filter remaining unit and floor and community types + .map((child) => ({ + uuid: child.uuid, + name: child.spaceName, + type: child.spaceType.type, + })); + } + + const childHierarchies = await Promise.all( + children + .filter( + (child) => + child.spaceType.type !== 'unit' && + child.spaceType.type !== 'floor' && + child.spaceType.type !== 'community', + ) // Filter remaining unit and floor and community types + .map(async (child) => ({ + uuid: child.uuid, + name: child.spaceName, + type: child.spaceType.type, + children: await this.buildHierarchy(child, true, 1, pageSize), + })), + ); + + return childHierarchies; + } + + async getUnitParentByUuid(unitUuid: string): Promise { + try { + const unit = await this.spaceRepository.findOne({ + where: { + uuid: unitUuid, + spaceType: { + type: 'unit', + }, + }, + relations: ['spaceType', 'parent', 'parent.spaceType'], + }); + if (!unit) { + throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); + } + + return { + uuid: unit.uuid, + name: unit.spaceName, + type: unit.spaceType.type, + parent: { + uuid: unit.parent.uuid, + name: unit.parent.spaceName, + type: unit.parent.spaceType.type, + }, + }; + } catch (err) { + throw new HttpException( + err.message, + err.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/unit/unit.module.ts b/src/unit/unit.module.ts new file mode 100644 index 0000000..0ebee41 --- /dev/null +++ b/src/unit/unit.module.ts @@ -0,0 +1,29 @@ +import { + MiddlewareConsumer, + Module, + NestModule, + RequestMethod, +} from '@nestjs/common'; +import { UnitService } from './services/unit.service'; +import { UnitController } from './controllers/unit.controller'; +import { ConfigModule } from '@nestjs/config'; +import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space.type.repository.module'; +import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; +import { CheckFloorTypeMiddleware } from 'src/middleware/CheckFloorTypeMiddleware'; + +@Module({ + imports: [ConfigModule, SpaceRepositoryModule, SpaceTypeRepositoryModule], + controllers: [UnitController], + providers: [UnitService, SpaceRepository, SpaceTypeRepository], + exports: [UnitService], +}) +export class UnitModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + consumer.apply(CheckFloorTypeMiddleware).forRoutes({ + path: '/unit', + method: RequestMethod.POST, + }); + } +} From 4401b4358c331739db6d81a065834108ff195e10 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 13 Apr 2024 18:07:54 +0300 Subject: [PATCH 30/75] Refactor filtering logic in UnitService --- src/unit/services/unit.service.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/unit/services/unit.service.ts b/src/unit/services/unit.service.ts index 4906f7d..6969717 100644 --- a/src/unit/services/unit.service.ts +++ b/src/unit/services/unit.service.ts @@ -117,8 +117,9 @@ export class UnitService { (child) => child.spaceType.type !== 'unit' && child.spaceType.type !== 'floor' && - child.spaceType.type !== 'community', - ) // Filter remaining unit and floor and community types + child.spaceType.type !== 'community' && + child.spaceType.type !== 'unit', + ) // Filter remaining unit and floor and community and unit types .map((child) => ({ uuid: child.uuid, name: child.spaceName, @@ -132,8 +133,9 @@ export class UnitService { (child) => child.spaceType.type !== 'unit' && child.spaceType.type !== 'floor' && - child.spaceType.type !== 'community', - ) // Filter remaining unit and floor and community types + child.spaceType.type !== 'community' && + child.spaceType.type !== 'unit', + ) // Filter remaining unit and floor and community and unit types .map(async (child) => ({ uuid: child.uuid, name: child.spaceName, From dffe347adf7fbb56a1263ce597e0096269f0a4a9 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 14 Apr 2024 08:54:44 +0300 Subject: [PATCH 31/75] Add CheckUnitTypeMiddleware and update room module --- src/app.module.ts | 1 + src/middleware/CheckUnitTypeMiddleware.ts | 74 ++++++++++ src/room/controllers/room.controller.ts | 73 ++++++---- src/room/dtos/add.room.dto.ts | 11 +- src/room/interface/room.interface.ts | 14 ++ src/room/interfaces/get.room.interface.ts | 12 -- src/room/room.module.ts | 26 +++- src/room/services/room.service.ts | 159 ++++++++++------------ 8 files changed, 236 insertions(+), 134 deletions(-) create mode 100644 src/middleware/CheckUnitTypeMiddleware.ts create mode 100644 src/room/interface/room.interface.ts delete mode 100644 src/room/interfaces/get.room.interface.ts diff --git a/src/app.module.ts b/src/app.module.ts index 1322e04..7050ec6 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -23,6 +23,7 @@ import { UnitModule } from './unit/unit.module'; BuildingModule, FloorModule, UnitModule, + RoomModule, HomeModule, RoomModule, GroupModule, diff --git a/src/middleware/CheckUnitTypeMiddleware.ts b/src/middleware/CheckUnitTypeMiddleware.ts new file mode 100644 index 0000000..7f5ba6a --- /dev/null +++ b/src/middleware/CheckUnitTypeMiddleware.ts @@ -0,0 +1,74 @@ +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { + Injectable, + NestMiddleware, + HttpStatus, + HttpException, +} from '@nestjs/common'; +import { Request, Response, NextFunction } from 'express'; + +@Injectable() +export class CheckUnitTypeMiddleware implements NestMiddleware { + constructor(private readonly spaceRepository: SpaceRepository) {} + + async use(req: Request, res: Response, next: NextFunction) { + try { + // Destructure request body for cleaner code + const { roomName, unitUuid } = req.body; + + // Guard clauses for early return + if (!roomName) { + return res.status(HttpStatus.BAD_REQUEST).json({ + statusCode: HttpStatus.BAD_REQUEST, + message: 'roomName is required', + }); + } + + if (!unitUuid) { + return res.status(HttpStatus.BAD_REQUEST).json({ + statusCode: HttpStatus.BAD_REQUEST, + message: 'unitUuid is required', + }); + } + + // Call function to check if unit is a unit + await this.checkFloorIsFloorType(unitUuid); + + // Call next middleware + next(); + } catch (error) { + // Handle errors + this.handleMiddlewareError(error, res); + } + } + + async checkFloorIsFloorType(unitUuid: string) { + const unitData = await this.spaceRepository.findOne({ + where: { uuid: unitUuid }, + relations: ['spaceType'], + }); + + // Throw error if unit not found + if (!unitData) { + throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); + } + + // Throw error if unit is not of type 'unit' + if (unitData.spaceType.type !== 'unit') { + throw new HttpException( + "unitUuid is not of type 'unit'", + HttpStatus.BAD_REQUEST, + ); + } + } + + // Function to handle middleware errors + private handleMiddlewareError(error: Error, res: Response) { + const status = + error instanceof HttpException + ? error.getStatus() + : HttpStatus.INTERNAL_SERVER_ERROR; + const message = error.message || 'Internal server error'; + res.status(status).json({ statusCode: status, message }); + } +} diff --git a/src/room/controllers/room.controller.ts b/src/room/controllers/room.controller.ts index b90210c..a998077 100644 --- a/src/room/controllers/room.controller.ts +++ b/src/room/controllers/room.controller.ts @@ -3,10 +3,11 @@ import { Body, Controller, Get, + HttpException, + HttpStatus, + Param, Post, UseGuards, - Query, - Param, } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; @@ -20,34 +21,56 @@ import { AddRoomDto } from '../dtos/add.room.dto'; export class RoomController { constructor(private readonly roomService: RoomService) {} - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Get() - async getRoomsByHomeId(@Query('homeId') homeId: string) { - try { - return await this.roomService.getRoomsByHomeId(homeId); - } catch (err) { - throw new Error(err); - } - } - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Get(':roomId') - async getRoomsByRoomId(@Param('roomId') roomId: string) { - try { - return await this.roomService.getRoomsByRoomId(roomId); - } catch (err) { - throw new Error(err); - } - } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post() async addRoom(@Body() addRoomDto: AddRoomDto) { try { - return await this.roomService.addRoom(addRoomDto); - } catch (err) { - throw new Error(err); + await this.roomService.addRoom(addRoomDto); + return { message: 'Room added successfully' }; + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get(':roomUuid') + async getRoomByUuid(@Param('roomUuid') roomUuid: string) { + try { + const room = await this.roomService.getRoomByUuid(roomUuid); + return room; + } catch (error) { + if (error.status === 404) { + throw new HttpException('Room not found', HttpStatus.NOT_FOUND); + } else { + throw new HttpException( + error.message || 'Internal server error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('parent/:roomUuid') + async getRoomParentByUuid(@Param('roomUuid') roomUuid: string) { + try { + const room = await this.roomService.getRoomParentByUuid(roomUuid); + return room; + } catch (error) { + if (error.status === 404) { + throw new HttpException('Room not found', HttpStatus.NOT_FOUND); + } else { + throw new HttpException( + error.message || 'Internal server error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } } } } diff --git a/src/room/dtos/add.room.dto.ts b/src/room/dtos/add.room.dto.ts index 3d39559..69425b1 100644 --- a/src/room/dtos/add.room.dto.ts +++ b/src/room/dtos/add.room.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString, IsNumberString } from 'class-validator'; +import { IsNotEmpty, IsString } from 'class-validator'; export class AddRoomDto { @ApiProperty({ @@ -11,10 +11,13 @@ export class AddRoomDto { public roomName: string; @ApiProperty({ - description: 'homeId', + description: 'unitUuid', required: true, }) - @IsNumberString() + @IsString() @IsNotEmpty() - public homeId: string; + public unitUuid: string; + constructor(dto: Partial) { + Object.assign(this, dto); + } } diff --git a/src/room/interface/room.interface.ts b/src/room/interface/room.interface.ts new file mode 100644 index 0000000..f98a8c9 --- /dev/null +++ b/src/room/interface/room.interface.ts @@ -0,0 +1,14 @@ +export interface GetRoomByUuidInterface { + uuid: string; + createdAt: Date; + updatedAt: Date; + name: string; + type: string; +} + +export interface RoomParentInterface { + uuid: string; + name: string; + type: string; + parent?: RoomParentInterface; +} diff --git a/src/room/interfaces/get.room.interface.ts b/src/room/interfaces/get.room.interface.ts deleted file mode 100644 index 56c0d49..0000000 --- a/src/room/interfaces/get.room.interface.ts +++ /dev/null @@ -1,12 +0,0 @@ -export class GetRoomDetailsInterface { - result: { - id: string; - name: string; - root_id: string; - }; -} -export class GetRoomsIdsInterface { - result: { - data: []; - }; -} diff --git a/src/room/room.module.ts b/src/room/room.module.ts index cd520c6..660fbec 100644 --- a/src/room/room.module.ts +++ b/src/room/room.module.ts @@ -1,11 +1,29 @@ -import { Module } from '@nestjs/common'; +import { + MiddlewareConsumer, + Module, + NestModule, + RequestMethod, +} from '@nestjs/common'; import { RoomService } from './services/room.service'; import { RoomController } from './controllers/room.controller'; import { ConfigModule } from '@nestjs/config'; +import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space.type.repository.module'; +import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; +import { CheckUnitTypeMiddleware } from 'src/middleware/CheckUnitTypeMiddleware'; + @Module({ - imports: [ConfigModule], + imports: [ConfigModule, SpaceRepositoryModule, SpaceTypeRepositoryModule], controllers: [RoomController], - providers: [RoomService], + providers: [RoomService, SpaceRepository, SpaceTypeRepository], exports: [RoomService], }) -export class RoomModule {} +export class RoomModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + consumer.apply(CheckUnitTypeMiddleware).forRoutes({ + path: '/room', + method: RequestMethod.POST, + }); + } +} diff --git a/src/room/services/room.service.ts b/src/room/services/room.service.ts index 095e8df..ae0d403 100644 --- a/src/room/services/room.service.ts +++ b/src/room/services/room.service.ts @@ -1,114 +1,95 @@ +import { SpaceTypeRepository } from '../../../libs/common/src/modules/space-type/repositories/space.type.repository'; import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; -import { TuyaContext } from '@tuya/tuya-connector-nodejs'; -import { ConfigService } from '@nestjs/config'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; import { AddRoomDto } from '../dtos'; import { - GetRoomDetailsInterface, - GetRoomsIdsInterface, -} from '../interfaces/get.room.interface'; + RoomParentInterface, + GetRoomByUuidInterface, +} from '../interface/room.interface'; @Injectable() export class RoomService { - private tuya: TuyaContext; - constructor(private readonly configService: ConfigService) { - const accessKey = this.configService.get('auth-config.ACCESS_KEY'); - const secretKey = this.configService.get('auth-config.SECRET_KEY'); - // const clientId = this.configService.get('auth-config.CLIENT_ID'); - this.tuya = new TuyaContext({ - baseUrl: 'https://openapi.tuyaeu.com', - accessKey, - secretKey, - }); - } + constructor( + private readonly spaceRepository: SpaceRepository, + private readonly spaceTypeRepository: SpaceTypeRepository, + ) {} - async getRoomsByHomeId(homeId: string) { - try { - const roomsIds = await this.getRoomsIds(homeId); - - const roomsDetails = await Promise.all( - roomsIds.result.data.map(async (roomId) => { - const roomData = await this.getRoomDetails(roomId); - return { - roomId: roomData?.result?.id, - roomName: roomData ? roomData.result.name : null, - }; - }), - ); - - return roomsDetails; - } catch (error) { - throw new HttpException( - 'Error fetching rooms', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - async getRoomsIds(homeId: string): Promise { - try { - const path = `/v2.0/cloud/space/child`; - const response = await this.tuya.request({ - method: 'GET', - path, - query: { space_id: homeId }, - }); - return response as GetRoomsIdsInterface; - } catch (error) { - throw new HttpException( - 'Error fetching rooms ids', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - async getRoomDetails(roomId: string): Promise { - // Added return type - try { - const path = `/v2.0/cloud/space/${roomId}`; - const response = await this.tuya.request({ - method: 'GET', - path, - }); - - return response as GetRoomDetailsInterface; // Cast response to RoomData - } catch (error) { - throw new HttpException( - 'Error fetching rooms details', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } async addRoom(addRoomDto: AddRoomDto) { try { - const path = `/v2.0/cloud/space/creation`; - const data = await this.tuya.request({ - method: 'POST', - path, - body: { name: addRoomDto.roomName, parent_id: addRoomDto.homeId }, + const spaceType = await this.spaceTypeRepository.findOne({ + where: { + type: 'room', + }, }); + await this.spaceRepository.save({ + spaceName: addRoomDto.roomName, + parent: { uuid: addRoomDto.unitUuid }, + spaceType: { uuid: spaceType.uuid }, + }); + } catch (err) { + throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + async getRoomByUuid(roomUuid: string): Promise { + try { + const room = await this.spaceRepository.findOne({ + where: { + uuid: roomUuid, + spaceType: { + type: 'room', + }, + }, + relations: ['spaceType'], + }); + if (!room) { + throw new HttpException('Room not found', HttpStatus.NOT_FOUND); + } return { - success: data.success, - roomId: data.result, + uuid: room.uuid, + createdAt: room.createdAt, + updatedAt: room.updatedAt, + name: room.spaceName, + type: room.spaceType.type, }; - } catch (error) { + } catch (err) { throw new HttpException( - 'Error adding room', - HttpStatus.INTERNAL_SERVER_ERROR, + err.message, + err.status || HttpStatus.INTERNAL_SERVER_ERROR, ); } } - async getRoomsByRoomId(roomId: string) { + + async getRoomParentByUuid(roomUuid: string): Promise { try { - const response = await this.getRoomDetails(roomId); + const room = await this.spaceRepository.findOne({ + where: { + uuid: roomUuid, + spaceType: { + type: 'room', + }, + }, + relations: ['spaceType', 'parent', 'parent.spaceType'], + }); + if (!room) { + throw new HttpException('Room not found', HttpStatus.NOT_FOUND); + } return { - homeId: response.result.root_id, - roomId: response.result.id, - roomName: response.result.name, + uuid: room.uuid, + name: room.spaceName, + type: room.spaceType.type, + parent: { + uuid: room.parent.uuid, + name: room.parent.spaceName, + type: room.parent.spaceType.type, + }, }; - } catch (error) { + } catch (err) { throw new HttpException( - 'Error fetching rooms', - HttpStatus.INTERNAL_SERVER_ERROR, + err.message, + err.status || HttpStatus.INTERNAL_SERVER_ERROR, ); } } From 42a4d0cc11dbd009c17935515d67cab5af7f0a43 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 14 Apr 2024 10:17:11 +0300 Subject: [PATCH 32/75] Add endpoint to rename community by UUID --- .../controllers/community.controller.ts | 27 ++++++++++ src/community/dtos/update.community.dto.ts | 16 ++++++ .../interface/community.interface.ts | 5 ++ src/community/services/community.service.ts | 51 ++++++++++++++++++- 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 src/community/dtos/update.community.dto.ts diff --git a/src/community/controllers/community.controller.ts b/src/community/controllers/community.controller.ts index 6b1a5a7..1a07791 100644 --- a/src/community/controllers/community.controller.ts +++ b/src/community/controllers/community.controller.ts @@ -7,6 +7,7 @@ import { HttpStatus, Param, Post, + Put, Query, UseGuards, } from '@nestjs/common'; @@ -14,6 +15,7 @@ import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { AddCommunityDto } from '../dtos/add.community.dto'; import { GetCommunityChildDto } from '../dtos/get.community.dto'; +import { UpdateCommunityNameDto } from '../dtos/update.community.dto'; @ApiTags('Community Module') @Controller({ @@ -82,4 +84,29 @@ export class CommunityController { } } } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Put('rename/:communityUuid') + async renameCommunityByUuid( + @Param('communityUuid') communityUuid: string, + @Body() updateCommunityDto: UpdateCommunityNameDto, + ) { + try { + const community = await this.communityService.renameCommunityByUuid( + communityUuid, + updateCommunityDto, + ); + return community; + } catch (error) { + if (error.status === 404) { + throw new HttpException('Community not found', HttpStatus.NOT_FOUND); + } else { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } } diff --git a/src/community/dtos/update.community.dto.ts b/src/community/dtos/update.community.dto.ts new file mode 100644 index 0000000..6f15d43 --- /dev/null +++ b/src/community/dtos/update.community.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class UpdateCommunityNameDto { + @ApiProperty({ + description: 'communityName', + required: true, + }) + @IsString() + @IsNotEmpty() + public communityName: string; + + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/community/interface/community.interface.ts b/src/community/interface/community.interface.ts index 7f769d3..ca18aca 100644 --- a/src/community/interface/community.interface.ts +++ b/src/community/interface/community.interface.ts @@ -13,3 +13,8 @@ export interface CommunityChildInterface { totalCount?: number; children?: CommunityChildInterface[]; } +export interface RenameCommunityByUuidInterface { + uuid: string; + name: string; + type: string; +} diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index a917d94..33cab98 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -1,13 +1,20 @@ import { GetCommunityChildDto } from './../dtos/get.community.dto'; import { SpaceTypeRepository } from './../../../libs/common/src/modules/space-type/repositories/space.type.repository'; -import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { + Injectable, + HttpException, + HttpStatus, + BadRequestException, +} from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { AddCommunityDto } from '../dtos'; import { CommunityChildInterface, GetCommunityByUuidInterface, + RenameCommunityByUuidInterface, } from '../interface/community.interface'; import { SpaceEntity } from '@app/common/modules/space/entities'; +import { UpdateCommunityNameDto } from '../dtos/update.community.dto'; @Injectable() export class CommunityService { @@ -131,4 +138,46 @@ export class CommunityService { return childHierarchies; } + async renameCommunityByUuid( + communityUuid: string, + updateCommunityDto: UpdateCommunityNameDto, + ): Promise { + try { + const community = await this.spaceRepository.findOneOrFail({ + where: { uuid: communityUuid }, + relations: ['spaceType'], + }); + + if ( + !community || + !community.spaceType || + community.spaceType.type !== 'community' + ) { + throw new BadRequestException('Invalid community UUID'); + } + + await this.spaceRepository.update( + { uuid: communityUuid }, + { spaceName: updateCommunityDto.communityName }, + ); + + // Fetch the updated community + const updatedCommunity = await this.spaceRepository.findOneOrFail({ + where: { uuid: communityUuid }, + relations: ['spaceType'], + }); + + return { + uuid: updatedCommunity.uuid, + name: updatedCommunity.spaceName, + type: updatedCommunity.spaceType.type, + }; + } catch (err) { + if (err instanceof BadRequestException) { + throw err; // Re-throw BadRequestException + } else { + throw new HttpException('Community not found', HttpStatus.NOT_FOUND); + } + } + } } From a9be4d315a4a233d5d7cee7bf86330733fa6e155 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 14 Apr 2024 10:31:49 +0300 Subject: [PATCH 33/75] Add endpoint to rename building by UUID --- .../controllers/building.controller.ts | 26 ++++++++++ src/building/dtos/update.building.dto.ts | 16 ++++++ src/building/interface/building.interface.ts | 5 ++ src/building/services/building.service.ts | 52 ++++++++++++++++++- 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 src/building/dtos/update.building.dto.ts diff --git a/src/building/controllers/building.controller.ts b/src/building/controllers/building.controller.ts index 17e01d4..01d278c 100644 --- a/src/building/controllers/building.controller.ts +++ b/src/building/controllers/building.controller.ts @@ -7,6 +7,7 @@ import { HttpStatus, Param, Post, + Put, Query, UseGuards, } from '@nestjs/common'; @@ -14,6 +15,7 @@ import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { AddBuildingDto } from '../dtos/add.building.dto'; import { GetBuildingChildDto } from '../dtos/get.building.dto'; +import { UpdateBuildingNameDto } from '../dtos/update.building.dto'; @ApiTags('Building Module') @Controller({ @@ -101,4 +103,28 @@ export class BuildingController { } } } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Put('rename/:buildingUuid') + async renameBuildingByUuid( + @Param('buildingUuid') buildingUuid: string, + @Body() updateBuildingDto: UpdateBuildingNameDto, + ) { + try { + const building = await this.buildingService.renameBuildingByUuid( + buildingUuid, + updateBuildingDto, + ); + return building; + } catch (error) { + if (error.status === 404) { + throw new HttpException('Building not found', HttpStatus.NOT_FOUND); + } else { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } } diff --git a/src/building/dtos/update.building.dto.ts b/src/building/dtos/update.building.dto.ts new file mode 100644 index 0000000..0f07cbe --- /dev/null +++ b/src/building/dtos/update.building.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class UpdateBuildingNameDto { + @ApiProperty({ + description: 'buildingName', + required: true, + }) + @IsString() + @IsNotEmpty() + public buildingName: string; + + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/building/interface/building.interface.ts b/src/building/interface/building.interface.ts index 92852a7..4bdf760 100644 --- a/src/building/interface/building.interface.ts +++ b/src/building/interface/building.interface.ts @@ -19,3 +19,8 @@ export interface BuildingParentInterface { type: string; parent?: BuildingParentInterface; } +export interface RenameBuildingByUuidInterface { + uuid: string; + name: string; + type: string; +} diff --git a/src/building/services/building.service.ts b/src/building/services/building.service.ts index fbaf8cb..528d7e4 100644 --- a/src/building/services/building.service.ts +++ b/src/building/services/building.service.ts @@ -1,14 +1,21 @@ import { GetBuildingChildDto } from '../dtos/get.building.dto'; import { SpaceTypeRepository } from '../../../libs/common/src/modules/space-type/repositories/space.type.repository'; -import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { + Injectable, + HttpException, + HttpStatus, + BadRequestException, +} from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { AddBuildingDto } from '../dtos'; import { BuildingChildInterface, BuildingParentInterface, GetBuildingByUuidInterface, + RenameBuildingByUuidInterface, } from '../interface/building.interface'; import { SpaceEntity } from '@app/common/modules/space/entities'; +import { UpdateBuildingNameDto } from '../dtos/update.building.dto'; @Injectable() export class BuildingService { @@ -179,4 +186,47 @@ export class BuildingService { ); } } + + async renameBuildingByUuid( + buildingUuid: string, + updateBuildingNameDto: UpdateBuildingNameDto, + ): Promise { + try { + const building = await this.spaceRepository.findOneOrFail({ + where: { uuid: buildingUuid }, + relations: ['spaceType'], + }); + + if ( + !building || + !building.spaceType || + building.spaceType.type !== 'building' + ) { + throw new BadRequestException('Invalid building UUID'); + } + + await this.spaceRepository.update( + { uuid: buildingUuid }, + { spaceName: updateBuildingNameDto.buildingName }, + ); + + // Fetch the updated building + const updatedBuilding = await this.spaceRepository.findOneOrFail({ + where: { uuid: buildingUuid }, + relations: ['spaceType'], + }); + + return { + uuid: updatedBuilding.uuid, + name: updatedBuilding.spaceName, + type: updatedBuilding.spaceType.type, + }; + } catch (err) { + if (err instanceof BadRequestException) { + throw err; // Re-throw BadRequestException + } else { + throw new HttpException('Building not found', HttpStatus.NOT_FOUND); + } + } + } } From 8dca50b24d35be2af16270276411d604beb04d91 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 14 Apr 2024 10:40:03 +0300 Subject: [PATCH 34/75] Add endpoint to rename floor by UUID --- src/floor/controllers/floor.controller.ts | 27 +++++++++++++ src/floor/dtos/update.floor.dto.ts | 16 ++++++++ src/floor/interface/floor.interface.ts | 5 +++ src/floor/services/floor.service.ts | 48 ++++++++++++++++++++++- 4 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 src/floor/dtos/update.floor.dto.ts diff --git a/src/floor/controllers/floor.controller.ts b/src/floor/controllers/floor.controller.ts index 6e3de22..67c7b44 100644 --- a/src/floor/controllers/floor.controller.ts +++ b/src/floor/controllers/floor.controller.ts @@ -7,6 +7,7 @@ import { HttpStatus, Param, Post, + Put, Query, UseGuards, } from '@nestjs/common'; @@ -14,6 +15,7 @@ import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { AddFloorDto } from '../dtos/add.floor.dto'; import { GetFloorChildDto } from '../dtos/get.floor.dto'; +import { UpdateFloorNameDto } from '../dtos/update.floor.dto'; @ApiTags('Floor Module') @Controller({ @@ -99,4 +101,29 @@ export class FloorController { } } } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Put('rename/:floorUuid') + async renameFloorByUuid( + @Param('floorUuid') floorUuid: string, + @Body() updateFloorNameDto: UpdateFloorNameDto, + ) { + try { + const floor = await this.floorService.renameFloorByUuid( + floorUuid, + updateFloorNameDto, + ); + return floor; + } catch (error) { + if (error.status === 404) { + throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); + } else { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } } diff --git a/src/floor/dtos/update.floor.dto.ts b/src/floor/dtos/update.floor.dto.ts new file mode 100644 index 0000000..11c97b0 --- /dev/null +++ b/src/floor/dtos/update.floor.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class UpdateFloorNameDto { + @ApiProperty({ + description: 'floorName', + required: true, + }) + @IsString() + @IsNotEmpty() + public floorName: string; + + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/floor/interface/floor.interface.ts b/src/floor/interface/floor.interface.ts index b70d504..eb6a5ec 100644 --- a/src/floor/interface/floor.interface.ts +++ b/src/floor/interface/floor.interface.ts @@ -19,3 +19,8 @@ export interface FloorParentInterface { type: string; parent?: FloorParentInterface; } +export interface RenameFloorByUuidInterface { + uuid: string; + name: string; + type: string; +} diff --git a/src/floor/services/floor.service.ts b/src/floor/services/floor.service.ts index 3485b88..bbf97c4 100644 --- a/src/floor/services/floor.service.ts +++ b/src/floor/services/floor.service.ts @@ -1,14 +1,21 @@ import { GetFloorChildDto } from '../dtos/get.floor.dto'; import { SpaceTypeRepository } from '../../../libs/common/src/modules/space-type/repositories/space.type.repository'; -import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { + Injectable, + HttpException, + HttpStatus, + BadRequestException, +} from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { AddFloorDto } from '../dtos'; import { FloorChildInterface, FloorParentInterface, GetFloorByUuidInterface, + RenameFloorByUuidInterface, } from '../interface/floor.interface'; import { SpaceEntity } from '@app/common/modules/space/entities'; +import { UpdateFloorNameDto } from '../dtos/update.floor.dto'; @Injectable() export class FloorService { @@ -177,4 +184,43 @@ export class FloorService { ); } } + + async renameFloorByUuid( + floorUuid: string, + updateFloorDto: UpdateFloorNameDto, + ): Promise { + try { + const floor = await this.spaceRepository.findOneOrFail({ + where: { uuid: floorUuid }, + relations: ['spaceType'], + }); + + if (!floor || !floor.spaceType || floor.spaceType.type !== 'floor') { + throw new BadRequestException('Invalid floor UUID'); + } + + await this.spaceRepository.update( + { uuid: floorUuid }, + { spaceName: updateFloorDto.floorName }, + ); + + // Fetch the updated floor + const updatedFloor = await this.spaceRepository.findOneOrFail({ + where: { uuid: floorUuid }, + relations: ['spaceType'], + }); + + return { + uuid: updatedFloor.uuid, + name: updatedFloor.spaceName, + type: updatedFloor.spaceType.type, + }; + } catch (err) { + if (err instanceof BadRequestException) { + throw err; // Re-throw BadRequestException + } else { + throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); + } + } + } } From bf60303ddc88ad9ede708874e70a9b8293af3340 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 14 Apr 2024 10:47:54 +0300 Subject: [PATCH 35/75] Add endpoint to rename unit by UUID --- src/unit/controllers/unit.controller.ts | 27 ++++++++++++++ src/unit/dtos/update.unit.dto.ts | 16 +++++++++ src/unit/interface/unit.interface.ts | 5 +++ src/unit/services/unit.service.ts | 48 ++++++++++++++++++++++++- 4 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 src/unit/dtos/update.unit.dto.ts diff --git a/src/unit/controllers/unit.controller.ts b/src/unit/controllers/unit.controller.ts index 40f35a1..cc746c8 100644 --- a/src/unit/controllers/unit.controller.ts +++ b/src/unit/controllers/unit.controller.ts @@ -7,6 +7,7 @@ import { HttpStatus, Param, Post, + Put, Query, UseGuards, } from '@nestjs/common'; @@ -14,6 +15,7 @@ import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { AddUnitDto } from '../dtos/add.unit.dto'; import { GetUnitChildDto } from '../dtos/get.unit.dto'; +import { UpdateUnitNameDto } from '../dtos/update.unit.dto'; @ApiTags('Unit Module') @Controller({ @@ -96,4 +98,29 @@ export class UnitController { } } } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Put('rename/:unitUuid') + async renameUnitByUuid( + @Param('unitUuid') unitUuid: string, + @Body() updateUnitNameDto: UpdateUnitNameDto, + ) { + try { + const unit = await this.unitService.renameUnitByUuid( + unitUuid, + updateUnitNameDto, + ); + return unit; + } catch (error) { + if (error.status === 404) { + throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); + } else { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } } diff --git a/src/unit/dtos/update.unit.dto.ts b/src/unit/dtos/update.unit.dto.ts new file mode 100644 index 0000000..2d69902 --- /dev/null +++ b/src/unit/dtos/update.unit.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class UpdateUnitNameDto { + @ApiProperty({ + description: 'unitName', + required: true, + }) + @IsString() + @IsNotEmpty() + public unitName: string; + + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/unit/interface/unit.interface.ts b/src/unit/interface/unit.interface.ts index 8db6bfa..8502a86 100644 --- a/src/unit/interface/unit.interface.ts +++ b/src/unit/interface/unit.interface.ts @@ -19,3 +19,8 @@ export interface UnitParentInterface { type: string; parent?: UnitParentInterface; } +export interface RenameUnitByUuidInterface { + uuid: string; + name: string; + type: string; +} diff --git a/src/unit/services/unit.service.ts b/src/unit/services/unit.service.ts index 6969717..1bff975 100644 --- a/src/unit/services/unit.service.ts +++ b/src/unit/services/unit.service.ts @@ -1,14 +1,21 @@ import { GetUnitChildDto } from '../dtos/get.unit.dto'; import { SpaceTypeRepository } from '../../../libs/common/src/modules/space-type/repositories/space.type.repository'; -import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { + Injectable, + HttpException, + HttpStatus, + BadRequestException, +} from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { AddUnitDto } from '../dtos'; import { UnitChildInterface, UnitParentInterface, GetUnitByUuidInterface, + RenameUnitByUuidInterface, } from '../interface/unit.interface'; import { SpaceEntity } from '@app/common/modules/space/entities'; +import { UpdateUnitNameDto } from '../dtos/update.unit.dto'; @Injectable() export class UnitService { @@ -179,4 +186,43 @@ export class UnitService { ); } } + + async renameUnitByUuid( + unitUuid: string, + updateUnitNameDto: UpdateUnitNameDto, + ): Promise { + try { + const unit = await this.spaceRepository.findOneOrFail({ + where: { uuid: unitUuid }, + relations: ['spaceType'], + }); + + if (!unit || !unit.spaceType || unit.spaceType.type !== 'unit') { + throw new BadRequestException('Invalid unit UUID'); + } + + await this.spaceRepository.update( + { uuid: unitUuid }, + { spaceName: updateUnitNameDto.unitName }, + ); + + // Fetch the updated unit + const updatedUnit = await this.spaceRepository.findOneOrFail({ + where: { uuid: unitUuid }, + relations: ['spaceType'], + }); + + return { + uuid: updatedUnit.uuid, + name: updatedUnit.spaceName, + type: updatedUnit.spaceType.type, + }; + } catch (err) { + if (err instanceof BadRequestException) { + throw err; // Re-throw BadRequestException + } else { + throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); + } + } + } } From 53628236a63c6156f471ac7f379e441b3b5a0086 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 14 Apr 2024 10:57:13 +0300 Subject: [PATCH 36/75] Add endpoint to rename room by UUID --- src/room/controllers/room.controller.ts | 27 ++++++++++++++ src/room/dtos/update.room.dto.ts | 16 +++++++++ src/room/interface/room.interface.ts | 5 +++ src/room/services/room.service.ts | 47 ++++++++++++++++++++++++- 4 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 src/room/dtos/update.room.dto.ts diff --git a/src/room/controllers/room.controller.ts b/src/room/controllers/room.controller.ts index a998077..b5ffb15 100644 --- a/src/room/controllers/room.controller.ts +++ b/src/room/controllers/room.controller.ts @@ -7,11 +7,13 @@ import { HttpStatus, Param, Post, + Put, UseGuards, } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { AddRoomDto } from '../dtos/add.room.dto'; +import { UpdateRoomNameDto } from '../dtos/update.room.dto'; @ApiTags('Room Module') @Controller({ @@ -73,4 +75,29 @@ export class RoomController { } } } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Put('rename/:roomUuid') + async renameRoomByUuid( + @Param('roomUuid') roomUuid: string, + @Body() updateRoomNameDto: UpdateRoomNameDto, + ) { + try { + const room = await this.roomService.renameRoomByUuid( + roomUuid, + updateRoomNameDto, + ); + return room; + } catch (error) { + if (error.status === 404) { + throw new HttpException('Room not found', HttpStatus.NOT_FOUND); + } else { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } } diff --git a/src/room/dtos/update.room.dto.ts b/src/room/dtos/update.room.dto.ts new file mode 100644 index 0000000..8f54092 --- /dev/null +++ b/src/room/dtos/update.room.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class UpdateRoomNameDto { + @ApiProperty({ + description: 'roomName', + required: true, + }) + @IsString() + @IsNotEmpty() + public roomName: string; + + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/room/interface/room.interface.ts b/src/room/interface/room.interface.ts index f98a8c9..3f1d817 100644 --- a/src/room/interface/room.interface.ts +++ b/src/room/interface/room.interface.ts @@ -12,3 +12,8 @@ export interface RoomParentInterface { type: string; parent?: RoomParentInterface; } +export interface RenameRoomByUuidInterface { + uuid: string; + name: string; + type: string; +} diff --git a/src/room/services/room.service.ts b/src/room/services/room.service.ts index ae0d403..24d5301 100644 --- a/src/room/services/room.service.ts +++ b/src/room/services/room.service.ts @@ -1,11 +1,18 @@ import { SpaceTypeRepository } from '../../../libs/common/src/modules/space-type/repositories/space.type.repository'; -import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { + Injectable, + HttpException, + HttpStatus, + BadRequestException, +} from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { AddRoomDto } from '../dtos'; import { RoomParentInterface, GetRoomByUuidInterface, + RenameRoomByUuidInterface, } from '../interface/room.interface'; +import { UpdateRoomNameDto } from '../dtos/update.room.dto'; @Injectable() export class RoomService { @@ -93,4 +100,42 @@ export class RoomService { ); } } + async renameRoomByUuid( + roomUuid: string, + updateRoomNameDto: UpdateRoomNameDto, + ): Promise { + try { + const room = await this.spaceRepository.findOneOrFail({ + where: { uuid: roomUuid }, + relations: ['spaceType'], + }); + + if (!room || !room.spaceType || room.spaceType.type !== 'room') { + throw new BadRequestException('Invalid room UUID'); + } + + await this.spaceRepository.update( + { uuid: roomUuid }, + { spaceName: updateRoomNameDto.roomName }, + ); + + // Fetch the updated room + const updateRoom = await this.spaceRepository.findOneOrFail({ + where: { uuid: roomUuid }, + relations: ['spaceType'], + }); + + return { + uuid: updateRoom.uuid, + name: updateRoom.spaceName, + type: updateRoom.spaceType.type, + }; + } catch (err) { + if (err instanceof BadRequestException) { + throw err; // Re-throw BadRequestException + } else { + throw new HttpException('Room not found', HttpStatus.NOT_FOUND); + } + } + } } From d92ae03eac04515913c7c2de2387dd7695d97c61 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 14 Apr 2024 11:28:52 +0300 Subject: [PATCH 37/75] Refactor error handling in controllers and services --- .../controllers/building.controller.ts | 50 ++++------ src/building/services/building.service.ts | 94 +++++++++++-------- .../controllers/community.controller.ts | 38 +++----- src/community/services/community.service.ts | 71 ++++++++------ src/floor/controllers/floor.controller.ts | 54 ++++------- src/floor/services/floor.service.ts | 86 +++++++++-------- src/room/controllers/room.controller.ts | 38 +++----- src/room/services/room.service.ts | 27 +++--- src/unit/controllers/unit.controller.ts | 50 ++++------ src/unit/services/unit.service.ts | 87 +++++++++-------- 10 files changed, 287 insertions(+), 308 deletions(-) diff --git a/src/building/controllers/building.controller.ts b/src/building/controllers/building.controller.ts index 01d278c..81365fb 100644 --- a/src/building/controllers/building.controller.ts +++ b/src/building/controllers/building.controller.ts @@ -35,7 +35,7 @@ export class BuildingController { } catch (error) { throw new HttpException( error.message || 'Internal server error', - HttpStatus.INTERNAL_SERVER_ERROR, + error.status || HttpStatus.INTERNAL_SERVER_ERROR, ); } } @@ -49,14 +49,10 @@ export class BuildingController { await this.buildingService.getBuildingByUuid(buildingUuid); return building; } catch (error) { - if (error.status === 404) { - throw new HttpException('Building not found', HttpStatus.NOT_FOUND); - } else { - throw new HttpException( - error.message || 'Internal server error', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } @@ -74,14 +70,10 @@ export class BuildingController { ); return building; } catch (error) { - if (error.status === 404) { - throw new HttpException('Building not found', HttpStatus.NOT_FOUND); - } else { - throw new HttpException( - error.message || 'Internal server error', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } @ApiBearerAuth() @@ -93,14 +85,10 @@ export class BuildingController { await this.buildingService.getBuildingParentByUuid(buildingUuid); return building; } catch (error) { - if (error.status === 404) { - throw new HttpException('Building not found', HttpStatus.NOT_FOUND); - } else { - throw new HttpException( - error.message || 'Internal server error', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } @ApiBearerAuth() @@ -117,14 +105,10 @@ export class BuildingController { ); return building; } catch (error) { - if (error.status === 404) { - throw new HttpException('Building not found', HttpStatus.NOT_FOUND); - } else { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } } diff --git a/src/building/services/building.service.ts b/src/building/services/building.service.ts index 528d7e4..62081e8 100644 --- a/src/building/services/building.service.ts +++ b/src/building/services/building.service.ts @@ -55,8 +55,12 @@ export class BuildingService { }, relations: ['spaceType'], }); - if (!building) { - throw new HttpException('Building not found', HttpStatus.NOT_FOUND); + if ( + !building || + !building.spaceType || + building.spaceType.type !== 'building' + ) { + throw new BadRequestException('Invalid building UUID'); } return { uuid: building.uuid, @@ -66,45 +70,53 @@ export class BuildingService { type: building.spaceType.type, }; } catch (err) { - throw new HttpException( - err.message, - err.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); + if (err instanceof BadRequestException) { + throw err; // Re-throw BadRequestException + } else { + throw new HttpException('Building not found', HttpStatus.NOT_FOUND); + } } } async getBuildingChildByUuid( buildingUuid: string, getBuildingChildDto: GetBuildingChildDto, ): Promise { - const { includeSubSpaces, page, pageSize } = getBuildingChildDto; + try { + const { includeSubSpaces, page, pageSize } = getBuildingChildDto; - const space = await this.spaceRepository.findOneOrFail({ - where: { uuid: buildingUuid }, - relations: ['children', 'spaceType'], - }); + const space = await this.spaceRepository.findOneOrFail({ + where: { uuid: buildingUuid }, + relations: ['children', 'spaceType'], + }); + if (!space || !space.spaceType || space.spaceType.type !== 'building') { + throw new BadRequestException('Invalid building UUID'); + } - if (space.spaceType.type !== 'building') { - throw new HttpException('Building not found', HttpStatus.NOT_FOUND); + const totalCount = await this.spaceRepository.count({ + where: { parent: { uuid: space.uuid } }, + }); + + const children = await this.buildHierarchy( + space, + includeSubSpaces, + page, + pageSize, + ); + + return { + uuid: space.uuid, + name: space.spaceName, + type: space.spaceType.type, + totalCount, + children, + }; + } catch (err) { + if (err instanceof BadRequestException) { + throw err; // Re-throw BadRequestException + } else { + throw new HttpException('Building not found', HttpStatus.NOT_FOUND); + } } - - const totalCount = await this.spaceRepository.count({ - where: { parent: { uuid: space.uuid } }, - }); - - const children = await this.buildHierarchy( - space, - includeSubSpaces, - page, - pageSize, - ); - - return { - uuid: space.uuid, - name: space.spaceName, - type: space.spaceType.type, - totalCount, - children, - }; } private async buildHierarchy( @@ -165,10 +177,13 @@ export class BuildingService { }, relations: ['spaceType', 'parent', 'parent.spaceType'], }); - if (!building) { - throw new HttpException('Building not found', HttpStatus.NOT_FOUND); + if ( + !building || + !building.spaceType || + building.spaceType.type !== 'building' + ) { + throw new BadRequestException('Invalid building UUID'); } - return { uuid: building.uuid, name: building.spaceName, @@ -180,10 +195,11 @@ export class BuildingService { }, }; } catch (err) { - throw new HttpException( - err.message, - err.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); + if (err instanceof BadRequestException) { + throw err; // Re-throw BadRequestException + } else { + throw new HttpException('Building not found', HttpStatus.NOT_FOUND); + } } } diff --git a/src/community/controllers/community.controller.ts b/src/community/controllers/community.controller.ts index 1a07791..f889e13 100644 --- a/src/community/controllers/community.controller.ts +++ b/src/community/controllers/community.controller.ts @@ -35,7 +35,7 @@ export class CommunityController { } catch (error) { throw new HttpException( error.message || 'Internal server error', - HttpStatus.INTERNAL_SERVER_ERROR, + error.status || HttpStatus.INTERNAL_SERVER_ERROR, ); } } @@ -49,14 +49,10 @@ export class CommunityController { await this.communityService.getCommunityByUuid(communityUuid); return community; } catch (error) { - if (error.status === 404) { - throw new HttpException('Community not found', HttpStatus.NOT_FOUND); - } else { - throw new HttpException( - error.message || 'Internal server error', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } @@ -74,14 +70,10 @@ export class CommunityController { ); return community; } catch (error) { - if (error.status === 404) { - throw new HttpException('Community not found', HttpStatus.NOT_FOUND); - } else { - throw new HttpException( - error.message || 'Internal server error', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } @@ -99,14 +91,10 @@ export class CommunityController { ); return community; } catch (error) { - if (error.status === 404) { - throw new HttpException('Community not found', HttpStatus.NOT_FOUND); - } else { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } } diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index 33cab98..5e9b7ea 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -53,8 +53,12 @@ export class CommunityService { }, relations: ['spaceType'], }); - if (!community) { - throw new HttpException('Community not found', HttpStatus.NOT_FOUND); + if ( + !community || + !community.spaceType || + community.spaceType.type !== 'community' + ) { + throw new BadRequestException('Invalid community UUID'); } return { uuid: community.uuid, @@ -64,42 +68,51 @@ export class CommunityService { type: community.spaceType.type, }; } catch (err) { - throw new HttpException( - err.message, - err.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); + if (err instanceof BadRequestException) { + throw err; // Re-throw BadRequestException + } else { + throw new HttpException('Community not found', HttpStatus.NOT_FOUND); + } } } async getCommunityChildByUuid( communityUuid: string, getCommunityChildDto: GetCommunityChildDto, ): Promise { - const { includeSubSpaces, page, pageSize } = getCommunityChildDto; + try { + const { includeSubSpaces, page, pageSize } = getCommunityChildDto; - const space = await this.spaceRepository.findOneOrFail({ - where: { uuid: communityUuid }, - relations: ['children', 'spaceType'], - }); + const space = await this.spaceRepository.findOneOrFail({ + where: { uuid: communityUuid }, + relations: ['children', 'spaceType'], + }); - if (space.spaceType.type !== 'community') { - throw new HttpException('Community not found', HttpStatus.NOT_FOUND); + if (!space || !space.spaceType || space.spaceType.type !== 'community') { + throw new BadRequestException('Invalid community UUID'); + } + const totalCount = await this.spaceRepository.count({ + where: { parent: { uuid: space.uuid } }, + }); + const children = await this.buildHierarchy( + space, + includeSubSpaces, + page, + pageSize, + ); + return { + uuid: space.uuid, + name: space.spaceName, + type: space.spaceType.type, + totalCount, + children, + }; + } catch (err) { + if (err instanceof BadRequestException) { + throw err; // Re-throw BadRequestException + } else { + throw new HttpException('Community not found', HttpStatus.NOT_FOUND); + } } - const totalCount = await this.spaceRepository.count({ - where: { parent: { uuid: space.uuid } }, - }); - const children = await this.buildHierarchy( - space, - includeSubSpaces, - page, - pageSize, - ); - return { - uuid: space.uuid, - name: space.spaceName, - type: space.spaceType.type, - totalCount, - children, - }; } private async buildHierarchy( diff --git a/src/floor/controllers/floor.controller.ts b/src/floor/controllers/floor.controller.ts index 67c7b44..87ac04a 100644 --- a/src/floor/controllers/floor.controller.ts +++ b/src/floor/controllers/floor.controller.ts @@ -25,8 +25,8 @@ import { UpdateFloorNameDto } from '../dtos/update.floor.dto'; export class FloorController { constructor(private readonly floorService: FloorService) {} - // @ApiBearerAuth() - // @UseGuards(JwtAuthGuard) + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) @Post() async addFloor(@Body() addFloorDto: AddFloorDto) { try { @@ -35,7 +35,7 @@ export class FloorController { } catch (error) { throw new HttpException( error.message || 'Internal server error', - HttpStatus.INTERNAL_SERVER_ERROR, + error.status || HttpStatus.INTERNAL_SERVER_ERROR, ); } } @@ -48,14 +48,10 @@ export class FloorController { const floor = await this.floorService.getFloorByUuid(floorUuid); return floor; } catch (error) { - if (error.status === 404) { - throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); - } else { - throw new HttpException( - error.message || 'Internal server error', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } @@ -73,14 +69,10 @@ export class FloorController { ); return floor; } catch (error) { - if (error.status === 404) { - throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); - } else { - throw new HttpException( - error.message || 'Internal server error', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } @ApiBearerAuth() @@ -91,14 +83,10 @@ export class FloorController { const floor = await this.floorService.getFloorParentByUuid(floorUuid); return floor; } catch (error) { - if (error.status === 404) { - throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); - } else { - throw new HttpException( - error.message || 'Internal server error', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } @@ -116,14 +104,10 @@ export class FloorController { ); return floor; } catch (error) { - if (error.status === 404) { - throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); - } else { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } } diff --git a/src/floor/services/floor.service.ts b/src/floor/services/floor.service.ts index bbf97c4..f4f0759 100644 --- a/src/floor/services/floor.service.ts +++ b/src/floor/services/floor.service.ts @@ -53,9 +53,10 @@ export class FloorService { }, relations: ['spaceType'], }); - if (!floor) { - throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); + if (!floor || !floor.spaceType || floor.spaceType.type !== 'floor') { + throw new BadRequestException('Invalid floor UUID'); } + return { uuid: floor.uuid, createdAt: floor.createdAt, @@ -64,45 +65,53 @@ export class FloorService { type: floor.spaceType.type, }; } catch (err) { - throw new HttpException( - err.message, - err.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); + if (err instanceof BadRequestException) { + throw err; // Re-throw BadRequestException + } else { + throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); + } } } async getFloorChildByUuid( floorUuid: string, getFloorChildDto: GetFloorChildDto, ): Promise { - const { includeSubSpaces, page, pageSize } = getFloorChildDto; + try { + const { includeSubSpaces, page, pageSize } = getFloorChildDto; - const space = await this.spaceRepository.findOneOrFail({ - where: { uuid: floorUuid }, - relations: ['children', 'spaceType'], - }); + const space = await this.spaceRepository.findOneOrFail({ + where: { uuid: floorUuid }, + relations: ['children', 'spaceType'], + }); - if (space.spaceType.type !== 'floor') { - throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); + if (!space || !space.spaceType || space.spaceType.type !== 'floor') { + throw new BadRequestException('Invalid floor UUID'); + } + const totalCount = await this.spaceRepository.count({ + where: { parent: { uuid: space.uuid } }, + }); + + const children = await this.buildHierarchy( + space, + includeSubSpaces, + page, + pageSize, + ); + + return { + uuid: space.uuid, + name: space.spaceName, + type: space.spaceType.type, + totalCount, + children, + }; + } catch (err) { + if (err instanceof BadRequestException) { + throw err; // Re-throw BadRequestException + } else { + throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); + } } - - const totalCount = await this.spaceRepository.count({ - where: { parent: { uuid: space.uuid } }, - }); - - const children = await this.buildHierarchy( - space, - includeSubSpaces, - page, - pageSize, - ); - - return { - uuid: space.uuid, - name: space.spaceName, - type: space.spaceType.type, - totalCount, - children, - }; } private async buildHierarchy( @@ -163,8 +172,8 @@ export class FloorService { }, relations: ['spaceType', 'parent', 'parent.spaceType'], }); - if (!floor) { - throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); + if (!floor || !floor.spaceType || floor.spaceType.type !== 'floor') { + throw new BadRequestException('Invalid floor UUID'); } return { @@ -178,10 +187,11 @@ export class FloorService { }, }; } catch (err) { - throw new HttpException( - err.message, - err.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); + if (err instanceof BadRequestException) { + throw err; // Re-throw BadRequestException + } else { + throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); + } } } diff --git a/src/room/controllers/room.controller.ts b/src/room/controllers/room.controller.ts index b5ffb15..43f6bd8 100644 --- a/src/room/controllers/room.controller.ts +++ b/src/room/controllers/room.controller.ts @@ -33,7 +33,7 @@ export class RoomController { } catch (error) { throw new HttpException( error.message || 'Internal server error', - HttpStatus.INTERNAL_SERVER_ERROR, + error.status || HttpStatus.INTERNAL_SERVER_ERROR, ); } } @@ -46,14 +46,10 @@ export class RoomController { const room = await this.roomService.getRoomByUuid(roomUuid); return room; } catch (error) { - if (error.status === 404) { - throw new HttpException('Room not found', HttpStatus.NOT_FOUND); - } else { - throw new HttpException( - error.message || 'Internal server error', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } @@ -65,14 +61,10 @@ export class RoomController { const room = await this.roomService.getRoomParentByUuid(roomUuid); return room; } catch (error) { - if (error.status === 404) { - throw new HttpException('Room not found', HttpStatus.NOT_FOUND); - } else { - throw new HttpException( - error.message || 'Internal server error', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } @@ -90,14 +82,10 @@ export class RoomController { ); return room; } catch (error) { - if (error.status === 404) { - throw new HttpException('Room not found', HttpStatus.NOT_FOUND); - } else { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } } diff --git a/src/room/services/room.service.ts b/src/room/services/room.service.ts index 24d5301..d6c8afa 100644 --- a/src/room/services/room.service.ts +++ b/src/room/services/room.service.ts @@ -50,9 +50,10 @@ export class RoomService { }, relations: ['spaceType'], }); - if (!room) { - throw new HttpException('Room not found', HttpStatus.NOT_FOUND); + if (!room || !room.spaceType || room.spaceType.type !== 'room') { + throw new BadRequestException('Invalid room UUID'); } + return { uuid: room.uuid, createdAt: room.createdAt, @@ -61,10 +62,11 @@ export class RoomService { type: room.spaceType.type, }; } catch (err) { - throw new HttpException( - err.message, - err.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); + if (err instanceof BadRequestException) { + throw err; // Re-throw BadRequestException + } else { + throw new HttpException('Room not found', HttpStatus.NOT_FOUND); + } } } @@ -79,8 +81,8 @@ export class RoomService { }, relations: ['spaceType', 'parent', 'parent.spaceType'], }); - if (!room) { - throw new HttpException('Room not found', HttpStatus.NOT_FOUND); + if (!room || !room.spaceType || room.spaceType.type !== 'room') { + throw new BadRequestException('Invalid room UUID'); } return { @@ -94,10 +96,11 @@ export class RoomService { }, }; } catch (err) { - throw new HttpException( - err.message, - err.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); + if (err instanceof BadRequestException) { + throw err; // Re-throw BadRequestException + } else { + throw new HttpException('Room not found', HttpStatus.NOT_FOUND); + } } } async renameRoomByUuid( diff --git a/src/unit/controllers/unit.controller.ts b/src/unit/controllers/unit.controller.ts index cc746c8..f56b9c7 100644 --- a/src/unit/controllers/unit.controller.ts +++ b/src/unit/controllers/unit.controller.ts @@ -35,7 +35,7 @@ export class UnitController { } catch (error) { throw new HttpException( error.message || 'Internal server error', - HttpStatus.INTERNAL_SERVER_ERROR, + error.status || HttpStatus.INTERNAL_SERVER_ERROR, ); } } @@ -48,14 +48,10 @@ export class UnitController { const unit = await this.unitService.getUnitByUuid(unitUuid); return unit; } catch (error) { - if (error.status === 404) { - throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); - } else { - throw new HttpException( - error.message || 'Internal server error', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } @@ -70,14 +66,10 @@ export class UnitController { const unit = await this.unitService.getUnitChildByUuid(unitUuid, query); return unit; } catch (error) { - if (error.status === 404) { - throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); - } else { - throw new HttpException( - error.message || 'Internal server error', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } @ApiBearerAuth() @@ -88,14 +80,10 @@ export class UnitController { const unit = await this.unitService.getUnitParentByUuid(unitUuid); return unit; } catch (error) { - if (error.status === 404) { - throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); - } else { - throw new HttpException( - error.message || 'Internal server error', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } @@ -113,14 +101,10 @@ export class UnitController { ); return unit; } catch (error) { - if (error.status === 404) { - throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); - } else { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } } diff --git a/src/unit/services/unit.service.ts b/src/unit/services/unit.service.ts index 1bff975..e84bc0f 100644 --- a/src/unit/services/unit.service.ts +++ b/src/unit/services/unit.service.ts @@ -53,8 +53,8 @@ export class UnitService { }, relations: ['spaceType'], }); - if (!unit) { - throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); + if (!unit || !unit.spaceType || unit.spaceType.type !== 'unit') { + throw new BadRequestException('Invalid unit UUID'); } return { uuid: unit.uuid, @@ -64,45 +64,54 @@ export class UnitService { type: unit.spaceType.type, }; } catch (err) { - throw new HttpException( - err.message, - err.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); + if (err instanceof BadRequestException) { + throw err; // Re-throw BadRequestException + } else { + throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); + } } } async getUnitChildByUuid( unitUuid: string, getUnitChildDto: GetUnitChildDto, ): Promise { - const { includeSubSpaces, page, pageSize } = getUnitChildDto; + try { + const { includeSubSpaces, page, pageSize } = getUnitChildDto; - const space = await this.spaceRepository.findOneOrFail({ - where: { uuid: unitUuid }, - relations: ['children', 'spaceType'], - }); + const space = await this.spaceRepository.findOneOrFail({ + where: { uuid: unitUuid }, + relations: ['children', 'spaceType'], + }); - if (space.spaceType.type !== 'unit') { - throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); + if (!space || !space.spaceType || space.spaceType.type !== 'unit') { + throw new BadRequestException('Invalid unit UUID'); + } + + const totalCount = await this.spaceRepository.count({ + where: { parent: { uuid: space.uuid } }, + }); + + const children = await this.buildHierarchy( + space, + includeSubSpaces, + page, + pageSize, + ); + + return { + uuid: space.uuid, + name: space.spaceName, + type: space.spaceType.type, + totalCount, + children, + }; + } catch (err) { + if (err instanceof BadRequestException) { + throw err; // Re-throw BadRequestException + } else { + throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); + } } - - const totalCount = await this.spaceRepository.count({ - where: { parent: { uuid: space.uuid } }, - }); - - const children = await this.buildHierarchy( - space, - includeSubSpaces, - page, - pageSize, - ); - - return { - uuid: space.uuid, - name: space.spaceName, - type: space.spaceType.type, - totalCount, - children, - }; } private async buildHierarchy( @@ -165,10 +174,9 @@ export class UnitService { }, relations: ['spaceType', 'parent', 'parent.spaceType'], }); - if (!unit) { - throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); + if (!unit || !unit.spaceType || unit.spaceType.type !== 'unit') { + throw new BadRequestException('Invalid unit UUID'); } - return { uuid: unit.uuid, name: unit.spaceName, @@ -180,10 +188,11 @@ export class UnitService { }, }; } catch (err) { - throw new HttpException( - err.message, - err.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); + if (err instanceof BadRequestException) { + throw err; // Re-throw BadRequestException + } else { + throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); + } } } From 99aca8cf01a830f8e9fbc2925a8deb9b9ca127d1 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 14 Apr 2024 13:13:56 +0300 Subject: [PATCH 38/75] Refactor middleware to guards for type checking --- src/building/building.module.ts | 17 +---- .../controllers/building.controller.ts | 3 +- src/building/services/building.service.ts | 9 ++- src/floor/controllers/floor.controller.ts | 3 +- src/floor/floor.module.ts | 17 +---- src/guards/building.type.guard.ts | 66 +++++++++++++++++ src/guards/community.type.guard.ts | 67 +++++++++++++++++ src/guards/floor.type.guard.ts | 66 +++++++++++++++++ src/guards/unit.type.guard.ts | 66 +++++++++++++++++ src/middleware/CheckBuildingTypeMiddleware.ts | 74 ------------------- .../CheckCommunityTypeMiddleware.ts | 74 ------------------- src/middleware/CheckFloorTypeMiddleware.ts | 74 ------------------- src/middleware/CheckUnitTypeMiddleware.ts | 74 ------------------- src/room/controllers/room.controller.ts | 3 +- src/room/room.module.ts | 17 +---- src/unit/controllers/unit.controller.ts | 3 +- src/unit/unit.module.ts | 17 +---- 17 files changed, 289 insertions(+), 361 deletions(-) create mode 100644 src/guards/building.type.guard.ts create mode 100644 src/guards/community.type.guard.ts create mode 100644 src/guards/floor.type.guard.ts create mode 100644 src/guards/unit.type.guard.ts delete mode 100644 src/middleware/CheckBuildingTypeMiddleware.ts delete mode 100644 src/middleware/CheckCommunityTypeMiddleware.ts delete mode 100644 src/middleware/CheckFloorTypeMiddleware.ts delete mode 100644 src/middleware/CheckUnitTypeMiddleware.ts diff --git a/src/building/building.module.ts b/src/building/building.module.ts index 2bdeb7e..7574f81 100644 --- a/src/building/building.module.ts +++ b/src/building/building.module.ts @@ -1,9 +1,4 @@ -import { - MiddlewareConsumer, - Module, - NestModule, - RequestMethod, -} from '@nestjs/common'; +import { Module } from '@nestjs/common'; import { BuildingService } from './services/building.service'; import { BuildingController } from './controllers/building.controller'; import { ConfigModule } from '@nestjs/config'; @@ -11,7 +6,6 @@ import { SpaceRepositoryModule } from '@app/common/modules/space/space.repositor import { SpaceRepository } from '@app/common/modules/space/repositories'; import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space.type.repository.module'; import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; -import { CheckCommunityTypeMiddleware } from 'src/middleware/CheckCommunityTypeMiddleware'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, SpaceTypeRepositoryModule], @@ -19,11 +13,4 @@ import { CheckCommunityTypeMiddleware } from 'src/middleware/CheckCommunityTypeM providers: [BuildingService, SpaceRepository, SpaceTypeRepository], exports: [BuildingService], }) -export class BuildingModule implements NestModule { - configure(consumer: MiddlewareConsumer) { - consumer.apply(CheckCommunityTypeMiddleware).forRoutes({ - path: '/building', - method: RequestMethod.POST, - }); - } -} +export class BuildingModule {} diff --git a/src/building/controllers/building.controller.ts b/src/building/controllers/building.controller.ts index 81365fb..ec4a616 100644 --- a/src/building/controllers/building.controller.ts +++ b/src/building/controllers/building.controller.ts @@ -16,6 +16,7 @@ import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { AddBuildingDto } from '../dtos/add.building.dto'; import { GetBuildingChildDto } from '../dtos/get.building.dto'; import { UpdateBuildingNameDto } from '../dtos/update.building.dto'; +import { CheckCommunityTypeGuard } from 'src/guards/community.type.guard'; @ApiTags('Building Module') @Controller({ @@ -26,7 +27,7 @@ export class BuildingController { constructor(private readonly buildingService: BuildingService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, CheckCommunityTypeGuard) @Post() async addBuilding(@Body() addBuildingDto: AddBuildingDto) { try { diff --git a/src/building/services/building.service.ts b/src/building/services/building.service.ts index 62081e8..7bfd33c 100644 --- a/src/building/services/building.service.ts +++ b/src/building/services/building.service.ts @@ -32,13 +32,20 @@ export class BuildingService { }, }); + if (!spaceType) { + throw new BadRequestException('Invalid building UUID'); + } await this.spaceRepository.save({ spaceName: addBuildingDto.buildingName, parent: { uuid: addBuildingDto.communityUuid }, spaceType: { uuid: spaceType.uuid }, }); } catch (err) { - throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR); + if (err instanceof BadRequestException) { + throw err; // Re-throw BadRequestException + } else { + throw new HttpException('Building not found', HttpStatus.NOT_FOUND); + } } } diff --git a/src/floor/controllers/floor.controller.ts b/src/floor/controllers/floor.controller.ts index 87ac04a..5c1021a 100644 --- a/src/floor/controllers/floor.controller.ts +++ b/src/floor/controllers/floor.controller.ts @@ -16,6 +16,7 @@ import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { AddFloorDto } from '../dtos/add.floor.dto'; import { GetFloorChildDto } from '../dtos/get.floor.dto'; import { UpdateFloorNameDto } from '../dtos/update.floor.dto'; +import { CheckBuildingTypeGuard } from 'src/guards/building.type.guard'; @ApiTags('Floor Module') @Controller({ @@ -26,7 +27,7 @@ export class FloorController { constructor(private readonly floorService: FloorService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, CheckBuildingTypeGuard) @Post() async addFloor(@Body() addFloorDto: AddFloorDto) { try { diff --git a/src/floor/floor.module.ts b/src/floor/floor.module.ts index 199f876..7085659 100644 --- a/src/floor/floor.module.ts +++ b/src/floor/floor.module.ts @@ -1,9 +1,4 @@ -import { - MiddlewareConsumer, - Module, - NestModule, - RequestMethod, -} from '@nestjs/common'; +import { Module } from '@nestjs/common'; import { FloorService } from './services/floor.service'; import { FloorController } from './controllers/floor.controller'; import { ConfigModule } from '@nestjs/config'; @@ -11,7 +6,6 @@ import { SpaceRepositoryModule } from '@app/common/modules/space/space.repositor import { SpaceRepository } from '@app/common/modules/space/repositories'; import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space.type.repository.module'; import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; -import { CheckBuildingTypeMiddleware } from 'src/middleware/CheckBuildingTypeMiddleware'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, SpaceTypeRepositoryModule], @@ -19,11 +13,4 @@ import { CheckBuildingTypeMiddleware } from 'src/middleware/CheckBuildingTypeMid providers: [FloorService, SpaceRepository, SpaceTypeRepository], exports: [FloorService], }) -export class FloorModule implements NestModule { - configure(consumer: MiddlewareConsumer) { - consumer.apply(CheckBuildingTypeMiddleware).forRoutes({ - path: '/floor', - method: RequestMethod.POST, - }); - } -} +export class FloorModule {} diff --git a/src/guards/building.type.guard.ts b/src/guards/building.type.guard.ts new file mode 100644 index 0000000..1e36d9f --- /dev/null +++ b/src/guards/building.type.guard.ts @@ -0,0 +1,66 @@ +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { + Injectable, + CanActivate, + HttpStatus, + BadRequestException, + ExecutionContext, +} from '@nestjs/common'; + +@Injectable() +export class CheckBuildingTypeGuard implements CanActivate { + constructor(private readonly spaceRepository: SpaceRepository) {} + + async canActivate(context: ExecutionContext): Promise { + const req = context.switchToHttp().getRequest(); + + try { + const { floorName, buildingUuid } = req.body; + + if (!floorName) { + throw new BadRequestException('floorName is required'); + } + + if (!buildingUuid) { + throw new BadRequestException('buildingUuid is required'); + } + + await this.checkBuildingIsBuildingType(buildingUuid); + + return true; + } catch (error) { + this.handleGuardError(error, context); + return false; + } + } + + async checkBuildingIsBuildingType(buildingUuid: string) { + const buildingData = await this.spaceRepository.findOne({ + where: { uuid: buildingUuid }, + relations: ['spaceType'], + }); + if ( + !buildingData || + !buildingData.spaceType || + buildingData.spaceType.type !== 'building' + ) { + throw new BadRequestException('Invalid building UUID'); + } + } + + private handleGuardError(error: Error, context: ExecutionContext) { + const response = context.switchToHttp().getResponse(); + console.error(error); + + if (error instanceof BadRequestException) { + response + .status(HttpStatus.BAD_REQUEST) + .json({ statusCode: HttpStatus.BAD_REQUEST, message: error.message }); + } else { + response.status(HttpStatus.NOT_FOUND).json({ + statusCode: HttpStatus.NOT_FOUND, + message: 'Building not found', + }); + } + } +} diff --git a/src/guards/community.type.guard.ts b/src/guards/community.type.guard.ts new file mode 100644 index 0000000..e8212f4 --- /dev/null +++ b/src/guards/community.type.guard.ts @@ -0,0 +1,67 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + HttpStatus, +} from '@nestjs/common'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { BadRequestException } from '@nestjs/common'; + +@Injectable() +export class CheckCommunityTypeGuard implements CanActivate { + constructor(private readonly spaceRepository: SpaceRepository) {} + + async canActivate(context: ExecutionContext): Promise { + const req = context.switchToHttp().getRequest(); + + try { + const { buildingName, communityUuid } = req.body; + + if (!buildingName) { + throw new BadRequestException('buildingName is required'); + } + + if (!communityUuid) { + throw new BadRequestException('communityUuid is required'); + } + + await this.checkCommunityIsCommunityType(communityUuid); + + return true; + } catch (error) { + this.handleGuardError(error, context); + return false; + } + } + + private async checkCommunityIsCommunityType(communityUuid: string) { + const communityData = await this.spaceRepository.findOne({ + where: { uuid: communityUuid }, + relations: ['spaceType'], + }); + + if ( + !communityData || + !communityData.spaceType || + communityData.spaceType.type !== 'community' + ) { + throw new BadRequestException('Invalid community UUID'); + } + } + + private handleGuardError(error: Error, context: ExecutionContext) { + const response = context.switchToHttp().getResponse(); + console.error(error); + + if (error instanceof BadRequestException) { + response + .status(HttpStatus.BAD_REQUEST) + .json({ statusCode: HttpStatus.BAD_REQUEST, message: error.message }); + } else { + response.status(HttpStatus.NOT_FOUND).json({ + statusCode: HttpStatus.NOT_FOUND, + message: 'Community not found', + }); + } + } +} diff --git a/src/guards/floor.type.guard.ts b/src/guards/floor.type.guard.ts new file mode 100644 index 0000000..8064cb2 --- /dev/null +++ b/src/guards/floor.type.guard.ts @@ -0,0 +1,66 @@ +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { + Injectable, + CanActivate, + HttpStatus, + ExecutionContext, + BadRequestException, +} from '@nestjs/common'; + +@Injectable() +export class CheckFloorTypeGuard implements CanActivate { + constructor(private readonly spaceRepository: SpaceRepository) {} + + async canActivate(context: ExecutionContext): Promise { + const req = context.switchToHttp().getRequest(); + try { + const { unitName, floorUuid } = req.body; + + if (!unitName) { + throw new BadRequestException('unitName is required'); + } + + if (!floorUuid) { + throw new BadRequestException('floorUuid is required'); + } + + await this.checkFloorIsFloorType(floorUuid); + + return true; + } catch (error) { + this.handleGuardError(error, context); + return false; + } + } + + async checkFloorIsFloorType(floorUuid: string) { + const floorData = await this.spaceRepository.findOne({ + where: { uuid: floorUuid }, + relations: ['spaceType'], + }); + + if ( + !floorData || + !floorData.spaceType || + floorData.spaceType.type !== 'floor' + ) { + throw new BadRequestException('Invalid floor UUID'); + } + } + + private handleGuardError(error: Error, context: ExecutionContext) { + const response = context.switchToHttp().getResponse(); + console.error(error); + + if (error instanceof BadRequestException) { + response + .status(HttpStatus.BAD_REQUEST) + .json({ statusCode: HttpStatus.BAD_REQUEST, message: error.message }); + } else { + response.status(HttpStatus.NOT_FOUND).json({ + statusCode: HttpStatus.NOT_FOUND, + message: 'Floor not found', + }); + } + } +} diff --git a/src/guards/unit.type.guard.ts b/src/guards/unit.type.guard.ts new file mode 100644 index 0000000..f1e292f --- /dev/null +++ b/src/guards/unit.type.guard.ts @@ -0,0 +1,66 @@ +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { + Injectable, + CanActivate, + HttpStatus, + BadRequestException, + ExecutionContext, +} from '@nestjs/common'; + +@Injectable() +export class CheckUnitTypeGuard implements CanActivate { + constructor(private readonly spaceRepository: SpaceRepository) {} + + async canActivate(context: ExecutionContext): Promise { + const req = context.switchToHttp().getRequest(); + try { + const { roomName, unitUuid } = req.body; + + if (!roomName) { + throw new BadRequestException('roomName is required'); + } + + if (!unitUuid) { + throw new BadRequestException('unitUuid is required'); + } + + await this.checkFloorIsFloorType(unitUuid); + + return true; + } catch (error) { + this.handleGuardError(error, context); + return false; + } + } + + async checkFloorIsFloorType(unitUuid: string) { + const unitData = await this.spaceRepository.findOne({ + where: { uuid: unitUuid }, + relations: ['spaceType'], + }); + + if ( + !unitData || + !unitData.spaceType || + unitData.spaceType.type !== 'unit' + ) { + throw new BadRequestException('Invalid unit UUID'); + } + } + + private handleGuardError(error: Error, context: ExecutionContext) { + const response = context.switchToHttp().getResponse(); + console.error(error); + + if (error instanceof BadRequestException) { + response + .status(HttpStatus.BAD_REQUEST) + .json({ statusCode: HttpStatus.BAD_REQUEST, message: error.message }); + } else { + response.status(HttpStatus.NOT_FOUND).json({ + statusCode: HttpStatus.NOT_FOUND, + message: 'Unit not found', + }); + } + } +} diff --git a/src/middleware/CheckBuildingTypeMiddleware.ts b/src/middleware/CheckBuildingTypeMiddleware.ts deleted file mode 100644 index 170cb9b..0000000 --- a/src/middleware/CheckBuildingTypeMiddleware.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { - Injectable, - NestMiddleware, - HttpStatus, - HttpException, -} from '@nestjs/common'; -import { Request, Response, NextFunction } from 'express'; - -@Injectable() -export class CheckBuildingTypeMiddleware implements NestMiddleware { - constructor(private readonly spaceRepository: SpaceRepository) {} - - async use(req: Request, res: Response, next: NextFunction) { - try { - // Destructure request body for cleaner code - const { floorName, buildingUuid } = req.body; - - // Guard clauses for early return - if (!floorName) { - return res.status(HttpStatus.BAD_REQUEST).json({ - statusCode: HttpStatus.BAD_REQUEST, - message: 'floorName is required', - }); - } - - if (!buildingUuid) { - return res.status(HttpStatus.BAD_REQUEST).json({ - statusCode: HttpStatus.BAD_REQUEST, - message: 'buildingUuid is required', - }); - } - - // Call function to check if building is a building - await this.checkBuildingIsBuildingType(buildingUuid); - - // Call next middleware - next(); - } catch (error) { - // Handle errors - this.handleMiddlewareError(error, res); - } - } - - async checkBuildingIsBuildingType(buildingUuid: string) { - const buildingData = await this.spaceRepository.findOne({ - where: { uuid: buildingUuid }, - relations: ['spaceType'], - }); - - // Throw error if building not found - if (!buildingData) { - throw new HttpException('Building not found', HttpStatus.NOT_FOUND); - } - - // Throw error if building is not of type 'building' - if (buildingData.spaceType.type !== 'building') { - throw new HttpException( - "buildingUuid is not of type 'building'", - HttpStatus.BAD_REQUEST, - ); - } - } - - // Function to handle middleware errors - private handleMiddlewareError(error: Error, res: Response) { - const status = - error instanceof HttpException - ? error.getStatus() - : HttpStatus.INTERNAL_SERVER_ERROR; - const message = error.message || 'Internal server error'; - res.status(status).json({ statusCode: status, message }); - } -} diff --git a/src/middleware/CheckCommunityTypeMiddleware.ts b/src/middleware/CheckCommunityTypeMiddleware.ts deleted file mode 100644 index 3eef679..0000000 --- a/src/middleware/CheckCommunityTypeMiddleware.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { - Injectable, - NestMiddleware, - HttpStatus, - HttpException, -} from '@nestjs/common'; -import { Request, Response, NextFunction } from 'express'; - -@Injectable() -export class CheckCommunityTypeMiddleware implements NestMiddleware { - constructor(private readonly spaceRepository: SpaceRepository) {} - - async use(req: Request, res: Response, next: NextFunction) { - try { - // Destructure request body for cleaner code - const { buildingName, communityUuid } = req.body; - - // Guard clauses for early return - if (!buildingName) { - return res.status(HttpStatus.BAD_REQUEST).json({ - statusCode: HttpStatus.BAD_REQUEST, - message: 'buildingName is required', - }); - } - - if (!communityUuid) { - return res.status(HttpStatus.BAD_REQUEST).json({ - statusCode: HttpStatus.BAD_REQUEST, - message: 'communityUuid is required', - }); - } - - // Call function to check if community is a building - await this.checkCommunityIsCommunityType(communityUuid); - - // Call next middleware - next(); - } catch (error) { - // Handle errors - this.handleMiddlewareError(error, res); - } - } - - async checkCommunityIsCommunityType(communityUuid: string) { - const communityData = await this.spaceRepository.findOne({ - where: { uuid: communityUuid }, - relations: ['spaceType'], - }); - - // Throw error if community not found - if (!communityData) { - throw new HttpException('Community not found', HttpStatus.NOT_FOUND); - } - - // Throw error if community is not of type 'community' - if (communityData.spaceType.type !== 'community') { - throw new HttpException( - "communityUuid is not of type 'community'", - HttpStatus.BAD_REQUEST, - ); - } - } - - // Function to handle middleware errors - private handleMiddlewareError(error: Error, res: Response) { - const status = - error instanceof HttpException - ? error.getStatus() - : HttpStatus.INTERNAL_SERVER_ERROR; - const message = error.message || 'Internal server error'; - res.status(status).json({ statusCode: status, message }); - } -} diff --git a/src/middleware/CheckFloorTypeMiddleware.ts b/src/middleware/CheckFloorTypeMiddleware.ts deleted file mode 100644 index e096750..0000000 --- a/src/middleware/CheckFloorTypeMiddleware.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { - Injectable, - NestMiddleware, - HttpStatus, - HttpException, -} from '@nestjs/common'; -import { Request, Response, NextFunction } from 'express'; - -@Injectable() -export class CheckFloorTypeMiddleware implements NestMiddleware { - constructor(private readonly spaceRepository: SpaceRepository) {} - - async use(req: Request, res: Response, next: NextFunction) { - try { - // Destructure request body for cleaner code - const { unitName, floorUuid } = req.body; - - // Guard clauses for early return - if (!unitName) { - return res.status(HttpStatus.BAD_REQUEST).json({ - statusCode: HttpStatus.BAD_REQUEST, - message: 'unitName is required', - }); - } - - if (!floorUuid) { - return res.status(HttpStatus.BAD_REQUEST).json({ - statusCode: HttpStatus.BAD_REQUEST, - message: 'floorUuid is required', - }); - } - - // Call function to check if floor is a floor - await this.checkFloorIsFloorType(floorUuid); - - // Call next middleware - next(); - } catch (error) { - // Handle errors - this.handleMiddlewareError(error, res); - } - } - - async checkFloorIsFloorType(floorUuid: string) { - const floorData = await this.spaceRepository.findOne({ - where: { uuid: floorUuid }, - relations: ['spaceType'], - }); - - // Throw error if floor not found - if (!floorData) { - throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); - } - - // Throw error if floor is not of type 'floor' - if (floorData.spaceType.type !== 'floor') { - throw new HttpException( - "floorUuid is not of type 'floor'", - HttpStatus.BAD_REQUEST, - ); - } - } - - // Function to handle middleware errors - private handleMiddlewareError(error: Error, res: Response) { - const status = - error instanceof HttpException - ? error.getStatus() - : HttpStatus.INTERNAL_SERVER_ERROR; - const message = error.message || 'Internal server error'; - res.status(status).json({ statusCode: status, message }); - } -} diff --git a/src/middleware/CheckUnitTypeMiddleware.ts b/src/middleware/CheckUnitTypeMiddleware.ts deleted file mode 100644 index 7f5ba6a..0000000 --- a/src/middleware/CheckUnitTypeMiddleware.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { - Injectable, - NestMiddleware, - HttpStatus, - HttpException, -} from '@nestjs/common'; -import { Request, Response, NextFunction } from 'express'; - -@Injectable() -export class CheckUnitTypeMiddleware implements NestMiddleware { - constructor(private readonly spaceRepository: SpaceRepository) {} - - async use(req: Request, res: Response, next: NextFunction) { - try { - // Destructure request body for cleaner code - const { roomName, unitUuid } = req.body; - - // Guard clauses for early return - if (!roomName) { - return res.status(HttpStatus.BAD_REQUEST).json({ - statusCode: HttpStatus.BAD_REQUEST, - message: 'roomName is required', - }); - } - - if (!unitUuid) { - return res.status(HttpStatus.BAD_REQUEST).json({ - statusCode: HttpStatus.BAD_REQUEST, - message: 'unitUuid is required', - }); - } - - // Call function to check if unit is a unit - await this.checkFloorIsFloorType(unitUuid); - - // Call next middleware - next(); - } catch (error) { - // Handle errors - this.handleMiddlewareError(error, res); - } - } - - async checkFloorIsFloorType(unitUuid: string) { - const unitData = await this.spaceRepository.findOne({ - where: { uuid: unitUuid }, - relations: ['spaceType'], - }); - - // Throw error if unit not found - if (!unitData) { - throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); - } - - // Throw error if unit is not of type 'unit' - if (unitData.spaceType.type !== 'unit') { - throw new HttpException( - "unitUuid is not of type 'unit'", - HttpStatus.BAD_REQUEST, - ); - } - } - - // Function to handle middleware errors - private handleMiddlewareError(error: Error, res: Response) { - const status = - error instanceof HttpException - ? error.getStatus() - : HttpStatus.INTERNAL_SERVER_ERROR; - const message = error.message || 'Internal server error'; - res.status(status).json({ statusCode: status, message }); - } -} diff --git a/src/room/controllers/room.controller.ts b/src/room/controllers/room.controller.ts index 43f6bd8..b0e4e3f 100644 --- a/src/room/controllers/room.controller.ts +++ b/src/room/controllers/room.controller.ts @@ -14,6 +14,7 @@ import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { AddRoomDto } from '../dtos/add.room.dto'; import { UpdateRoomNameDto } from '../dtos/update.room.dto'; +import { CheckUnitTypeGuard } from 'src/guards/unit.type.guard'; @ApiTags('Room Module') @Controller({ @@ -24,7 +25,7 @@ export class RoomController { constructor(private readonly roomService: RoomService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, CheckUnitTypeGuard) @Post() async addRoom(@Body() addRoomDto: AddRoomDto) { try { diff --git a/src/room/room.module.ts b/src/room/room.module.ts index 660fbec..8411cd4 100644 --- a/src/room/room.module.ts +++ b/src/room/room.module.ts @@ -1,9 +1,4 @@ -import { - MiddlewareConsumer, - Module, - NestModule, - RequestMethod, -} from '@nestjs/common'; +import { Module } from '@nestjs/common'; import { RoomService } from './services/room.service'; import { RoomController } from './controllers/room.controller'; import { ConfigModule } from '@nestjs/config'; @@ -11,7 +6,6 @@ import { SpaceRepositoryModule } from '@app/common/modules/space/space.repositor import { SpaceRepository } from '@app/common/modules/space/repositories'; import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space.type.repository.module'; import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; -import { CheckUnitTypeMiddleware } from 'src/middleware/CheckUnitTypeMiddleware'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, SpaceTypeRepositoryModule], @@ -19,11 +13,4 @@ import { CheckUnitTypeMiddleware } from 'src/middleware/CheckUnitTypeMiddleware' providers: [RoomService, SpaceRepository, SpaceTypeRepository], exports: [RoomService], }) -export class RoomModule implements NestModule { - configure(consumer: MiddlewareConsumer) { - consumer.apply(CheckUnitTypeMiddleware).forRoutes({ - path: '/room', - method: RequestMethod.POST, - }); - } -} +export class RoomModule {} diff --git a/src/unit/controllers/unit.controller.ts b/src/unit/controllers/unit.controller.ts index f56b9c7..f36a510 100644 --- a/src/unit/controllers/unit.controller.ts +++ b/src/unit/controllers/unit.controller.ts @@ -16,6 +16,7 @@ import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { AddUnitDto } from '../dtos/add.unit.dto'; import { GetUnitChildDto } from '../dtos/get.unit.dto'; import { UpdateUnitNameDto } from '../dtos/update.unit.dto'; +import { CheckFloorTypeGuard } from 'src/guards/floor.type.guard'; @ApiTags('Unit Module') @Controller({ @@ -26,7 +27,7 @@ export class UnitController { constructor(private readonly unitService: UnitService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, CheckFloorTypeGuard) @Post() async addUnit(@Body() addUnitDto: AddUnitDto) { try { diff --git a/src/unit/unit.module.ts b/src/unit/unit.module.ts index 0ebee41..eaf571f 100644 --- a/src/unit/unit.module.ts +++ b/src/unit/unit.module.ts @@ -1,9 +1,4 @@ -import { - MiddlewareConsumer, - Module, - NestModule, - RequestMethod, -} from '@nestjs/common'; +import { Module } from '@nestjs/common'; import { UnitService } from './services/unit.service'; import { UnitController } from './controllers/unit.controller'; import { ConfigModule } from '@nestjs/config'; @@ -11,7 +6,6 @@ import { SpaceRepositoryModule } from '@app/common/modules/space/space.repositor import { SpaceRepository } from '@app/common/modules/space/repositories'; import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space.type.repository.module'; import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; -import { CheckFloorTypeMiddleware } from 'src/middleware/CheckFloorTypeMiddleware'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, SpaceTypeRepositoryModule], @@ -19,11 +13,4 @@ import { CheckFloorTypeMiddleware } from 'src/middleware/CheckFloorTypeMiddlewar providers: [UnitService, SpaceRepository, SpaceTypeRepository], exports: [UnitService], }) -export class UnitModule implements NestModule { - configure(consumer: MiddlewareConsumer) { - consumer.apply(CheckFloorTypeMiddleware).forRoutes({ - path: '/unit', - method: RequestMethod.POST, - }); - } -} +export class UnitModule {} From 9837227fc572ba4dd7ba15b8db2c2232fb383aed Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 16 Apr 2024 11:11:18 +0300 Subject: [PATCH 39/75] Add user space DTO, entity, repository, and module --- .../src/modules/user-space/dtos/index.ts | 1 + .../modules/user-space/dtos/user.space.dto.ts | 15 ++++++++++ .../src/modules/user-space/entities/index.ts | 1 + .../user-space/entities/user.space.entity.ts | 28 +++++++++++++++++++ .../modules/user-space/repositories/index.ts | 1 + .../repositories/user.space.repository.ts | 10 +++++++ .../user.space.repository.module.ts | 11 ++++++++ 7 files changed, 67 insertions(+) create mode 100644 libs/common/src/modules/user-space/dtos/index.ts create mode 100644 libs/common/src/modules/user-space/dtos/user.space.dto.ts create mode 100644 libs/common/src/modules/user-space/entities/index.ts create mode 100644 libs/common/src/modules/user-space/entities/user.space.entity.ts create mode 100644 libs/common/src/modules/user-space/repositories/index.ts create mode 100644 libs/common/src/modules/user-space/repositories/user.space.repository.ts create mode 100644 libs/common/src/modules/user-space/user.space.repository.module.ts diff --git a/libs/common/src/modules/user-space/dtos/index.ts b/libs/common/src/modules/user-space/dtos/index.ts new file mode 100644 index 0000000..41572f5 --- /dev/null +++ b/libs/common/src/modules/user-space/dtos/index.ts @@ -0,0 +1 @@ +export * from './user.space.dto'; diff --git a/libs/common/src/modules/user-space/dtos/user.space.dto.ts b/libs/common/src/modules/user-space/dtos/user.space.dto.ts new file mode 100644 index 0000000..b9ef4d0 --- /dev/null +++ b/libs/common/src/modules/user-space/dtos/user.space.dto.ts @@ -0,0 +1,15 @@ +import { IsNotEmpty, IsString } from 'class-validator'; + +export class UserSpaceDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public spaceUuid: string; + + @IsString() + @IsNotEmpty() + public userUuid: string; +} diff --git a/libs/common/src/modules/user-space/entities/index.ts b/libs/common/src/modules/user-space/entities/index.ts new file mode 100644 index 0000000..ef6849a --- /dev/null +++ b/libs/common/src/modules/user-space/entities/index.ts @@ -0,0 +1 @@ +export * from './user.space.entity'; diff --git a/libs/common/src/modules/user-space/entities/user.space.entity.ts b/libs/common/src/modules/user-space/entities/user.space.entity.ts new file mode 100644 index 0000000..c41dfc0 --- /dev/null +++ b/libs/common/src/modules/user-space/entities/user.space.entity.ts @@ -0,0 +1,28 @@ +import { Column, Entity, ManyToOne } from 'typeorm'; +import { UserSpaceDto } from '../dtos'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { SpaceEntity } from '../../space/entities'; +import { UserEntity } from '../../user/entities'; + +@Entity({ name: 'user-space' }) +export class UserSpaceEntity extends AbstractEntity { + @Column({ + type: 'uuid', + default: () => 'gen_random_uuid()', // Use gen_random_uuid() for default value + nullable: false, + }) + public uuid: string; + + @ManyToOne(() => UserEntity, (user) => user.userSpaces, { nullable: false }) + user: UserEntity; + + @ManyToOne(() => SpaceEntity, (space) => space.userSpaces, { + nullable: false, + }) + space: SpaceEntity; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/user-space/repositories/index.ts b/libs/common/src/modules/user-space/repositories/index.ts new file mode 100644 index 0000000..3ad6d48 --- /dev/null +++ b/libs/common/src/modules/user-space/repositories/index.ts @@ -0,0 +1 @@ +export * from './user.space.repository'; diff --git a/libs/common/src/modules/user-space/repositories/user.space.repository.ts b/libs/common/src/modules/user-space/repositories/user.space.repository.ts new file mode 100644 index 0000000..b4b7507 --- /dev/null +++ b/libs/common/src/modules/user-space/repositories/user.space.repository.ts @@ -0,0 +1,10 @@ +import { DataSource, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common'; +import { UserSpaceEntity } from '../entities/user.space.entity'; + +@Injectable() +export class UserSpaceRepository extends Repository { + constructor(private dataSource: DataSource) { + super(UserSpaceEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/modules/user-space/user.space.repository.module.ts b/libs/common/src/modules/user-space/user.space.repository.module.ts new file mode 100644 index 0000000..9655252 --- /dev/null +++ b/libs/common/src/modules/user-space/user.space.repository.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { UserSpaceEntity } from './entities/user.space.entity'; + +@Module({ + providers: [], + exports: [], + controllers: [], + imports: [TypeOrmModule.forFeature([UserSpaceEntity])], +}) +export class UserSpaceRepositoryModule {} From 6673bcd913c25edc99db54fe9b3f5c157b857540 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 16 Apr 2024 11:11:36 +0300 Subject: [PATCH 40/75] Add UserSpaceEntity relation to SpaceEntity and UserEntity --- libs/common/src/modules/space/entities/space.entity.ts | 4 ++++ libs/common/src/modules/user/entities/index.ts | 1 + libs/common/src/modules/user/entities/user.entity.ts | 6 +++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index 1f523f3..af6922e 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -2,6 +2,7 @@ import { Column, Entity, ManyToOne, OneToMany } from 'typeorm'; import { SpaceDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { SpaceTypeEntity } from '../../space-type/entities'; +import { UserSpaceEntity } from '../../user-space/entities'; @Entity({ name: 'space' }) export class SpaceEntity extends AbstractEntity { @@ -26,6 +27,9 @@ export class SpaceEntity extends AbstractEntity { }) spaceType: SpaceTypeEntity; + @OneToMany(() => UserSpaceEntity, (userSpace) => userSpace.space) + userSpaces: UserSpaceEntity[]; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/user/entities/index.ts b/libs/common/src/modules/user/entities/index.ts index e69de29..e4aa507 100644 --- a/libs/common/src/modules/user/entities/index.ts +++ b/libs/common/src/modules/user/entities/index.ts @@ -0,0 +1 @@ +export * from './user.entity'; diff --git a/libs/common/src/modules/user/entities/user.entity.ts b/libs/common/src/modules/user/entities/user.entity.ts index 63f2185..3212662 100644 --- a/libs/common/src/modules/user/entities/user.entity.ts +++ b/libs/common/src/modules/user/entities/user.entity.ts @@ -1,6 +1,7 @@ -import { Column, Entity } from 'typeorm'; +import { Column, Entity, OneToMany } from 'typeorm'; import { UserDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { UserSpaceEntity } from '../../user-space/entities'; @Entity({ name: 'user' }) export class UserEntity extends AbstractEntity { @@ -36,6 +37,9 @@ export class UserEntity extends AbstractEntity { }) public isUserVerified: boolean; + @OneToMany(() => UserSpaceEntity, (userSpace) => userSpace.user) + userSpaces: UserSpaceEntity[]; + constructor(partial: Partial) { super(); Object.assign(this, partial); From 35b41e1be189ededf2d9607d2b82bb5c3e4bf9a7 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 16 Apr 2024 11:11:47 +0300 Subject: [PATCH 41/75] Add UserSpaceEntity to database module imports --- libs/common/src/database/database.module.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index c604089..aa4f781 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -9,6 +9,7 @@ import { HomeEntity } from '../modules/home/entities'; import { ProductEntity } from '../modules/product/entities'; import { SpaceEntity } from '../modules/space/entities'; import { SpaceTypeEntity } from '../modules/space-type/entities'; +import { UserSpaceEntity } from '../modules/user-space/entities'; @Module({ imports: [ @@ -31,6 +32,7 @@ import { SpaceTypeEntity } from '../modules/space-type/entities'; ProductEntity, SpaceEntity, SpaceTypeEntity, + UserSpaceEntity, ], namingStrategy: new SnakeNamingStrategy(), synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))), From c12b11b81d922c7fb88cf1b6b26d7e5033e8c26f Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:33:28 +0300 Subject: [PATCH 42/75] Add UserSpaceRepositoryModule and UserSpaceRepository to BuildingModule --- src/building/building.module.ts | 16 +++++++- .../controllers/building.controller.ts | 23 +++++++++++- src/building/dtos/get.building.dto.ts | 10 +++++ src/building/interface/building.interface.ts | 5 +++ src/building/services/building.service.ts | 37 +++++++++++++++++++ 5 files changed, 88 insertions(+), 3 deletions(-) diff --git a/src/building/building.module.ts b/src/building/building.module.ts index 7574f81..5507453 100644 --- a/src/building/building.module.ts +++ b/src/building/building.module.ts @@ -6,11 +6,23 @@ import { SpaceRepositoryModule } from '@app/common/modules/space/space.repositor import { SpaceRepository } from '@app/common/modules/space/repositories'; import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space.type.repository.module'; import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; +import { UserSpaceRepositoryModule } from '@app/common/modules/user-space/user.space.repository.module'; +import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; @Module({ - imports: [ConfigModule, SpaceRepositoryModule, SpaceTypeRepositoryModule], + imports: [ + ConfigModule, + SpaceRepositoryModule, + SpaceTypeRepositoryModule, + UserSpaceRepositoryModule, + ], controllers: [BuildingController], - providers: [BuildingService, SpaceRepository, SpaceTypeRepository], + providers: [ + BuildingService, + SpaceRepository, + SpaceTypeRepository, + UserSpaceRepository, + ], exports: [BuildingService], }) export class BuildingModule {} diff --git a/src/building/controllers/building.controller.ts b/src/building/controllers/building.controller.ts index ec4a616..9e7a873 100644 --- a/src/building/controllers/building.controller.ts +++ b/src/building/controllers/building.controller.ts @@ -10,11 +10,15 @@ import { Put, Query, UseGuards, + ValidationPipe, } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { AddBuildingDto } from '../dtos/add.building.dto'; -import { GetBuildingChildDto } from '../dtos/get.building.dto'; +import { + GetBuildingByUserIdDto, + GetBuildingChildDto, +} from '../dtos/get.building.dto'; import { UpdateBuildingNameDto } from '../dtos/update.building.dto'; import { CheckCommunityTypeGuard } from 'src/guards/community.type.guard'; @@ -92,6 +96,23 @@ export class BuildingController { ); } } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get() + async getBuildingsByUserId( + @Query(ValidationPipe) dto: GetBuildingByUserIdDto, + ) { + try { + return await this.buildingService.getBuildingsByUserId(dto.userUuid); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Put('rename/:buildingUuid') diff --git a/src/building/dtos/get.building.dto.ts b/src/building/dtos/get.building.dto.ts index f762469..720201a 100644 --- a/src/building/dtos/get.building.dto.ts +++ b/src/building/dtos/get.building.dto.ts @@ -49,3 +49,13 @@ export class GetBuildingChildDto { }) public includeSubSpaces: boolean = false; } + +export class GetBuildingByUserIdDto { + @ApiProperty({ + description: 'userUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public userUuid: string; +} diff --git a/src/building/interface/building.interface.ts b/src/building/interface/building.interface.ts index 4bdf760..1127456 100644 --- a/src/building/interface/building.interface.ts +++ b/src/building/interface/building.interface.ts @@ -24,3 +24,8 @@ export interface RenameBuildingByUuidInterface { name: string; type: string; } +export interface GetBuildingByUserUuidInterface { + uuid: string; + name: string; + type: string; +} diff --git a/src/building/services/building.service.ts b/src/building/services/building.service.ts index 7bfd33c..726697b 100644 --- a/src/building/services/building.service.ts +++ b/src/building/services/building.service.ts @@ -11,17 +11,20 @@ import { AddBuildingDto } from '../dtos'; import { BuildingChildInterface, BuildingParentInterface, + GetBuildingByUserUuidInterface, GetBuildingByUuidInterface, RenameBuildingByUuidInterface, } from '../interface/building.interface'; import { SpaceEntity } from '@app/common/modules/space/entities'; import { UpdateBuildingNameDto } from '../dtos/update.building.dto'; +import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; @Injectable() export class BuildingService { constructor( private readonly spaceRepository: SpaceRepository, private readonly spaceTypeRepository: SpaceTypeRepository, + private readonly userSpaceRepository: UserSpaceRepository, ) {} async addBuilding(addBuildingDto: AddBuildingDto) { @@ -210,6 +213,40 @@ export class BuildingService { } } + async getBuildingsByUserId( + userUuid: string, + ): Promise { + try { + const buildings = await this.userSpaceRepository.find({ + relations: ['space', 'space.spaceType'], + where: { + user: { uuid: userUuid }, + space: { spaceType: { type: 'building' } }, + }, + }); + + if (buildings.length === 0) { + throw new HttpException( + 'this user has no buildings', + HttpStatus.NOT_FOUND, + ); + } + const spaces = buildings.map((building) => ({ + uuid: building.space.uuid, + name: building.space.spaceName, + type: building.space.spaceType.type, + })); + + return spaces; + } catch (err) { + if (err instanceof HttpException) { + throw err; + } else { + throw new HttpException('user not found', HttpStatus.NOT_FOUND); + } + } + } + async renameBuildingByUuid( buildingUuid: string, updateBuildingNameDto: UpdateBuildingNameDto, From 230ed4eac1f69118811abb2202c00ff5eae7f825 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:33:37 +0300 Subject: [PATCH 43/75] Add UserSpaceRepositoryModule and UserSpaceRepository to CommunityModule --- src/community/community.module.ts | 16 +++++++- .../controllers/community.controller.ts | 22 ++++++++++- src/community/dtos/get.community.dto.ts | 10 +++++ .../interface/community.interface.ts | 6 +++ src/community/services/community.service.ts | 37 +++++++++++++++++++ 5 files changed, 88 insertions(+), 3 deletions(-) diff --git a/src/community/community.module.ts b/src/community/community.module.ts index 36b9da2..de5a7c0 100644 --- a/src/community/community.module.ts +++ b/src/community/community.module.ts @@ -6,11 +6,23 @@ import { SpaceRepositoryModule } from '@app/common/modules/space/space.repositor import { SpaceRepository } from '@app/common/modules/space/repositories'; import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space.type.repository.module'; import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; +import { UserSpaceRepositoryModule } from '@app/common/modules/user-space/user.space.repository.module'; +import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; @Module({ - imports: [ConfigModule, SpaceRepositoryModule, SpaceTypeRepositoryModule], + imports: [ + ConfigModule, + SpaceRepositoryModule, + SpaceTypeRepositoryModule, + UserSpaceRepositoryModule, + ], controllers: [CommunityController], - providers: [CommunityService, SpaceRepository, SpaceTypeRepository], + providers: [ + CommunityService, + SpaceRepository, + SpaceTypeRepository, + UserSpaceRepository, + ], exports: [CommunityService], }) export class CommunityModule {} diff --git a/src/community/controllers/community.controller.ts b/src/community/controllers/community.controller.ts index f889e13..ea9805c 100644 --- a/src/community/controllers/community.controller.ts +++ b/src/community/controllers/community.controller.ts @@ -10,11 +10,15 @@ import { Put, Query, UseGuards, + ValidationPipe, } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { AddCommunityDto } from '../dtos/add.community.dto'; -import { GetCommunityChildDto } from '../dtos/get.community.dto'; +import { + GetCommunitiesByUserIdDto, + GetCommunityChildDto, +} from '../dtos/get.community.dto'; import { UpdateCommunityNameDto } from '../dtos/update.community.dto'; @ApiTags('Community Module') @@ -77,6 +81,22 @@ export class CommunityController { } } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get() + async getCommunitiesByUserId( + @Query(ValidationPipe) dto: GetCommunitiesByUserIdDto, + ) { + try { + return await this.communityService.getCommunitiesByUserId(dto.userUuid); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Put('rename/:communityUuid') diff --git a/src/community/dtos/get.community.dto.ts b/src/community/dtos/get.community.dto.ts index be614e5..5d0e08c 100644 --- a/src/community/dtos/get.community.dto.ts +++ b/src/community/dtos/get.community.dto.ts @@ -49,3 +49,13 @@ export class GetCommunityChildDto { }) public includeSubSpaces: boolean = false; } + +export class GetCommunitiesByUserIdDto { + @ApiProperty({ + description: 'userUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public userUuid: string; +} diff --git a/src/community/interface/community.interface.ts b/src/community/interface/community.interface.ts index ca18aca..31c0579 100644 --- a/src/community/interface/community.interface.ts +++ b/src/community/interface/community.interface.ts @@ -18,3 +18,9 @@ export interface RenameCommunityByUuidInterface { name: string; type: string; } + +export interface GetCommunityByUserUuidInterface { + uuid: string; + name: string; + type: string; +} diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index 5e9b7ea..1c3109a 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -10,17 +10,20 @@ import { SpaceRepository } from '@app/common/modules/space/repositories'; import { AddCommunityDto } from '../dtos'; import { CommunityChildInterface, + GetCommunityByUserUuidInterface, GetCommunityByUuidInterface, RenameCommunityByUuidInterface, } from '../interface/community.interface'; import { SpaceEntity } from '@app/common/modules/space/entities'; import { UpdateCommunityNameDto } from '../dtos/update.community.dto'; +import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; @Injectable() export class CommunityService { constructor( private readonly spaceRepository: SpaceRepository, private readonly spaceTypeRepository: SpaceTypeRepository, + private readonly userSpaceRepository: UserSpaceRepository, ) {} async addCommunity(addCommunityDto: AddCommunityDto) { @@ -151,6 +154,40 @@ export class CommunityService { return childHierarchies; } + + async getCommunitiesByUserId( + userUuid: string, + ): Promise { + try { + const communities = await this.userSpaceRepository.find({ + relations: ['space', 'space.spaceType'], + where: { + user: { uuid: userUuid }, + space: { spaceType: { type: 'community' } }, + }, + }); + + if (communities.length === 0) { + throw new HttpException( + 'this user has no communities', + HttpStatus.NOT_FOUND, + ); + } + const spaces = communities.map((community) => ({ + uuid: community.space.uuid, + name: community.space.spaceName, + type: community.space.spaceType.type, + })); + + return spaces; + } catch (err) { + if (err instanceof HttpException) { + throw err; + } else { + throw new HttpException('user not found', HttpStatus.NOT_FOUND); + } + } + } async renameCommunityByUuid( communityUuid: string, updateCommunityDto: UpdateCommunityNameDto, From c03bb1ef3677f4b7e9c983a23705045380673c10 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:33:53 +0300 Subject: [PATCH 44/75] Add GetFloorsByUserId endpoint and related functionality --- src/floor/controllers/floor.controller.ts | 17 ++++++++++- src/floor/dtos/get.floor.dto.ts | 10 ++++++ src/floor/floor.module.ts | 16 ++++++++-- src/floor/interface/floor.interface.ts | 6 ++++ src/floor/services/floor.service.ts | 37 +++++++++++++++++++++++ 5 files changed, 83 insertions(+), 3 deletions(-) diff --git a/src/floor/controllers/floor.controller.ts b/src/floor/controllers/floor.controller.ts index 5c1021a..c1fa66f 100644 --- a/src/floor/controllers/floor.controller.ts +++ b/src/floor/controllers/floor.controller.ts @@ -10,11 +10,12 @@ import { Put, Query, UseGuards, + ValidationPipe, } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { AddFloorDto } from '../dtos/add.floor.dto'; -import { GetFloorChildDto } from '../dtos/get.floor.dto'; +import { GetFloorByUserIdDto, GetFloorChildDto } from '../dtos/get.floor.dto'; import { UpdateFloorNameDto } from '../dtos/update.floor.dto'; import { CheckBuildingTypeGuard } from 'src/guards/building.type.guard'; @@ -91,6 +92,20 @@ export class FloorController { } } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get() + async getFloorsByUserId(@Query(ValidationPipe) dto: GetFloorByUserIdDto) { + try { + return await this.floorService.getFloorsByUserId(dto.userUuid); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Put('rename/:floorUuid') diff --git a/src/floor/dtos/get.floor.dto.ts b/src/floor/dtos/get.floor.dto.ts index 23a8e56..40d0aed 100644 --- a/src/floor/dtos/get.floor.dto.ts +++ b/src/floor/dtos/get.floor.dto.ts @@ -49,3 +49,13 @@ export class GetFloorChildDto { }) public includeSubSpaces: boolean = false; } + +export class GetFloorByUserIdDto { + @ApiProperty({ + description: 'userUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public userUuid: string; +} diff --git a/src/floor/floor.module.ts b/src/floor/floor.module.ts index 7085659..f247fea 100644 --- a/src/floor/floor.module.ts +++ b/src/floor/floor.module.ts @@ -6,11 +6,23 @@ import { SpaceRepositoryModule } from '@app/common/modules/space/space.repositor import { SpaceRepository } from '@app/common/modules/space/repositories'; import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space.type.repository.module'; import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; +import { UserSpaceRepositoryModule } from '@app/common/modules/user-space/user.space.repository.module'; +import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; @Module({ - imports: [ConfigModule, SpaceRepositoryModule, SpaceTypeRepositoryModule], + imports: [ + ConfigModule, + SpaceRepositoryModule, + SpaceTypeRepositoryModule, + UserSpaceRepositoryModule, + ], controllers: [FloorController], - providers: [FloorService, SpaceRepository, SpaceTypeRepository], + providers: [ + FloorService, + SpaceRepository, + SpaceTypeRepository, + UserSpaceRepository, + ], exports: [FloorService], }) export class FloorModule {} diff --git a/src/floor/interface/floor.interface.ts b/src/floor/interface/floor.interface.ts index eb6a5ec..37f35c4 100644 --- a/src/floor/interface/floor.interface.ts +++ b/src/floor/interface/floor.interface.ts @@ -24,3 +24,9 @@ export interface RenameFloorByUuidInterface { name: string; type: string; } + +export interface GetFloorByUserUuidInterface { + uuid: string; + name: string; + type: string; +} diff --git a/src/floor/services/floor.service.ts b/src/floor/services/floor.service.ts index f4f0759..0cf19da 100644 --- a/src/floor/services/floor.service.ts +++ b/src/floor/services/floor.service.ts @@ -11,17 +11,20 @@ import { AddFloorDto } from '../dtos'; import { FloorChildInterface, FloorParentInterface, + GetFloorByUserUuidInterface, GetFloorByUuidInterface, RenameFloorByUuidInterface, } from '../interface/floor.interface'; import { SpaceEntity } from '@app/common/modules/space/entities'; import { UpdateFloorNameDto } from '../dtos/update.floor.dto'; +import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; @Injectable() export class FloorService { constructor( private readonly spaceRepository: SpaceRepository, private readonly spaceTypeRepository: SpaceTypeRepository, + private readonly userSpaceRepository: UserSpaceRepository, ) {} async addFloor(addFloorDto: AddFloorDto) { @@ -195,6 +198,40 @@ export class FloorService { } } + async getFloorsByUserId( + userUuid: string, + ): Promise { + try { + const floors = await this.userSpaceRepository.find({ + relations: ['space', 'space.spaceType'], + where: { + user: { uuid: userUuid }, + space: { spaceType: { type: 'floor' } }, + }, + }); + + if (floors.length === 0) { + throw new HttpException( + 'this user has no floors', + HttpStatus.NOT_FOUND, + ); + } + const spaces = floors.map((floor) => ({ + uuid: floor.space.uuid, + name: floor.space.spaceName, + type: floor.space.spaceType.type, + })); + + return spaces; + } catch (err) { + if (err instanceof HttpException) { + throw err; + } else { + throw new HttpException('user not found', HttpStatus.NOT_FOUND); + } + } + } + async renameFloorByUuid( floorUuid: string, updateFloorDto: UpdateFloorNameDto, From 5c3edb0bab5a464c76aa1ae0abe4f37de69b2441 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:34:03 +0300 Subject: [PATCH 45/75] Add endpoint to get rooms by user ID --- src/room/controllers/room.controller.ts | 17 ++++++++++++ src/room/dtos/get.room.dto.ts | 12 +++++++++ src/room/interface/room.interface.ts | 5 ++++ src/room/room.module.ts | 16 +++++++++-- src/room/services/room.service.ts | 35 +++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 src/room/dtos/get.room.dto.ts diff --git a/src/room/controllers/room.controller.ts b/src/room/controllers/room.controller.ts index b0e4e3f..80ef7cf 100644 --- a/src/room/controllers/room.controller.ts +++ b/src/room/controllers/room.controller.ts @@ -8,13 +8,16 @@ import { Param, Post, Put, + Query, UseGuards, + ValidationPipe, } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { AddRoomDto } from '../dtos/add.room.dto'; import { UpdateRoomNameDto } from '../dtos/update.room.dto'; import { CheckUnitTypeGuard } from 'src/guards/unit.type.guard'; +import { GetRoomByUserIdDto } from '../dtos/get.room.dto'; @ApiTags('Room Module') @Controller({ @@ -69,6 +72,20 @@ export class RoomController { } } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get() + async getRoomsByUserId(@Query(ValidationPipe) dto: GetRoomByUserIdDto) { + try { + return await this.roomService.getRoomsByUserId(dto.userUuid); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Put('rename/:roomUuid') diff --git a/src/room/dtos/get.room.dto.ts b/src/room/dtos/get.room.dto.ts new file mode 100644 index 0000000..f919b85 --- /dev/null +++ b/src/room/dtos/get.room.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class GetRoomByUserIdDto { + @ApiProperty({ + description: 'userUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public userUuid: string; +} diff --git a/src/room/interface/room.interface.ts b/src/room/interface/room.interface.ts index 3f1d817..49473a3 100644 --- a/src/room/interface/room.interface.ts +++ b/src/room/interface/room.interface.ts @@ -17,3 +17,8 @@ export interface RenameRoomByUuidInterface { name: string; type: string; } +export interface GetRoomByUserUuidInterface { + uuid: string; + name: string; + type: string; +} diff --git a/src/room/room.module.ts b/src/room/room.module.ts index 8411cd4..d83d6fb 100644 --- a/src/room/room.module.ts +++ b/src/room/room.module.ts @@ -6,11 +6,23 @@ import { SpaceRepositoryModule } from '@app/common/modules/space/space.repositor import { SpaceRepository } from '@app/common/modules/space/repositories'; import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space.type.repository.module'; import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; +import { UserSpaceRepositoryModule } from '@app/common/modules/user-space/user.space.repository.module'; +import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; @Module({ - imports: [ConfigModule, SpaceRepositoryModule, SpaceTypeRepositoryModule], + imports: [ + ConfigModule, + SpaceRepositoryModule, + SpaceTypeRepositoryModule, + UserSpaceRepositoryModule, + ], controllers: [RoomController], - providers: [RoomService, SpaceRepository, SpaceTypeRepository], + providers: [ + RoomService, + SpaceRepository, + SpaceTypeRepository, + UserSpaceRepository, + ], exports: [RoomService], }) export class RoomModule {} diff --git a/src/room/services/room.service.ts b/src/room/services/room.service.ts index d6c8afa..7b0b7f5 100644 --- a/src/room/services/room.service.ts +++ b/src/room/services/room.service.ts @@ -11,14 +11,17 @@ import { RoomParentInterface, GetRoomByUuidInterface, RenameRoomByUuidInterface, + GetRoomByUserUuidInterface, } from '../interface/room.interface'; import { UpdateRoomNameDto } from '../dtos/update.room.dto'; +import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; @Injectable() export class RoomService { constructor( private readonly spaceRepository: SpaceRepository, private readonly spaceTypeRepository: SpaceTypeRepository, + private readonly userSpaceRepository: UserSpaceRepository, ) {} async addRoom(addRoomDto: AddRoomDto) { @@ -103,6 +106,38 @@ export class RoomService { } } } + + async getRoomsByUserId( + userUuid: string, + ): Promise { + try { + const rooms = await this.userSpaceRepository.find({ + relations: ['space', 'space.spaceType'], + where: { + user: { uuid: userUuid }, + space: { spaceType: { type: 'room' } }, + }, + }); + + if (rooms.length === 0) { + throw new HttpException('this user has no rooms', HttpStatus.NOT_FOUND); + } + const spaces = rooms.map((room) => ({ + uuid: room.space.uuid, + name: room.space.spaceName, + type: room.space.spaceType.type, + })); + + return spaces; + } catch (err) { + if (err instanceof HttpException) { + throw err; + } else { + throw new HttpException('user not found', HttpStatus.NOT_FOUND); + } + } + } + async renameRoomByUuid( roomUuid: string, updateRoomNameDto: UpdateRoomNameDto, From f30dd755a201549151422cb4bd5d93e7c53ba97d Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:34:15 +0300 Subject: [PATCH 46/75] Add endpoint to get units by user ID --- src/unit/controllers/unit.controller.ts | 17 ++++++++++++- src/unit/dtos/get.unit.dto.ts | 9 +++++++ src/unit/interface/unit.interface.ts | 5 ++++ src/unit/services/unit.service.ts | 32 +++++++++++++++++++++++++ src/unit/unit.module.ts | 16 +++++++++++-- 5 files changed, 76 insertions(+), 3 deletions(-) diff --git a/src/unit/controllers/unit.controller.ts b/src/unit/controllers/unit.controller.ts index f36a510..245dd85 100644 --- a/src/unit/controllers/unit.controller.ts +++ b/src/unit/controllers/unit.controller.ts @@ -10,11 +10,12 @@ import { Put, Query, UseGuards, + ValidationPipe, } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { AddUnitDto } from '../dtos/add.unit.dto'; -import { GetUnitChildDto } from '../dtos/get.unit.dto'; +import { GetUnitByUserIdDto, GetUnitChildDto } from '../dtos/get.unit.dto'; import { UpdateUnitNameDto } from '../dtos/update.unit.dto'; import { CheckFloorTypeGuard } from 'src/guards/floor.type.guard'; @@ -88,6 +89,20 @@ export class UnitController { } } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get() + async getUnitsByUserId(@Query(ValidationPipe) dto: GetUnitByUserIdDto) { + try { + return await this.unitService.getUnitsByUserId(dto.userUuid); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Put('rename/:unitUuid') diff --git a/src/unit/dtos/get.unit.dto.ts b/src/unit/dtos/get.unit.dto.ts index b96147b..82837aa 100644 --- a/src/unit/dtos/get.unit.dto.ts +++ b/src/unit/dtos/get.unit.dto.ts @@ -49,3 +49,12 @@ export class GetUnitChildDto { }) public includeSubSpaces: boolean = false; } +export class GetUnitByUserIdDto { + @ApiProperty({ + description: 'userUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public userUuid: string; +} diff --git a/src/unit/interface/unit.interface.ts b/src/unit/interface/unit.interface.ts index 8502a86..39fac9a 100644 --- a/src/unit/interface/unit.interface.ts +++ b/src/unit/interface/unit.interface.ts @@ -24,3 +24,8 @@ export interface RenameUnitByUuidInterface { name: string; type: string; } +export interface GetUnitByUserUuidInterface { + uuid: string; + name: string; + type: string; +} diff --git a/src/unit/services/unit.service.ts b/src/unit/services/unit.service.ts index e84bc0f..27bebbc 100644 --- a/src/unit/services/unit.service.ts +++ b/src/unit/services/unit.service.ts @@ -13,15 +13,18 @@ import { UnitParentInterface, GetUnitByUuidInterface, RenameUnitByUuidInterface, + GetUnitByUserUuidInterface, } from '../interface/unit.interface'; import { SpaceEntity } from '@app/common/modules/space/entities'; import { UpdateUnitNameDto } from '../dtos/update.unit.dto'; +import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; @Injectable() export class UnitService { constructor( private readonly spaceRepository: SpaceRepository, private readonly spaceTypeRepository: SpaceTypeRepository, + private readonly userSpaceRepository: UserSpaceRepository, ) {} async addUnit(addUnitDto: AddUnitDto) { @@ -195,7 +198,36 @@ export class UnitService { } } } + async getUnitsByUserId( + userUuid: string, + ): Promise { + try { + const units = await this.userSpaceRepository.find({ + relations: ['space', 'space.spaceType'], + where: { + user: { uuid: userUuid }, + space: { spaceType: { type: 'unit' } }, + }, + }); + if (units.length === 0) { + throw new HttpException('this user has no units', HttpStatus.NOT_FOUND); + } + const spaces = units.map((unit) => ({ + uuid: unit.space.uuid, + name: unit.space.spaceName, + type: unit.space.spaceType.type, + })); + + return spaces; + } catch (err) { + if (err instanceof HttpException) { + throw err; + } else { + throw new HttpException('user not found', HttpStatus.NOT_FOUND); + } + } + } async renameUnitByUuid( unitUuid: string, updateUnitNameDto: UpdateUnitNameDto, diff --git a/src/unit/unit.module.ts b/src/unit/unit.module.ts index eaf571f..2f0234e 100644 --- a/src/unit/unit.module.ts +++ b/src/unit/unit.module.ts @@ -6,11 +6,23 @@ import { SpaceRepositoryModule } from '@app/common/modules/space/space.repositor import { SpaceRepository } from '@app/common/modules/space/repositories'; import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space.type.repository.module'; import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; +import { UserSpaceRepositoryModule } from '@app/common/modules/user-space/user.space.repository.module'; +import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; @Module({ - imports: [ConfigModule, SpaceRepositoryModule, SpaceTypeRepositoryModule], + imports: [ + ConfigModule, + SpaceRepositoryModule, + SpaceTypeRepositoryModule, + UserSpaceRepositoryModule, + ], controllers: [UnitController], - providers: [UnitService, SpaceRepository, SpaceTypeRepository], + providers: [ + UnitService, + SpaceRepository, + SpaceTypeRepository, + UserSpaceRepository, + ], exports: [UnitService], }) export class UnitModule {} From 74b747110693e500c2a0ca0eed51095111b5cc09 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 16 Apr 2024 16:11:43 +0300 Subject: [PATCH 47/75] Refactor controllers to use userUuid as route param --- src/building/controllers/building.controller.ts | 14 ++++---------- src/building/dtos/get.building.dto.ts | 10 ---------- src/community/controllers/community.controller.ts | 14 ++++---------- src/community/dtos/get.community.dto.ts | 10 ---------- src/floor/controllers/floor.controller.ts | 9 ++++----- src/floor/dtos/get.floor.dto.ts | 10 ---------- src/room/controllers/room.controller.ts | 9 +++------ src/room/dtos/get.room.dto.ts | 12 ------------ src/unit/controllers/unit.controller.ts | 9 ++++----- 9 files changed, 19 insertions(+), 78 deletions(-) delete mode 100644 src/room/dtos/get.room.dto.ts diff --git a/src/building/controllers/building.controller.ts b/src/building/controllers/building.controller.ts index 9e7a873..bac6d18 100644 --- a/src/building/controllers/building.controller.ts +++ b/src/building/controllers/building.controller.ts @@ -10,15 +10,11 @@ import { Put, Query, UseGuards, - ValidationPipe, } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { AddBuildingDto } from '../dtos/add.building.dto'; -import { - GetBuildingByUserIdDto, - GetBuildingChildDto, -} from '../dtos/get.building.dto'; +import { GetBuildingChildDto } from '../dtos/get.building.dto'; import { UpdateBuildingNameDto } from '../dtos/update.building.dto'; import { CheckCommunityTypeGuard } from 'src/guards/community.type.guard'; @@ -99,12 +95,10 @@ export class BuildingController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Get() - async getBuildingsByUserId( - @Query(ValidationPipe) dto: GetBuildingByUserIdDto, - ) { + @Get('user/:userUuid') + async getBuildingsByUserId(@Param('userUuid') userUuid: string) { try { - return await this.buildingService.getBuildingsByUserId(dto.userUuid); + return await this.buildingService.getBuildingsByUserId(userUuid); } catch (error) { throw new HttpException( error.message || 'Internal server error', diff --git a/src/building/dtos/get.building.dto.ts b/src/building/dtos/get.building.dto.ts index 720201a..f762469 100644 --- a/src/building/dtos/get.building.dto.ts +++ b/src/building/dtos/get.building.dto.ts @@ -49,13 +49,3 @@ export class GetBuildingChildDto { }) public includeSubSpaces: boolean = false; } - -export class GetBuildingByUserIdDto { - @ApiProperty({ - description: 'userUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public userUuid: string; -} diff --git a/src/community/controllers/community.controller.ts b/src/community/controllers/community.controller.ts index ea9805c..ab46154 100644 --- a/src/community/controllers/community.controller.ts +++ b/src/community/controllers/community.controller.ts @@ -10,15 +10,11 @@ import { Put, Query, UseGuards, - ValidationPipe, } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { AddCommunityDto } from '../dtos/add.community.dto'; -import { - GetCommunitiesByUserIdDto, - GetCommunityChildDto, -} from '../dtos/get.community.dto'; +import { GetCommunityChildDto } from '../dtos/get.community.dto'; import { UpdateCommunityNameDto } from '../dtos/update.community.dto'; @ApiTags('Community Module') @@ -83,12 +79,10 @@ export class CommunityController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Get() - async getCommunitiesByUserId( - @Query(ValidationPipe) dto: GetCommunitiesByUserIdDto, - ) { + @Get('user/:userUuid') + async getCommunitiesByUserId(@Param('userUuid') userUuid: string) { try { - return await this.communityService.getCommunitiesByUserId(dto.userUuid); + return await this.communityService.getCommunitiesByUserId(userUuid); } catch (error) { throw new HttpException( error.message || 'Internal server error', diff --git a/src/community/dtos/get.community.dto.ts b/src/community/dtos/get.community.dto.ts index 5d0e08c..be614e5 100644 --- a/src/community/dtos/get.community.dto.ts +++ b/src/community/dtos/get.community.dto.ts @@ -49,13 +49,3 @@ export class GetCommunityChildDto { }) public includeSubSpaces: boolean = false; } - -export class GetCommunitiesByUserIdDto { - @ApiProperty({ - description: 'userUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public userUuid: string; -} diff --git a/src/floor/controllers/floor.controller.ts b/src/floor/controllers/floor.controller.ts index c1fa66f..fd22325 100644 --- a/src/floor/controllers/floor.controller.ts +++ b/src/floor/controllers/floor.controller.ts @@ -10,12 +10,11 @@ import { Put, Query, UseGuards, - ValidationPipe, } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { AddFloorDto } from '../dtos/add.floor.dto'; -import { GetFloorByUserIdDto, GetFloorChildDto } from '../dtos/get.floor.dto'; +import { GetFloorChildDto } from '../dtos/get.floor.dto'; import { UpdateFloorNameDto } from '../dtos/update.floor.dto'; import { CheckBuildingTypeGuard } from 'src/guards/building.type.guard'; @@ -94,10 +93,10 @@ export class FloorController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Get() - async getFloorsByUserId(@Query(ValidationPipe) dto: GetFloorByUserIdDto) { + @Get('user/:userUuid') + async getFloorsByUserId(@Param('userUuid') userUuid: string) { try { - return await this.floorService.getFloorsByUserId(dto.userUuid); + return await this.floorService.getFloorsByUserId(userUuid); } catch (error) { throw new HttpException( error.message || 'Internal server error', diff --git a/src/floor/dtos/get.floor.dto.ts b/src/floor/dtos/get.floor.dto.ts index 40d0aed..23a8e56 100644 --- a/src/floor/dtos/get.floor.dto.ts +++ b/src/floor/dtos/get.floor.dto.ts @@ -49,13 +49,3 @@ export class GetFloorChildDto { }) public includeSubSpaces: boolean = false; } - -export class GetFloorByUserIdDto { - @ApiProperty({ - description: 'userUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public userUuid: string; -} diff --git a/src/room/controllers/room.controller.ts b/src/room/controllers/room.controller.ts index 80ef7cf..dee131f 100644 --- a/src/room/controllers/room.controller.ts +++ b/src/room/controllers/room.controller.ts @@ -8,16 +8,13 @@ import { Param, Post, Put, - Query, UseGuards, - ValidationPipe, } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { AddRoomDto } from '../dtos/add.room.dto'; import { UpdateRoomNameDto } from '../dtos/update.room.dto'; import { CheckUnitTypeGuard } from 'src/guards/unit.type.guard'; -import { GetRoomByUserIdDto } from '../dtos/get.room.dto'; @ApiTags('Room Module') @Controller({ @@ -74,10 +71,10 @@ export class RoomController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Get() - async getRoomsByUserId(@Query(ValidationPipe) dto: GetRoomByUserIdDto) { + @Get('user/:userUuid') + async getRoomsByUserId(@Param('userUuid') userUuid: string) { try { - return await this.roomService.getRoomsByUserId(dto.userUuid); + return await this.roomService.getRoomsByUserId(userUuid); } catch (error) { throw new HttpException( error.message || 'Internal server error', diff --git a/src/room/dtos/get.room.dto.ts b/src/room/dtos/get.room.dto.ts deleted file mode 100644 index f919b85..0000000 --- a/src/room/dtos/get.room.dto.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; - -export class GetRoomByUserIdDto { - @ApiProperty({ - description: 'userUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public userUuid: string; -} diff --git a/src/unit/controllers/unit.controller.ts b/src/unit/controllers/unit.controller.ts index 245dd85..5289200 100644 --- a/src/unit/controllers/unit.controller.ts +++ b/src/unit/controllers/unit.controller.ts @@ -10,12 +10,11 @@ import { Put, Query, UseGuards, - ValidationPipe, } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { AddUnitDto } from '../dtos/add.unit.dto'; -import { GetUnitByUserIdDto, GetUnitChildDto } from '../dtos/get.unit.dto'; +import { GetUnitChildDto } from '../dtos/get.unit.dto'; import { UpdateUnitNameDto } from '../dtos/update.unit.dto'; import { CheckFloorTypeGuard } from 'src/guards/floor.type.guard'; @@ -91,10 +90,10 @@ export class UnitController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Get() - async getUnitsByUserId(@Query(ValidationPipe) dto: GetUnitByUserIdDto) { + @Get('user/:userUuid') + async getUnitsByUserId(@Param('userUuid') userUuid: string) { try { - return await this.unitService.getUnitsByUserId(dto.userUuid); + return await this.unitService.getUnitsByUserId(userUuid); } catch (error) { throw new HttpException( error.message || 'Internal server error', From 1d9bb96a87ad7a0f9aa6d9ea552f35205d2b43f6 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 16 Apr 2024 17:22:02 +0300 Subject: [PATCH 48/75] Add UserRepository and User Building functionality --- src/building/building.module.ts | 4 ++++ .../controllers/building.controller.ts | 18 +++++++++++++-- src/building/dtos/add.building.dto.ts | 19 ++++++++++++++++ src/building/services/building.service.ts | 22 +++++++++++++++++-- 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/building/building.module.ts b/src/building/building.module.ts index 5507453..80391fe 100644 --- a/src/building/building.module.ts +++ b/src/building/building.module.ts @@ -8,6 +8,8 @@ import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space. import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; import { UserSpaceRepositoryModule } from '@app/common/modules/user-space/user.space.repository.module'; import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; +import { UserRepository } from '@app/common/modules/user/repositories'; +import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; @Module({ imports: [ @@ -15,6 +17,7 @@ import { UserSpaceRepository } from '@app/common/modules/user-space/repositories SpaceRepositoryModule, SpaceTypeRepositoryModule, UserSpaceRepositoryModule, + UserRepositoryModule, ], controllers: [BuildingController], providers: [ @@ -22,6 +25,7 @@ import { UserSpaceRepository } from '@app/common/modules/user-space/repositories SpaceRepository, SpaceTypeRepository, UserSpaceRepository, + UserRepository, ], exports: [BuildingService], }) diff --git a/src/building/controllers/building.controller.ts b/src/building/controllers/building.controller.ts index bac6d18..8517ee1 100644 --- a/src/building/controllers/building.controller.ts +++ b/src/building/controllers/building.controller.ts @@ -13,10 +13,11 @@ import { } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; -import { AddBuildingDto } from '../dtos/add.building.dto'; +import { AddBuildingDto, AddUserBuildingDto } from '../dtos/add.building.dto'; import { GetBuildingChildDto } from '../dtos/get.building.dto'; import { UpdateBuildingNameDto } from '../dtos/update.building.dto'; import { CheckCommunityTypeGuard } from 'src/guards/community.type.guard'; +import { CheckUserBuildingGuard } from 'src/guards/user.building.guard'; @ApiTags('Building Module') @Controller({ @@ -92,7 +93,20 @@ export class BuildingController { ); } } - + @ApiBearerAuth() + @UseGuards(JwtAuthGuard, CheckUserBuildingGuard) + @Post('user') + async addUserBuilding(@Body() addUserBuildingDto: AddUserBuildingDto) { + try { + await this.buildingService.addUserBuilding(addUserBuildingDto); + return { message: 'user building added successfully' }; + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('user/:userUuid') diff --git a/src/building/dtos/add.building.dto.ts b/src/building/dtos/add.building.dto.ts index e9268c0..5d79231 100644 --- a/src/building/dtos/add.building.dto.ts +++ b/src/building/dtos/add.building.dto.ts @@ -21,3 +21,22 @@ export class AddBuildingDto { Object.assign(this, dto); } } +export class AddUserBuildingDto { + @ApiProperty({ + description: 'buildingUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public buildingUuid: string; + @ApiProperty({ + description: 'userUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public userUuid: string; + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/building/services/building.service.ts b/src/building/services/building.service.ts index 726697b..fcb803b 100644 --- a/src/building/services/building.service.ts +++ b/src/building/services/building.service.ts @@ -7,7 +7,7 @@ import { BadRequestException, } from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { AddBuildingDto } from '../dtos'; +import { AddBuildingDto, AddUserBuildingDto } from '../dtos'; import { BuildingChildInterface, BuildingParentInterface, @@ -246,7 +246,25 @@ export class BuildingService { } } } - + async addUserBuilding(addUserBuildingDto: AddUserBuildingDto) { + try { + await this.userSpaceRepository.save({ + user: { uuid: addUserBuildingDto.userUuid }, + space: { uuid: addUserBuildingDto.buildingUuid }, + }); + } catch (err) { + if (err.code === '23505') { + throw new HttpException( + 'User already belongs to this building', + HttpStatus.BAD_REQUEST, + ); + } + throw new HttpException( + err.message || 'Internal Server Error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } async renameBuildingByUuid( buildingUuid: string, updateBuildingNameDto: UpdateBuildingNameDto, From ae63ac67177ba5701ca53bb022bf96be2d9433be Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 16 Apr 2024 17:22:12 +0300 Subject: [PATCH 49/75] Add UserRepository and addUserCommunity endpoint --- src/community/community.module.ts | 4 ++++ .../controllers/community.controller.ts | 21 ++++++++++++++++-- src/community/dtos/add.community.dto.ts | 19 ++++++++++++++++ src/community/services/community.service.ts | 22 ++++++++++++++++++- 4 files changed, 63 insertions(+), 3 deletions(-) diff --git a/src/community/community.module.ts b/src/community/community.module.ts index de5a7c0..742e3ad 100644 --- a/src/community/community.module.ts +++ b/src/community/community.module.ts @@ -8,6 +8,8 @@ import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space. import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; import { UserSpaceRepositoryModule } from '@app/common/modules/user-space/user.space.repository.module'; import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; +import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; +import { UserRepository } from '@app/common/modules/user/repositories'; @Module({ imports: [ @@ -15,6 +17,7 @@ import { UserSpaceRepository } from '@app/common/modules/user-space/repositories SpaceRepositoryModule, SpaceTypeRepositoryModule, UserSpaceRepositoryModule, + UserRepositoryModule, ], controllers: [CommunityController], providers: [ @@ -22,6 +25,7 @@ import { UserSpaceRepository } from '@app/common/modules/user-space/repositories SpaceRepository, SpaceTypeRepository, UserSpaceRepository, + UserRepository, ], exports: [CommunityService], }) diff --git a/src/community/controllers/community.controller.ts b/src/community/controllers/community.controller.ts index ab46154..f20aa3d 100644 --- a/src/community/controllers/community.controller.ts +++ b/src/community/controllers/community.controller.ts @@ -13,9 +13,13 @@ import { } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; -import { AddCommunityDto } from '../dtos/add.community.dto'; +import { + AddCommunityDto, + AddUserCommunityDto, +} from '../dtos/add.community.dto'; import { GetCommunityChildDto } from '../dtos/get.community.dto'; import { UpdateCommunityNameDto } from '../dtos/update.community.dto'; +import { CheckUserCommunityGuard } from 'src/guards/user.community.guard'; @ApiTags('Community Module') @Controller({ @@ -90,7 +94,20 @@ export class CommunityController { ); } } - + @ApiBearerAuth() + @UseGuards(JwtAuthGuard, CheckUserCommunityGuard) + @Post('user') + async addUserCommunity(@Body() addUserCommunityDto: AddUserCommunityDto) { + try { + await this.communityService.addUserCommunity(addUserCommunityDto); + return { message: 'user community added successfully' }; + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Put('rename/:communityUuid') diff --git a/src/community/dtos/add.community.dto.ts b/src/community/dtos/add.community.dto.ts index 96dce06..06aec7c 100644 --- a/src/community/dtos/add.community.dto.ts +++ b/src/community/dtos/add.community.dto.ts @@ -14,3 +14,22 @@ export class AddCommunityDto { Object.assign(this, dto); } } +export class AddUserCommunityDto { + @ApiProperty({ + description: 'communityUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public communityUuid: string; + @ApiProperty({ + description: 'userUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public userUuid: string; + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index 1c3109a..e88290f 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -7,7 +7,7 @@ import { BadRequestException, } from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { AddCommunityDto } from '../dtos'; +import { AddCommunityDto, AddUserCommunityDto } from '../dtos'; import { CommunityChildInterface, GetCommunityByUserUuidInterface, @@ -188,6 +188,26 @@ export class CommunityService { } } } + + async addUserCommunity(addUserCommunityDto: AddUserCommunityDto) { + try { + await this.userSpaceRepository.save({ + user: { uuid: addUserCommunityDto.userUuid }, + space: { uuid: addUserCommunityDto.communityUuid }, + }); + } catch (err) { + if (err.code === '23505') { + throw new HttpException( + 'User already belongs to this community', + HttpStatus.BAD_REQUEST, + ); + } + throw new HttpException( + err.message || 'Internal Server Error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } async renameCommunityByUuid( communityUuid: string, updateCommunityDto: UpdateCommunityNameDto, From 5bf3215c6be90096f8f6d4fabf995a2acf617b6e Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 16 Apr 2024 17:22:26 +0300 Subject: [PATCH 50/75] Add CheckUserFloorGuard and implement addUserFloor functionality --- src/floor/controllers/floor.controller.ts | 18 +++++++++++++++++- src/floor/dtos/add.floor.dto.ts | 19 +++++++++++++++++++ src/floor/floor.module.ts | 4 ++++ src/floor/services/floor.service.ts | 22 ++++++++++++++++++++-- 4 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/floor/controllers/floor.controller.ts b/src/floor/controllers/floor.controller.ts index fd22325..2033918 100644 --- a/src/floor/controllers/floor.controller.ts +++ b/src/floor/controllers/floor.controller.ts @@ -13,10 +13,11 @@ import { } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; -import { AddFloorDto } from '../dtos/add.floor.dto'; +import { AddFloorDto, AddUserFloorDto } from '../dtos/add.floor.dto'; import { GetFloorChildDto } from '../dtos/get.floor.dto'; import { UpdateFloorNameDto } from '../dtos/update.floor.dto'; import { CheckBuildingTypeGuard } from 'src/guards/building.type.guard'; +import { CheckUserFloorGuard } from 'src/guards/user.floor.guard'; @ApiTags('Floor Module') @Controller({ @@ -91,6 +92,21 @@ export class FloorController { } } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard, CheckUserFloorGuard) + @Post('user') + async addUserFloor(@Body() addUserFloorDto: AddUserFloorDto) { + try { + await this.floorService.addUserFloor(addUserFloorDto); + return { message: 'user floor added successfully' }; + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('user/:userUuid') diff --git a/src/floor/dtos/add.floor.dto.ts b/src/floor/dtos/add.floor.dto.ts index 9f2de58..3d1655a 100644 --- a/src/floor/dtos/add.floor.dto.ts +++ b/src/floor/dtos/add.floor.dto.ts @@ -21,3 +21,22 @@ export class AddFloorDto { Object.assign(this, dto); } } +export class AddUserFloorDto { + @ApiProperty({ + description: 'floorUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public floorUuid: string; + @ApiProperty({ + description: 'userUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public userUuid: string; + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/floor/floor.module.ts b/src/floor/floor.module.ts index f247fea..71a6c67 100644 --- a/src/floor/floor.module.ts +++ b/src/floor/floor.module.ts @@ -8,6 +8,8 @@ import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space. import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; import { UserSpaceRepositoryModule } from '@app/common/modules/user-space/user.space.repository.module'; import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; +import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; +import { UserRepository } from '@app/common/modules/user/repositories'; @Module({ imports: [ @@ -15,6 +17,7 @@ import { UserSpaceRepository } from '@app/common/modules/user-space/repositories SpaceRepositoryModule, SpaceTypeRepositoryModule, UserSpaceRepositoryModule, + UserRepositoryModule, ], controllers: [FloorController], providers: [ @@ -22,6 +25,7 @@ import { UserSpaceRepository } from '@app/common/modules/user-space/repositories SpaceRepository, SpaceTypeRepository, UserSpaceRepository, + UserRepository, ], exports: [FloorService], }) diff --git a/src/floor/services/floor.service.ts b/src/floor/services/floor.service.ts index 0cf19da..91de93b 100644 --- a/src/floor/services/floor.service.ts +++ b/src/floor/services/floor.service.ts @@ -7,7 +7,7 @@ import { BadRequestException, } from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { AddFloorDto } from '../dtos'; +import { AddFloorDto, AddUserFloorDto } from '../dtos'; import { FloorChildInterface, FloorParentInterface, @@ -231,7 +231,25 @@ export class FloorService { } } } - + async addUserFloor(addUserFloorDto: AddUserFloorDto) { + try { + await this.userSpaceRepository.save({ + user: { uuid: addUserFloorDto.userUuid }, + space: { uuid: addUserFloorDto.floorUuid }, + }); + } catch (err) { + if (err.code === '23505') { + throw new HttpException( + 'User already belongs to this floor', + HttpStatus.BAD_REQUEST, + ); + } + throw new HttpException( + err.message || 'Internal Server Error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } async renameFloorByUuid( floorUuid: string, updateFloorDto: UpdateFloorNameDto, From 3afdd4922bbf205aab4ffdc51fa91c0bfab7262a Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 16 Apr 2024 17:22:38 +0300 Subject: [PATCH 51/75] Add AddUserRoomDto and endpoint to add user to room --- src/room/controllers/room.controller.ts | 18 ++++++++++++++++-- src/room/dtos/add.room.dto.ts | 19 +++++++++++++++++++ src/room/room.module.ts | 4 ++++ src/room/services/room.service.ts | 22 ++++++++++++++++++++-- 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/room/controllers/room.controller.ts b/src/room/controllers/room.controller.ts index dee131f..054bedf 100644 --- a/src/room/controllers/room.controller.ts +++ b/src/room/controllers/room.controller.ts @@ -12,9 +12,10 @@ import { } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; -import { AddRoomDto } from '../dtos/add.room.dto'; +import { AddRoomDto, AddUserRoomDto } from '../dtos/add.room.dto'; import { UpdateRoomNameDto } from '../dtos/update.room.dto'; import { CheckUnitTypeGuard } from 'src/guards/unit.type.guard'; +import { CheckUserRoomGuard } from 'src/guards/user.room.guard'; @ApiTags('Room Module') @Controller({ @@ -68,7 +69,20 @@ export class RoomController { ); } } - + @ApiBearerAuth() + @UseGuards(JwtAuthGuard, CheckUserRoomGuard) + @Post('user') + async addUserRoom(@Body() addUserRoomDto: AddUserRoomDto) { + try { + await this.roomService.addUserRoom(addUserRoomDto); + return { message: 'user room added successfully' }; + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('user/:userUuid') diff --git a/src/room/dtos/add.room.dto.ts b/src/room/dtos/add.room.dto.ts index 69425b1..2718a29 100644 --- a/src/room/dtos/add.room.dto.ts +++ b/src/room/dtos/add.room.dto.ts @@ -21,3 +21,22 @@ export class AddRoomDto { Object.assign(this, dto); } } +export class AddUserRoomDto { + @ApiProperty({ + description: 'roomUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public roomUuid: string; + @ApiProperty({ + description: 'userUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public userUuid: string; + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/room/room.module.ts b/src/room/room.module.ts index d83d6fb..2d6d98c 100644 --- a/src/room/room.module.ts +++ b/src/room/room.module.ts @@ -8,6 +8,8 @@ import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space. import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; import { UserSpaceRepositoryModule } from '@app/common/modules/user-space/user.space.repository.module'; import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; +import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; +import { UserRepository } from '@app/common/modules/user/repositories'; @Module({ imports: [ @@ -15,6 +17,7 @@ import { UserSpaceRepository } from '@app/common/modules/user-space/repositories SpaceRepositoryModule, SpaceTypeRepositoryModule, UserSpaceRepositoryModule, + UserRepositoryModule, ], controllers: [RoomController], providers: [ @@ -22,6 +25,7 @@ import { UserSpaceRepository } from '@app/common/modules/user-space/repositories SpaceRepository, SpaceTypeRepository, UserSpaceRepository, + UserRepository, ], exports: [RoomService], }) diff --git a/src/room/services/room.service.ts b/src/room/services/room.service.ts index 7b0b7f5..4771c25 100644 --- a/src/room/services/room.service.ts +++ b/src/room/services/room.service.ts @@ -6,7 +6,7 @@ import { BadRequestException, } from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { AddRoomDto } from '../dtos'; +import { AddRoomDto, AddUserRoomDto } from '../dtos'; import { RoomParentInterface, GetRoomByUuidInterface, @@ -137,7 +137,25 @@ export class RoomService { } } } - + async addUserRoom(addUserRoomDto: AddUserRoomDto) { + try { + await this.userSpaceRepository.save({ + user: { uuid: addUserRoomDto.userUuid }, + space: { uuid: addUserRoomDto.roomUuid }, + }); + } catch (err) { + if (err.code === '23505') { + throw new HttpException( + 'User already belongs to this room', + HttpStatus.BAD_REQUEST, + ); + } + throw new HttpException( + err.message || 'Internal Server Error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } async renameRoomByUuid( roomUuid: string, updateRoomNameDto: UpdateRoomNameDto, From d9782b3218e60f195ae68fb1d972ff18c1329f98 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 16 Apr 2024 17:22:47 +0300 Subject: [PATCH 52/75] Add functionality to add user to a unit --- src/unit/controllers/unit.controller.ts | 18 ++++++++++++++++-- src/unit/dtos/add.unit.dto.ts | 19 +++++++++++++++++++ src/unit/services/unit.service.ts | 22 +++++++++++++++++++++- src/unit/unit.module.ts | 4 ++++ 4 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/unit/controllers/unit.controller.ts b/src/unit/controllers/unit.controller.ts index 5289200..a3e59e9 100644 --- a/src/unit/controllers/unit.controller.ts +++ b/src/unit/controllers/unit.controller.ts @@ -13,10 +13,11 @@ import { } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; -import { AddUnitDto } from '../dtos/add.unit.dto'; +import { AddUnitDto, AddUserUnitDto } from '../dtos/add.unit.dto'; import { GetUnitChildDto } from '../dtos/get.unit.dto'; import { UpdateUnitNameDto } from '../dtos/update.unit.dto'; import { CheckFloorTypeGuard } from 'src/guards/floor.type.guard'; +import { CheckUserUnitGuard } from 'src/guards/user.unit.guard'; @ApiTags('Unit Module') @Controller({ @@ -87,7 +88,20 @@ export class UnitController { ); } } - + @ApiBearerAuth() + @UseGuards(JwtAuthGuard, CheckUserUnitGuard) + @Post('user') + async addUserUnit(@Body() addUserUnitDto: AddUserUnitDto) { + try { + await this.unitService.addUserUnit(addUserUnitDto); + return { message: 'user unit added successfully' }; + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('user/:userUuid') diff --git a/src/unit/dtos/add.unit.dto.ts b/src/unit/dtos/add.unit.dto.ts index 40f164a..e42d1bb 100644 --- a/src/unit/dtos/add.unit.dto.ts +++ b/src/unit/dtos/add.unit.dto.ts @@ -21,3 +21,22 @@ export class AddUnitDto { Object.assign(this, dto); } } +export class AddUserUnitDto { + @ApiProperty({ + description: 'unitUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public unitUuid: string; + @ApiProperty({ + description: 'userUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public userUuid: string; + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/unit/services/unit.service.ts b/src/unit/services/unit.service.ts index 27bebbc..867300c 100644 --- a/src/unit/services/unit.service.ts +++ b/src/unit/services/unit.service.ts @@ -7,7 +7,7 @@ import { BadRequestException, } from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { AddUnitDto } from '../dtos'; +import { AddUnitDto, AddUserUnitDto } from '../dtos'; import { UnitChildInterface, UnitParentInterface, @@ -228,6 +228,26 @@ export class UnitService { } } } + + async addUserUnit(addUserUnitDto: AddUserUnitDto) { + try { + await this.userSpaceRepository.save({ + user: { uuid: addUserUnitDto.userUuid }, + space: { uuid: addUserUnitDto.unitUuid }, + }); + } catch (err) { + if (err.code === '23505') { + throw new HttpException( + 'User already belongs to this unit', + HttpStatus.BAD_REQUEST, + ); + } + throw new HttpException( + err.message || 'Internal Server Error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } async renameUnitByUuid( unitUuid: string, updateUnitNameDto: UpdateUnitNameDto, diff --git a/src/unit/unit.module.ts b/src/unit/unit.module.ts index 2f0234e..569ef39 100644 --- a/src/unit/unit.module.ts +++ b/src/unit/unit.module.ts @@ -8,6 +8,8 @@ import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space. import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; import { UserSpaceRepositoryModule } from '@app/common/modules/user-space/user.space.repository.module'; import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; +import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; +import { UserRepository } from '@app/common/modules/user/repositories'; @Module({ imports: [ @@ -15,6 +17,7 @@ import { UserSpaceRepository } from '@app/common/modules/user-space/repositories SpaceRepositoryModule, SpaceTypeRepositoryModule, UserSpaceRepositoryModule, + UserRepositoryModule, ], controllers: [UnitController], providers: [ @@ -22,6 +25,7 @@ import { UserSpaceRepository } from '@app/common/modules/user-space/repositories SpaceRepository, SpaceTypeRepository, UserSpaceRepository, + UserRepository, ], exports: [UnitService], }) From c2d6c7ffd551d3591dfc98a910c05f6c2edb5d1e Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 16 Apr 2024 17:23:01 +0300 Subject: [PATCH 53/75] Add user space type guards --- src/guards/user.building.guard.ts | 70 ++++++++++++++++++++++++++++++ src/guards/user.community.guard.ts | 70 ++++++++++++++++++++++++++++++ src/guards/user.floor.guard.ts | 70 ++++++++++++++++++++++++++++++ src/guards/user.room.guard.ts | 70 ++++++++++++++++++++++++++++++ src/guards/user.unit.guard.ts | 70 ++++++++++++++++++++++++++++++ 5 files changed, 350 insertions(+) create mode 100644 src/guards/user.building.guard.ts create mode 100644 src/guards/user.community.guard.ts create mode 100644 src/guards/user.floor.guard.ts create mode 100644 src/guards/user.room.guard.ts create mode 100644 src/guards/user.unit.guard.ts diff --git a/src/guards/user.building.guard.ts b/src/guards/user.building.guard.ts new file mode 100644 index 0000000..b47124d --- /dev/null +++ b/src/guards/user.building.guard.ts @@ -0,0 +1,70 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + HttpStatus, +} from '@nestjs/common'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { BadRequestException, NotFoundException } from '@nestjs/common'; +import { UserRepository } from '@app/common/modules/user/repositories'; + +@Injectable() +export class CheckUserBuildingGuard implements CanActivate { + constructor( + private readonly spaceRepository: SpaceRepository, + private readonly userRepository: UserRepository, + ) {} + + async canActivate(context: ExecutionContext): Promise { + const req = context.switchToHttp().getRequest(); + + try { + const { userUuid, buildingUuid } = req.body; + + await this.checkUserIsFound(userUuid); + + await this.checkBuildingIsFound(buildingUuid); + + return true; + } catch (error) { + this.handleGuardError(error, context); + return false; + } + } + + private async checkUserIsFound(userUuid: string) { + const userData = await this.userRepository.findOne({ + where: { uuid: userUuid }, + }); + if (!userData) { + throw new NotFoundException('User not found'); + } + } + + private async checkBuildingIsFound(spaceUuid: string) { + const spaceData = await this.spaceRepository.findOne({ + where: { uuid: spaceUuid, spaceType: { type: 'building' } }, + relations: ['spaceType'], + }); + if (!spaceData) { + throw new NotFoundException('Building not found'); + } + } + + private handleGuardError(error: Error, context: ExecutionContext) { + const response = context.switchToHttp().getResponse(); + if ( + error instanceof BadRequestException || + error instanceof NotFoundException + ) { + response + .status(HttpStatus.NOT_FOUND) + .json({ statusCode: HttpStatus.NOT_FOUND, message: error.message }); + } else { + response.status(HttpStatus.BAD_REQUEST).json({ + statusCode: HttpStatus.BAD_REQUEST, + message: 'invalid userUuid or buildingUuid', + }); + } + } +} diff --git a/src/guards/user.community.guard.ts b/src/guards/user.community.guard.ts new file mode 100644 index 0000000..04e08b4 --- /dev/null +++ b/src/guards/user.community.guard.ts @@ -0,0 +1,70 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + HttpStatus, +} from '@nestjs/common'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { BadRequestException, NotFoundException } from '@nestjs/common'; +import { UserRepository } from '@app/common/modules/user/repositories'; + +@Injectable() +export class CheckUserCommunityGuard implements CanActivate { + constructor( + private readonly spaceRepository: SpaceRepository, + private readonly userRepository: UserRepository, + ) {} + + async canActivate(context: ExecutionContext): Promise { + const req = context.switchToHttp().getRequest(); + + try { + const { userUuid, communityUuid } = req.body; + + await this.checkUserIsFound(userUuid); + + await this.checkCommunityIsFound(communityUuid); + + return true; + } catch (error) { + this.handleGuardError(error, context); + return false; + } + } + + private async checkUserIsFound(userUuid: string) { + const userData = await this.userRepository.findOne({ + where: { uuid: userUuid }, + }); + if (!userData) { + throw new NotFoundException('User not found'); + } + } + + private async checkCommunityIsFound(spaceUuid: string) { + const spaceData = await this.spaceRepository.findOne({ + where: { uuid: spaceUuid, spaceType: { type: 'community' } }, + relations: ['spaceType'], + }); + if (!spaceData) { + throw new NotFoundException('Community not found'); + } + } + + private handleGuardError(error: Error, context: ExecutionContext) { + const response = context.switchToHttp().getResponse(); + if ( + error instanceof BadRequestException || + error instanceof NotFoundException + ) { + response + .status(HttpStatus.NOT_FOUND) + .json({ statusCode: HttpStatus.NOT_FOUND, message: error.message }); + } else { + response.status(HttpStatus.BAD_REQUEST).json({ + statusCode: HttpStatus.BAD_REQUEST, + message: 'invalid userUuid or communityUuid', + }); + } + } +} diff --git a/src/guards/user.floor.guard.ts b/src/guards/user.floor.guard.ts new file mode 100644 index 0000000..9235fb8 --- /dev/null +++ b/src/guards/user.floor.guard.ts @@ -0,0 +1,70 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + HttpStatus, +} from '@nestjs/common'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { BadRequestException, NotFoundException } from '@nestjs/common'; +import { UserRepository } from '@app/common/modules/user/repositories'; + +@Injectable() +export class CheckUserFloorGuard implements CanActivate { + constructor( + private readonly spaceRepository: SpaceRepository, + private readonly userRepository: UserRepository, + ) {} + + async canActivate(context: ExecutionContext): Promise { + const req = context.switchToHttp().getRequest(); + + try { + const { userUuid, floorUuid } = req.body; + + await this.checkUserIsFound(userUuid); + + await this.checkFloorIsFound(floorUuid); + + return true; + } catch (error) { + this.handleGuardError(error, context); + return false; + } + } + + private async checkUserIsFound(userUuid: string) { + const userData = await this.userRepository.findOne({ + where: { uuid: userUuid }, + }); + if (!userData) { + throw new NotFoundException('User not found'); + } + } + + private async checkFloorIsFound(spaceUuid: string) { + const spaceData = await this.spaceRepository.findOne({ + where: { uuid: spaceUuid, spaceType: { type: 'floor' } }, + relations: ['spaceType'], + }); + if (!spaceData) { + throw new NotFoundException('Floor not found'); + } + } + + private handleGuardError(error: Error, context: ExecutionContext) { + const response = context.switchToHttp().getResponse(); + if ( + error instanceof BadRequestException || + error instanceof NotFoundException + ) { + response + .status(HttpStatus.NOT_FOUND) + .json({ statusCode: HttpStatus.NOT_FOUND, message: error.message }); + } else { + response.status(HttpStatus.BAD_REQUEST).json({ + statusCode: HttpStatus.BAD_REQUEST, + message: 'invalid userUuid or floorUuid', + }); + } + } +} diff --git a/src/guards/user.room.guard.ts b/src/guards/user.room.guard.ts new file mode 100644 index 0000000..0ecf817 --- /dev/null +++ b/src/guards/user.room.guard.ts @@ -0,0 +1,70 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + HttpStatus, +} from '@nestjs/common'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { BadRequestException, NotFoundException } from '@nestjs/common'; +import { UserRepository } from '@app/common/modules/user/repositories'; + +@Injectable() +export class CheckUserRoomGuard implements CanActivate { + constructor( + private readonly spaceRepository: SpaceRepository, + private readonly userRepository: UserRepository, + ) {} + + async canActivate(context: ExecutionContext): Promise { + const req = context.switchToHttp().getRequest(); + + try { + const { userUuid, roomUuid } = req.body; + + await this.checkUserIsFound(userUuid); + + await this.checkRoomIsFound(roomUuid); + + return true; + } catch (error) { + this.handleGuardError(error, context); + return false; + } + } + + private async checkUserIsFound(userUuid: string) { + const userData = await this.userRepository.findOne({ + where: { uuid: userUuid }, + }); + if (!userData) { + throw new NotFoundException('User not found'); + } + } + + private async checkRoomIsFound(spaceUuid: string) { + const spaceData = await this.spaceRepository.findOne({ + where: { uuid: spaceUuid, spaceType: { type: 'room' } }, + relations: ['spaceType'], + }); + if (!spaceData) { + throw new NotFoundException('Room not found'); + } + } + + private handleGuardError(error: Error, context: ExecutionContext) { + const response = context.switchToHttp().getResponse(); + if ( + error instanceof BadRequestException || + error instanceof NotFoundException + ) { + response + .status(HttpStatus.NOT_FOUND) + .json({ statusCode: HttpStatus.NOT_FOUND, message: error.message }); + } else { + response.status(HttpStatus.BAD_REQUEST).json({ + statusCode: HttpStatus.BAD_REQUEST, + message: 'invalid userUuid or roomUuid', + }); + } + } +} diff --git a/src/guards/user.unit.guard.ts b/src/guards/user.unit.guard.ts new file mode 100644 index 0000000..4d7b1ab --- /dev/null +++ b/src/guards/user.unit.guard.ts @@ -0,0 +1,70 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + HttpStatus, +} from '@nestjs/common'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { BadRequestException, NotFoundException } from '@nestjs/common'; +import { UserRepository } from '@app/common/modules/user/repositories'; + +@Injectable() +export class CheckUserUnitGuard implements CanActivate { + constructor( + private readonly spaceRepository: SpaceRepository, + private readonly userRepository: UserRepository, + ) {} + + async canActivate(context: ExecutionContext): Promise { + const req = context.switchToHttp().getRequest(); + + try { + const { userUuid, unitUuid } = req.body; + + await this.checkUserIsFound(userUuid); + + await this.checkUnitIsFound(unitUuid); + + return true; + } catch (error) { + this.handleGuardError(error, context); + return false; + } + } + + private async checkUserIsFound(userUuid: string) { + const userData = await this.userRepository.findOne({ + where: { uuid: userUuid }, + }); + if (!userData) { + throw new NotFoundException('User not found'); + } + } + + private async checkUnitIsFound(spaceUuid: string) { + const spaceData = await this.spaceRepository.findOne({ + where: { uuid: spaceUuid, spaceType: { type: 'unit' } }, + relations: ['spaceType'], + }); + if (!spaceData) { + throw new NotFoundException('Unit not found'); + } + } + + private handleGuardError(error: Error, context: ExecutionContext) { + const response = context.switchToHttp().getResponse(); + if ( + error instanceof BadRequestException || + error instanceof NotFoundException + ) { + response + .status(HttpStatus.NOT_FOUND) + .json({ statusCode: HttpStatus.NOT_FOUND, message: error.message }); + } else { + response.status(HttpStatus.BAD_REQUEST).json({ + statusCode: HttpStatus.BAD_REQUEST, + message: 'invalid userUuid or unitUuid', + }); + } + } +} From 0bfb47bc331edb3071443443d8317c7cbcd7d57c Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 16 Apr 2024 17:23:11 +0300 Subject: [PATCH 54/75] Add unique constraint for user and space in UserSpaceEntity --- .../src/modules/user-space/entities/user.space.entity.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/common/src/modules/user-space/entities/user.space.entity.ts b/libs/common/src/modules/user-space/entities/user.space.entity.ts index c41dfc0..a6caaa5 100644 --- a/libs/common/src/modules/user-space/entities/user.space.entity.ts +++ b/libs/common/src/modules/user-space/entities/user.space.entity.ts @@ -1,10 +1,11 @@ -import { Column, Entity, ManyToOne } from 'typeorm'; +import { Column, Entity, ManyToOne, Unique } from 'typeorm'; import { UserSpaceDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { SpaceEntity } from '../../space/entities'; import { UserEntity } from '../../user/entities'; @Entity({ name: 'user-space' }) +@Unique(['user', 'space']) export class UserSpaceEntity extends AbstractEntity { @Column({ type: 'uuid', From fbdf187ee17719aee1f01459dca4bfc691f10e7e Mon Sep 17 00:00:00 2001 From: VirajBrainvire Date: Wed, 17 Apr 2024 18:52:58 +0530 Subject: [PATCH 55/75] SPRINT-1 related tasks done --- libs/common/src/auth/auth.module.ts | 18 ++--- libs/common/src/auth/services/auth.service.ts | 41 ++++++++++- .../src/auth/strategies/jwt.strategy.ts | 2 +- .../auth/strategies/refresh-token.strategy.ts | 42 ++++++++++++ .../src/constants/permission-type.enum.ts | 4 ++ libs/common/src/database/database.module.ts | 5 ++ .../src/guards/jwt-refresh.auth.guard.ts | 11 +++ .../device/device.repository.module.ts | 13 ++++ .../device/dtos/device-user-type.dto.ts | 19 ++++++ .../src/modules/device/dtos/device.dto.ts | 20 ++++++ libs/common/src/modules/device/dtos/index.ts | 2 + .../entities/device-user-type.entity.ts | 26 +++++++ .../modules/device/entities/device.entity.ts | 32 +++++++++ .../src/modules/device/entities/index.ts | 2 + libs/common/src/modules/device/index.ts | 1 + .../device-user-type.repository.ts | 10 +++ .../device/repositories/device.repository.ts | 10 +++ .../src/modules/device/repositories/index.ts | 2 + .../src/modules/permission/dtos/index.ts | 1 + .../modules/permission/dtos/permission.dto.ts | 11 +++ .../src/modules/permission/entities/index.ts | 1 + .../permission/entities/permission.entity.ts | 18 +++++ .../permission.repository.module.ts | 11 +++ .../modules/permission/repositories/index.ts | 1 + .../repositories/permission.repository.ts | 10 +++ .../src/modules/user/entities/user.entity.ts | 11 +++ package-lock.json | 41 +++++++++++ package.json | 1 + src/app.module.ts | 2 + src/auth/controllers/user-auth.controller.ts | 33 +++++++++ src/auth/services/user-auth.service.ts | 46 +++++++++++-- src/config/index.ts | 4 +- .../controllers/index.ts | 0 .../user-device-permission.controller.ts | 68 +++++++++++++++++++ src/user-device-permission/dtos/index.ts | 0 .../dtos/user-device-permission.add.dto.ts | 28 ++++++++ .../dtos/user-device-permission.edit.dto.ts | 7 ++ src/user-device-permission/services/index.ts | 0 .../user-device-permission.service.ts | 32 +++++++++ .../user-device-permission.module.ts | 21 ++++++ 40 files changed, 588 insertions(+), 19 deletions(-) create mode 100644 libs/common/src/auth/strategies/refresh-token.strategy.ts create mode 100644 libs/common/src/constants/permission-type.enum.ts create mode 100644 libs/common/src/guards/jwt-refresh.auth.guard.ts create mode 100644 libs/common/src/modules/device/device.repository.module.ts create mode 100644 libs/common/src/modules/device/dtos/device-user-type.dto.ts create mode 100644 libs/common/src/modules/device/dtos/device.dto.ts create mode 100644 libs/common/src/modules/device/dtos/index.ts create mode 100644 libs/common/src/modules/device/entities/device-user-type.entity.ts create mode 100644 libs/common/src/modules/device/entities/device.entity.ts create mode 100644 libs/common/src/modules/device/entities/index.ts create mode 100644 libs/common/src/modules/device/index.ts create mode 100644 libs/common/src/modules/device/repositories/device-user-type.repository.ts create mode 100644 libs/common/src/modules/device/repositories/device.repository.ts create mode 100644 libs/common/src/modules/device/repositories/index.ts create mode 100644 libs/common/src/modules/permission/dtos/index.ts create mode 100644 libs/common/src/modules/permission/dtos/permission.dto.ts create mode 100644 libs/common/src/modules/permission/entities/index.ts create mode 100644 libs/common/src/modules/permission/entities/permission.entity.ts create mode 100644 libs/common/src/modules/permission/permission.repository.module.ts create mode 100644 libs/common/src/modules/permission/repositories/index.ts create mode 100644 libs/common/src/modules/permission/repositories/permission.repository.ts create mode 100644 src/user-device-permission/controllers/index.ts create mode 100644 src/user-device-permission/controllers/user-device-permission.controller.ts create mode 100644 src/user-device-permission/dtos/index.ts create mode 100644 src/user-device-permission/dtos/user-device-permission.add.dto.ts create mode 100644 src/user-device-permission/dtos/user-device-permission.edit.dto.ts create mode 100644 src/user-device-permission/services/index.ts create mode 100644 src/user-device-permission/services/user-device-permission.service.ts create mode 100644 src/user-device-permission/user-device-permission.module.ts diff --git a/libs/common/src/auth/auth.module.ts b/libs/common/src/auth/auth.module.ts index 236fea3..c74aa81 100644 --- a/libs/common/src/auth/auth.module.ts +++ b/libs/common/src/auth/auth.module.ts @@ -7,22 +7,22 @@ import { JwtStrategy } from './strategies/jwt.strategy'; import { UserSessionRepository } from '../modules/session/repositories/session.repository'; import { AuthService } from './services/auth.service'; import { UserRepository } from '../modules/user/repositories'; +import { RefreshTokenStrategy } from './strategies/refresh-token.strategy'; @Module({ imports: [ ConfigModule.forRoot(), PassportModule, - JwtModule.registerAsync({ - imports: [ConfigModule], - inject: [ConfigService], - useFactory: async (configService: ConfigService) => ({ - secret: configService.get('JWT_SECRET'), - signOptions: { expiresIn: configService.get('JWT_EXPIRE_TIME') }, - }), - }), + JwtModule.register({}), HelperModule, ], - providers: [JwtStrategy, UserSessionRepository, AuthService, UserRepository], + providers: [ + JwtStrategy, + RefreshTokenStrategy, + UserSessionRepository, + AuthService, + UserRepository, + ], exports: [AuthService], }) export class AuthModule {} diff --git a/libs/common/src/auth/services/auth.service.ts b/libs/common/src/auth/services/auth.service.ts index d305d94..5e57550 100644 --- a/libs/common/src/auth/services/auth.service.ts +++ b/libs/common/src/auth/services/auth.service.ts @@ -1,9 +1,11 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; +import * as argon2 from 'argon2'; import { HelperHashService } from '../../helper/services'; import { UserRepository } from '../../../../common/src/modules/user/repositories'; import { UserSessionRepository } from '../../../../common/src/modules/session/repositories/session.repository'; import { UserSessionEntity } from '../../../../common/src/modules/session/entities'; +import { ConfigService } from '@nestjs/config'; @Injectable() export class AuthService { @@ -12,6 +14,7 @@ export class AuthService { private readonly userRepository: UserRepository, private readonly sessionRepository: UserSessionRepository, private readonly helperHashService: HelperHashService, + private readonly configService: ConfigService, ) {} async validateUser(email: string, pass: string): Promise { @@ -40,6 +43,24 @@ export class AuthService { return await this.sessionRepository.save(data); } + async getTokens(payload) { + const [accessToken, refreshToken] = await Promise.all([ + this.jwtService.signAsync(payload, { + secret: this.configService.get('JWT_SECRET'), + expiresIn: '24h', + }), + this.jwtService.signAsync(payload, { + secret: this.configService.get('JWT_SECRET'), + expiresIn: '7d', + }), + ]); + + return { + accessToken, + refreshToken, + }; + } + async login(user: any) { const payload = { email: user.email, @@ -48,8 +69,22 @@ export class AuthService { type: user.type, sessionId: user.sessionId, }; - return { - access_token: this.jwtService.sign(payload), - }; + const tokens = await this.getTokens(payload); + await this.updateRefreshToken(user.uuid, tokens.refreshToken); + return tokens; + } + + async updateRefreshToken(userId: string, refreshToken: string) { + const hashedRefreshToken = await this.hashData(refreshToken); + await this.userRepository.update( + { uuid: userId }, + { + refreshToken: hashedRefreshToken, + }, + ); + } + + hashData(data: string) { + return argon2.hash(data); } } diff --git a/libs/common/src/auth/strategies/jwt.strategy.ts b/libs/common/src/auth/strategies/jwt.strategy.ts index ac43543..67faecf 100644 --- a/libs/common/src/auth/strategies/jwt.strategy.ts +++ b/libs/common/src/auth/strategies/jwt.strategy.ts @@ -6,7 +6,7 @@ import { UserSessionRepository } from '../../../src/modules/session/repositories import { AuthInterface } from '../interfaces/auth.interface'; @Injectable() -export class JwtStrategy extends PassportStrategy(Strategy) { +export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { constructor( private readonly sessionRepository: UserSessionRepository, private readonly configService: ConfigService, diff --git a/libs/common/src/auth/strategies/refresh-token.strategy.ts b/libs/common/src/auth/strategies/refresh-token.strategy.ts new file mode 100644 index 0000000..9b21010 --- /dev/null +++ b/libs/common/src/auth/strategies/refresh-token.strategy.ts @@ -0,0 +1,42 @@ +import { ConfigService } from '@nestjs/config'; +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { PassportStrategy } from '@nestjs/passport'; +import { BadRequestException, Injectable } from '@nestjs/common'; +import { UserSessionRepository } from '../../../src/modules/session/repositories/session.repository'; +import { AuthInterface } from '../interfaces/auth.interface'; + +@Injectable() +export class RefreshTokenStrategy extends PassportStrategy( + Strategy, + 'jwt-refresh', +) { + constructor( + private readonly sessionRepository: UserSessionRepository, + private readonly configService: ConfigService, + ) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: configService.get('JWT_SECRET'), + }); + } + + async validate(payload: AuthInterface) { + const validateUser = await this.sessionRepository.findOne({ + where: { + uuid: payload.sessionId, + isLoggedOut: false, + }, + }); + if (validateUser) { + return { + email: payload.email, + userId: payload.id, + uuid: payload.uuid, + sessionId: payload.sessionId, + }; + } else { + throw new BadRequestException('Unauthorized'); + } + } +} diff --git a/libs/common/src/constants/permission-type.enum.ts b/libs/common/src/constants/permission-type.enum.ts new file mode 100644 index 0000000..67fea31 --- /dev/null +++ b/libs/common/src/constants/permission-type.enum.ts @@ -0,0 +1,4 @@ +export enum PermissionType { + READ = 'READ', + CONTROLLABLE = 'CONTROLLABLE', +} diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index dd29899..8af20fb 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -7,6 +7,8 @@ import { UserSessionEntity } from '../modules/session/entities/session.entity'; import { UserOtpEntity } from '../modules/user-otp/entities'; import { HomeEntity } from '../modules/home/entities'; import { ProductEntity } from '../modules/product/entities'; +import { DeviceEntity, DeviceUserPermissionEntity } from '../modules/device/entities'; +import { PermissionTypeEntity } from '../modules/permission/entities'; @Module({ imports: [ @@ -27,6 +29,9 @@ import { ProductEntity } from '../modules/product/entities'; UserOtpEntity, HomeEntity, ProductEntity, + DeviceUserPermissionEntity, + DeviceEntity, + PermissionTypeEntity ], namingStrategy: new SnakeNamingStrategy(), synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))), diff --git a/libs/common/src/guards/jwt-refresh.auth.guard.ts b/libs/common/src/guards/jwt-refresh.auth.guard.ts new file mode 100644 index 0000000..62c23d8 --- /dev/null +++ b/libs/common/src/guards/jwt-refresh.auth.guard.ts @@ -0,0 +1,11 @@ +import { UnauthorizedException } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +export class RefreshTokenGuard extends AuthGuard('jwt-refresh') { + handleRequest(err, user) { + if (err || !user) { + throw err || new UnauthorizedException(); + } + return user; + } +} diff --git a/libs/common/src/modules/device/device.repository.module.ts b/libs/common/src/modules/device/device.repository.module.ts new file mode 100644 index 0000000..b3d35d7 --- /dev/null +++ b/libs/common/src/modules/device/device.repository.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { DeviceEntity, DeviceUserPermissionEntity } from './entities'; + +@Module({ + providers: [], + exports: [], + controllers: [], + imports: [ + TypeOrmModule.forFeature([DeviceEntity, DeviceUserPermissionEntity]), + ], +}) +export class DeviceRepositoryModule {} diff --git a/libs/common/src/modules/device/dtos/device-user-type.dto.ts b/libs/common/src/modules/device/dtos/device-user-type.dto.ts new file mode 100644 index 0000000..0571356 --- /dev/null +++ b/libs/common/src/modules/device/dtos/device-user-type.dto.ts @@ -0,0 +1,19 @@ +import { IsNotEmpty, IsString } from 'class-validator'; + +export class DeviceUserTypeDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public userUuid: string; + + @IsString() + @IsNotEmpty() + public deviceUuid: string; + + @IsString() + @IsNotEmpty() + public permissionTypeUuid: string; +} diff --git a/libs/common/src/modules/device/dtos/device.dto.ts b/libs/common/src/modules/device/dtos/device.dto.ts new file mode 100644 index 0000000..fbc07c3 --- /dev/null +++ b/libs/common/src/modules/device/dtos/device.dto.ts @@ -0,0 +1,20 @@ +import { IsNotEmpty, IsString } from "class-validator"; + +export class DeviceDto{ + + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + spaceUuid:string; + + @IsString() + @IsNotEmpty() + deviceTuyaUuid:string; + + @IsString() + @IsNotEmpty() + productUuid:string; +} \ No newline at end of file diff --git a/libs/common/src/modules/device/dtos/index.ts b/libs/common/src/modules/device/dtos/index.ts new file mode 100644 index 0000000..9d647c8 --- /dev/null +++ b/libs/common/src/modules/device/dtos/index.ts @@ -0,0 +1,2 @@ +export * from './device.dto'; +export * from './device-user-type.dto'; diff --git a/libs/common/src/modules/device/entities/device-user-type.entity.ts b/libs/common/src/modules/device/entities/device-user-type.entity.ts new file mode 100644 index 0000000..66d05a7 --- /dev/null +++ b/libs/common/src/modules/device/entities/device-user-type.entity.ts @@ -0,0 +1,26 @@ +import { Column, Entity } from 'typeorm'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { DeviceUserTypeDto } from '../dtos/device-user-type.dto'; + +@Entity({ name: 'device-user-permission' }) +export class DeviceUserPermissionEntity extends AbstractEntity { + @Column({ + nullable: false, + }) + public userUuid: string; + + @Column({ + nullable: false, + }) + deviceUuid: string; + + @Column({ + nullable: false, + }) + public permissionTypeUuid: string; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/device/entities/device.entity.ts b/libs/common/src/modules/device/entities/device.entity.ts new file mode 100644 index 0000000..e8df1f0 --- /dev/null +++ b/libs/common/src/modules/device/entities/device.entity.ts @@ -0,0 +1,32 @@ +import { Column, Entity } from 'typeorm'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { DeviceDto } from '../dtos/device.dto'; + +@Entity({ name: 'device' }) +export class DeviceEntity extends AbstractEntity { + @Column({ + nullable: false, + }) + public spaceUuid: string; + + @Column({ + nullable: false, + }) + deviceTuyaUuid: string; + + @Column({ + nullable: false, + }) + public productUuid: string; + + @Column({ + nullable: true, + default: true, + }) + isActive: true; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/device/entities/index.ts b/libs/common/src/modules/device/entities/index.ts new file mode 100644 index 0000000..d6cf7b2 --- /dev/null +++ b/libs/common/src/modules/device/entities/index.ts @@ -0,0 +1,2 @@ +export * from './device.entity'; +export * from './device-user-type.entity'; diff --git a/libs/common/src/modules/device/index.ts b/libs/common/src/modules/device/index.ts new file mode 100644 index 0000000..0529a42 --- /dev/null +++ b/libs/common/src/modules/device/index.ts @@ -0,0 +1 @@ +export * from './device.repository.module'; diff --git a/libs/common/src/modules/device/repositories/device-user-type.repository.ts b/libs/common/src/modules/device/repositories/device-user-type.repository.ts new file mode 100644 index 0000000..e3d2176 --- /dev/null +++ b/libs/common/src/modules/device/repositories/device-user-type.repository.ts @@ -0,0 +1,10 @@ +import { DataSource, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common'; +import { DeviceUserPermissionEntity } from '../entities'; + +@Injectable() +export class DeviceUserTypeRepository extends Repository { + constructor(private dataSource: DataSource) { + super(DeviceUserPermissionEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/modules/device/repositories/device.repository.ts b/libs/common/src/modules/device/repositories/device.repository.ts new file mode 100644 index 0000000..267c06c --- /dev/null +++ b/libs/common/src/modules/device/repositories/device.repository.ts @@ -0,0 +1,10 @@ +import { DataSource, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common'; +import { DeviceEntity } from '../entities'; + +@Injectable() +export class DeviceRepository extends Repository { + constructor(private dataSource: DataSource) { + super(DeviceEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/modules/device/repositories/index.ts b/libs/common/src/modules/device/repositories/index.ts new file mode 100644 index 0000000..f91e07f --- /dev/null +++ b/libs/common/src/modules/device/repositories/index.ts @@ -0,0 +1,2 @@ +export * from './device.repository'; +export * from './device-user-type.repository'; diff --git a/libs/common/src/modules/permission/dtos/index.ts b/libs/common/src/modules/permission/dtos/index.ts new file mode 100644 index 0000000..cd80da6 --- /dev/null +++ b/libs/common/src/modules/permission/dtos/index.ts @@ -0,0 +1 @@ +export * from './permission.dto' \ No newline at end of file diff --git a/libs/common/src/modules/permission/dtos/permission.dto.ts b/libs/common/src/modules/permission/dtos/permission.dto.ts new file mode 100644 index 0000000..86912ee --- /dev/null +++ b/libs/common/src/modules/permission/dtos/permission.dto.ts @@ -0,0 +1,11 @@ +import { PermissionType } from '@app/common/constants/permission-type.enum'; +import { IsEnum, IsNotEmpty, IsString } from 'class-validator'; + +export class PermissionTypeDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsEnum(PermissionType) + public type: PermissionType; +} diff --git a/libs/common/src/modules/permission/entities/index.ts b/libs/common/src/modules/permission/entities/index.ts new file mode 100644 index 0000000..2d9cedb --- /dev/null +++ b/libs/common/src/modules/permission/entities/index.ts @@ -0,0 +1 @@ +export * from './permission.entity' \ No newline at end of file diff --git a/libs/common/src/modules/permission/entities/permission.entity.ts b/libs/common/src/modules/permission/entities/permission.entity.ts new file mode 100644 index 0000000..d4d17de --- /dev/null +++ b/libs/common/src/modules/permission/entities/permission.entity.ts @@ -0,0 +1,18 @@ +import { Column, Entity } from 'typeorm'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { PermissionType } from '@app/common/constants/permission-type.enum'; +import { PermissionTypeDto } from '../dtos/permission.dto'; + +@Entity({ name: 'permission-type' }) +export class PermissionTypeEntity extends AbstractEntity { + @Column({ + nullable: false, + enum: Object.values(PermissionType), + }) + type: string; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/permission/permission.repository.module.ts b/libs/common/src/modules/permission/permission.repository.module.ts new file mode 100644 index 0000000..6819e9b --- /dev/null +++ b/libs/common/src/modules/permission/permission.repository.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { PermissionTypeEntity } from './entities/permission.entity'; + +@Module({ + providers: [], + exports: [], + controllers: [], + imports: [TypeOrmModule.forFeature([PermissionTypeEntity])], +}) +export class PermissionTypeRepositoryModule {} diff --git a/libs/common/src/modules/permission/repositories/index.ts b/libs/common/src/modules/permission/repositories/index.ts new file mode 100644 index 0000000..cdd0f3a --- /dev/null +++ b/libs/common/src/modules/permission/repositories/index.ts @@ -0,0 +1 @@ +export * from './permission.repository' \ No newline at end of file diff --git a/libs/common/src/modules/permission/repositories/permission.repository.ts b/libs/common/src/modules/permission/repositories/permission.repository.ts new file mode 100644 index 0000000..c9a3f94 --- /dev/null +++ b/libs/common/src/modules/permission/repositories/permission.repository.ts @@ -0,0 +1,10 @@ +import { DataSource, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common'; +import { PermissionTypeEntity } from '../entities/permission.entity'; + +@Injectable() +export class PermissionTypeRepository extends Repository { + constructor(private dataSource: DataSource) { + super(PermissionTypeEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/modules/user/entities/user.entity.ts b/libs/common/src/modules/user/entities/user.entity.ts index 63f2185..a55608f 100644 --- a/libs/common/src/modules/user/entities/user.entity.ts +++ b/libs/common/src/modules/user/entities/user.entity.ts @@ -30,12 +30,23 @@ export class UserEntity extends AbstractEntity { }) public lastName: string; + @Column({ + nullable: true, + }) + public refreshToken: string; + @Column({ nullable: true, default: false, }) public isUserVerified: boolean; + @Column({ + nullable: false, + default: true, + }) + public isActive: boolean; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/package-lock.json b/package-lock.json index a41b90f..f333ed5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@nestjs/swagger": "^7.3.0", "@nestjs/typeorm": "^10.0.2", "@tuya/tuya-connector-nodejs": "^2.1.2", + "argon2": "^0.40.1", "axios": "^1.6.7", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", @@ -2111,6 +2112,14 @@ "npm": ">=5.0.0" } }, + "node_modules/@phc/format": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz", + "integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -3032,6 +3041,20 @@ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "devOptional": true }, + "node_modules/argon2": { + "version": "0.40.1", + "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.40.1.tgz", + "integrity": "sha512-DjtHDwd7pm12qeWyfihHoM8Bn5vGcgH6sKwgPqwNYroRmxlrzadHEvMyuvQxN/V8YSyRRKD5x6ito09q1e9OyA==", + "hasInstallScript": true, + "dependencies": { + "@phc/format": "^1.0.0", + "node-addon-api": "^7.1.0", + "node-gyp-build": "^4.8.0" + }, + "engines": { + "node": ">=16.17.0" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -7168,6 +7191,14 @@ "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", "dev": true }, + "node_modules/node-addon-api": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz", + "integrity": "sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==", + "engines": { + "node": "^16 || ^18 || >= 20" + } + }, "node_modules/node-emoji": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", @@ -7196,6 +7227,16 @@ } } }, + "node_modules/node-gyp-build": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", + "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", diff --git a/package.json b/package.json index 759b7e8..a8502be 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@nestjs/swagger": "^7.3.0", "@nestjs/typeorm": "^10.0.2", "@tuya/tuya-connector-nodejs": "^2.1.2", + "argon2": "^0.40.1", "axios": "^1.6.7", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", diff --git a/src/app.module.ts b/src/app.module.ts index b3c0106..ef0f28e 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -8,6 +8,7 @@ import { HomeModule } from './home/home.module'; import { RoomModule } from './room/room.module'; import { GroupModule } from './group/group.module'; import { DeviceModule } from './device/device.module'; +import { UserDevicePermissionModule } from './user-device-permission/user-device-permission.module'; @Module({ imports: [ ConfigModule.forRoot({ @@ -19,6 +20,7 @@ import { DeviceModule } from './device/device.module'; RoomModule, GroupModule, DeviceModule, + UserDevicePermissionModule ], controllers: [AuthenticationController], }) diff --git a/src/auth/controllers/user-auth.controller.ts b/src/auth/controllers/user-auth.controller.ts index c434ef8..f2a4b6f 100644 --- a/src/auth/controllers/user-auth.controller.ts +++ b/src/auth/controllers/user-auth.controller.ts @@ -2,9 +2,11 @@ import { Body, Controller, Delete, + Get, HttpStatus, Param, Post, + Req, UseGuards, } from '@nestjs/common'; import { UserAuthService } from '../services/user-auth.service'; @@ -14,6 +16,8 @@ import { ResponseMessage } from '../../../libs/common/src/response/response.deco import { UserLoginDto } from '../dtos/user-login.dto'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { ForgetPasswordDto, UserOtpDto, VerifyOtpDto } from '../dtos'; +import { Request } from 'express'; +import { RefreshTokenGuard } from '@app/common/guards/jwt-refresh.auth.guard'; @Controller({ version: '1', @@ -93,4 +97,33 @@ export class UserAuthController { message: 'Password changed successfully', }; } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('user/list') + async userList(@Req() req) { + const userList = await this.userAuthService.userList(); + return { + statusCode: HttpStatus.OK, + data: userList, + message: 'User List Fetched Successfully', + }; + } + + @ApiBearerAuth() + @UseGuards(RefreshTokenGuard) + @Get('refresh-token') + async refreshToken(@Req() req) { + const refreshToken = await this.userAuthService.refreshToken( + req.user.uuid, + req.headers.authorization, + req.user.type, + req.user.sessionId, + ); + return { + statusCode: HttpStatus.OK, + data: refreshToken, + message: 'Refresh Token added Successfully', + }; + } } diff --git a/src/auth/services/user-auth.service.ts b/src/auth/services/user-auth.service.ts index 6c1e172..7b278b2 100644 --- a/src/auth/services/user-auth.service.ts +++ b/src/auth/services/user-auth.service.ts @@ -1,6 +1,7 @@ import { UserRepository } from '../../../libs/common/src/modules/user/repositories'; import { BadRequestException, + ForbiddenException, Injectable, UnauthorizedException, } from '@nestjs/common'; @@ -14,7 +15,7 @@ import { ForgetPasswordDto, UserOtpDto, VerifyOtpDto } from '../dtos'; import { EmailService } from '../../../libs/common/src/util/email.service'; import { OtpType } from '../../../libs/common/src/constants/otp-type.enum'; import { UserEntity } from '../../../libs/common/src/modules/user/entities/user.entity'; -import { ILoginResponse } from '../constants/login.response.constant'; +import * as argon2 from 'argon2'; @Injectable() export class UserAuthService { @@ -64,7 +65,7 @@ export class UserAuthService { ); } - async userLogin(data: UserLoginDto): Promise { + async userLogin(data: UserLoginDto) { const user = await this.authService.validateUser(data.email, data.password); if (!user) { throw new UnauthorizedException('Invalid login credentials.'); @@ -86,7 +87,7 @@ export class UserAuthService { return await this.authService.login({ email: user.email, - userId: user.id, + userId: user.uuid, uuid: user.uuid, sessionId: session[1].uuid, }); @@ -97,7 +98,7 @@ export class UserAuthService { if (!user) { throw new BadRequestException('User does not found'); } - return await this.userRepository.delete({ uuid }); + return await this.userRepository.update({ uuid }, { isActive: false }); } async findOneById(id: string): Promise { @@ -148,4 +149,41 @@ export class UserAuthService { return true; } + + async userList(): Promise { + return await this.userRepository.find({ + where: { isActive: true }, + select: { + firstName: true, + lastName: true, + email: true, + isActive: true, + }, + }); + } + + async refreshToken( + userId: string, + refreshToken: string, + type: string, + sessionId: string, + ) { + const user = await this.userRepository.findOne({ where: { uuid: userId } }); + if (!user || !user.refreshToken) + throw new ForbiddenException('Access Denied'); + const refreshTokenMatches = await argon2.verify( + user.refreshToken, + refreshToken, + ); + if (!refreshTokenMatches) throw new ForbiddenException('Access Denied'); + const tokens = await this.authService.getTokens({ + email: user.email, + userId: user.uuid, + uuid: user.uuid, + type, + sessionId, + }); + await this.authService.updateRefreshToken(user.uuid, tokens.refreshToken); + return tokens; + } } diff --git a/src/config/index.ts b/src/config/index.ts index 0dfc023..d7d0014 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -1,4 +1,4 @@ import AuthConfig from './auth.config'; import AppConfig from './app.config'; - -export default [AuthConfig, AppConfig]; +import JwtConfig from './jwt.config'; +export default [AuthConfig, AppConfig, JwtConfig]; diff --git a/src/user-device-permission/controllers/index.ts b/src/user-device-permission/controllers/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/user-device-permission/controllers/user-device-permission.controller.ts b/src/user-device-permission/controllers/user-device-permission.controller.ts new file mode 100644 index 0000000..bfd98cb --- /dev/null +++ b/src/user-device-permission/controllers/user-device-permission.controller.ts @@ -0,0 +1,68 @@ +import { + Body, + Controller, + HttpStatus, + Param, + Post, + Put, + UseGuards, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { UserDevicePermissionService } from '../services/user-device-permission.service'; +import { UserDevicePermissionAddDto } from '../dtos/user-device-permission.add.dto'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { UserDevicePermissionEditDto } from '../dtos/user-device-permission.edit.dto'; + +@ApiTags('Device Permission Module') +@Controller({ + version: '1', + path: 'device-permission', +}) +export class UserDevicePermissionController { + constructor( + private readonly userDevicePermissionService: UserDevicePermissionService, + ) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post('add') + async addDevicePermission( + @Body() userDevicePermissionDto: UserDevicePermissionAddDto, + ) { + try { + const addDetails = + await this.userDevicePermissionService.addUserPermission( + userDevicePermissionDto, + ); + return { + statusCode: HttpStatus.CREATED, + message: 'User Permission for Devices Added Successfully', + data: addDetails, + }; + } catch (err) { + throw new Error(err); + } + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Put('edit/:userId') + async editDevicePermission( + @Param('userId') userId: string, + @Body() userDevicePermissionEditDto: UserDevicePermissionEditDto, + ) { + try { + await this.userDevicePermissionService.editUserPermission( + userId, + userDevicePermissionEditDto, + ); + return { + statusCode: HttpStatus.OK, + message: 'User Permission for Devices Updated Successfully', + data: {}, + }; + } catch (err) { + throw new Error(err); + } + } +} diff --git a/src/user-device-permission/dtos/index.ts b/src/user-device-permission/dtos/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/user-device-permission/dtos/user-device-permission.add.dto.ts b/src/user-device-permission/dtos/user-device-permission.add.dto.ts new file mode 100644 index 0000000..c7459df --- /dev/null +++ b/src/user-device-permission/dtos/user-device-permission.add.dto.ts @@ -0,0 +1,28 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class UserDevicePermissionAddDto { + @ApiProperty({ + description: 'user id', + required: true, + }) + @IsString() + @IsNotEmpty() + userId: string; + + @ApiProperty({ + description: 'permission type id', + required: true, + }) + @IsString() + @IsNotEmpty() + permissionTypeId: string; + + @ApiProperty({ + description: 'device id', + required: true, + }) + @IsString() + @IsNotEmpty() + deviceId: string; +} diff --git a/src/user-device-permission/dtos/user-device-permission.edit.dto.ts b/src/user-device-permission/dtos/user-device-permission.edit.dto.ts new file mode 100644 index 0000000..7cc4fce --- /dev/null +++ b/src/user-device-permission/dtos/user-device-permission.edit.dto.ts @@ -0,0 +1,7 @@ +import { OmitType } from '@nestjs/swagger'; +import { UserDevicePermissionAddDto } from './user-device-permission.add.dto'; + +export class UserDevicePermissionEditDto extends OmitType( + UserDevicePermissionAddDto, + ['userId'], +) {} diff --git a/src/user-device-permission/services/index.ts b/src/user-device-permission/services/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/user-device-permission/services/user-device-permission.service.ts b/src/user-device-permission/services/user-device-permission.service.ts new file mode 100644 index 0000000..7dbb971 --- /dev/null +++ b/src/user-device-permission/services/user-device-permission.service.ts @@ -0,0 +1,32 @@ +import { DeviceUserTypeRepository } from '@app/common/modules/device/repositories'; +import { Injectable } from '@nestjs/common'; +import { UserDevicePermissionAddDto } from '../dtos/user-device-permission.add.dto'; +import { UserDevicePermissionEditDto } from '../dtos/user-device-permission.edit.dto'; + +@Injectable() +export class UserDevicePermissionService { + constructor( + private readonly deviceUserTypeRepository: DeviceUserTypeRepository, + ) {} + + async addUserPermission(userDevicePermissionDto: UserDevicePermissionAddDto) { + return await this.deviceUserTypeRepository.save({ + userUuid: userDevicePermissionDto.userId, + deviceUuid: userDevicePermissionDto.deviceId, + permissionTypeUuid: userDevicePermissionDto.permissionTypeId, + }); + } + + async editUserPermission( + userId: string, + userDevicePermissionEditDto: UserDevicePermissionEditDto, + ) { + return await this.deviceUserTypeRepository.update( + { userUuid: userId }, + { + deviceUuid: userDevicePermissionEditDto.deviceId, + permissionTypeUuid: userDevicePermissionEditDto.permissionTypeId, + }, + ); + } +} diff --git a/src/user-device-permission/user-device-permission.module.ts b/src/user-device-permission/user-device-permission.module.ts new file mode 100644 index 0000000..edd9991 --- /dev/null +++ b/src/user-device-permission/user-device-permission.module.ts @@ -0,0 +1,21 @@ +import { DeviceRepositoryModule } from '@app/common/modules/device'; +import { + DeviceRepository, + DeviceUserTypeRepository, +} from '@app/common/modules/device/repositories'; +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { UserDevicePermissionService } from './services/user-device-permission.service'; +import { UserDevicePermissionController } from './controllers/user-device-permission.controller'; + +@Module({ + imports: [ConfigModule, DeviceRepositoryModule], + controllers: [UserDevicePermissionController], + providers: [ + DeviceUserTypeRepository, + DeviceRepository, + UserDevicePermissionService, + ], + exports: [UserDevicePermissionService], +}) +export class UserDevicePermissionModule {} From 871544f00773e0b063fe0e49cabeb2caaef5310f Mon Sep 17 00:00:00 2001 From: VirajBrainvire Date: Thu, 18 Apr 2024 12:03:33 +0530 Subject: [PATCH 56/75] relations added --- .../device/entities/device-user-type.entity.ts | 18 +++++++++++++++++- .../modules/device/entities/device.entity.ts | 14 +++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/libs/common/src/modules/device/entities/device-user-type.entity.ts b/libs/common/src/modules/device/entities/device-user-type.entity.ts index 66d05a7..904b18a 100644 --- a/libs/common/src/modules/device/entities/device-user-type.entity.ts +++ b/libs/common/src/modules/device/entities/device-user-type.entity.ts @@ -1,6 +1,8 @@ -import { Column, Entity } from 'typeorm'; +import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { DeviceUserTypeDto } from '../dtos/device-user-type.dto'; +import { DeviceEntity } from './device.entity'; +import { PermissionTypeEntity } from '../../permission/entities'; @Entity({ name: 'device-user-permission' }) export class DeviceUserPermissionEntity extends AbstractEntity { @@ -19,6 +21,20 @@ export class DeviceUserPermissionEntity extends AbstractEntity DeviceEntity, { + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + @JoinColumn({ name: 'device_uuid', referencedColumnName: 'uuid' }) + device: DeviceEntity; + + @ManyToOne(() => PermissionTypeEntity, { + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + @JoinColumn({ name: 'permission_type_uuid', referencedColumnName: 'uuid' }) + type: PermissionTypeEntity; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/device/entities/device.entity.ts b/libs/common/src/modules/device/entities/device.entity.ts index e8df1f0..d90d2d1 100644 --- a/libs/common/src/modules/device/entities/device.entity.ts +++ b/libs/common/src/modules/device/entities/device.entity.ts @@ -1,6 +1,7 @@ -import { Column, Entity } from 'typeorm'; +import { Column, Entity, OneToMany } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { DeviceDto } from '../dtos/device.dto'; +import { DeviceUserPermissionEntity } from './device-user-type.entity'; @Entity({ name: 'device' }) export class DeviceEntity extends AbstractEntity { @@ -25,6 +26,17 @@ export class DeviceEntity extends AbstractEntity { }) isActive: true; + @OneToMany( + () => DeviceUserPermissionEntity, + (permission) => permission.device, + { + nullable: true, + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }, + ) + permission: DeviceUserPermissionEntity[]; + constructor(partial: Partial) { super(); Object.assign(this, partial); From 921b44d4ed3ea3a70959608cb79f209d6b282dc9 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Thu, 18 Apr 2024 09:42:55 +0300 Subject: [PATCH 57/75] Add group module entities, DTOs, repository, and repository module --- libs/common/src/database/database.module.ts | 2 ++ .../src/modules/group/dtos/group.dto.ts | 11 +++++++++ libs/common/src/modules/group/dtos/index.ts | 1 + .../modules/group/entities/group.entity.ts | 23 +++++++++++++++++++ .../src/modules/group/entities/index.ts | 1 + .../modules/group/group.repository.module.ts | 11 +++++++++ .../group/repositories/group.repository.ts | 10 ++++++++ .../src/modules/group/repositories/index.ts | 1 + 8 files changed, 60 insertions(+) create mode 100644 libs/common/src/modules/group/dtos/group.dto.ts create mode 100644 libs/common/src/modules/group/dtos/index.ts create mode 100644 libs/common/src/modules/group/entities/group.entity.ts create mode 100644 libs/common/src/modules/group/entities/index.ts create mode 100644 libs/common/src/modules/group/group.repository.module.ts create mode 100644 libs/common/src/modules/group/repositories/group.repository.ts create mode 100644 libs/common/src/modules/group/repositories/index.ts diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index aa4f781..543f522 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -10,6 +10,7 @@ import { ProductEntity } from '../modules/product/entities'; import { SpaceEntity } from '../modules/space/entities'; import { SpaceTypeEntity } from '../modules/space-type/entities'; import { UserSpaceEntity } from '../modules/user-space/entities'; +import { GroupEntity } from '../modules/group/entities'; @Module({ imports: [ @@ -33,6 +34,7 @@ import { UserSpaceEntity } from '../modules/user-space/entities'; SpaceEntity, SpaceTypeEntity, UserSpaceEntity, + GroupEntity, ], namingStrategy: new SnakeNamingStrategy(), synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))), diff --git a/libs/common/src/modules/group/dtos/group.dto.ts b/libs/common/src/modules/group/dtos/group.dto.ts new file mode 100644 index 0000000..d3696b8 --- /dev/null +++ b/libs/common/src/modules/group/dtos/group.dto.ts @@ -0,0 +1,11 @@ +import { IsNotEmpty, IsString } from 'class-validator'; + +export class GroupDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public groupName: string; +} diff --git a/libs/common/src/modules/group/dtos/index.ts b/libs/common/src/modules/group/dtos/index.ts new file mode 100644 index 0000000..ba43fbc --- /dev/null +++ b/libs/common/src/modules/group/dtos/index.ts @@ -0,0 +1 @@ +export * from './group.dto'; diff --git a/libs/common/src/modules/group/entities/group.entity.ts b/libs/common/src/modules/group/entities/group.entity.ts new file mode 100644 index 0000000..9835e63 --- /dev/null +++ b/libs/common/src/modules/group/entities/group.entity.ts @@ -0,0 +1,23 @@ +import { Column, Entity } from 'typeorm'; +import { GroupDto } from '../dtos'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; + +@Entity({ name: 'group' }) +export class GroupEntity extends AbstractEntity { + @Column({ + type: 'uuid', + default: () => 'gen_random_uuid()', // Use gen_random_uuid() for default value + nullable: false, + }) + public uuid: string; + + @Column({ + nullable: false, + }) + public groupName: string; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/group/entities/index.ts b/libs/common/src/modules/group/entities/index.ts new file mode 100644 index 0000000..50f4201 --- /dev/null +++ b/libs/common/src/modules/group/entities/index.ts @@ -0,0 +1 @@ +export * from './group.entity'; diff --git a/libs/common/src/modules/group/group.repository.module.ts b/libs/common/src/modules/group/group.repository.module.ts new file mode 100644 index 0000000..5b711e5 --- /dev/null +++ b/libs/common/src/modules/group/group.repository.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { GroupEntity } from './entities/group.entity'; + +@Module({ + providers: [], + exports: [], + controllers: [], + imports: [TypeOrmModule.forFeature([GroupEntity])], +}) +export class GroupRepositoryModule {} diff --git a/libs/common/src/modules/group/repositories/group.repository.ts b/libs/common/src/modules/group/repositories/group.repository.ts new file mode 100644 index 0000000..824d671 --- /dev/null +++ b/libs/common/src/modules/group/repositories/group.repository.ts @@ -0,0 +1,10 @@ +import { DataSource, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common'; +import { GroupEntity } from '../entities/group.entity'; + +@Injectable() +export class GroupRepository extends Repository { + constructor(private dataSource: DataSource) { + super(GroupEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/modules/group/repositories/index.ts b/libs/common/src/modules/group/repositories/index.ts new file mode 100644 index 0000000..7018977 --- /dev/null +++ b/libs/common/src/modules/group/repositories/index.ts @@ -0,0 +1 @@ +export * from './group.repository'; From 239dbc84b12c1cb4cb327598904e22543eb2def8 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Thu, 18 Apr 2024 10:44:34 +0300 Subject: [PATCH 58/75] Add GroupDeviceEntity and related modules --- libs/common/src/database/database.module.ts | 7 ++++- .../modules/device/entities/device.entity.ts | 7 +++++ .../group-device/dtos/group.device.dto.ts | 15 ++++++++++ .../src/modules/group-device/dtos/index.ts | 1 + .../entities/group.device.entity.ts | 30 +++++++++++++++++++ .../modules/group-device/entities/index.ts | 1 + .../group.device.repository.module.ts | 11 +++++++ .../repositories/group.device.repository.ts | 10 +++++++ .../group-device/repositories/index.ts | 1 + .../modules/group/entities/group.entity.ts | 6 +++- 10 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 libs/common/src/modules/group-device/dtos/group.device.dto.ts create mode 100644 libs/common/src/modules/group-device/dtos/index.ts create mode 100644 libs/common/src/modules/group-device/entities/group.device.entity.ts create mode 100644 libs/common/src/modules/group-device/entities/index.ts create mode 100644 libs/common/src/modules/group-device/group.device.repository.module.ts create mode 100644 libs/common/src/modules/group-device/repositories/group.device.repository.ts create mode 100644 libs/common/src/modules/group-device/repositories/index.ts diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index 06cff73..819be74 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -7,12 +7,16 @@ import { UserSessionEntity } from '../modules/session/entities/session.entity'; import { UserOtpEntity } from '../modules/user-otp/entities'; import { HomeEntity } from '../modules/home/entities'; import { ProductEntity } from '../modules/product/entities'; -import { DeviceEntity, DeviceUserPermissionEntity } from '../modules/device/entities'; +import { + DeviceEntity, + DeviceUserPermissionEntity, +} from '../modules/device/entities'; import { PermissionTypeEntity } from '../modules/permission/entities'; import { SpaceEntity } from '../modules/space/entities'; import { SpaceTypeEntity } from '../modules/space-type/entities'; import { UserSpaceEntity } from '../modules/user-space/entities'; import { GroupEntity } from '../modules/group/entities'; +import { GroupDeviceEntity } from '../modules/group-device/entities'; @Module({ imports: [ @@ -40,6 +44,7 @@ import { GroupEntity } from '../modules/group/entities'; SpaceTypeEntity, UserSpaceEntity, GroupEntity, + GroupDeviceEntity, ], namingStrategy: new SnakeNamingStrategy(), synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))), diff --git a/libs/common/src/modules/device/entities/device.entity.ts b/libs/common/src/modules/device/entities/device.entity.ts index d90d2d1..2c1837a 100644 --- a/libs/common/src/modules/device/entities/device.entity.ts +++ b/libs/common/src/modules/device/entities/device.entity.ts @@ -2,6 +2,7 @@ import { Column, Entity, OneToMany } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { DeviceDto } from '../dtos/device.dto'; import { DeviceUserPermissionEntity } from './device-user-type.entity'; +import { GroupDeviceEntity } from '../../group-device/entities'; @Entity({ name: 'device' }) export class DeviceEntity extends AbstractEntity { @@ -37,6 +38,12 @@ export class DeviceEntity extends AbstractEntity { ) permission: DeviceUserPermissionEntity[]; + @OneToMany( + () => GroupDeviceEntity, + (userGroupDevices) => userGroupDevices.device, + ) + userGroupDevices: GroupDeviceEntity[]; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/group-device/dtos/group.device.dto.ts b/libs/common/src/modules/group-device/dtos/group.device.dto.ts new file mode 100644 index 0000000..1a4d51c --- /dev/null +++ b/libs/common/src/modules/group-device/dtos/group.device.dto.ts @@ -0,0 +1,15 @@ +import { IsNotEmpty, IsString } from 'class-validator'; + +export class GroupDeviceDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public deviceUuid: string; + + @IsString() + @IsNotEmpty() + public groupUuid: string; +} diff --git a/libs/common/src/modules/group-device/dtos/index.ts b/libs/common/src/modules/group-device/dtos/index.ts new file mode 100644 index 0000000..66bc84a --- /dev/null +++ b/libs/common/src/modules/group-device/dtos/index.ts @@ -0,0 +1 @@ +export * from './group.device.dto'; diff --git a/libs/common/src/modules/group-device/entities/group.device.entity.ts b/libs/common/src/modules/group-device/entities/group.device.entity.ts new file mode 100644 index 0000000..d0ac5e7 --- /dev/null +++ b/libs/common/src/modules/group-device/entities/group.device.entity.ts @@ -0,0 +1,30 @@ +import { Column, Entity, ManyToOne } from 'typeorm'; +import { GroupDeviceDto } from '../dtos'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { DeviceEntity } from '../../device/entities'; +import { GroupEntity } from '../../group/entities'; + +@Entity({ name: 'group-device' }) +export class GroupDeviceEntity extends AbstractEntity { + @Column({ + type: 'uuid', + default: () => 'gen_random_uuid()', // Use gen_random_uuid() for default value + nullable: false, + }) + public uuid: string; + + @ManyToOne(() => DeviceEntity, (device) => device.userGroupDevices, { + nullable: false, + }) + device: DeviceEntity; + + @ManyToOne(() => GroupEntity, (group) => group.groupDevices, { + nullable: false, + }) + group: GroupEntity; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/group-device/entities/index.ts b/libs/common/src/modules/group-device/entities/index.ts new file mode 100644 index 0000000..6b96f11 --- /dev/null +++ b/libs/common/src/modules/group-device/entities/index.ts @@ -0,0 +1 @@ +export * from './group.device.entity'; diff --git a/libs/common/src/modules/group-device/group.device.repository.module.ts b/libs/common/src/modules/group-device/group.device.repository.module.ts new file mode 100644 index 0000000..a3af56d --- /dev/null +++ b/libs/common/src/modules/group-device/group.device.repository.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { GroupDeviceEntity } from './entities/group.device.entity'; + +@Module({ + providers: [], + exports: [], + controllers: [], + imports: [TypeOrmModule.forFeature([GroupDeviceEntity])], +}) +export class GroupDeviceRepositoryModule {} diff --git a/libs/common/src/modules/group-device/repositories/group.device.repository.ts b/libs/common/src/modules/group-device/repositories/group.device.repository.ts new file mode 100644 index 0000000..472c5aa --- /dev/null +++ b/libs/common/src/modules/group-device/repositories/group.device.repository.ts @@ -0,0 +1,10 @@ +import { DataSource, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common'; +import { GroupDeviceEntity } from '../entities/group.device.entity'; + +@Injectable() +export class GroupDeviceRepository extends Repository { + constructor(private dataSource: DataSource) { + super(GroupDeviceEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/modules/group-device/repositories/index.ts b/libs/common/src/modules/group-device/repositories/index.ts new file mode 100644 index 0000000..2b40191 --- /dev/null +++ b/libs/common/src/modules/group-device/repositories/index.ts @@ -0,0 +1 @@ +export * from './group.device.repository'; diff --git a/libs/common/src/modules/group/entities/group.entity.ts b/libs/common/src/modules/group/entities/group.entity.ts index 9835e63..745ca42 100644 --- a/libs/common/src/modules/group/entities/group.entity.ts +++ b/libs/common/src/modules/group/entities/group.entity.ts @@ -1,6 +1,7 @@ -import { Column, Entity } from 'typeorm'; +import { Column, Entity, OneToMany } from 'typeorm'; import { GroupDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { GroupDeviceEntity } from '../../group-device/entities'; @Entity({ name: 'group' }) export class GroupEntity extends AbstractEntity { @@ -16,6 +17,9 @@ export class GroupEntity extends AbstractEntity { }) public groupName: string; + @OneToMany(() => GroupDeviceEntity, (groupDevice) => groupDevice.group) + groupDevices: GroupDeviceEntity[]; + constructor(partial: Partial) { super(); Object.assign(this, partial); From af7a9dbd7237e13a8dbf5f1dea98d01c9f4bd974 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Thu, 18 Apr 2024 10:46:21 +0300 Subject: [PATCH 59/75] Remove unused imports and fix formatting issues --- libs/common/src/auth/auth.module.ts | 2 +- .../src/modules/device/dtos/device.dto.ts | 31 +++++++++---------- .../src/modules/permission/dtos/index.ts | 2 +- .../src/modules/permission/entities/index.ts | 2 +- .../modules/permission/repositories/index.ts | 2 +- src/app.module.ts | 2 +- src/auth/controllers/user-auth.controller.ts | 3 +- 7 files changed, 21 insertions(+), 23 deletions(-) diff --git a/libs/common/src/auth/auth.module.ts b/libs/common/src/auth/auth.module.ts index c74aa81..b6c6d45 100644 --- a/libs/common/src/auth/auth.module.ts +++ b/libs/common/src/auth/auth.module.ts @@ -1,6 +1,6 @@ import { PassportModule } from '@nestjs/passport'; import { JwtModule } from '@nestjs/jwt'; -import { ConfigModule, ConfigService } from '@nestjs/config'; +import { ConfigModule } from '@nestjs/config'; import { Module } from '@nestjs/common'; import { HelperModule } from '../helper/helper.module'; import { JwtStrategy } from './strategies/jwt.strategy'; diff --git a/libs/common/src/modules/device/dtos/device.dto.ts b/libs/common/src/modules/device/dtos/device.dto.ts index fbc07c3..8873f31 100644 --- a/libs/common/src/modules/device/dtos/device.dto.ts +++ b/libs/common/src/modules/device/dtos/device.dto.ts @@ -1,20 +1,19 @@ -import { IsNotEmpty, IsString } from "class-validator"; +import { IsNotEmpty, IsString } from 'class-validator'; -export class DeviceDto{ +export class DeviceDto { + @IsString() + @IsNotEmpty() + public uuid: string; - @IsString() - @IsNotEmpty() - public uuid: string; - - @IsString() - @IsNotEmpty() - spaceUuid:string; + @IsString() + @IsNotEmpty() + spaceUuid: string; - @IsString() - @IsNotEmpty() - deviceTuyaUuid:string; + @IsString() + @IsNotEmpty() + deviceTuyaUuid: string; - @IsString() - @IsNotEmpty() - productUuid:string; -} \ No newline at end of file + @IsString() + @IsNotEmpty() + productUuid: string; +} diff --git a/libs/common/src/modules/permission/dtos/index.ts b/libs/common/src/modules/permission/dtos/index.ts index cd80da6..48e985e 100644 --- a/libs/common/src/modules/permission/dtos/index.ts +++ b/libs/common/src/modules/permission/dtos/index.ts @@ -1 +1 @@ -export * from './permission.dto' \ No newline at end of file +export * from './permission.dto'; diff --git a/libs/common/src/modules/permission/entities/index.ts b/libs/common/src/modules/permission/entities/index.ts index 2d9cedb..90a8fd8 100644 --- a/libs/common/src/modules/permission/entities/index.ts +++ b/libs/common/src/modules/permission/entities/index.ts @@ -1 +1 @@ -export * from './permission.entity' \ No newline at end of file +export * from './permission.entity'; diff --git a/libs/common/src/modules/permission/repositories/index.ts b/libs/common/src/modules/permission/repositories/index.ts index cdd0f3a..528b955 100644 --- a/libs/common/src/modules/permission/repositories/index.ts +++ b/libs/common/src/modules/permission/repositories/index.ts @@ -1 +1 @@ -export * from './permission.repository' \ No newline at end of file +export * from './permission.repository'; diff --git a/src/app.module.ts b/src/app.module.ts index d6daa3d..1a60f18 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -29,7 +29,7 @@ import { UnitModule } from './unit/unit.module'; RoomModule, GroupModule, DeviceModule, - UserDevicePermissionModule + UserDevicePermissionModule, ], controllers: [AuthenticationController], }) diff --git a/src/auth/controllers/user-auth.controller.ts b/src/auth/controllers/user-auth.controller.ts index f2a4b6f..f8c45f9 100644 --- a/src/auth/controllers/user-auth.controller.ts +++ b/src/auth/controllers/user-auth.controller.ts @@ -16,7 +16,6 @@ import { ResponseMessage } from '../../../libs/common/src/response/response.deco import { UserLoginDto } from '../dtos/user-login.dto'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { ForgetPasswordDto, UserOtpDto, VerifyOtpDto } from '../dtos'; -import { Request } from 'express'; import { RefreshTokenGuard } from '@app/common/guards/jwt-refresh.auth.guard'; @Controller({ @@ -101,7 +100,7 @@ export class UserAuthController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('user/list') - async userList(@Req() req) { + async userList() { const userList = await this.userAuthService.userList(); return { statusCode: HttpStatus.OK, From 18e7e35d352759791b3fa6f6333c28a123bc19d2 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 21 Apr 2024 16:27:32 +0300 Subject: [PATCH 60/75] Add unique constraint to GroupDeviceEntity and isActive flag to both GroupDeviceEntity and GroupEntity --- .../group-device/entities/group.device.entity.ts | 8 +++++++- libs/common/src/modules/group/entities/group.entity.ts | 10 +++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/libs/common/src/modules/group-device/entities/group.device.entity.ts b/libs/common/src/modules/group-device/entities/group.device.entity.ts index d0ac5e7..276a2b6 100644 --- a/libs/common/src/modules/group-device/entities/group.device.entity.ts +++ b/libs/common/src/modules/group-device/entities/group.device.entity.ts @@ -1,10 +1,11 @@ -import { Column, Entity, ManyToOne } from 'typeorm'; +import { Column, Entity, ManyToOne, Unique } from 'typeorm'; import { GroupDeviceDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { DeviceEntity } from '../../device/entities'; import { GroupEntity } from '../../group/entities'; @Entity({ name: 'group-device' }) +@Unique(['device', 'group']) export class GroupDeviceEntity extends AbstractEntity { @Column({ type: 'uuid', @@ -23,6 +24,11 @@ export class GroupDeviceEntity extends AbstractEntity { }) group: GroupEntity; + @Column({ + nullable: true, + default: true, + }) + public isActive: boolean; constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/group/entities/group.entity.ts b/libs/common/src/modules/group/entities/group.entity.ts index 745ca42..7cea8e8 100644 --- a/libs/common/src/modules/group/entities/group.entity.ts +++ b/libs/common/src/modules/group/entities/group.entity.ts @@ -17,9 +17,17 @@ export class GroupEntity extends AbstractEntity { }) public groupName: string; - @OneToMany(() => GroupDeviceEntity, (groupDevice) => groupDevice.group) + @OneToMany(() => GroupDeviceEntity, (groupDevice) => groupDevice.group, { + cascade: true, + onDelete: 'CASCADE', + }) groupDevices: GroupDeviceEntity[]; + @Column({ + nullable: true, + default: true, + }) + public isActive: boolean; constructor(partial: Partial) { super(); Object.assign(this, partial); From 7abfe2974613be2c79ef54ca98d8a6e634a23098 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 21 Apr 2024 16:27:53 +0300 Subject: [PATCH 61/75] Refactor group controller methods and add device product guard --- src/group/controllers/group.controller.ts | 90 ++++--- src/group/dtos/add.group.dto.ts | 24 +- src/group/dtos/get.group.dto.ts | 28 -- src/group/dtos/rename.group.dto copy.ts | 10 +- src/group/group.module.ts | 20 +- src/group/interfaces/get.group.interface.ts | 28 +- src/group/services/group.service.ts | 273 +++++++++----------- src/guards/device.product.guard.ts | 71 +++++ 8 files changed, 284 insertions(+), 260 deletions(-) delete mode 100644 src/group/dtos/get.group.dto.ts create mode 100644 src/guards/device.product.guard.ts diff --git a/src/group/controllers/group.controller.ts b/src/group/controllers/group.controller.ts index be0c7a3..2b6dd82 100644 --- a/src/group/controllers/group.controller.ts +++ b/src/group/controllers/group.controller.ts @@ -5,17 +5,18 @@ import { Get, Post, UseGuards, - Query, Param, Put, Delete, + HttpException, + HttpStatus, } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { AddGroupDto } from '../dtos/add.group.dto'; -import { GetGroupDto } from '../dtos/get.group.dto'; import { ControlGroupDto } from '../dtos/control.group.dto'; import { RenameGroupDto } from '../dtos/rename.group.dto copy'; +import { CheckProductUuidForAllDevicesGuard } from 'src/guards/device.product.guard'; @ApiTags('Group Module') @Controller({ @@ -25,34 +26,43 @@ import { RenameGroupDto } from '../dtos/rename.group.dto copy'; export class GroupController { constructor(private readonly groupService: GroupService) {} - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Get() - async getGroupsByHomeId(@Query() getGroupsDto: GetGroupDto) { + // @ApiBearerAuth() + // @UseGuards(JwtAuthGuard) + @Get('space/:spaceUuid') + async getGroupsBySpaceUuid(@Param('spaceUuid') spaceUuid: string) { try { - return await this.groupService.getGroupsByHomeId(getGroupsDto); - } catch (err) { - throw new Error(err); + return await this.groupService.getGroupsBySpaceUuid(spaceUuid); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Get(':groupId') - async getGroupsByGroupId(@Param('groupId') groupId: number) { + // @ApiBearerAuth() + // @UseGuards(JwtAuthGuard) + @Get(':groupUuid') + async getGroupsByGroupId(@Param('groupUuid') groupUuid: string) { try { - return await this.groupService.getGroupsByGroupId(groupId); - } catch (err) { - throw new Error(err); + return await this.groupService.getGroupsByGroupUuid(groupUuid); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + // @ApiBearerAuth() + @UseGuards(CheckProductUuidForAllDevicesGuard) @Post() async addGroup(@Body() addGroupDto: AddGroupDto) { try { return await this.groupService.addGroup(addGroupDto); - } catch (err) { - throw new Error(err); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } @@ -67,25 +77,37 @@ export class GroupController { } } - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Put('rename') - async renameGroup(@Body() renameGroupDto: RenameGroupDto) { + // @ApiBearerAuth() + // @UseGuards(JwtAuthGuard) + @Put('rename/:groupUuid') + async renameGroupByUuid( + @Param('groupUuid') groupUuid: string, + @Body() renameGroupDto: RenameGroupDto, + ) { try { - return await this.groupService.renameGroup(renameGroupDto); - } catch (err) { - throw new Error(err); + return await this.groupService.renameGroupByUuid( + groupUuid, + renameGroupDto, + ); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Delete(':groupId') - async deleteGroup(@Param('groupId') groupId: number) { + // @ApiBearerAuth() + // @UseGuards(JwtAuthGuard) + @Delete(':groupUuid') + async deleteGroup(@Param('groupUuid') groupUuid: string) { try { - return await this.groupService.deleteGroup(groupId); - } catch (err) { - throw new Error(err); + return await this.groupService.deleteGroup(groupUuid); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } } diff --git a/src/group/dtos/add.group.dto.ts b/src/group/dtos/add.group.dto.ts index b91f793..aa2a562 100644 --- a/src/group/dtos/add.group.dto.ts +++ b/src/group/dtos/add.group.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString, IsNumberString } from 'class-validator'; +import { IsNotEmpty, IsString, IsArray } from 'class-validator'; export class AddGroupDto { @ApiProperty({ @@ -11,26 +11,10 @@ export class AddGroupDto { public groupName: string; @ApiProperty({ - description: 'homeId', + description: 'deviceUuids', required: true, }) - @IsNumberString() + @IsArray() @IsNotEmpty() - public homeId: string; - - @ApiProperty({ - description: 'productId', - required: true, - }) - @IsString() - @IsNotEmpty() - public productId: string; - - @ApiProperty({ - description: 'The list of up to 20 device IDs, separated with commas (,)', - required: true, - }) - @IsString() - @IsNotEmpty() - public deviceIds: string; + public deviceUuids: [string]; } diff --git a/src/group/dtos/get.group.dto.ts b/src/group/dtos/get.group.dto.ts deleted file mode 100644 index aad234b..0000000 --- a/src/group/dtos/get.group.dto.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsNumberString } from 'class-validator'; - -export class GetGroupDto { - @ApiProperty({ - description: 'homeId', - required: true, - }) - @IsNumberString() - @IsNotEmpty() - public homeId: string; - - @ApiProperty({ - description: 'pageSize', - required: true, - }) - @IsNumberString() - @IsNotEmpty() - public pageSize: number; - - @ApiProperty({ - description: 'pageNo', - required: true, - }) - @IsNumberString() - @IsNotEmpty() - public pageNo: number; -} diff --git a/src/group/dtos/rename.group.dto copy.ts b/src/group/dtos/rename.group.dto copy.ts index a85f41b..f2b0c00 100644 --- a/src/group/dtos/rename.group.dto copy.ts +++ b/src/group/dtos/rename.group.dto copy.ts @@ -1,15 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString, IsNumberString } from 'class-validator'; +import { IsNotEmpty, IsString } from 'class-validator'; export class RenameGroupDto { - @ApiProperty({ - description: 'groupId', - required: true, - }) - @IsNumberString() - @IsNotEmpty() - public groupId: string; - @ApiProperty({ description: 'groupName', required: true, diff --git a/src/group/group.module.ts b/src/group/group.module.ts index 3969d39..61d95ab 100644 --- a/src/group/group.module.ts +++ b/src/group/group.module.ts @@ -2,10 +2,26 @@ import { Module } from '@nestjs/common'; import { GroupService } from './services/group.service'; import { GroupController } from './controllers/group.controller'; import { ConfigModule } from '@nestjs/config'; +import { GroupRepositoryModule } from '@app/common/modules/group/group.repository.module'; +import { GroupRepository } from '@app/common/modules/group/repositories'; +import { GroupDeviceRepositoryModule } from '@app/common/modules/group-device/group.device.repository.module'; +import { GroupDeviceRepository } from '@app/common/modules/group-device/repositories'; +import { DeviceRepositoryModule } from '@app/common/modules/device'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; @Module({ - imports: [ConfigModule], + imports: [ + ConfigModule, + GroupRepositoryModule, + GroupDeviceRepositoryModule, + DeviceRepositoryModule, + ], controllers: [GroupController], - providers: [GroupService], + providers: [ + GroupService, + GroupRepository, + GroupDeviceRepository, + DeviceRepository, + ], exports: [GroupService], }) export class GroupModule {} diff --git a/src/group/interfaces/get.group.interface.ts b/src/group/interfaces/get.group.interface.ts index 970c343..525fa04 100644 --- a/src/group/interfaces/get.group.interface.ts +++ b/src/group/interfaces/get.group.interface.ts @@ -1,25 +1,15 @@ -export class GetGroupDetailsInterface { - result: { - id: string; - name: string; - }; +export interface GetGroupDetailsInterface { + groupUuid: string; + groupName: string; + createdAt: Date; + updatedAt: Date; } -export class GetGroupsInterface { - result: { - count: number; - data_list: []; - }; +export interface GetGroupsBySpaceUuidInterface { + groupUuid: string; + groupName: string; } -export class addGroupInterface { - success: boolean; - msg: string; - result: { - id: string; - }; -} - -export class controlGroupInterface { +export interface controlGroupInterface { success: boolean; result: boolean; msg: string; diff --git a/src/group/services/group.service.ts b/src/group/services/group.service.ts index 07fbacf..f559737 100644 --- a/src/group/services/group.service.ts +++ b/src/group/services/group.service.ts @@ -1,21 +1,30 @@ -import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { + Injectable, + HttpException, + HttpStatus, + BadRequestException, +} from '@nestjs/common'; import { TuyaContext } from '@tuya/tuya-connector-nodejs'; import { ConfigService } from '@nestjs/config'; import { AddGroupDto } from '../dtos/add.group.dto'; import { GetGroupDetailsInterface, - GetGroupsInterface, - addGroupInterface, + GetGroupsBySpaceUuidInterface, controlGroupInterface, } from '../interfaces/get.group.interface'; -import { GetGroupDto } from '../dtos/get.group.dto'; import { ControlGroupDto } from '../dtos/control.group.dto'; import { RenameGroupDto } from '../dtos/rename.group.dto copy'; +import { GroupRepository } from '@app/common/modules/group/repositories'; +import { GroupDeviceRepository } from '@app/common/modules/group-device/repositories'; @Injectable() export class GroupService { private tuya: TuyaContext; - constructor(private readonly configService: ConfigService) { + constructor( + private readonly configService: ConfigService, + private readonly groupRepository: GroupRepository, + private readonly groupDeviceRepository: GroupDeviceRepository, + ) { const accessKey = this.configService.get('auth-config.ACCESS_KEY'); const secretKey = this.configService.get('auth-config.SECRET_KEY'); // const clientId = this.configService.get('auth-config.CLIENT_ID'); @@ -26,83 +35,80 @@ export class GroupService { }); } - async getGroupsByHomeId(getGroupDto: GetGroupDto) { + async getGroupsBySpaceUuid( + spaceUuid: string, + ): Promise { try { - const response = await this.getGroupsTuya(getGroupDto); - - const groups = response.result.data_list.map((group: any) => ({ - groupId: group.id, - groupName: group.name, - })); - - return { - count: response.result.count, - groups: groups, - }; - } catch (error) { - throw new HttpException( - 'Error fetching groups', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - - async getGroupsTuya(getGroupDto: GetGroupDto): Promise { - try { - const path = `/v2.0/cloud/thing/group`; - const response = await this.tuya.request({ - method: 'GET', - path, - query: { - space_id: getGroupDto.homeId, - page_size: getGroupDto.pageSize, - page_no: getGroupDto.pageNo, + const groupDevices = await this.groupDeviceRepository.find({ + relations: ['group', 'device'], + where: { + device: { spaceUuid }, + isActive: true, }, }); - return response as unknown as GetGroupsInterface; + + // Extract and return only the group entities + const groups = groupDevices.map((groupDevice) => { + return { + groupUuid: groupDevice.uuid, + groupName: groupDevice.group.groupName, + }; + }); + if (groups.length > 0) { + return groups; + } else { + throw new HttpException( + 'this space has no groups', + HttpStatus.NOT_FOUND, + ); + } } catch (error) { throw new HttpException( - 'Error fetching groups ', - HttpStatus.INTERNAL_SERVER_ERROR, + error.message || 'Error fetching groups', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, ); } } async addGroup(addGroupDto: AddGroupDto) { - const response = await this.addGroupTuya(addGroupDto); + try { + const group = await this.groupRepository.save({ + groupName: addGroupDto.groupName, + }); - if (response.success) { - return { - success: true, - groupId: response.result.id, - }; - } else { + const groupDevicePromises = addGroupDto.deviceUuids.map( + async (deviceUuid) => { + await this.saveGroupDevice(group.uuid, deviceUuid); + }, + ); + + await Promise.all(groupDevicePromises); + } catch (err) { + if (err.code === '23505') { + throw new HttpException( + 'User already belongs to this group', + HttpStatus.BAD_REQUEST, + ); + } throw new HttpException( - response.msg || 'Unknown error', - HttpStatus.BAD_REQUEST, + err.message || 'Internal Server Error', + HttpStatus.INTERNAL_SERVER_ERROR, ); } } - async addGroupTuya(addGroupDto: AddGroupDto): Promise { + + private async saveGroupDevice(groupUuid: string, deviceUuid: string) { try { - const path = `/v2.0/cloud/thing/group`; - const response = await this.tuya.request({ - method: 'POST', - path, - body: { - space_id: addGroupDto.homeId, - name: addGroupDto.groupName, - product_id: addGroupDto.productId, - device_ids: addGroupDto.deviceIds, + await this.groupDeviceRepository.save({ + group: { + uuid: groupUuid, + }, + device: { + uuid: deviceUuid, }, }); - - return response as addGroupInterface; } catch (error) { - throw new HttpException( - 'Error adding group', - HttpStatus.INTERNAL_SERVER_ERROR, - ); + throw error; } } @@ -141,105 +147,76 @@ export class GroupService { } } - async renameGroup(renameGroupDto: RenameGroupDto) { - const response = await this.renameGroupTuya(renameGroupDto); - - if (response.success) { - return { - success: response.success, - result: response.result, - msg: response.msg, - }; - } else { - throw new HttpException( - response.msg || 'Unknown error', - HttpStatus.BAD_REQUEST, - ); - } - } - async renameGroupTuya( + async renameGroupByUuid( + groupUuid: string, renameGroupDto: RenameGroupDto, - ): Promise { + ): Promise { try { - const path = `/v2.0/cloud/thing/group/${renameGroupDto.groupId}/${renameGroupDto.groupName}`; - const response = await this.tuya.request({ - method: 'PUT', - path, + await this.groupRepository.update( + { uuid: groupUuid }, + { groupName: renameGroupDto.groupName }, + ); + + // Fetch the updated floor + const updatedGroup = await this.groupRepository.findOneOrFail({ + where: { uuid: groupUuid }, }); - - return response as controlGroupInterface; - } catch (error) { - throw new HttpException( - 'Error rename group', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - - async deleteGroup(groupId: number) { - const response = await this.deleteGroupTuya(groupId); - - if (response.success) { return { - success: response.success, - result: response.result, - msg: response.msg, - }; - } else { - throw new HttpException( - response.msg || 'Unknown error', - HttpStatus.BAD_REQUEST, - ); - } - } - async deleteGroupTuya(groupId: number): Promise { - try { - const path = `/v2.0/cloud/thing/group/${groupId}`; - const response = await this.tuya.request({ - method: 'DELETE', - path, - }); - - return response as controlGroupInterface; - } catch (error) { - throw new HttpException( - 'Error delete group', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - - async getGroupsByGroupId(groupId: number) { - try { - const response = await this.getGroupsByGroupIdTuya(groupId); - - return { - groupId: response.result.id, - groupName: response.result.name, + groupUuid: updatedGroup.uuid, + groupName: updatedGroup.groupName, }; + } catch (error) { + throw new HttpException('Group not found', HttpStatus.NOT_FOUND); + } + } + + async deleteGroup(groupUuid: string) { + try { + const group = await this.getGroupsByGroupUuid(groupUuid); + + if (!group) { + throw new HttpException('Group not found', HttpStatus.NOT_FOUND); + } + + await this.groupRepository.update( + { uuid: groupUuid }, + { isActive: false }, + ); + + return { message: 'Group deleted successfully' }; } catch (error) { throw new HttpException( - 'Error fetching group', - HttpStatus.INTERNAL_SERVER_ERROR, + error.message || 'Error deleting group', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, ); } } - async getGroupsByGroupIdTuya( - groupId: number, + async getGroupsByGroupUuid( + groupUuid: string, ): Promise { try { - const path = `/v2.0/cloud/thing/group/${groupId}`; - const response = await this.tuya.request({ - method: 'GET', - path, + const group = await this.groupRepository.findOne({ + where: { + uuid: groupUuid, + isActive: true, + }, }); - return response as GetGroupDetailsInterface; - } catch (error) { - throw new HttpException( - 'Error fetching group ', - HttpStatus.INTERNAL_SERVER_ERROR, - ); + if (!group) { + throw new BadRequestException('Invalid group UUID'); + } + return { + groupUuid: group.uuid, + groupName: group.groupName, + createdAt: group.createdAt, + updatedAt: group.updatedAt, + }; + } catch (err) { + if (err instanceof BadRequestException) { + throw err; // Re-throw BadRequestException + } else { + throw new HttpException('Group not found', HttpStatus.NOT_FOUND); + } } } } diff --git a/src/guards/device.product.guard.ts b/src/guards/device.product.guard.ts new file mode 100644 index 0000000..2118401 --- /dev/null +++ b/src/guards/device.product.guard.ts @@ -0,0 +1,71 @@ +import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { + Injectable, + CanActivate, + HttpStatus, + BadRequestException, + ExecutionContext, +} from '@nestjs/common'; + +@Injectable() +export class CheckProductUuidForAllDevicesGuard implements CanActivate { + constructor(private readonly deviceRepository: DeviceRepository) {} + + async canActivate(context: ExecutionContext): Promise { + const req = context.switchToHttp().getRequest(); + + try { + const { deviceUuids } = req.body; + console.log(deviceUuids); + + await this.checkAllDevicesHaveSameProductUuid(deviceUuids); + + return true; + } catch (error) { + this.handleGuardError(error, context); + return false; + } + } + + async checkAllDevicesHaveSameProductUuid(deviceUuids: string[]) { + const firstDevice = await this.deviceRepository.findOne({ + where: { uuid: deviceUuids[0] }, + }); + + if (!firstDevice) { + throw new BadRequestException('First device not found'); + } + + const firstProductUuid = firstDevice.productUuid; + + for (let i = 1; i < deviceUuids.length; i++) { + const device = await this.deviceRepository.findOne({ + where: { uuid: deviceUuids[i] }, + }); + + if (!device) { + throw new BadRequestException(`Device ${deviceUuids[i]} not found`); + } + + if (device.productUuid !== firstProductUuid) { + throw new BadRequestException(`Devices have different product UUIDs`); + } + } + } + + private handleGuardError(error: Error, context: ExecutionContext) { + const response = context.switchToHttp().getResponse(); + console.error(error); + + if (error instanceof BadRequestException) { + response + .status(HttpStatus.BAD_REQUEST) + .json({ statusCode: HttpStatus.BAD_REQUEST, message: error.message }); + } else { + response.status(HttpStatus.NOT_FOUND).json({ + statusCode: HttpStatus.NOT_FOUND, + message: 'Device not found', + }); + } + } +} From fc7907d204c27b9eaf6b7333536597d8929cdbc9 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 22 Apr 2024 10:05:54 +0300 Subject: [PATCH 62/75] Refactor group controller and service for better code organization --- src/group/controllers/group.controller.ts | 27 +++++----- src/group/dtos/control.group.dto.ts | 20 +++++--- src/group/services/group.service.ts | 61 ++++++++++++++++++----- 3 files changed, 77 insertions(+), 31 deletions(-) diff --git a/src/group/controllers/group.controller.ts b/src/group/controllers/group.controller.ts index 2b6dd82..0f74ff6 100644 --- a/src/group/controllers/group.controller.ts +++ b/src/group/controllers/group.controller.ts @@ -26,8 +26,8 @@ import { CheckProductUuidForAllDevicesGuard } from 'src/guards/device.product.gu export class GroupController { constructor(private readonly groupService: GroupService) {} - // @ApiBearerAuth() - // @UseGuards(JwtAuthGuard) + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) @Get('space/:spaceUuid') async getGroupsBySpaceUuid(@Param('spaceUuid') spaceUuid: string) { try { @@ -39,8 +39,8 @@ export class GroupController { ); } } - // @ApiBearerAuth() - // @UseGuards(JwtAuthGuard) + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) @Get(':groupUuid') async getGroupsByGroupId(@Param('groupUuid') groupUuid: string) { try { @@ -52,8 +52,8 @@ export class GroupController { ); } } - // @ApiBearerAuth() - @UseGuards(CheckProductUuidForAllDevicesGuard) + @ApiBearerAuth() + @UseGuards(JwtAuthGuard, CheckProductUuidForAllDevicesGuard) @Post() async addGroup(@Body() addGroupDto: AddGroupDto) { try { @@ -72,13 +72,16 @@ export class GroupController { async controlGroup(@Body() controlGroupDto: ControlGroupDto) { try { return await this.groupService.controlGroup(controlGroupDto); - } catch (err) { - throw new Error(err); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } - // @ApiBearerAuth() - // @UseGuards(JwtAuthGuard) + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) @Put('rename/:groupUuid') async renameGroupByUuid( @Param('groupUuid') groupUuid: string, @@ -97,8 +100,8 @@ export class GroupController { } } - // @ApiBearerAuth() - // @UseGuards(JwtAuthGuard) + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) @Delete(':groupUuid') async deleteGroup(@Param('groupUuid') groupUuid: string) { try { diff --git a/src/group/dtos/control.group.dto.ts b/src/group/dtos/control.group.dto.ts index 33a6870..e3b48e9 100644 --- a/src/group/dtos/control.group.dto.ts +++ b/src/group/dtos/control.group.dto.ts @@ -1,20 +1,26 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsObject, IsNumberString } from 'class-validator'; +import { IsNotEmpty, IsString } from 'class-validator'; export class ControlGroupDto { @ApiProperty({ - description: 'groupId', + description: 'groupUuid', required: true, }) - @IsNumberString() + @IsString() @IsNotEmpty() - public groupId: string; + public groupUuid: string; @ApiProperty({ - description: 'example {"switch_1":true,"add_ele":300}', + description: 'code', required: true, }) - @IsObject() + @IsString() @IsNotEmpty() - public properties: object; + public code: string; + @ApiProperty({ + description: 'value', + required: true, + }) + @IsNotEmpty() + public value: any; } diff --git a/src/group/services/group.service.ts b/src/group/services/group.service.ts index f559737..63e63e9 100644 --- a/src/group/services/group.service.ts +++ b/src/group/services/group.service.ts @@ -10,12 +10,13 @@ import { AddGroupDto } from '../dtos/add.group.dto'; import { GetGroupDetailsInterface, GetGroupsBySpaceUuidInterface, - controlGroupInterface, } from '../interfaces/get.group.interface'; import { ControlGroupDto } from '../dtos/control.group.dto'; import { RenameGroupDto } from '../dtos/rename.group.dto copy'; import { GroupRepository } from '@app/common/modules/group/repositories'; import { GroupDeviceRepository } from '@app/common/modules/group-device/repositories'; +import { controlDeviceInterface } from 'src/device/interfaces/get.device.interface'; +import { ControlDeviceDto } from 'src/device/dtos'; @Injectable() export class GroupService { @@ -111,9 +112,24 @@ export class GroupService { throw error; } } - - async controlGroup(controlGroupDto: ControlGroupDto) { - const response = await this.controlGroupTuya(controlGroupDto); + async getDevicesByGroupUuid(groupUuid: string) { + try { + const devices = await this.groupDeviceRepository.find({ + relations: ['device'], + where: { + group: { + uuid: groupUuid, + }, + isActive: true, + }, + }); + return devices; + } catch (error) { + throw error; + } + } + async controlDevice(controlDeviceDto: ControlDeviceDto) { + const response = await this.controlDeviceTuya(controlDeviceDto); if (response.success) { return response; @@ -124,24 +140,45 @@ export class GroupService { ); } } - async controlGroupTuya( - controlGroupDto: ControlGroupDto, - ): Promise { + async controlDeviceTuya( + controlDeviceDto: ControlDeviceDto, + ): Promise { try { - const path = `/v2.0/cloud/thing/group/properties`; + const path = `/v1.0/iot-03/devices/${controlDeviceDto.deviceId}/commands`; const response = await this.tuya.request({ method: 'POST', path, body: { - group_id: controlGroupDto.groupId, - properties: controlGroupDto.properties, + commands: [ + { code: controlDeviceDto.code, value: controlDeviceDto.value }, + ], }, }); - return response as controlGroupInterface; + return response as controlDeviceInterface; } catch (error) { throw new HttpException( - 'Error control group', + 'Error control device from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async controlGroup(controlGroupDto: ControlGroupDto) { + const devices = await this.getDevicesByGroupUuid(controlGroupDto.groupUuid); + + try { + await Promise.all( + devices.map(async (device) => { + return this.controlDevice({ + deviceId: device.device.deviceTuyaUuid, + code: controlGroupDto.code, + value: controlGroupDto.value, + }); + }), + ); + } catch (error) { + throw new HttpException( + 'Error controlling devices', HttpStatus.INTERNAL_SERVER_ERROR, ); } From bef2f1e4ad26a0b2e610532a4b98beda28a85499 Mon Sep 17 00:00:00 2001 From: VirajBrainvire Date: Tue, 23 Apr 2024 10:38:09 +0530 Subject: [PATCH 63/75] added listing api --- .../permission/entities/permission.entity.ts | 14 +++++++++++++- .../user-device-permission.controller.ts | 19 +++++++++++++++++++ .../user-device-permission.service.ts | 4 ++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/libs/common/src/modules/permission/entities/permission.entity.ts b/libs/common/src/modules/permission/entities/permission.entity.ts index d4d17de..f2ba950 100644 --- a/libs/common/src/modules/permission/entities/permission.entity.ts +++ b/libs/common/src/modules/permission/entities/permission.entity.ts @@ -1,7 +1,8 @@ -import { Column, Entity } from 'typeorm'; +import { Column, Entity, OneToMany } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { PermissionType } from '@app/common/constants/permission-type.enum'; import { PermissionTypeDto } from '../dtos/permission.dto'; +import { DeviceUserPermissionEntity } from '../../device/entities'; @Entity({ name: 'permission-type' }) export class PermissionTypeEntity extends AbstractEntity { @@ -11,6 +12,17 @@ export class PermissionTypeEntity extends AbstractEntity { }) type: string; + @OneToMany( + () => DeviceUserPermissionEntity, + (permission) => permission.type, + { + nullable: true, + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }, + ) + permission: DeviceUserPermissionEntity[]; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/src/user-device-permission/controllers/user-device-permission.controller.ts b/src/user-device-permission/controllers/user-device-permission.controller.ts index bfd98cb..91d944c 100644 --- a/src/user-device-permission/controllers/user-device-permission.controller.ts +++ b/src/user-device-permission/controllers/user-device-permission.controller.ts @@ -1,6 +1,7 @@ import { Body, Controller, + Get, HttpStatus, Param, Post, @@ -65,4 +66,22 @@ export class UserDevicePermissionController { throw new Error(err); } } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('list') + async fetchDevicePermission() { + try { + const deviceDetails = + await this.userDevicePermissionService.fetchuserPermission(); + return { + statusCode: HttpStatus.OK, + message: 'Device Details fetched Successfully', + data: deviceDetails, + }; + } catch (err) { + throw new Error(err); + } + } + } diff --git a/src/user-device-permission/services/user-device-permission.service.ts b/src/user-device-permission/services/user-device-permission.service.ts index 7dbb971..e50d59f 100644 --- a/src/user-device-permission/services/user-device-permission.service.ts +++ b/src/user-device-permission/services/user-device-permission.service.ts @@ -29,4 +29,8 @@ export class UserDevicePermissionService { }, ); } + + async fetchuserPermission() { + return await this.deviceUserTypeRepository.find(); + } } From 59badb41a3e8e83977a5e0f9450c3b32601493f9 Mon Sep 17 00:00:00 2001 From: VirajBrainvire Date: Tue, 23 Apr 2024 12:41:37 +0530 Subject: [PATCH 64/75] auth permission guard added --- src/device/controllers/device.controller.ts | 18 ++-- src/device/device.module.ts | 7 +- src/guards/device.permission.guard.ts | 97 +++++++++++++++++++++ 3 files changed, 112 insertions(+), 10 deletions(-) create mode 100644 src/guards/device.permission.guard.ts diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index 136e94f..387e765 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -19,6 +19,8 @@ import { GetDeviceByRoomIdDto, } from '../dtos/get.device.dto'; import { ControlDeviceDto } from '../dtos/control.device.dto'; +import { AuthGuardWithRoles } from 'src/guards/device.permission.guard'; +import { PermissionType } from '@app/common/constants/permission-type.enum'; @ApiTags('Device Module') @Controller({ @@ -29,7 +31,7 @@ export class DeviceController { constructor(private readonly deviceService: DeviceService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @AuthGuardWithRoles(PermissionType.READ) @Get('room') async getDevicesByRoomId( @Query() getDeviceByRoomIdDto: GetDeviceByRoomIdDto, @@ -41,7 +43,7 @@ export class DeviceController { } } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @AuthGuardWithRoles(PermissionType.READ) @Get('group') async getDevicesByGroupId( @Query() getDeviceByGroupIdDto: GetDeviceByGroupIdDto, @@ -55,7 +57,7 @@ export class DeviceController { } } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @AuthGuardWithRoles(PermissionType.READ) @Get(':deviceId') async getDeviceDetailsByDeviceId(@Param('deviceId') deviceId: string) { try { @@ -65,7 +67,7 @@ export class DeviceController { } } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @AuthGuardWithRoles(PermissionType.READ) @Get(':deviceId/functions') async getDeviceInstructionByDeviceId(@Param('deviceId') deviceId: string) { try { @@ -75,7 +77,7 @@ export class DeviceController { } } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @AuthGuardWithRoles(PermissionType.READ) @Get(':deviceId/functions/status') async getDevicesInstructionStatus(@Param('deviceId') deviceId: string) { try { @@ -85,7 +87,7 @@ export class DeviceController { } } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @AuthGuardWithRoles(PermissionType.CONTROLLABLE) @Post('room') async addDeviceInRoom(@Body() addDeviceInRoomDto: AddDeviceInRoomDto) { try { @@ -95,7 +97,7 @@ export class DeviceController { } } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @AuthGuardWithRoles(PermissionType.CONTROLLABLE) @Post('group') async addDeviceInGroup(@Body() addDeviceInGroupDto: AddDeviceInGroupDto) { try { @@ -105,7 +107,7 @@ export class DeviceController { } } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @AuthGuardWithRoles(PermissionType.CONTROLLABLE) @Post('control') async controlDevice(@Body() controlDeviceDto: ControlDeviceDto) { try { diff --git a/src/device/device.module.ts b/src/device/device.module.ts index d72db05..55d50b4 100644 --- a/src/device/device.module.ts +++ b/src/device/device.module.ts @@ -4,10 +4,13 @@ import { DeviceController } from './controllers/device.controller'; import { ConfigModule } from '@nestjs/config'; import { ProductRepositoryModule } from '@app/common/modules/product/product.repository.module'; import { ProductRepository } from '@app/common/modules/product/repositories'; +import { DeviceRepositoryModule } from '@app/common/modules/device'; +import { DeviceUserTypeRepository } from '@app/common/modules/device/repositories'; +import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; @Module({ - imports: [ConfigModule, ProductRepositoryModule], + imports: [ConfigModule, ProductRepositoryModule,DeviceRepositoryModule], controllers: [DeviceController], - providers: [DeviceService, ProductRepository], + providers: [DeviceService, ProductRepository,DeviceUserTypeRepository,PermissionTypeRepository], exports: [DeviceService], }) export class DeviceModule {} diff --git a/src/guards/device.permission.guard.ts b/src/guards/device.permission.guard.ts new file mode 100644 index 0000000..3fc80af --- /dev/null +++ b/src/guards/device.permission.guard.ts @@ -0,0 +1,97 @@ +import { PermissionType } from '@app/common/constants/permission-type.enum'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { DeviceUserTypeRepository } from '@app/common/modules/device/repositories'; +import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; +import { + Injectable, + CanActivate, + HttpStatus, + ExecutionContext, + BadRequestException, + applyDecorators, + SetMetadata, + UseGuards, +} from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; + +@Injectable() +export class DevicePermissionGuard implements CanActivate { + constructor( + private reflector: Reflector, + private readonly deviceUserTypeRepository: DeviceUserTypeRepository, + private readonly permissionTypeRepository: PermissionTypeRepository, + ) {} + + async canActivate(context: ExecutionContext): Promise { + const req = context.switchToHttp().getRequest(); + try { + const { deviceId } = req.headers; + const userId = req.user.uuid; + + const requirePermission = + this.reflector.getAllAndOverride('permission', [ + context.getHandler(), + context.getClass(), + ]); + + if (!requirePermission) { + return true; + } + await this.checkDevicePermission(deviceId, userId, requirePermission); + + return true; + } catch (error) { + this.handleGuardError(error, context); + return false; + } + } + + async checkDevicePermission( + deviceId: string, + userId: string, + requirePermission, + ) { + const [userPermissionDetails, permissionDetails] = await Promise.all([ + this.deviceUserTypeRepository.findOne({ + where: { deviceUuid: deviceId, userUuid: userId }, + }), + this.permissionTypeRepository.findOne({ + where: { + type: requirePermission, + }, + }), + ]); + if (!userPermissionDetails) { + throw new BadRequestException('User Permission Details Not Found'); + } + if (userPermissionDetails.permissionTypeUuid !== permissionDetails.uuid) { + throw new BadRequestException( + `User Does not have a ${requirePermission}`, + ); + } + } + + private handleGuardError(error: Error, context: ExecutionContext) { + const response = context.switchToHttp().getResponse(); + console.error(error); + + if (error instanceof BadRequestException) { + response + .status(HttpStatus.BAD_REQUEST) + .json({ statusCode: HttpStatus.BAD_REQUEST, message: error.message }); + } else { + response.status(HttpStatus.NOT_FOUND).json({ + statusCode: HttpStatus.NOT_FOUND, + message: 'User Permission not found', + }); + } + } +} + +export function AuthGuardWithRoles(permission?: string) { + return applyDecorators( + SetMetadata('permission', permission), + UseGuards(JwtAuthGuard), + UseGuards(DevicePermissionGuard), + ); +} From d61a1b92e7dbe3957296c19df7a1a80ccb18f7ee Mon Sep 17 00:00:00 2001 From: VirajBrainvire Date: Tue, 23 Apr 2024 16:24:58 +0530 Subject: [PATCH 65/75] Device Related API changes are done --- .../entities/group.device.entity.ts | 12 ++ src/device/device.module.ts | 16 ++- src/device/services/device.service.ts | 105 +++++++++++++++--- 3 files changed, 115 insertions(+), 18 deletions(-) diff --git a/libs/common/src/modules/group-device/entities/group.device.entity.ts b/libs/common/src/modules/group-device/entities/group.device.entity.ts index 276a2b6..8a39dc7 100644 --- a/libs/common/src/modules/group-device/entities/group.device.entity.ts +++ b/libs/common/src/modules/group-device/entities/group.device.entity.ts @@ -14,6 +14,18 @@ export class GroupDeviceEntity extends AbstractEntity { }) public uuid: string; + @Column({ + type: 'string', + nullable: false, + }) + deviceUuid: string; + + @Column({ + type: 'string', + nullable: false, + }) + groupUuid: string; + @ManyToOne(() => DeviceEntity, (device) => device.userGroupDevices, { nullable: false, }) diff --git a/src/device/device.module.ts b/src/device/device.module.ts index 55d50b4..3a829fc 100644 --- a/src/device/device.module.ts +++ b/src/device/device.module.ts @@ -5,12 +5,22 @@ import { ConfigModule } from '@nestjs/config'; import { ProductRepositoryModule } from '@app/common/modules/product/product.repository.module'; import { ProductRepository } from '@app/common/modules/product/repositories'; import { DeviceRepositoryModule } from '@app/common/modules/device'; -import { DeviceUserTypeRepository } from '@app/common/modules/device/repositories'; +import { DeviceRepository, DeviceUserTypeRepository } from '@app/common/modules/device/repositories'; import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { GroupDeviceRepository } from '@app/common/modules/group-device/repositories'; @Module({ - imports: [ConfigModule, ProductRepositoryModule,DeviceRepositoryModule], + imports: [ConfigModule, ProductRepositoryModule, DeviceRepositoryModule], controllers: [DeviceController], - providers: [DeviceService, ProductRepository,DeviceUserTypeRepository,PermissionTypeRepository], + providers: [ + DeviceService, + ProductRepository, + DeviceUserTypeRepository, + PermissionTypeRepository, + SpaceRepository, + DeviceRepository, + GroupDeviceRepository + ], exports: [DeviceService], }) export class DeviceModule {} diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 0fa0be7..2560efb 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -1,4 +1,9 @@ -import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { + Injectable, + HttpException, + HttpStatus, + NotFoundException, +} from '@nestjs/common'; import { TuyaContext } from '@tuya/tuya-connector-nodejs'; import { ConfigService } from '@nestjs/config'; import { @@ -23,6 +28,9 @@ import { import { ControlDeviceDto } from '../dtos/control.device.dto'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { ProductRepository } from '@app/common/modules/product/repositories'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { GroupDeviceRepository } from '@app/common/modules/group-device/repositories'; @Injectable() export class DeviceService { @@ -30,6 +38,9 @@ export class DeviceService { constructor( private readonly configService: ConfigService, private readonly productRepository: ProductRepository, + private readonly spaceRepository: SpaceRepository, + private readonly deviceRepository: DeviceRepository, + private readonly groupDeviceRepository: GroupDeviceRepository, ) { const accessKey = this.configService.get('auth-config.ACCESS_KEY'); const secretKey = this.configService.get('auth-config.SECRET_KEY'); @@ -42,6 +53,15 @@ export class DeviceService { async getDevicesByRoomId(getDeviceByRoomIdDto: GetDeviceByRoomIdDto) { try { + const findRoom = await this.spaceRepository.findOne({ + where: { + uuid: getDeviceByRoomIdDto.roomId, + }, + }); + if (!findRoom) { + throw new NotFoundException('Room Details Not Found'); + } + const response = await this.getDevicesByRoomIdTuya(getDeviceByRoomIdDto); return { @@ -126,7 +146,27 @@ export class DeviceService { } async addDeviceInRoom(addDeviceInRoomDto: AddDeviceInRoomDto) { - const response = await this.addDeviceInRoomTuya(addDeviceInRoomDto); + const [deviceDetails, roomDetails] = await Promise.all([ + this.deviceRepository.findOne({ + where: { + uuid: addDeviceInRoomDto.deviceId, + }, + }), + this.spaceRepository.findOne({ + where: { + uuid: addDeviceInRoomDto.roomId, + }, + }), + ]); + + if (!roomDetails) { + throw new NotFoundException('Room Details Not Found'); + } + + if (!deviceDetails) { + throw new NotFoundException('Device Details Not Found'); + } + const response = await this.addDeviceInRooms(addDeviceInRoomDto); if (response.success) { return { @@ -141,7 +181,7 @@ export class DeviceService { ); } } - async addDeviceInRoomTuya( + async addDeviceInRooms( addDeviceInRoomDto: AddDeviceInRoomDto, ): Promise { try { @@ -153,7 +193,6 @@ export class DeviceService { space_id: addDeviceInRoomDto.roomId, }, }); - return response as addDeviceInRoomInterface; } catch (error) { throw new HttpException( @@ -163,7 +202,17 @@ export class DeviceService { } } async addDeviceInGroup(addDeviceInGroupDto: AddDeviceInGroupDto) { - const response = await this.addDeviceInGroupTuya(addDeviceInGroupDto); + const deviceDetails = this.deviceRepository.findOne({ + where: { + uuid: addDeviceInGroupDto.deviceId, + }, + }); + + if (!deviceDetails) { + throw new NotFoundException('Device Details Not Found'); + } + + const response = await this.addDeviceInGroups(addDeviceInGroupDto); if (response.success) { return { @@ -178,21 +227,21 @@ export class DeviceService { ); } } - async addDeviceInGroupTuya( + async addDeviceInGroups( addDeviceInGroupDto: AddDeviceInGroupDto, ): Promise { try { - const path = `/v2.0/cloud/thing/group/${addDeviceInGroupDto.groupId}/device`; - const response = await this.tuya.request({ - method: 'PUT', - path, - body: { - space_id: addDeviceInGroupDto.homeId, - device_ids: addDeviceInGroupDto.deviceId, - }, + await this.groupDeviceRepository.create({ + deviceUuid: addDeviceInGroupDto.deviceId, + groupUuid: addDeviceInGroupDto.groupId, }); - return response as addDeviceInRoomInterface; + return { + success: true, + msg: 'Group is Added to Specific Device', + result: true, + } as addDeviceInRoomInterface; + } catch (error) { throw new HttpException( 'Error adding device in group from Tuya', @@ -239,6 +288,15 @@ export class DeviceService { async getDeviceDetailsByDeviceId(deviceId: string) { try { + const deviceDetails = await this.deviceRepository.findOne({ + where: { + uuid: deviceId, + }, + }); + if (!deviceDetails) { + throw new NotFoundException('Device Details Not Found'); + } + const response = await this.getDeviceDetailsByDeviceIdTuya(deviceId); return { @@ -335,6 +393,15 @@ export class DeviceService { deviceId: string, ): Promise { try { + const deviceDetails = await this.deviceRepository.findOne({ + where: { + uuid: deviceId, + }, + }); + if (!deviceDetails) { + throw new NotFoundException('Device Details Not Found'); + } + const response = await this.getDeviceInstructionByDeviceIdTuya(deviceId); const productId: string = await this.getProductIdByDeviceId(deviceId); @@ -383,6 +450,14 @@ export class DeviceService { } async getDevicesInstructionStatus(deviceId: string) { try { + const deviceDetails = await this.deviceRepository.findOne({ + where: { + uuid: deviceId, + }, + }); + if (!deviceDetails) { + throw new NotFoundException('Device Details Not Found'); + } const deviceStatus = await this.getDevicesInstructionStatusTuya(deviceId); const productId: string = await this.getProductIdByDeviceId(deviceId); const productType: string = From c311c51f687b8819353c2e5bb5da7b4c7f744fae Mon Sep 17 00:00:00 2001 From: VirajBrainvire Date: Tue, 23 Apr 2024 18:09:50 +0530 Subject: [PATCH 66/75] API related changes done --- src/device/interfaces/get.device.interface.ts | 2 +- src/device/services/device.service.ts | 29 +++++++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/device/interfaces/get.device.interface.ts b/src/device/interfaces/get.device.interface.ts index 30f57f8..d513c17 100644 --- a/src/device/interfaces/get.device.interface.ts +++ b/src/device/interfaces/get.device.interface.ts @@ -8,7 +8,7 @@ export interface GetDeviceDetailsInterface { export interface GetDevicesByRoomIdInterface { success: boolean; msg: string; - result: []; + result:any; } export interface GetDevicesByGroupIdInterface { diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 2560efb..dfa2e91 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -82,7 +82,12 @@ export class DeviceService { ): Promise { try { const path = `/v2.0/cloud/thing/space/device`; - const response = await this.tuya.request({ + const getDeviceByRoomId = await this.deviceRepository.find({ + where: { + deviceTuyaUuid: getDeviceByRoomIdDto.roomId, + }, + }); + const response:any = await this.tuya.request({ method: 'GET', path, query: { @@ -90,7 +95,26 @@ export class DeviceService { page_size: getDeviceByRoomIdDto.pageSize, }, }); - return response as GetDevicesByRoomIdInterface; + if (!getDeviceByRoomId.length) { + throw new NotFoundException('Devices Not Found'); + } + + const matchingRecords = []; + + getDeviceByRoomId.forEach((item1) => { + const matchingItem = response.find( + (item2) => item1.deviceTuyaUuid === item2.uuid, + ); + if (matchingItem) { + matchingRecords.push({...matchingItem }); + } + }); + + return { + success:true, + msg:'Device Tuya Details Fetched successfully', + result:matchingRecords + }; } catch (error) { throw new HttpException( 'Error fetching devices by room from Tuya', @@ -241,7 +265,6 @@ export class DeviceService { msg: 'Group is Added to Specific Device', result: true, } as addDeviceInRoomInterface; - } catch (error) { throw new HttpException( 'Error adding device in group from Tuya', From 4060beb9384c12fdc6c7e265d073ab154eb7af13 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 27 Apr 2024 18:41:14 +0300 Subject: [PATCH 67/75] Refactor import statements and fix formatting issues --- src/device/device.module.ts | 7 +++++-- src/device/interfaces/get.device.interface.ts | 2 +- src/device/services/device.service.ts | 10 +++++----- .../controllers/user-device-permission.controller.ts | 1 - 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/device/device.module.ts b/src/device/device.module.ts index 3a829fc..5421c0d 100644 --- a/src/device/device.module.ts +++ b/src/device/device.module.ts @@ -5,7 +5,10 @@ import { ConfigModule } from '@nestjs/config'; import { ProductRepositoryModule } from '@app/common/modules/product/product.repository.module'; import { ProductRepository } from '@app/common/modules/product/repositories'; import { DeviceRepositoryModule } from '@app/common/modules/device'; -import { DeviceRepository, DeviceUserTypeRepository } from '@app/common/modules/device/repositories'; +import { + DeviceRepository, + DeviceUserTypeRepository, +} from '@app/common/modules/device/repositories'; import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { GroupDeviceRepository } from '@app/common/modules/group-device/repositories'; @@ -19,7 +22,7 @@ import { GroupDeviceRepository } from '@app/common/modules/group-device/reposito PermissionTypeRepository, SpaceRepository, DeviceRepository, - GroupDeviceRepository + GroupDeviceRepository, ], exports: [DeviceService], }) diff --git a/src/device/interfaces/get.device.interface.ts b/src/device/interfaces/get.device.interface.ts index d513c17..1550ad9 100644 --- a/src/device/interfaces/get.device.interface.ts +++ b/src/device/interfaces/get.device.interface.ts @@ -8,7 +8,7 @@ export interface GetDeviceDetailsInterface { export interface GetDevicesByRoomIdInterface { success: boolean; msg: string; - result:any; + result: any; } export interface GetDevicesByGroupIdInterface { diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index dfa2e91..e1ebb0b 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -87,7 +87,7 @@ export class DeviceService { deviceTuyaUuid: getDeviceByRoomIdDto.roomId, }, }); - const response:any = await this.tuya.request({ + const response: any = await this.tuya.request({ method: 'GET', path, query: { @@ -106,14 +106,14 @@ export class DeviceService { (item2) => item1.deviceTuyaUuid === item2.uuid, ); if (matchingItem) { - matchingRecords.push({...matchingItem }); + matchingRecords.push({ ...matchingItem }); } }); return { - success:true, - msg:'Device Tuya Details Fetched successfully', - result:matchingRecords + success: true, + msg: 'Device Tuya Details Fetched successfully', + result: matchingRecords, }; } catch (error) { throw new HttpException( diff --git a/src/user-device-permission/controllers/user-device-permission.controller.ts b/src/user-device-permission/controllers/user-device-permission.controller.ts index 91d944c..f5e253f 100644 --- a/src/user-device-permission/controllers/user-device-permission.controller.ts +++ b/src/user-device-permission/controllers/user-device-permission.controller.ts @@ -83,5 +83,4 @@ export class UserDevicePermissionController { throw new Error(err); } } - } From 165dfb575db1b7acf24f444fdee1bdb9cd54e121 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 27 Apr 2024 18:42:22 +0300 Subject: [PATCH 68/75] Refactor import statements in device controller --- src/device/controllers/device.controller.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index 387e765..a34c798 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -1,15 +1,6 @@ import { DeviceService } from '../services/device.service'; -import { - Body, - Controller, - Get, - Post, - UseGuards, - Query, - Param, -} from '@nestjs/common'; +import { Body, Controller, Get, Post, Query, Param } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; -import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { AddDeviceInGroupDto, AddDeviceInRoomDto, From 8c790b0d9628bd1e681a08bbf2965cab97959afb Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 27 Apr 2024 23:27:40 +0300 Subject: [PATCH 69/75] Refactor Device Module --- .../modules/device/entities/device.entity.ts | 24 +- .../product/entities/product.entity.ts | 8 +- .../modules/space/entities/space.entity.ts | 7 + src/device/controllers/device.controller.ts | 159 ++++--- src/device/device.module.ts | 10 +- src/device/dtos/add.device.dto.ts | 30 +- src/device/dtos/control.device.dto.ts | 4 +- src/device/dtos/get.device.dto.ts | 39 +- src/device/interfaces/get.device.interface.ts | 65 ++- src/device/services/device.service.ts | 448 ++++++------------ src/group/services/group.service.ts | 26 +- src/guards/device.product.guard.ts | 4 +- src/guards/group.guard.ts | 81 ++++ src/guards/room.guard.ts | 100 ++++ 14 files changed, 527 insertions(+), 478 deletions(-) create mode 100644 src/guards/group.guard.ts create mode 100644 src/guards/room.guard.ts diff --git a/libs/common/src/modules/device/entities/device.entity.ts b/libs/common/src/modules/device/entities/device.entity.ts index 2c1837a..df27e44 100644 --- a/libs/common/src/modules/device/entities/device.entity.ts +++ b/libs/common/src/modules/device/entities/device.entity.ts @@ -1,26 +1,19 @@ -import { Column, Entity, OneToMany } from 'typeorm'; +import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { DeviceDto } from '../dtos/device.dto'; import { DeviceUserPermissionEntity } from './device-user-type.entity'; import { GroupDeviceEntity } from '../../group-device/entities'; +import { SpaceEntity } from '../../space/entities'; +import { ProductEntity } from '../../product/entities'; @Entity({ name: 'device' }) +@Unique(['spaceDevice', 'deviceTuyaUuid']) export class DeviceEntity extends AbstractEntity { - @Column({ - nullable: false, - }) - public spaceUuid: string; - @Column({ nullable: false, }) deviceTuyaUuid: string; - @Column({ - nullable: false, - }) - public productUuid: string; - @Column({ nullable: true, default: true, @@ -44,6 +37,15 @@ export class DeviceEntity extends AbstractEntity { ) userGroupDevices: GroupDeviceEntity[]; + @ManyToOne(() => SpaceEntity, (space) => space.devicesSpaceEntity, { + nullable: false, + }) + spaceDevice: SpaceEntity; + + @ManyToOne(() => ProductEntity, (product) => product.devicesProductEntity, { + nullable: false, + }) + productDevice: ProductEntity; constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/product/entities/product.entity.ts b/libs/common/src/modules/product/entities/product.entity.ts index 5f04d66..6553dc1 100644 --- a/libs/common/src/modules/product/entities/product.entity.ts +++ b/libs/common/src/modules/product/entities/product.entity.ts @@ -1,6 +1,7 @@ -import { Column, Entity } from 'typeorm'; +import { Column, Entity, OneToMany } from 'typeorm'; import { ProductDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { DeviceEntity } from '../../device/entities'; @Entity({ name: 'product' }) export class ProductEntity extends AbstractEntity { @@ -20,6 +21,11 @@ export class ProductEntity extends AbstractEntity { }) public prodType: string; + @OneToMany( + () => DeviceEntity, + (devicesProductEntity) => devicesProductEntity.productDevice, + ) + devicesProductEntity: DeviceEntity[]; constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index af6922e..56f7010 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -3,6 +3,7 @@ import { SpaceDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { SpaceTypeEntity } from '../../space-type/entities'; import { UserSpaceEntity } from '../../user-space/entities'; +import { DeviceEntity } from '../../device/entities'; @Entity({ name: 'space' }) export class SpaceEntity extends AbstractEntity { @@ -30,6 +31,12 @@ export class SpaceEntity extends AbstractEntity { @OneToMany(() => UserSpaceEntity, (userSpace) => userSpace.space) userSpaces: UserSpaceEntity[]; + @OneToMany( + () => DeviceEntity, + (devicesSpaceEntity) => devicesSpaceEntity.spaceDevice, + ) + devicesSpaceEntity: DeviceEntity[]; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index a34c798..a3bbadd 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -1,5 +1,15 @@ import { DeviceService } from '../services/device.service'; -import { Body, Controller, Get, Post, Query, Param } from '@nestjs/common'; +import { + Body, + Controller, + Get, + Post, + Query, + Param, + HttpException, + HttpStatus, + UseGuards, +} from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { AddDeviceInGroupDto, @@ -7,11 +17,12 @@ import { } from '../dtos/add.device.dto'; import { GetDeviceByGroupIdDto, - GetDeviceByRoomIdDto, + GetDeviceByRoomUuidDto, } from '../dtos/get.device.dto'; import { ControlDeviceDto } from '../dtos/control.device.dto'; -import { AuthGuardWithRoles } from 'src/guards/device.permission.guard'; -import { PermissionType } from '@app/common/constants/permission-type.enum'; +import { CheckRoomGuard } from 'src/guards/room.guard'; +import { CheckGroupGuard } from 'src/guards/group.guard'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; @ApiTags('Device Module') @Controller({ @@ -22,19 +33,38 @@ export class DeviceController { constructor(private readonly deviceService: DeviceService) {} @ApiBearerAuth() - @AuthGuardWithRoles(PermissionType.READ) + @UseGuards(JwtAuthGuard, CheckRoomGuard) @Get('room') async getDevicesByRoomId( - @Query() getDeviceByRoomIdDto: GetDeviceByRoomIdDto, + @Query() getDeviceByRoomUuidDto: GetDeviceByRoomUuidDto, ) { try { - return await this.deviceService.getDevicesByRoomId(getDeviceByRoomIdDto); - } catch (err) { - throw new Error(err); + return await this.deviceService.getDevicesByRoomId( + getDeviceByRoomUuidDto, + ); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard, CheckRoomGuard) + @Post('room') + async addDeviceInRoom(@Body() addDeviceInRoomDto: AddDeviceInRoomDto) { + try { + return await this.deviceService.addDeviceInRoom(addDeviceInRoomDto); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } @ApiBearerAuth() - @AuthGuardWithRoles(PermissionType.READ) + @UseGuards(JwtAuthGuard, CheckGroupGuard) @Get('group') async getDevicesByGroupId( @Query() getDeviceByGroupIdDto: GetDeviceByGroupIdDto, @@ -43,68 +73,81 @@ export class DeviceController { return await this.deviceService.getDevicesByGroupId( getDeviceByGroupIdDto, ); - } catch (err) { - throw new Error(err); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } @ApiBearerAuth() - @AuthGuardWithRoles(PermissionType.READ) - @Get(':deviceId') - async getDeviceDetailsByDeviceId(@Param('deviceId') deviceId: string) { - try { - return await this.deviceService.getDeviceDetailsByDeviceId(deviceId); - } catch (err) { - throw new Error(err); - } - } - @ApiBearerAuth() - @AuthGuardWithRoles(PermissionType.READ) - @Get(':deviceId/functions') - async getDeviceInstructionByDeviceId(@Param('deviceId') deviceId: string) { - try { - return await this.deviceService.getDeviceInstructionByDeviceId(deviceId); - } catch (err) { - throw new Error(err); - } - } - @ApiBearerAuth() - @AuthGuardWithRoles(PermissionType.READ) - @Get(':deviceId/functions/status') - async getDevicesInstructionStatus(@Param('deviceId') deviceId: string) { - try { - return await this.deviceService.getDevicesInstructionStatus(deviceId); - } catch (err) { - throw new Error(err); - } - } - @ApiBearerAuth() - @AuthGuardWithRoles(PermissionType.CONTROLLABLE) - @Post('room') - async addDeviceInRoom(@Body() addDeviceInRoomDto: AddDeviceInRoomDto) { - try { - return await this.deviceService.addDeviceInRoom(addDeviceInRoomDto); - } catch (err) { - throw new Error(err); - } - } - @ApiBearerAuth() - @AuthGuardWithRoles(PermissionType.CONTROLLABLE) + @UseGuards(JwtAuthGuard, CheckGroupGuard) @Post('group') async addDeviceInGroup(@Body() addDeviceInGroupDto: AddDeviceInGroupDto) { try { return await this.deviceService.addDeviceInGroup(addDeviceInGroupDto); - } catch (err) { - throw new Error(err); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } @ApiBearerAuth() - @AuthGuardWithRoles(PermissionType.CONTROLLABLE) + @UseGuards(JwtAuthGuard) + @Get(':deviceUuid') + async getDeviceDetailsByDeviceId(@Param('deviceUuid') deviceUuid: string) { + try { + return await this.deviceService.getDeviceDetailsByDeviceId(deviceUuid); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get(':deviceUuid/functions') + async getDeviceInstructionByDeviceId( + @Param('deviceUuid') deviceUuid: string, + ) { + try { + return await this.deviceService.getDeviceInstructionByDeviceId( + deviceUuid, + ); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get(':deviceUuid/functions/status') + async getDevicesInstructionStatus(@Param('deviceUuid') deviceUuid: string) { + try { + return await this.deviceService.getDevicesInstructionStatus(deviceUuid); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) @Post('control') async controlDevice(@Body() controlDeviceDto: ControlDeviceDto) { try { return await this.deviceService.controlDevice(controlDeviceDto); - } catch (err) { - throw new Error(err); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } } diff --git a/src/device/device.module.ts b/src/device/device.module.ts index 5421c0d..a9fcdc4 100644 --- a/src/device/device.module.ts +++ b/src/device/device.module.ts @@ -12,8 +12,15 @@ import { import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { GroupDeviceRepository } from '@app/common/modules/group-device/repositories'; +import { GroupRepository } from '@app/common/modules/group/repositories'; +import { GroupRepositoryModule } from '@app/common/modules/group/group.repository.module'; @Module({ - imports: [ConfigModule, ProductRepositoryModule, DeviceRepositoryModule], + imports: [ + ConfigModule, + ProductRepositoryModule, + DeviceRepositoryModule, + GroupRepositoryModule, + ], controllers: [DeviceController], providers: [ DeviceService, @@ -23,6 +30,7 @@ import { GroupDeviceRepository } from '@app/common/modules/group-device/reposito SpaceRepository, DeviceRepository, GroupDeviceRepository, + GroupRepository, ], exports: [DeviceService], }) diff --git a/src/device/dtos/add.device.dto.ts b/src/device/dtos/add.device.dto.ts index 8b1dfe5..4adc470 100644 --- a/src/device/dtos/add.device.dto.ts +++ b/src/device/dtos/add.device.dto.ts @@ -1,45 +1,37 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString, IsNumberString } from 'class-validator'; +import { IsNotEmpty, IsString } from 'class-validator'; export class AddDeviceInRoomDto { @ApiProperty({ - description: 'deviceId', + description: 'deviceTuyaUuid', required: true, }) @IsString() @IsNotEmpty() - public deviceId: string; + public deviceTuyaUuid: string; @ApiProperty({ - description: 'roomId', + description: 'roomUuid', required: true, }) - @IsNumberString() + @IsString() @IsNotEmpty() - public roomId: string; + public roomUuid: string; } export class AddDeviceInGroupDto { @ApiProperty({ - description: 'deviceId', + description: 'deviceUuid', required: true, }) @IsString() @IsNotEmpty() - public deviceId: string; + public deviceUuid: string; @ApiProperty({ - description: 'homeId', + description: 'groupUuid', required: true, }) - @IsNumberString() + @IsString() @IsNotEmpty() - public homeId: string; - - @ApiProperty({ - description: 'groupId', - required: true, - }) - @IsNumberString() - @IsNotEmpty() - public groupId: string; + public groupUuid: string; } diff --git a/src/device/dtos/control.device.dto.ts b/src/device/dtos/control.device.dto.ts index 660cdaf..1382dc0 100644 --- a/src/device/dtos/control.device.dto.ts +++ b/src/device/dtos/control.device.dto.ts @@ -3,12 +3,12 @@ import { IsNotEmpty, IsString } from 'class-validator'; export class ControlDeviceDto { @ApiProperty({ - description: 'deviceId', + description: 'deviceUuid', required: true, }) @IsString() @IsNotEmpty() - public deviceId: string; + public deviceUuid: string; @ApiProperty({ description: 'code', diff --git a/src/device/dtos/get.device.dto.ts b/src/device/dtos/get.device.dto.ts index d49a714..5b5200e 100644 --- a/src/device/dtos/get.device.dto.ts +++ b/src/device/dtos/get.device.dto.ts @@ -1,44 +1,21 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsNumberString } from 'class-validator'; +import { IsNotEmpty, IsString } from 'class-validator'; -export class GetDeviceByRoomIdDto { +export class GetDeviceByRoomUuidDto { @ApiProperty({ - description: 'roomId', + description: 'roomUuid', required: true, }) - @IsNumberString() + @IsString() @IsNotEmpty() - public roomId: string; - - @ApiProperty({ - description: 'pageSize', - required: true, - }) - @IsNumberString() - @IsNotEmpty() - public pageSize: number; + public roomUuid: string; } export class GetDeviceByGroupIdDto { @ApiProperty({ - description: 'groupId', + description: 'groupUuid', required: true, }) - @IsNumberString() + @IsString() @IsNotEmpty() - public groupId: string; - - @ApiProperty({ - description: 'pageSize', - required: true, - }) - @IsNumberString() - @IsNotEmpty() - public pageSize: number; - @ApiProperty({ - description: 'pageNo', - required: true, - }) - @IsNumberString() - @IsNotEmpty() - public pageNo: number; + public groupUuid: string; } diff --git a/src/device/interfaces/get.device.interface.ts b/src/device/interfaces/get.device.interface.ts index 1550ad9..f7012f7 100644 --- a/src/device/interfaces/get.device.interface.ts +++ b/src/device/interfaces/get.device.interface.ts @@ -1,23 +1,28 @@ export interface GetDeviceDetailsInterface { - result: { - productId: string; - }; - success: boolean; - msg: string; -} -export interface GetDevicesByRoomIdInterface { - success: boolean; - msg: string; - result: any; -} - -export interface GetDevicesByGroupIdInterface { - success: boolean; - msg: string; - result: { - count: number; - data_list: []; - }; + activeTime: number; + assetId: string; + category: string; + categoryName: string; + createTime: number; + gatewayId: string; + icon: string; + id: string; + ip: string; + lat: string; + localKey: string; + lon: string; + model: string; + name: string; + nodeId: string; + online: boolean; + productId?: string; + productName?: string; + sub: boolean; + timeZone: string; + updateTime: number; + uuid: string; + productType: string; + productUuid: string; } export interface addDeviceInRoomInterface { @@ -44,21 +49,13 @@ export interface GetDeviceDetailsFunctionsStatusInterface { success: boolean; msg: string; } -export interface GetProductInterface { - productType: string; - productId: string; -} export interface DeviceInstructionResponse { - success: boolean; - result: { - productId: string; - productType: string; - functions: { - code: string; - values: any[]; - dataType: string; - }[]; - }; - msg: string; + productUuid: string; + productType: string; + functions: { + code: string; + values: any[]; + dataType: string; + }[]; } diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index e1ebb0b..ca5fbba 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -15,15 +15,11 @@ import { GetDeviceDetailsFunctionsInterface, GetDeviceDetailsFunctionsStatusInterface, GetDeviceDetailsInterface, - GetDevicesByGroupIdInterface, - GetDevicesByRoomIdInterface, - GetProductInterface, - addDeviceInRoomInterface, controlDeviceInterface, } from '../interfaces/get.device.interface'; import { GetDeviceByGroupIdDto, - GetDeviceByRoomIdDto, + GetDeviceByRoomUuidDto, } from '../dtos/get.device.dto'; import { ControlDeviceDto } from '../dtos/control.device.dto'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; @@ -51,25 +47,31 @@ export class DeviceService { }); } - async getDevicesByRoomId(getDeviceByRoomIdDto: GetDeviceByRoomIdDto) { + async getDevicesByRoomId( + getDeviceByRoomUuidDto: GetDeviceByRoomUuidDto, + ): Promise { try { - const findRoom = await this.spaceRepository.findOne({ + const devices = await this.deviceRepository.find({ where: { - uuid: getDeviceByRoomIdDto.roomId, + spaceDevice: { uuid: getDeviceByRoomUuidDto.roomUuid }, }, + relations: ['spaceDevice', 'productDevice'], }); - if (!findRoom) { - throw new NotFoundException('Room Details Not Found'); - } - - const response = await this.getDevicesByRoomIdTuya(getDeviceByRoomIdDto); - - return { - success: response.success, - devices: response.result, - msg: response.msg, - }; + const devicesData = await Promise.all( + devices.map(async (device) => { + return { + ...(await this.getDeviceDetailsByDeviceIdTuya( + device.deviceTuyaUuid, + )), + uuid: device.uuid, + productUuid: device.productDevice.uuid, + productType: device.productDevice.prodType, + } as GetDeviceDetailsInterface; + }), + ); + return devicesData; } catch (error) { + // Handle the error here throw new HttpException( 'Error fetching devices by room', HttpStatus.INTERNAL_SERVER_ERROR, @@ -77,68 +79,26 @@ export class DeviceService { } } - async getDevicesByRoomIdTuya( - getDeviceByRoomIdDto: GetDeviceByRoomIdDto, - ): Promise { - try { - const path = `/v2.0/cloud/thing/space/device`; - const getDeviceByRoomId = await this.deviceRepository.find({ - where: { - deviceTuyaUuid: getDeviceByRoomIdDto.roomId, - }, - }); - const response: any = await this.tuya.request({ - method: 'GET', - path, - query: { - space_ids: getDeviceByRoomIdDto.roomId, - page_size: getDeviceByRoomIdDto.pageSize, - }, - }); - if (!getDeviceByRoomId.length) { - throw new NotFoundException('Devices Not Found'); - } - - const matchingRecords = []; - - getDeviceByRoomId.forEach((item1) => { - const matchingItem = response.find( - (item2) => item1.deviceTuyaUuid === item2.uuid, - ); - if (matchingItem) { - matchingRecords.push({ ...matchingItem }); - } - }); - - return { - success: true, - msg: 'Device Tuya Details Fetched successfully', - result: matchingRecords, - }; - } catch (error) { - throw new HttpException( - 'Error fetching devices by room from Tuya', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } async getDevicesByGroupId(getDeviceByGroupIdDto: GetDeviceByGroupIdDto) { try { - const devicesIds: GetDevicesByGroupIdInterface = - await this.getDevicesByGroupIdTuya(getDeviceByGroupIdDto); - const devicesDetails = await Promise.all( - devicesIds.result.data_list.map(async (device: any) => { - const deviceData = await this.getDeviceDetailsByDeviceId( - device.dev_id, - ); - return deviceData.result; + const groupDevices = await this.groupDeviceRepository.find({ + where: { group: { uuid: getDeviceByGroupIdDto.groupUuid } }, + relations: ['device'], + }); + const devicesData = await Promise.all( + groupDevices.map(async (device) => { + return { + ...(await this.getDeviceDetailsByDeviceIdTuya( + device.device.deviceTuyaUuid, + )), + uuid: device.uuid, + productUuid: device.device.productDevice.uuid, + productType: device.device.productDevice.prodType, + } as GetDeviceDetailsInterface; }), ); - return { - success: devicesIds.success, - devices: devicesDetails, - msg: devicesIds.msg, - }; + + return devicesData; } catch (error) { throw new HttpException( 'Error fetching devices by group', @@ -147,149 +107,81 @@ export class DeviceService { } } - async getDevicesByGroupIdTuya( - getDeviceByGroupIdDto: GetDeviceByGroupIdDto, - ): Promise { - try { - const path = `/v2.0/cloud/thing/group/${getDeviceByGroupIdDto.groupId}/devices`; - const response = await this.tuya.request({ - method: 'GET', - path, - query: { - page_size: getDeviceByGroupIdDto.pageSize, - page_no: getDeviceByGroupIdDto.pageNo, - }, - }); - return response as GetDevicesByGroupIdInterface; - } catch (error) { - throw new HttpException( - 'Error fetching devices by group from Tuya', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - async addDeviceInRoom(addDeviceInRoomDto: AddDeviceInRoomDto) { - const [deviceDetails, roomDetails] = await Promise.all([ - this.deviceRepository.findOne({ - where: { - uuid: addDeviceInRoomDto.deviceId, - }, - }), - this.spaceRepository.findOne({ - where: { - uuid: addDeviceInRoomDto.roomId, - }, - }), - ]); - - if (!roomDetails) { - throw new NotFoundException('Room Details Not Found'); - } - - if (!deviceDetails) { - throw new NotFoundException('Device Details Not Found'); - } - const response = await this.addDeviceInRooms(addDeviceInRoomDto); - - if (response.success) { - return { - success: response.success, - result: response.result, - msg: response.msg, - }; - } else { - throw new HttpException( - response.msg || 'Unknown error', - HttpStatus.BAD_REQUEST, - ); - } - } - async addDeviceInRooms( - addDeviceInRoomDto: AddDeviceInRoomDto, - ): Promise { try { - const path = `/v2.0/cloud/thing/${addDeviceInRoomDto.deviceId}/transfer`; - const response = await this.tuya.request({ - method: 'POST', - path, - body: { - space_id: addDeviceInRoomDto.roomId, - }, - }); - return response as addDeviceInRoomInterface; - } catch (error) { - throw new HttpException( - 'Error adding device in room from Tuya', - HttpStatus.INTERNAL_SERVER_ERROR, + const device = await this.getDeviceDetailsByDeviceIdTuya( + addDeviceInRoomDto.deviceTuyaUuid, ); + + if (!device.productUuid) { + throw new Error('Product UUID is missing for the device.'); + } + + await this.deviceRepository.save({ + deviceTuyaUuid: addDeviceInRoomDto.deviceTuyaUuid, + spaceDevice: { uuid: addDeviceInRoomDto.roomUuid }, + productDevice: { uuid: device.productUuid }, + }); + return { message: 'device added in room successfully' }; + } catch (error) { + if (error.code === '23505') { + throw new Error('Device already exists in the room.'); + } else { + throw new Error('Failed to add device in room'); + } } } + async addDeviceInGroup(addDeviceInGroupDto: AddDeviceInGroupDto) { - const deviceDetails = this.deviceRepository.findOne({ - where: { - uuid: addDeviceInGroupDto.deviceId, - }, - }); - - if (!deviceDetails) { - throw new NotFoundException('Device Details Not Found'); - } - - const response = await this.addDeviceInGroups(addDeviceInGroupDto); - - if (response.success) { - return { - success: response.success, - result: response.result, - msg: response.msg, - }; - } else { - throw new HttpException( - response.msg || 'Unknown error', - HttpStatus.BAD_REQUEST, - ); - } - } - async addDeviceInGroups( - addDeviceInGroupDto: AddDeviceInGroupDto, - ): Promise { try { - await this.groupDeviceRepository.create({ - deviceUuid: addDeviceInGroupDto.deviceId, - groupUuid: addDeviceInGroupDto.groupId, + await this.groupDeviceRepository.save({ + device: { uuid: addDeviceInGroupDto.deviceUuid }, + group: { uuid: addDeviceInGroupDto.groupUuid }, }); - - return { - success: true, - msg: 'Group is Added to Specific Device', - result: true, - } as addDeviceInRoomInterface; + return { message: 'device added in group successfully' }; } catch (error) { - throw new HttpException( - 'Error adding device in group from Tuya', - HttpStatus.INTERNAL_SERVER_ERROR, - ); + if (error.code === '23505') { + throw new Error('Device already exists in the group.'); + } else { + throw new Error('Failed to add device in group'); + } } } async controlDevice(controlDeviceDto: ControlDeviceDto) { - const response = await this.controlDeviceTuya(controlDeviceDto); + try { + const deviceDetails = await this.deviceRepository.findOne({ + where: { + uuid: controlDeviceDto.deviceUuid, + }, + }); - if (response.success) { - return response; - } else { - throw new HttpException( - response.msg || 'Unknown error', - HttpStatus.BAD_REQUEST, + if (!deviceDetails || !deviceDetails.deviceTuyaUuid) { + throw new NotFoundException('Device Not Found'); + } + const response = await this.controlDeviceTuya( + deviceDetails.deviceTuyaUuid, + controlDeviceDto, ); + + if (response.success) { + return response; + } else { + throw new HttpException( + response.msg || 'Unknown error', + HttpStatus.BAD_REQUEST, + ); + } + } catch (error) { + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); } } async controlDeviceTuya( + deviceUuid: string, controlDeviceDto: ControlDeviceDto, ): Promise { try { - const path = `/v1.0/iot-03/devices/${controlDeviceDto.deviceId}/commands`; + const path = `/v1.0/iot-03/devices/${deviceUuid}/commands`; const response = await this.tuya.request({ method: 'POST', path, @@ -309,29 +201,30 @@ export class DeviceService { } } - async getDeviceDetailsByDeviceId(deviceId: string) { + async getDeviceDetailsByDeviceId(deviceUuid: string) { try { const deviceDetails = await this.deviceRepository.findOne({ where: { - uuid: deviceId, + uuid: deviceUuid, }, }); + if (!deviceDetails) { - throw new NotFoundException('Device Details Not Found'); + throw new NotFoundException('Device Not Found'); } - const response = await this.getDeviceDetailsByDeviceIdTuya(deviceId); + const response = await this.getDeviceDetailsByDeviceIdTuya( + deviceDetails.deviceTuyaUuid, + ); return { - success: response.success, - result: response.result, - msg: response.msg, + ...response, + uuid: deviceDetails.uuid, + productUuid: deviceDetails.productDevice.uuid, + productType: deviceDetails.productDevice.prodType, }; } catch (error) { - throw new HttpException( - 'Error fetching device details', - HttpStatus.INTERNAL_SERVER_ERROR, - ); + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); } } async getDeviceDetailsByDeviceIdTuya( @@ -343,19 +236,14 @@ export class DeviceService { method: 'GET', path, }); + // Convert keys to camel case const camelCaseResponse = convertKeysToCamelCase(response); - const productType: string = await this.getProductTypeByProductId( - camelCaseResponse.result.productId, - ); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { productName, productId, ...rest } = camelCaseResponse.result; return { - result: { - ...camelCaseResponse.result, - productType: productType, - }, - success: camelCaseResponse.success, - msg: camelCaseResponse.msg, + ...rest, } as GetDeviceDetailsInterface; } catch (error) { throw new HttpException( @@ -364,93 +252,39 @@ export class DeviceService { ); } } - async getProductIdByDeviceId(deviceId: string) { - try { - const deviceDetails: GetDeviceDetailsInterface = - await this.getDeviceDetailsByDeviceId(deviceId); - - return deviceDetails.result.productId; - } catch (error) { - throw new HttpException( - 'Error fetching product id by device id', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - async getProductByProductId(productId: string): Promise { - try { - const product = await this.productRepository - .createQueryBuilder('product') - .where('product.prodId = :productId', { productId }) - .select(['product.prodId', 'product.prodType']) - .getOne(); - - if (product) { - return { - productType: product.prodType, - productId: product.prodId, - }; - } else { - throw new HttpException('Product not found', HttpStatus.NOT_FOUND); - } - } catch (error) { - throw new HttpException( - 'Error fetching product by product id from db', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - async getProductTypeByProductId(productId: string) { - try { - const product = await this.getProductByProductId(productId); - return product.productType; - } catch (error) { - throw new HttpException( - 'Error getting product type by product id', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } async getDeviceInstructionByDeviceId( - deviceId: string, + deviceUuid: string, ): Promise { try { const deviceDetails = await this.deviceRepository.findOne({ where: { - uuid: deviceId, + uuid: deviceUuid, }, + relations: ['productDevice'], }); + if (!deviceDetails) { - throw new NotFoundException('Device Details Not Found'); + throw new NotFoundException('Device Not Found'); } - const response = await this.getDeviceInstructionByDeviceIdTuya(deviceId); - - const productId: string = await this.getProductIdByDeviceId(deviceId); - const productType: string = - await this.getProductTypeByProductId(productId); + const response = await this.getDeviceInstructionByDeviceIdTuya( + deviceDetails.deviceTuyaUuid, + ); return { - success: response.success, - result: { - productId: productId, - productType: productType, - functions: response.result.functions.map((fun: any) => { - return { - code: fun.code, - values: fun.values, - dataType: fun.type, - }; - }), - }, - msg: response.msg, + productUuid: deviceDetails.productDevice.uuid, + productType: deviceDetails.productDevice.prodType, + functions: response.result.functions.map((fun: any) => { + return { + code: fun.code, + values: fun.values, + dataType: fun.type, + }; + }), }; } catch (error) { - throw new HttpException( - 'Error fetching device functions by device id', - HttpStatus.INTERNAL_SERVER_ERROR, - ); + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); } } @@ -471,28 +305,26 @@ export class DeviceService { ); } } - async getDevicesInstructionStatus(deviceId: string) { + async getDevicesInstructionStatus(deviceUuid: string) { try { const deviceDetails = await this.deviceRepository.findOne({ where: { - uuid: deviceId, + uuid: deviceUuid, }, + relations: ['productDevice'], }); + if (!deviceDetails) { - throw new NotFoundException('Device Details Not Found'); + throw new NotFoundException('Device Not Found'); } - const deviceStatus = await this.getDevicesInstructionStatusTuya(deviceId); - const productId: string = await this.getProductIdByDeviceId(deviceId); - const productType: string = - await this.getProductTypeByProductId(productId); + const deviceStatus = await this.getDevicesInstructionStatusTuya( + deviceDetails.deviceTuyaUuid, + ); + return { - result: { - productId: productId, - productType: productType, - status: deviceStatus.result[0].status, - }, - success: deviceStatus.success, - msg: deviceStatus.msg, + productUuid: deviceDetails.productDevice.uuid, + productType: deviceDetails.productDevice.prodType, + status: deviceStatus.result[0].status, }; } catch (error) { throw new HttpException( @@ -503,7 +335,7 @@ export class DeviceService { } async getDevicesInstructionStatusTuya( - deviceId: string, + deviceUuid: string, ): Promise { try { const path = `/v1.0/iot-03/devices/status`; @@ -511,10 +343,10 @@ export class DeviceService { method: 'GET', path, query: { - device_ids: deviceId, + device_ids: deviceUuid, }, }); - return response as unknown as GetDeviceDetailsFunctionsStatusInterface; + return response as GetDeviceDetailsFunctionsStatusInterface; } catch (error) { throw new HttpException( 'Error fetching device functions status from Tuya', diff --git a/src/group/services/group.service.ts b/src/group/services/group.service.ts index 63e63e9..24fffeb 100644 --- a/src/group/services/group.service.ts +++ b/src/group/services/group.service.ts @@ -43,7 +43,7 @@ export class GroupService { const groupDevices = await this.groupDeviceRepository.find({ relations: ['group', 'device'], where: { - device: { spaceUuid }, + device: { spaceDevice: { uuid: spaceUuid } }, isActive: true, }, }); @@ -129,22 +129,26 @@ export class GroupService { } } async controlDevice(controlDeviceDto: ControlDeviceDto) { - const response = await this.controlDeviceTuya(controlDeviceDto); + try { + const response = await this.controlDeviceTuya(controlDeviceDto); - if (response.success) { - return response; - } else { - throw new HttpException( - response.msg || 'Unknown error', - HttpStatus.BAD_REQUEST, - ); + if (response.success) { + return response; + } else { + throw new HttpException( + response.msg || 'Unknown error', + HttpStatus.BAD_REQUEST, + ); + } + } catch (error) { + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); } } async controlDeviceTuya( controlDeviceDto: ControlDeviceDto, ): Promise { try { - const path = `/v1.0/iot-03/devices/${controlDeviceDto.deviceId}/commands`; + const path = `/v1.0/iot-03/devices/${controlDeviceDto.deviceUuid}/commands`; const response = await this.tuya.request({ method: 'POST', path, @@ -170,7 +174,7 @@ export class GroupService { await Promise.all( devices.map(async (device) => { return this.controlDevice({ - deviceId: device.device.deviceTuyaUuid, + deviceUuid: device.device.deviceTuyaUuid, code: controlGroupDto.code, value: controlGroupDto.value, }); diff --git a/src/guards/device.product.guard.ts b/src/guards/device.product.guard.ts index 2118401..e1b4c62 100644 --- a/src/guards/device.product.guard.ts +++ b/src/guards/device.product.guard.ts @@ -36,7 +36,7 @@ export class CheckProductUuidForAllDevicesGuard implements CanActivate { throw new BadRequestException('First device not found'); } - const firstProductUuid = firstDevice.productUuid; + const firstProductUuid = firstDevice.productDevice.uuid; for (let i = 1; i < deviceUuids.length; i++) { const device = await this.deviceRepository.findOne({ @@ -47,7 +47,7 @@ export class CheckProductUuidForAllDevicesGuard implements CanActivate { throw new BadRequestException(`Device ${deviceUuids[i]} not found`); } - if (device.productUuid !== firstProductUuid) { + if (device.productDevice.uuid !== firstProductUuid) { throw new BadRequestException(`Devices have different product UUIDs`); } } diff --git a/src/guards/group.guard.ts b/src/guards/group.guard.ts new file mode 100644 index 0000000..1fe70e4 --- /dev/null +++ b/src/guards/group.guard.ts @@ -0,0 +1,81 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + HttpStatus, +} from '@nestjs/common'; + +import { BadRequestException, NotFoundException } from '@nestjs/common'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { GroupRepository } from '@app/common/modules/group/repositories'; + +@Injectable() +export class CheckGroupGuard implements CanActivate { + constructor( + private readonly groupRepository: GroupRepository, + private readonly deviceRepository: DeviceRepository, + ) {} + + async canActivate(context: ExecutionContext): Promise { + const req = context.switchToHttp().getRequest(); + + try { + if (req.query && req.query.groupUuid) { + const { groupUuid } = req.query; + await this.checkGroupIsFound(groupUuid); + } else if (req.body && req.body.groupUuid && req.body.deviceUuid) { + const { groupUuid, deviceUuid } = req.body; + await this.checkGroupIsFound(groupUuid); + await this.checkDeviceIsFound(deviceUuid); + } else { + throw new BadRequestException('Invalid request parameters'); + } + + return true; + } catch (error) { + this.handleGuardError(error, context); + return false; + } + } + + private async checkGroupIsFound(groupUuid: string) { + const group = await this.groupRepository.findOne({ + where: { + uuid: groupUuid, + }, + }); + + if (!group) { + throw new NotFoundException('Group not found'); + } + } + async checkDeviceIsFound(deviceUuid: string) { + const device = await this.deviceRepository.findOne({ + where: { + uuid: deviceUuid, + }, + }); + + if (!device) { + throw new NotFoundException('Device not found'); + } + } + + private handleGuardError(error: Error, context: ExecutionContext) { + const response = context.switchToHttp().getResponse(); + if (error instanceof NotFoundException) { + response + .status(HttpStatus.NOT_FOUND) + .json({ statusCode: HttpStatus.NOT_FOUND, message: error.message }); + } else if (error instanceof BadRequestException) { + response + .status(HttpStatus.BAD_REQUEST) + .json({ statusCode: HttpStatus.BAD_REQUEST, message: error.message }); + } else { + response.status(HttpStatus.BAD_REQUEST).json({ + statusCode: HttpStatus.BAD_REQUEST, + message: 'Invalid UUID', + }); + } + } +} diff --git a/src/guards/room.guard.ts b/src/guards/room.guard.ts new file mode 100644 index 0000000..71e54c4 --- /dev/null +++ b/src/guards/room.guard.ts @@ -0,0 +1,100 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + HttpStatus, +} from '@nestjs/common'; +import { TuyaContext } from '@tuya/tuya-connector-nodejs'; + +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { BadRequestException, NotFoundException } from '@nestjs/common'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class CheckRoomGuard implements CanActivate { + private tuya: TuyaContext; + constructor( + private readonly configService: ConfigService, + private readonly spaceRepository: SpaceRepository, + private readonly deviceRepository: DeviceRepository, + ) { + const accessKey = this.configService.get('auth-config.ACCESS_KEY'); + const secretKey = this.configService.get('auth-config.SECRET_KEY'); + this.tuya = new TuyaContext({ + baseUrl: 'https://openapi.tuyaeu.com', + accessKey, + secretKey, + }); + } + + async canActivate(context: ExecutionContext): Promise { + const req = context.switchToHttp().getRequest(); + + try { + console.log(req.body); + + if (req.query && req.query.roomUuid) { + const { roomUuid } = req.query; + await this.checkRoomIsFound(roomUuid); + } else if (req.body && req.body.roomUuid && req.body.deviceTuyaUuid) { + const { roomUuid, deviceTuyaUuid } = req.body; + await this.checkRoomIsFound(roomUuid); + await this.checkDeviceIsFoundFromTuya(deviceTuyaUuid); + } else { + throw new BadRequestException('Invalid request parameters'); + } + + return true; + } catch (error) { + this.handleGuardError(error, context); + return false; + } + } + + private async checkRoomIsFound(roomUuid: string) { + const room = await this.spaceRepository.findOne({ + where: { + uuid: roomUuid, + spaceType: { + type: 'room', + }, + }, + }); + if (!room) { + throw new NotFoundException('Room not found'); + } + } + async checkDeviceIsFoundFromTuya(deviceTuyaUuid: string) { + console.log('deviceTuyaUuid: ', deviceTuyaUuid); + + const path = `/v1.1/iot-03/devices/${deviceTuyaUuid}`; + const response = await this.tuya.request({ + method: 'GET', + path, + }); + console.log('checkDeviceIsFoundFromTuya: ', response); + + if (!response.success) { + throw new NotFoundException('Device not found'); + } + } + + private handleGuardError(error: Error, context: ExecutionContext) { + const response = context.switchToHttp().getResponse(); + if (error instanceof NotFoundException) { + response + .status(HttpStatus.NOT_FOUND) + .json({ statusCode: HttpStatus.NOT_FOUND, message: error.message }); + } else if (error instanceof BadRequestException) { + response + .status(HttpStatus.BAD_REQUEST) + .json({ statusCode: HttpStatus.BAD_REQUEST, message: error.message }); + } else { + response.status(HttpStatus.BAD_REQUEST).json({ + statusCode: HttpStatus.BAD_REQUEST, + message: 'Invalid UUID', + }); + } + } +} From bdcc065d7318aa005f7f1e81014aa85d3bbe2d91 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 27 Apr 2024 23:31:00 +0300 Subject: [PATCH 70/75] Remove Home Module --- libs/common/src/database/database.module.ts | 2 - libs/common/src/modules/home/dtos/home.dto.ts | 19 --- libs/common/src/modules/home/dtos/index.ts | 1 - .../src/modules/home/entities/home.entity.ts | 34 ------ .../common/src/modules/home/entities/index.ts | 1 - .../modules/home/home.repository.module.ts | 11 -- .../home/repositories/home.repository.ts | 10 -- .../src/modules/home/repositories/index.ts | 1 - src/app.module.ts | 2 - src/home/controllers/home.controller.ts | 54 --------- src/home/controllers/index.ts | 1 - src/home/dtos/add.home.dto.ts | 20 ---- src/home/dtos/index.ts | 1 - src/home/home.module.ts | 14 --- src/home/interfaces/get.home.interface.ts | 6 - src/home/services/home.service.ts | 113 ------------------ src/home/services/index.ts | 1 - 17 files changed, 291 deletions(-) delete mode 100644 libs/common/src/modules/home/dtos/home.dto.ts delete mode 100644 libs/common/src/modules/home/dtos/index.ts delete mode 100644 libs/common/src/modules/home/entities/home.entity.ts delete mode 100644 libs/common/src/modules/home/entities/index.ts delete mode 100644 libs/common/src/modules/home/home.repository.module.ts delete mode 100644 libs/common/src/modules/home/repositories/home.repository.ts delete mode 100644 libs/common/src/modules/home/repositories/index.ts delete mode 100644 src/home/controllers/home.controller.ts delete mode 100644 src/home/controllers/index.ts delete mode 100644 src/home/dtos/add.home.dto.ts delete mode 100644 src/home/dtos/index.ts delete mode 100644 src/home/home.module.ts delete mode 100644 src/home/interfaces/get.home.interface.ts delete mode 100644 src/home/services/home.service.ts delete mode 100644 src/home/services/index.ts diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index 819be74..911cfb5 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -5,7 +5,6 @@ import { SnakeNamingStrategy } from './strategies'; import { UserEntity } from '../modules/user/entities/user.entity'; import { UserSessionEntity } from '../modules/session/entities/session.entity'; import { UserOtpEntity } from '../modules/user-otp/entities'; -import { HomeEntity } from '../modules/home/entities'; import { ProductEntity } from '../modules/product/entities'; import { DeviceEntity, @@ -35,7 +34,6 @@ import { GroupDeviceEntity } from '../modules/group-device/entities'; UserEntity, UserSessionEntity, UserOtpEntity, - HomeEntity, ProductEntity, DeviceUserPermissionEntity, DeviceEntity, diff --git a/libs/common/src/modules/home/dtos/home.dto.ts b/libs/common/src/modules/home/dtos/home.dto.ts deleted file mode 100644 index eae0630..0000000 --- a/libs/common/src/modules/home/dtos/home.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { IsNotEmpty, IsString } from 'class-validator'; - -export class HomeDto { - @IsString() - @IsNotEmpty() - public uuid: string; - - @IsString() - @IsNotEmpty() - public userUuid: string; - - @IsString() - @IsNotEmpty() - public homeId: string; - - @IsString() - @IsNotEmpty() - public homeName: string; -} diff --git a/libs/common/src/modules/home/dtos/index.ts b/libs/common/src/modules/home/dtos/index.ts deleted file mode 100644 index 217847e..0000000 --- a/libs/common/src/modules/home/dtos/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './home.dto'; diff --git a/libs/common/src/modules/home/entities/home.entity.ts b/libs/common/src/modules/home/entities/home.entity.ts deleted file mode 100644 index ac85641..0000000 --- a/libs/common/src/modules/home/entities/home.entity.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Column, Entity } from 'typeorm'; -import { HomeDto } from '../dtos'; -import { AbstractEntity } from '../../abstract/entities/abstract.entity'; - -@Entity({ name: 'home' }) -export class HomeEntity extends AbstractEntity { - @Column({ - type: 'uuid', - default: () => 'gen_random_uuid()', // Use gen_random_uuid() for default value - nullable: false, - }) - public uuid: string; - - @Column({ - nullable: false, - }) - userUuid: string; - - @Column({ - nullable: false, - unique: true, - }) - public homeId: string; - - @Column({ - nullable: false, - }) - public homeName: string; - - constructor(partial: Partial) { - super(); - Object.assign(this, partial); - } -} diff --git a/libs/common/src/modules/home/entities/index.ts b/libs/common/src/modules/home/entities/index.ts deleted file mode 100644 index dbf8038..0000000 --- a/libs/common/src/modules/home/entities/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './home.entity'; diff --git a/libs/common/src/modules/home/home.repository.module.ts b/libs/common/src/modules/home/home.repository.module.ts deleted file mode 100644 index e2527b8..0000000 --- a/libs/common/src/modules/home/home.repository.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { HomeEntity } from './entities/home.entity'; - -@Module({ - providers: [], - exports: [], - controllers: [], - imports: [TypeOrmModule.forFeature([HomeEntity])], -}) -export class HomeRepositoryModule {} diff --git a/libs/common/src/modules/home/repositories/home.repository.ts b/libs/common/src/modules/home/repositories/home.repository.ts deleted file mode 100644 index 3f525e2..0000000 --- a/libs/common/src/modules/home/repositories/home.repository.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { DataSource, Repository } from 'typeorm'; -import { Injectable } from '@nestjs/common'; -import { HomeEntity } from '../entities/home.entity'; - -@Injectable() -export class HomeRepository extends Repository { - constructor(private dataSource: DataSource) { - super(HomeEntity, dataSource.createEntityManager()); - } -} diff --git a/libs/common/src/modules/home/repositories/index.ts b/libs/common/src/modules/home/repositories/index.ts deleted file mode 100644 index af66ad7..0000000 --- a/libs/common/src/modules/home/repositories/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './home.repository'; diff --git a/src/app.module.ts b/src/app.module.ts index 1a60f18..4243057 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -4,7 +4,6 @@ import config from './config'; import { AuthenticationModule } from './auth/auth.module'; import { AuthenticationController } from './auth/controllers/authentication.controller'; import { UserModule } from './users/user.module'; -import { HomeModule } from './home/home.module'; import { RoomModule } from './room/room.module'; import { GroupModule } from './group/group.module'; import { DeviceModule } from './device/device.module'; @@ -25,7 +24,6 @@ import { UnitModule } from './unit/unit.module'; FloorModule, UnitModule, RoomModule, - HomeModule, RoomModule, GroupModule, DeviceModule, diff --git a/src/home/controllers/home.controller.ts b/src/home/controllers/home.controller.ts deleted file mode 100644 index c8aeb4b..0000000 --- a/src/home/controllers/home.controller.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { HomeService } from './../services/home.service'; -import { - Body, - Controller, - Get, - Post, - Param, - UseGuards, - Query, -} from '@nestjs/common'; -import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; -import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; -import { AddHomeDto } from '../dtos/add.home.dto'; - -@ApiTags('Home Module') -@Controller({ - version: '1', - path: 'home', -}) -export class HomeController { - constructor(private readonly homeService: HomeService) {} - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Get() - async getHomesByUserId(@Query('userUuid') userUuid: string) { - try { - return await this.homeService.getHomesByUserId(userUuid); - } catch (err) { - throw new Error(err); - } - } - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Get(':homeId') - async getHomesByHomeId(@Param('homeId') homeId: string) { - try { - return await this.homeService.getHomeByHomeId(homeId); - } catch (err) { - throw new Error(err); - } - } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Post() - async addHome(@Body() addHomeDto: AddHomeDto) { - try { - return await this.homeService.addHome(addHomeDto); - } catch (err) { - throw new Error(err); - } - } -} diff --git a/src/home/controllers/index.ts b/src/home/controllers/index.ts deleted file mode 100644 index 66a5c99..0000000 --- a/src/home/controllers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './home.controller'; diff --git a/src/home/dtos/add.home.dto.ts b/src/home/dtos/add.home.dto.ts deleted file mode 100644 index f0d5fbe..0000000 --- a/src/home/dtos/add.home.dto.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; - -export class AddHomeDto { - @ApiProperty({ - description: 'userUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public userUuid: string; - - @ApiProperty({ - description: 'homeName', - required: true, - }) - @IsString() - @IsNotEmpty() - public homeName: string; -} diff --git a/src/home/dtos/index.ts b/src/home/dtos/index.ts deleted file mode 100644 index 912a7bd..0000000 --- a/src/home/dtos/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './add.home.dto'; diff --git a/src/home/home.module.ts b/src/home/home.module.ts deleted file mode 100644 index 76af90d..0000000 --- a/src/home/home.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Module } from '@nestjs/common'; -import { HomeService } from './services/home.service'; -import { HomeController } from './controllers/home.controller'; -import { ConfigModule } from '@nestjs/config'; -import { HomeRepositoryModule } from '@app/common/modules/home/home.repository.module'; -import { HomeRepository } from '@app/common/modules/home/repositories'; - -@Module({ - imports: [ConfigModule, HomeRepositoryModule], - controllers: [HomeController], - providers: [HomeService, HomeRepository], - exports: [HomeService], -}) -export class HomeModule {} diff --git a/src/home/interfaces/get.home.interface.ts b/src/home/interfaces/get.home.interface.ts deleted file mode 100644 index c7015f8..0000000 --- a/src/home/interfaces/get.home.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -export class GetHomeDetailsInterface { - result: { - id: string; - name: string; - }; -} diff --git a/src/home/services/home.service.ts b/src/home/services/home.service.ts deleted file mode 100644 index f1ad1f4..0000000 --- a/src/home/services/home.service.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { HomeRepository } from './../../../libs/common/src/modules/home/repositories/home.repository'; -import { HomeEntity } from './../../../libs/common/src/modules/home/entities/home.entity'; -import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; -import { TuyaContext } from '@tuya/tuya-connector-nodejs'; -import { ConfigService } from '@nestjs/config'; -import { AddHomeDto } from '../dtos'; -import { GetHomeDetailsInterface } from '../interfaces/get.home.interface'; - -@Injectable() -export class HomeService { - private tuya: TuyaContext; - constructor( - private readonly configService: ConfigService, - private readonly homeRepository: HomeRepository, - ) { - const accessKey = this.configService.get('auth-config.ACCESS_KEY'); - const secretKey = this.configService.get('auth-config.SECRET_KEY'); - // const clientId = this.configService.get('auth-config.CLIENT_ID'); - this.tuya = new TuyaContext({ - baseUrl: 'https://openapi.tuyaeu.com', - accessKey, - secretKey, - }); - } - - async getHomesByUserId(userUuid: string) { - const homesData = await this.findHomes(userUuid); - - const homesMapper = homesData.map((home) => ({ - homeId: home.homeId, - homeName: home.homeName, - })); - - return homesMapper; - } - - async findHomes(userUuid: string) { - try { - return await this.homeRepository.find({ - where: { - userUuid: userUuid, - }, - }); - } catch (error) { - throw new HttpException( - 'Error get homes', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - async addHome(addHomeDto: AddHomeDto) { - try { - const path = `/v2.0/cloud/space/creation`; - const data = await this.tuya.request({ - method: 'POST', - path, - body: { name: addHomeDto.homeName }, - }); - if (data.success) { - const homeEntity = { - userUuid: addHomeDto.userUuid, - homeId: data.result, - homeName: addHomeDto.homeName, - } as HomeEntity; - const savedHome = await this.homeRepository.save(homeEntity); - return { - homeId: savedHome.homeId, - homeName: savedHome.homeName, - }; - } - return { - success: data.success, - homeId: data.result, - }; - } catch (error) { - throw new HttpException( - 'Error adding home', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - async getHomeDetails(homeId: string): Promise { - try { - const path = `/v2.0/cloud/space/${homeId}`; - const response = await this.tuya.request({ - method: 'GET', - path, - }); - - return response as GetHomeDetailsInterface; - } catch (error) { - throw new HttpException( - 'Error fetching home details', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - async getHomeByHomeId(homeId: string) { - try { - const response = await this.getHomeDetails(homeId); - - return { - homeId: response.result.id, - homeName: response.result.name, - }; - } catch (error) { - throw new HttpException( - 'Error fetching home', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } -} diff --git a/src/home/services/index.ts b/src/home/services/index.ts deleted file mode 100644 index 23d0070..0000000 --- a/src/home/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './home.service'; From 307761c9155cebec99c7f460273fa66149b80b49 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 28 Apr 2024 09:37:11 +0300 Subject: [PATCH 71/75] Refactor controlDevice method for better readability --- src/group/services/group.service.ts | 27 ++++++++++++++------------- src/guards/device.product.guard.ts | 9 +++++---- src/guards/room.guard.ts | 5 ----- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/group/services/group.service.ts b/src/group/services/group.service.ts index 24fffeb..9f6c3ac 100644 --- a/src/group/services/group.service.ts +++ b/src/group/services/group.service.ts @@ -16,7 +16,6 @@ import { RenameGroupDto } from '../dtos/rename.group.dto copy'; import { GroupRepository } from '@app/common/modules/group/repositories'; import { GroupDeviceRepository } from '@app/common/modules/group-device/repositories'; import { controlDeviceInterface } from 'src/device/interfaces/get.device.interface'; -import { ControlDeviceDto } from 'src/device/dtos'; @Injectable() export class GroupService { @@ -84,6 +83,7 @@ export class GroupService { ); await Promise.all(groupDevicePromises); + return { message: 'Group added successfully' }; } catch (err) { if (err.code === '23505') { throw new HttpException( @@ -128,9 +128,9 @@ export class GroupService { throw error; } } - async controlDevice(controlDeviceDto: ControlDeviceDto) { + async controlDevice(deviceUuid: string, code: string, value: any) { try { - const response = await this.controlDeviceTuya(controlDeviceDto); + const response = await this.controlDeviceTuya(deviceUuid, code, value); if (response.success) { return response; @@ -145,17 +145,17 @@ export class GroupService { } } async controlDeviceTuya( - controlDeviceDto: ControlDeviceDto, + deviceUuid: string, + code: string, + value: any, ): Promise { try { - const path = `/v1.0/iot-03/devices/${controlDeviceDto.deviceUuid}/commands`; + const path = `/v1.0/iot-03/devices/${deviceUuid}/commands`; const response = await this.tuya.request({ method: 'POST', path, body: { - commands: [ - { code: controlDeviceDto.code, value: controlDeviceDto.value }, - ], + commands: [{ code, value: value }], }, }); @@ -173,13 +173,14 @@ export class GroupService { try { await Promise.all( devices.map(async (device) => { - return this.controlDevice({ - deviceUuid: device.device.deviceTuyaUuid, - code: controlGroupDto.code, - value: controlGroupDto.value, - }); + return this.controlDevice( + device.device.deviceTuyaUuid, + controlGroupDto.code, + controlGroupDto.value, + ); }), ); + return { message: 'Group controlled successfully', success: true }; } catch (error) { throw new HttpException( 'Error controlling devices', diff --git a/src/guards/device.product.guard.ts b/src/guards/device.product.guard.ts index e1b4c62..108307a 100644 --- a/src/guards/device.product.guard.ts +++ b/src/guards/device.product.guard.ts @@ -16,7 +16,6 @@ export class CheckProductUuidForAllDevicesGuard implements CanActivate { try { const { deviceUuids } = req.body; - console.log(deviceUuids); await this.checkAllDevicesHaveSameProductUuid(deviceUuids); @@ -30,25 +29,27 @@ export class CheckProductUuidForAllDevicesGuard implements CanActivate { async checkAllDevicesHaveSameProductUuid(deviceUuids: string[]) { const firstDevice = await this.deviceRepository.findOne({ where: { uuid: deviceUuids[0] }, + relations: ['productDevice'], }); if (!firstDevice) { throw new BadRequestException('First device not found'); } - const firstProductUuid = firstDevice.productDevice.uuid; + const firstProductType = firstDevice.productDevice.prodType; for (let i = 1; i < deviceUuids.length; i++) { const device = await this.deviceRepository.findOne({ where: { uuid: deviceUuids[i] }, + relations: ['productDevice'], }); if (!device) { throw new BadRequestException(`Device ${deviceUuids[i]} not found`); } - if (device.productDevice.uuid !== firstProductUuid) { - throw new BadRequestException(`Devices have different product UUIDs`); + if (device.productDevice.prodType !== firstProductType) { + throw new BadRequestException(`Devices have different product types`); } } } diff --git a/src/guards/room.guard.ts b/src/guards/room.guard.ts index 71e54c4..15b34b5 100644 --- a/src/guards/room.guard.ts +++ b/src/guards/room.guard.ts @@ -32,8 +32,6 @@ export class CheckRoomGuard implements CanActivate { const req = context.switchToHttp().getRequest(); try { - console.log(req.body); - if (req.query && req.query.roomUuid) { const { roomUuid } = req.query; await this.checkRoomIsFound(roomUuid); @@ -66,14 +64,11 @@ export class CheckRoomGuard implements CanActivate { } } async checkDeviceIsFoundFromTuya(deviceTuyaUuid: string) { - console.log('deviceTuyaUuid: ', deviceTuyaUuid); - const path = `/v1.1/iot-03/devices/${deviceTuyaUuid}`; const response = await this.tuya.request({ method: 'GET', path, }); - console.log('checkDeviceIsFoundFromTuya: ', response); if (!response.success) { throw new NotFoundException('Device not found'); From c376e69e678b6361c7ca682d08b3a81c86c275b6 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 28 Apr 2024 10:40:09 +0300 Subject: [PATCH 72/75] Add productUuid to response in getDeviceDetails method --- src/device/services/device.service.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index ca5fbba..ee7a911 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -239,11 +239,18 @@ export class DeviceService { // Convert keys to camel case const camelCaseResponse = convertKeysToCamelCase(response); + const deviceDetails = await this.deviceRepository.findOne({ + where: { + deviceTuyaUuid: deviceId, + }, + relations: ['productDevice'], + }); // eslint-disable-next-line @typescript-eslint/no-unused-vars const { productName, productId, ...rest } = camelCaseResponse.result; return { ...rest, + productUuid: deviceDetails.productDevice.uuid, } as GetDeviceDetailsInterface; } catch (error) { throw new HttpException( From cd2f4966701d323ea9b122796077cfa86ca52ff8 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 28 Apr 2024 11:39:49 +0300 Subject: [PATCH 73/75] Refactor unit service to remove unnecessary includeSubSpaces parameter --- src/unit/dtos/get.unit.dto.ts | 23 +---------------------- src/unit/services/unit.service.ts | 9 ++------- 2 files changed, 3 insertions(+), 29 deletions(-) diff --git a/src/unit/dtos/get.unit.dto.ts b/src/unit/dtos/get.unit.dto.ts index 82837aa..2fae52a 100644 --- a/src/unit/dtos/get.unit.dto.ts +++ b/src/unit/dtos/get.unit.dto.ts @@ -1,13 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { Transform } from 'class-transformer'; -import { - IsBoolean, - IsInt, - IsNotEmpty, - IsOptional, - IsString, - Min, -} from 'class-validator'; +import { IsInt, IsNotEmpty, IsString, Min } from 'class-validator'; export class GetUnitDto { @ApiProperty({ @@ -35,19 +27,6 @@ export class GetUnitChildDto { @Min(1, { message: 'Page size must not be less than 1' }) @IsNotEmpty() public pageSize: number; - - @ApiProperty({ - example: true, - description: 'Flag to determine whether to fetch full hierarchy', - required: false, - default: false, - }) - @IsOptional() - @IsBoolean() - @Transform((value) => { - return value.obj.includeSubSpaces === 'true'; - }) - public includeSubSpaces: boolean = false; } export class GetUnitByUserIdDto { @ApiProperty({ diff --git a/src/unit/services/unit.service.ts b/src/unit/services/unit.service.ts index 867300c..1daa297 100644 --- a/src/unit/services/unit.service.ts +++ b/src/unit/services/unit.service.ts @@ -79,7 +79,7 @@ export class UnitService { getUnitChildDto: GetUnitChildDto, ): Promise { try { - const { includeSubSpaces, page, pageSize } = getUnitChildDto; + const { page, pageSize } = getUnitChildDto; const space = await this.spaceRepository.findOneOrFail({ where: { uuid: unitUuid }, @@ -94,12 +94,7 @@ export class UnitService { where: { parent: { uuid: space.uuid } }, }); - const children = await this.buildHierarchy( - space, - includeSubSpaces, - page, - pageSize, - ); + const children = await this.buildHierarchy(space, false, page, pageSize); return { uuid: space.uuid, From 2a6cce345d3d6b2233768ad4b08267be866bc158 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 28 Apr 2024 11:53:06 +0300 Subject: [PATCH 74/75] Refactor error handling in DeviceService --- src/device/services/device.service.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index ee7a911..d4c2a2b 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -125,9 +125,15 @@ export class DeviceService { return { message: 'device added in room successfully' }; } catch (error) { if (error.code === '23505') { - throw new Error('Device already exists in the room.'); + throw new HttpException( + 'Device already exists in the room', + HttpStatus.BAD_REQUEST, + ); } else { - throw new Error('Failed to add device in room'); + throw new HttpException( + 'Failed to add device in room', + HttpStatus.INTERNAL_SERVER_ERROR, + ); } } } @@ -141,9 +147,15 @@ export class DeviceService { return { message: 'device added in group successfully' }; } catch (error) { if (error.code === '23505') { - throw new Error('Device already exists in the group.'); + throw new HttpException( + 'Device already exists in the group', + HttpStatus.BAD_REQUEST, + ); } else { - throw new Error('Failed to add device in group'); + throw new HttpException( + 'Failed to add device in group', + HttpStatus.INTERNAL_SERVER_ERROR, + ); } } } From 9e40cdb0edc29e1dffb069f335ba656b15f1075d Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 29 Apr 2024 11:18:17 +0300 Subject: [PATCH 75/75] Remove unused imports and dependencies --- src/device/services/device.service.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index d4c2a2b..112deb5 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -23,8 +23,6 @@ import { } from '../dtos/get.device.dto'; import { ControlDeviceDto } from '../dtos/control.device.dto'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; -import { ProductRepository } from '@app/common/modules/product/repositories'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; import { DeviceRepository } from '@app/common/modules/device/repositories'; import { GroupDeviceRepository } from '@app/common/modules/group-device/repositories'; @@ -33,8 +31,6 @@ export class DeviceService { private tuya: TuyaContext; constructor( private readonly configService: ConfigService, - private readonly productRepository: ProductRepository, - private readonly spaceRepository: SpaceRepository, private readonly deviceRepository: DeviceRepository, private readonly groupDeviceRepository: GroupDeviceRepository, ) {