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 01/22] 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 02/22] 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 03/22] 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 04/22] 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 05/22] 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 06/22] 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 07/22] 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 08/22] 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 09/22] 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 10/22] 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 11/22] 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 12/22] 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 13/22] 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 14/22] 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 15/22] 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 16/22] 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 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 17/22] 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 18/22] 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 19/22] 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 20/22] 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 21/22] 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 22/22] 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, ); }