diff --git a/libs/common/src/modules/device/entities/device.entity.ts b/libs/common/src/modules/device/entities/device.entity.ts index 2c1837a..df27e44 100644 --- a/libs/common/src/modules/device/entities/device.entity.ts +++ b/libs/common/src/modules/device/entities/device.entity.ts @@ -1,26 +1,19 @@ -import { Column, Entity, OneToMany } from 'typeorm'; +import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { DeviceDto } from '../dtos/device.dto'; import { DeviceUserPermissionEntity } from './device-user-type.entity'; import { GroupDeviceEntity } from '../../group-device/entities'; +import { SpaceEntity } from '../../space/entities'; +import { ProductEntity } from '../../product/entities'; @Entity({ name: 'device' }) +@Unique(['spaceDevice', 'deviceTuyaUuid']) export class DeviceEntity extends AbstractEntity { - @Column({ - nullable: false, - }) - public spaceUuid: string; - @Column({ nullable: false, }) deviceTuyaUuid: string; - @Column({ - nullable: false, - }) - public productUuid: string; - @Column({ nullable: true, default: true, @@ -44,6 +37,15 @@ export class DeviceEntity extends AbstractEntity { ) userGroupDevices: GroupDeviceEntity[]; + @ManyToOne(() => SpaceEntity, (space) => space.devicesSpaceEntity, { + nullable: false, + }) + spaceDevice: SpaceEntity; + + @ManyToOne(() => ProductEntity, (product) => product.devicesProductEntity, { + nullable: false, + }) + productDevice: ProductEntity; constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/product/entities/product.entity.ts b/libs/common/src/modules/product/entities/product.entity.ts index 5f04d66..6553dc1 100644 --- a/libs/common/src/modules/product/entities/product.entity.ts +++ b/libs/common/src/modules/product/entities/product.entity.ts @@ -1,6 +1,7 @@ -import { Column, Entity } from 'typeorm'; +import { Column, Entity, OneToMany } from 'typeorm'; import { ProductDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { DeviceEntity } from '../../device/entities'; @Entity({ name: 'product' }) export class ProductEntity extends AbstractEntity { @@ -20,6 +21,11 @@ export class ProductEntity extends AbstractEntity { }) public prodType: string; + @OneToMany( + () => DeviceEntity, + (devicesProductEntity) => devicesProductEntity.productDevice, + ) + devicesProductEntity: DeviceEntity[]; constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index af6922e..56f7010 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -3,6 +3,7 @@ import { SpaceDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { SpaceTypeEntity } from '../../space-type/entities'; import { UserSpaceEntity } from '../../user-space/entities'; +import { DeviceEntity } from '../../device/entities'; @Entity({ name: 'space' }) export class SpaceEntity extends AbstractEntity { @@ -30,6 +31,12 @@ export class SpaceEntity extends AbstractEntity { @OneToMany(() => UserSpaceEntity, (userSpace) => userSpace.space) userSpaces: UserSpaceEntity[]; + @OneToMany( + () => DeviceEntity, + (devicesSpaceEntity) => devicesSpaceEntity.spaceDevice, + ) + devicesSpaceEntity: DeviceEntity[]; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index a34c798..a3bbadd 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -1,5 +1,15 @@ import { DeviceService } from '../services/device.service'; -import { Body, Controller, Get, Post, Query, Param } from '@nestjs/common'; +import { + Body, + Controller, + Get, + Post, + Query, + Param, + HttpException, + HttpStatus, + UseGuards, +} from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { AddDeviceInGroupDto, @@ -7,11 +17,12 @@ import { } from '../dtos/add.device.dto'; import { GetDeviceByGroupIdDto, - GetDeviceByRoomIdDto, + GetDeviceByRoomUuidDto, } from '../dtos/get.device.dto'; import { ControlDeviceDto } from '../dtos/control.device.dto'; -import { AuthGuardWithRoles } from 'src/guards/device.permission.guard'; -import { PermissionType } from '@app/common/constants/permission-type.enum'; +import { CheckRoomGuard } from 'src/guards/room.guard'; +import { CheckGroupGuard } from 'src/guards/group.guard'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; @ApiTags('Device Module') @Controller({ @@ -22,19 +33,38 @@ export class DeviceController { constructor(private readonly deviceService: DeviceService) {} @ApiBearerAuth() - @AuthGuardWithRoles(PermissionType.READ) + @UseGuards(JwtAuthGuard, CheckRoomGuard) @Get('room') async getDevicesByRoomId( - @Query() getDeviceByRoomIdDto: GetDeviceByRoomIdDto, + @Query() getDeviceByRoomUuidDto: GetDeviceByRoomUuidDto, ) { try { - return await this.deviceService.getDevicesByRoomId(getDeviceByRoomIdDto); - } catch (err) { - throw new Error(err); + return await this.deviceService.getDevicesByRoomId( + getDeviceByRoomUuidDto, + ); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard, CheckRoomGuard) + @Post('room') + async addDeviceInRoom(@Body() addDeviceInRoomDto: AddDeviceInRoomDto) { + try { + return await this.deviceService.addDeviceInRoom(addDeviceInRoomDto); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } @ApiBearerAuth() - @AuthGuardWithRoles(PermissionType.READ) + @UseGuards(JwtAuthGuard, CheckGroupGuard) @Get('group') async getDevicesByGroupId( @Query() getDeviceByGroupIdDto: GetDeviceByGroupIdDto, @@ -43,68 +73,81 @@ export class DeviceController { return await this.deviceService.getDevicesByGroupId( getDeviceByGroupIdDto, ); - } catch (err) { - throw new Error(err); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } @ApiBearerAuth() - @AuthGuardWithRoles(PermissionType.READ) - @Get(':deviceId') - async getDeviceDetailsByDeviceId(@Param('deviceId') deviceId: string) { - try { - return await this.deviceService.getDeviceDetailsByDeviceId(deviceId); - } catch (err) { - throw new Error(err); - } - } - @ApiBearerAuth() - @AuthGuardWithRoles(PermissionType.READ) - @Get(':deviceId/functions') - async getDeviceInstructionByDeviceId(@Param('deviceId') deviceId: string) { - try { - return await this.deviceService.getDeviceInstructionByDeviceId(deviceId); - } catch (err) { - throw new Error(err); - } - } - @ApiBearerAuth() - @AuthGuardWithRoles(PermissionType.READ) - @Get(':deviceId/functions/status') - async getDevicesInstructionStatus(@Param('deviceId') deviceId: string) { - try { - return await this.deviceService.getDevicesInstructionStatus(deviceId); - } catch (err) { - throw new Error(err); - } - } - @ApiBearerAuth() - @AuthGuardWithRoles(PermissionType.CONTROLLABLE) - @Post('room') - async addDeviceInRoom(@Body() addDeviceInRoomDto: AddDeviceInRoomDto) { - try { - return await this.deviceService.addDeviceInRoom(addDeviceInRoomDto); - } catch (err) { - throw new Error(err); - } - } - @ApiBearerAuth() - @AuthGuardWithRoles(PermissionType.CONTROLLABLE) + @UseGuards(JwtAuthGuard, CheckGroupGuard) @Post('group') async addDeviceInGroup(@Body() addDeviceInGroupDto: AddDeviceInGroupDto) { try { return await this.deviceService.addDeviceInGroup(addDeviceInGroupDto); - } catch (err) { - throw new Error(err); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } @ApiBearerAuth() - @AuthGuardWithRoles(PermissionType.CONTROLLABLE) + @UseGuards(JwtAuthGuard) + @Get(':deviceUuid') + async getDeviceDetailsByDeviceId(@Param('deviceUuid') deviceUuid: string) { + try { + return await this.deviceService.getDeviceDetailsByDeviceId(deviceUuid); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get(':deviceUuid/functions') + async getDeviceInstructionByDeviceId( + @Param('deviceUuid') deviceUuid: string, + ) { + try { + return await this.deviceService.getDeviceInstructionByDeviceId( + deviceUuid, + ); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get(':deviceUuid/functions/status') + async getDevicesInstructionStatus(@Param('deviceUuid') deviceUuid: string) { + try { + return await this.deviceService.getDevicesInstructionStatus(deviceUuid); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) @Post('control') async controlDevice(@Body() controlDeviceDto: ControlDeviceDto) { try { return await this.deviceService.controlDevice(controlDeviceDto); - } catch (err) { - throw new Error(err); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); } } } diff --git a/src/device/device.module.ts b/src/device/device.module.ts index 5421c0d..a9fcdc4 100644 --- a/src/device/device.module.ts +++ b/src/device/device.module.ts @@ -12,8 +12,15 @@ import { import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { GroupDeviceRepository } from '@app/common/modules/group-device/repositories'; +import { GroupRepository } from '@app/common/modules/group/repositories'; +import { GroupRepositoryModule } from '@app/common/modules/group/group.repository.module'; @Module({ - imports: [ConfigModule, ProductRepositoryModule, DeviceRepositoryModule], + imports: [ + ConfigModule, + ProductRepositoryModule, + DeviceRepositoryModule, + GroupRepositoryModule, + ], controllers: [DeviceController], providers: [ DeviceService, @@ -23,6 +30,7 @@ import { GroupDeviceRepository } from '@app/common/modules/group-device/reposito SpaceRepository, DeviceRepository, GroupDeviceRepository, + GroupRepository, ], exports: [DeviceService], }) diff --git a/src/device/dtos/add.device.dto.ts b/src/device/dtos/add.device.dto.ts index 8b1dfe5..4adc470 100644 --- a/src/device/dtos/add.device.dto.ts +++ b/src/device/dtos/add.device.dto.ts @@ -1,45 +1,37 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString, IsNumberString } from 'class-validator'; +import { IsNotEmpty, IsString } from 'class-validator'; export class AddDeviceInRoomDto { @ApiProperty({ - description: 'deviceId', + description: 'deviceTuyaUuid', required: true, }) @IsString() @IsNotEmpty() - public deviceId: string; + public deviceTuyaUuid: string; @ApiProperty({ - description: 'roomId', + description: 'roomUuid', required: true, }) - @IsNumberString() + @IsString() @IsNotEmpty() - public roomId: string; + public roomUuid: string; } export class AddDeviceInGroupDto { @ApiProperty({ - description: 'deviceId', + description: 'deviceUuid', required: true, }) @IsString() @IsNotEmpty() - public deviceId: string; + public deviceUuid: string; @ApiProperty({ - description: 'homeId', + description: 'groupUuid', required: true, }) - @IsNumberString() + @IsString() @IsNotEmpty() - public homeId: string; - - @ApiProperty({ - description: 'groupId', - required: true, - }) - @IsNumberString() - @IsNotEmpty() - public groupId: string; + public groupUuid: string; } diff --git a/src/device/dtos/control.device.dto.ts b/src/device/dtos/control.device.dto.ts index 660cdaf..1382dc0 100644 --- a/src/device/dtos/control.device.dto.ts +++ b/src/device/dtos/control.device.dto.ts @@ -3,12 +3,12 @@ import { IsNotEmpty, IsString } from 'class-validator'; export class ControlDeviceDto { @ApiProperty({ - description: 'deviceId', + description: 'deviceUuid', required: true, }) @IsString() @IsNotEmpty() - public deviceId: string; + public deviceUuid: string; @ApiProperty({ description: 'code', diff --git a/src/device/dtos/get.device.dto.ts b/src/device/dtos/get.device.dto.ts index d49a714..5b5200e 100644 --- a/src/device/dtos/get.device.dto.ts +++ b/src/device/dtos/get.device.dto.ts @@ -1,44 +1,21 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsNumberString } from 'class-validator'; +import { IsNotEmpty, IsString } from 'class-validator'; -export class GetDeviceByRoomIdDto { +export class GetDeviceByRoomUuidDto { @ApiProperty({ - description: 'roomId', + description: 'roomUuid', required: true, }) - @IsNumberString() + @IsString() @IsNotEmpty() - public roomId: string; - - @ApiProperty({ - description: 'pageSize', - required: true, - }) - @IsNumberString() - @IsNotEmpty() - public pageSize: number; + public roomUuid: string; } export class GetDeviceByGroupIdDto { @ApiProperty({ - description: 'groupId', + description: 'groupUuid', required: true, }) - @IsNumberString() + @IsString() @IsNotEmpty() - public groupId: string; - - @ApiProperty({ - description: 'pageSize', - required: true, - }) - @IsNumberString() - @IsNotEmpty() - public pageSize: number; - @ApiProperty({ - description: 'pageNo', - required: true, - }) - @IsNumberString() - @IsNotEmpty() - public pageNo: number; + public groupUuid: string; } diff --git a/src/device/interfaces/get.device.interface.ts b/src/device/interfaces/get.device.interface.ts index 1550ad9..f7012f7 100644 --- a/src/device/interfaces/get.device.interface.ts +++ b/src/device/interfaces/get.device.interface.ts @@ -1,23 +1,28 @@ export interface GetDeviceDetailsInterface { - result: { - productId: string; - }; - success: boolean; - msg: string; -} -export interface GetDevicesByRoomIdInterface { - success: boolean; - msg: string; - result: any; -} - -export interface GetDevicesByGroupIdInterface { - success: boolean; - msg: string; - result: { - count: number; - data_list: []; - }; + activeTime: number; + assetId: string; + category: string; + categoryName: string; + createTime: number; + gatewayId: string; + icon: string; + id: string; + ip: string; + lat: string; + localKey: string; + lon: string; + model: string; + name: string; + nodeId: string; + online: boolean; + productId?: string; + productName?: string; + sub: boolean; + timeZone: string; + updateTime: number; + uuid: string; + productType: string; + productUuid: string; } export interface addDeviceInRoomInterface { @@ -44,21 +49,13 @@ export interface GetDeviceDetailsFunctionsStatusInterface { success: boolean; msg: string; } -export interface GetProductInterface { - productType: string; - productId: string; -} export interface DeviceInstructionResponse { - success: boolean; - result: { - productId: string; - productType: string; - functions: { - code: string; - values: any[]; - dataType: string; - }[]; - }; - msg: string; + productUuid: string; + productType: string; + functions: { + code: string; + values: any[]; + dataType: string; + }[]; } diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index e1ebb0b..ca5fbba 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -15,15 +15,11 @@ import { GetDeviceDetailsFunctionsInterface, GetDeviceDetailsFunctionsStatusInterface, GetDeviceDetailsInterface, - GetDevicesByGroupIdInterface, - GetDevicesByRoomIdInterface, - GetProductInterface, - addDeviceInRoomInterface, controlDeviceInterface, } from '../interfaces/get.device.interface'; import { GetDeviceByGroupIdDto, - GetDeviceByRoomIdDto, + GetDeviceByRoomUuidDto, } from '../dtos/get.device.dto'; import { ControlDeviceDto } from '../dtos/control.device.dto'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; @@ -51,25 +47,31 @@ export class DeviceService { }); } - async getDevicesByRoomId(getDeviceByRoomIdDto: GetDeviceByRoomIdDto) { + async getDevicesByRoomId( + getDeviceByRoomUuidDto: GetDeviceByRoomUuidDto, + ): Promise { try { - const findRoom = await this.spaceRepository.findOne({ + const devices = await this.deviceRepository.find({ where: { - uuid: getDeviceByRoomIdDto.roomId, + spaceDevice: { uuid: getDeviceByRoomUuidDto.roomUuid }, }, + relations: ['spaceDevice', 'productDevice'], }); - if (!findRoom) { - throw new NotFoundException('Room Details Not Found'); - } - - const response = await this.getDevicesByRoomIdTuya(getDeviceByRoomIdDto); - - return { - success: response.success, - devices: response.result, - msg: response.msg, - }; + const devicesData = await Promise.all( + devices.map(async (device) => { + return { + ...(await this.getDeviceDetailsByDeviceIdTuya( + device.deviceTuyaUuid, + )), + uuid: device.uuid, + productUuid: device.productDevice.uuid, + productType: device.productDevice.prodType, + } as GetDeviceDetailsInterface; + }), + ); + return devicesData; } catch (error) { + // Handle the error here throw new HttpException( 'Error fetching devices by room', HttpStatus.INTERNAL_SERVER_ERROR, @@ -77,68 +79,26 @@ export class DeviceService { } } - async getDevicesByRoomIdTuya( - getDeviceByRoomIdDto: GetDeviceByRoomIdDto, - ): Promise { - try { - const path = `/v2.0/cloud/thing/space/device`; - const getDeviceByRoomId = await this.deviceRepository.find({ - where: { - deviceTuyaUuid: getDeviceByRoomIdDto.roomId, - }, - }); - const response: any = await this.tuya.request({ - method: 'GET', - path, - query: { - space_ids: getDeviceByRoomIdDto.roomId, - page_size: getDeviceByRoomIdDto.pageSize, - }, - }); - if (!getDeviceByRoomId.length) { - throw new NotFoundException('Devices Not Found'); - } - - const matchingRecords = []; - - getDeviceByRoomId.forEach((item1) => { - const matchingItem = response.find( - (item2) => item1.deviceTuyaUuid === item2.uuid, - ); - if (matchingItem) { - matchingRecords.push({ ...matchingItem }); - } - }); - - return { - success: true, - msg: 'Device Tuya Details Fetched successfully', - result: matchingRecords, - }; - } catch (error) { - throw new HttpException( - 'Error fetching devices by room from Tuya', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } async getDevicesByGroupId(getDeviceByGroupIdDto: GetDeviceByGroupIdDto) { try { - const devicesIds: GetDevicesByGroupIdInterface = - await this.getDevicesByGroupIdTuya(getDeviceByGroupIdDto); - const devicesDetails = await Promise.all( - devicesIds.result.data_list.map(async (device: any) => { - const deviceData = await this.getDeviceDetailsByDeviceId( - device.dev_id, - ); - return deviceData.result; + const groupDevices = await this.groupDeviceRepository.find({ + where: { group: { uuid: getDeviceByGroupIdDto.groupUuid } }, + relations: ['device'], + }); + const devicesData = await Promise.all( + groupDevices.map(async (device) => { + return { + ...(await this.getDeviceDetailsByDeviceIdTuya( + device.device.deviceTuyaUuid, + )), + uuid: device.uuid, + productUuid: device.device.productDevice.uuid, + productType: device.device.productDevice.prodType, + } as GetDeviceDetailsInterface; }), ); - return { - success: devicesIds.success, - devices: devicesDetails, - msg: devicesIds.msg, - }; + + return devicesData; } catch (error) { throw new HttpException( 'Error fetching devices by group', @@ -147,149 +107,81 @@ export class DeviceService { } } - async getDevicesByGroupIdTuya( - getDeviceByGroupIdDto: GetDeviceByGroupIdDto, - ): Promise { - try { - const path = `/v2.0/cloud/thing/group/${getDeviceByGroupIdDto.groupId}/devices`; - const response = await this.tuya.request({ - method: 'GET', - path, - query: { - page_size: getDeviceByGroupIdDto.pageSize, - page_no: getDeviceByGroupIdDto.pageNo, - }, - }); - return response as GetDevicesByGroupIdInterface; - } catch (error) { - throw new HttpException( - 'Error fetching devices by group from Tuya', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - async addDeviceInRoom(addDeviceInRoomDto: AddDeviceInRoomDto) { - const [deviceDetails, roomDetails] = await Promise.all([ - this.deviceRepository.findOne({ - where: { - uuid: addDeviceInRoomDto.deviceId, - }, - }), - this.spaceRepository.findOne({ - where: { - uuid: addDeviceInRoomDto.roomId, - }, - }), - ]); - - if (!roomDetails) { - throw new NotFoundException('Room Details Not Found'); - } - - if (!deviceDetails) { - throw new NotFoundException('Device Details Not Found'); - } - const response = await this.addDeviceInRooms(addDeviceInRoomDto); - - if (response.success) { - return { - success: response.success, - result: response.result, - msg: response.msg, - }; - } else { - throw new HttpException( - response.msg || 'Unknown error', - HttpStatus.BAD_REQUEST, - ); - } - } - async addDeviceInRooms( - addDeviceInRoomDto: AddDeviceInRoomDto, - ): Promise { try { - const path = `/v2.0/cloud/thing/${addDeviceInRoomDto.deviceId}/transfer`; - const response = await this.tuya.request({ - method: 'POST', - path, - body: { - space_id: addDeviceInRoomDto.roomId, - }, - }); - return response as addDeviceInRoomInterface; - } catch (error) { - throw new HttpException( - 'Error adding device in room from Tuya', - HttpStatus.INTERNAL_SERVER_ERROR, + const device = await this.getDeviceDetailsByDeviceIdTuya( + addDeviceInRoomDto.deviceTuyaUuid, ); + + if (!device.productUuid) { + throw new Error('Product UUID is missing for the device.'); + } + + await this.deviceRepository.save({ + deviceTuyaUuid: addDeviceInRoomDto.deviceTuyaUuid, + spaceDevice: { uuid: addDeviceInRoomDto.roomUuid }, + productDevice: { uuid: device.productUuid }, + }); + return { message: 'device added in room successfully' }; + } catch (error) { + if (error.code === '23505') { + throw new Error('Device already exists in the room.'); + } else { + throw new Error('Failed to add device in room'); + } } } + async addDeviceInGroup(addDeviceInGroupDto: AddDeviceInGroupDto) { - const deviceDetails = this.deviceRepository.findOne({ - where: { - uuid: addDeviceInGroupDto.deviceId, - }, - }); - - if (!deviceDetails) { - throw new NotFoundException('Device Details Not Found'); - } - - const response = await this.addDeviceInGroups(addDeviceInGroupDto); - - if (response.success) { - return { - success: response.success, - result: response.result, - msg: response.msg, - }; - } else { - throw new HttpException( - response.msg || 'Unknown error', - HttpStatus.BAD_REQUEST, - ); - } - } - async addDeviceInGroups( - addDeviceInGroupDto: AddDeviceInGroupDto, - ): Promise { try { - await this.groupDeviceRepository.create({ - deviceUuid: addDeviceInGroupDto.deviceId, - groupUuid: addDeviceInGroupDto.groupId, + await this.groupDeviceRepository.save({ + device: { uuid: addDeviceInGroupDto.deviceUuid }, + group: { uuid: addDeviceInGroupDto.groupUuid }, }); - - return { - success: true, - msg: 'Group is Added to Specific Device', - result: true, - } as addDeviceInRoomInterface; + return { message: 'device added in group successfully' }; } catch (error) { - throw new HttpException( - 'Error adding device in group from Tuya', - HttpStatus.INTERNAL_SERVER_ERROR, - ); + if (error.code === '23505') { + throw new Error('Device already exists in the group.'); + } else { + throw new Error('Failed to add device in group'); + } } } async controlDevice(controlDeviceDto: ControlDeviceDto) { - const response = await this.controlDeviceTuya(controlDeviceDto); + try { + const deviceDetails = await this.deviceRepository.findOne({ + where: { + uuid: controlDeviceDto.deviceUuid, + }, + }); - if (response.success) { - return response; - } else { - throw new HttpException( - response.msg || 'Unknown error', - HttpStatus.BAD_REQUEST, + if (!deviceDetails || !deviceDetails.deviceTuyaUuid) { + throw new NotFoundException('Device Not Found'); + } + const response = await this.controlDeviceTuya( + deviceDetails.deviceTuyaUuid, + controlDeviceDto, ); + + if (response.success) { + return response; + } else { + throw new HttpException( + response.msg || 'Unknown error', + HttpStatus.BAD_REQUEST, + ); + } + } catch (error) { + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); } } async controlDeviceTuya( + deviceUuid: string, controlDeviceDto: ControlDeviceDto, ): Promise { try { - const path = `/v1.0/iot-03/devices/${controlDeviceDto.deviceId}/commands`; + const path = `/v1.0/iot-03/devices/${deviceUuid}/commands`; const response = await this.tuya.request({ method: 'POST', path, @@ -309,29 +201,30 @@ export class DeviceService { } } - async getDeviceDetailsByDeviceId(deviceId: string) { + async getDeviceDetailsByDeviceId(deviceUuid: string) { try { const deviceDetails = await this.deviceRepository.findOne({ where: { - uuid: deviceId, + uuid: deviceUuid, }, }); + if (!deviceDetails) { - throw new NotFoundException('Device Details Not Found'); + throw new NotFoundException('Device Not Found'); } - const response = await this.getDeviceDetailsByDeviceIdTuya(deviceId); + const response = await this.getDeviceDetailsByDeviceIdTuya( + deviceDetails.deviceTuyaUuid, + ); return { - success: response.success, - result: response.result, - msg: response.msg, + ...response, + uuid: deviceDetails.uuid, + productUuid: deviceDetails.productDevice.uuid, + productType: deviceDetails.productDevice.prodType, }; } catch (error) { - throw new HttpException( - 'Error fetching device details', - HttpStatus.INTERNAL_SERVER_ERROR, - ); + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); } } async getDeviceDetailsByDeviceIdTuya( @@ -343,19 +236,14 @@ export class DeviceService { method: 'GET', path, }); + // Convert keys to camel case const camelCaseResponse = convertKeysToCamelCase(response); - const productType: string = await this.getProductTypeByProductId( - camelCaseResponse.result.productId, - ); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { productName, productId, ...rest } = camelCaseResponse.result; return { - result: { - ...camelCaseResponse.result, - productType: productType, - }, - success: camelCaseResponse.success, - msg: camelCaseResponse.msg, + ...rest, } as GetDeviceDetailsInterface; } catch (error) { throw new HttpException( @@ -364,93 +252,39 @@ export class DeviceService { ); } } - async getProductIdByDeviceId(deviceId: string) { - try { - const deviceDetails: GetDeviceDetailsInterface = - await this.getDeviceDetailsByDeviceId(deviceId); - - return deviceDetails.result.productId; - } catch (error) { - throw new HttpException( - 'Error fetching product id by device id', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - async getProductByProductId(productId: string): Promise { - try { - const product = await this.productRepository - .createQueryBuilder('product') - .where('product.prodId = :productId', { productId }) - .select(['product.prodId', 'product.prodType']) - .getOne(); - - if (product) { - return { - productType: product.prodType, - productId: product.prodId, - }; - } else { - throw new HttpException('Product not found', HttpStatus.NOT_FOUND); - } - } catch (error) { - throw new HttpException( - 'Error fetching product by product id from db', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - async getProductTypeByProductId(productId: string) { - try { - const product = await this.getProductByProductId(productId); - return product.productType; - } catch (error) { - throw new HttpException( - 'Error getting product type by product id', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } async getDeviceInstructionByDeviceId( - deviceId: string, + deviceUuid: string, ): Promise { try { const deviceDetails = await this.deviceRepository.findOne({ where: { - uuid: deviceId, + uuid: deviceUuid, }, + relations: ['productDevice'], }); + if (!deviceDetails) { - throw new NotFoundException('Device Details Not Found'); + throw new NotFoundException('Device Not Found'); } - const response = await this.getDeviceInstructionByDeviceIdTuya(deviceId); - - const productId: string = await this.getProductIdByDeviceId(deviceId); - const productType: string = - await this.getProductTypeByProductId(productId); + const response = await this.getDeviceInstructionByDeviceIdTuya( + deviceDetails.deviceTuyaUuid, + ); return { - success: response.success, - result: { - productId: productId, - productType: productType, - functions: response.result.functions.map((fun: any) => { - return { - code: fun.code, - values: fun.values, - dataType: fun.type, - }; - }), - }, - msg: response.msg, + productUuid: deviceDetails.productDevice.uuid, + productType: deviceDetails.productDevice.prodType, + functions: response.result.functions.map((fun: any) => { + return { + code: fun.code, + values: fun.values, + dataType: fun.type, + }; + }), }; } catch (error) { - throw new HttpException( - 'Error fetching device functions by device id', - HttpStatus.INTERNAL_SERVER_ERROR, - ); + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); } } @@ -471,28 +305,26 @@ export class DeviceService { ); } } - async getDevicesInstructionStatus(deviceId: string) { + async getDevicesInstructionStatus(deviceUuid: string) { try { const deviceDetails = await this.deviceRepository.findOne({ where: { - uuid: deviceId, + uuid: deviceUuid, }, + relations: ['productDevice'], }); + if (!deviceDetails) { - throw new NotFoundException('Device Details Not Found'); + throw new NotFoundException('Device Not Found'); } - const deviceStatus = await this.getDevicesInstructionStatusTuya(deviceId); - const productId: string = await this.getProductIdByDeviceId(deviceId); - const productType: string = - await this.getProductTypeByProductId(productId); + const deviceStatus = await this.getDevicesInstructionStatusTuya( + deviceDetails.deviceTuyaUuid, + ); + return { - result: { - productId: productId, - productType: productType, - status: deviceStatus.result[0].status, - }, - success: deviceStatus.success, - msg: deviceStatus.msg, + productUuid: deviceDetails.productDevice.uuid, + productType: deviceDetails.productDevice.prodType, + status: deviceStatus.result[0].status, }; } catch (error) { throw new HttpException( @@ -503,7 +335,7 @@ export class DeviceService { } async getDevicesInstructionStatusTuya( - deviceId: string, + deviceUuid: string, ): Promise { try { const path = `/v1.0/iot-03/devices/status`; @@ -511,10 +343,10 @@ export class DeviceService { method: 'GET', path, query: { - device_ids: deviceId, + device_ids: deviceUuid, }, }); - return response as unknown as GetDeviceDetailsFunctionsStatusInterface; + return response as GetDeviceDetailsFunctionsStatusInterface; } catch (error) { throw new HttpException( 'Error fetching device functions status from Tuya', diff --git a/src/group/services/group.service.ts b/src/group/services/group.service.ts index 63e63e9..24fffeb 100644 --- a/src/group/services/group.service.ts +++ b/src/group/services/group.service.ts @@ -43,7 +43,7 @@ export class GroupService { const groupDevices = await this.groupDeviceRepository.find({ relations: ['group', 'device'], where: { - device: { spaceUuid }, + device: { spaceDevice: { uuid: spaceUuid } }, isActive: true, }, }); @@ -129,22 +129,26 @@ export class GroupService { } } async controlDevice(controlDeviceDto: ControlDeviceDto) { - const response = await this.controlDeviceTuya(controlDeviceDto); + try { + const response = await this.controlDeviceTuya(controlDeviceDto); - if (response.success) { - return response; - } else { - throw new HttpException( - response.msg || 'Unknown error', - HttpStatus.BAD_REQUEST, - ); + if (response.success) { + return response; + } else { + throw new HttpException( + response.msg || 'Unknown error', + HttpStatus.BAD_REQUEST, + ); + } + } catch (error) { + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); } } async controlDeviceTuya( controlDeviceDto: ControlDeviceDto, ): Promise { try { - const path = `/v1.0/iot-03/devices/${controlDeviceDto.deviceId}/commands`; + const path = `/v1.0/iot-03/devices/${controlDeviceDto.deviceUuid}/commands`; const response = await this.tuya.request({ method: 'POST', path, @@ -170,7 +174,7 @@ export class GroupService { await Promise.all( devices.map(async (device) => { return this.controlDevice({ - deviceId: device.device.deviceTuyaUuid, + deviceUuid: device.device.deviceTuyaUuid, code: controlGroupDto.code, value: controlGroupDto.value, }); diff --git a/src/guards/device.product.guard.ts b/src/guards/device.product.guard.ts index 2118401..e1b4c62 100644 --- a/src/guards/device.product.guard.ts +++ b/src/guards/device.product.guard.ts @@ -36,7 +36,7 @@ export class CheckProductUuidForAllDevicesGuard implements CanActivate { throw new BadRequestException('First device not found'); } - const firstProductUuid = firstDevice.productUuid; + const firstProductUuid = firstDevice.productDevice.uuid; for (let i = 1; i < deviceUuids.length; i++) { const device = await this.deviceRepository.findOne({ @@ -47,7 +47,7 @@ export class CheckProductUuidForAllDevicesGuard implements CanActivate { throw new BadRequestException(`Device ${deviceUuids[i]} not found`); } - if (device.productUuid !== firstProductUuid) { + if (device.productDevice.uuid !== firstProductUuid) { throw new BadRequestException(`Devices have different product UUIDs`); } } diff --git a/src/guards/group.guard.ts b/src/guards/group.guard.ts new file mode 100644 index 0000000..1fe70e4 --- /dev/null +++ b/src/guards/group.guard.ts @@ -0,0 +1,81 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + HttpStatus, +} from '@nestjs/common'; + +import { BadRequestException, NotFoundException } from '@nestjs/common'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { GroupRepository } from '@app/common/modules/group/repositories'; + +@Injectable() +export class CheckGroupGuard implements CanActivate { + constructor( + private readonly groupRepository: GroupRepository, + private readonly deviceRepository: DeviceRepository, + ) {} + + async canActivate(context: ExecutionContext): Promise { + const req = context.switchToHttp().getRequest(); + + try { + if (req.query && req.query.groupUuid) { + const { groupUuid } = req.query; + await this.checkGroupIsFound(groupUuid); + } else if (req.body && req.body.groupUuid && req.body.deviceUuid) { + const { groupUuid, deviceUuid } = req.body; + await this.checkGroupIsFound(groupUuid); + await this.checkDeviceIsFound(deviceUuid); + } else { + throw new BadRequestException('Invalid request parameters'); + } + + return true; + } catch (error) { + this.handleGuardError(error, context); + return false; + } + } + + private async checkGroupIsFound(groupUuid: string) { + const group = await this.groupRepository.findOne({ + where: { + uuid: groupUuid, + }, + }); + + if (!group) { + throw new NotFoundException('Group not found'); + } + } + async checkDeviceIsFound(deviceUuid: string) { + const device = await this.deviceRepository.findOne({ + where: { + uuid: deviceUuid, + }, + }); + + if (!device) { + throw new NotFoundException('Device not found'); + } + } + + private handleGuardError(error: Error, context: ExecutionContext) { + const response = context.switchToHttp().getResponse(); + if (error instanceof NotFoundException) { + response + .status(HttpStatus.NOT_FOUND) + .json({ statusCode: HttpStatus.NOT_FOUND, message: error.message }); + } else if (error instanceof BadRequestException) { + response + .status(HttpStatus.BAD_REQUEST) + .json({ statusCode: HttpStatus.BAD_REQUEST, message: error.message }); + } else { + response.status(HttpStatus.BAD_REQUEST).json({ + statusCode: HttpStatus.BAD_REQUEST, + message: 'Invalid UUID', + }); + } + } +} diff --git a/src/guards/room.guard.ts b/src/guards/room.guard.ts new file mode 100644 index 0000000..71e54c4 --- /dev/null +++ b/src/guards/room.guard.ts @@ -0,0 +1,100 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + HttpStatus, +} from '@nestjs/common'; +import { TuyaContext } from '@tuya/tuya-connector-nodejs'; + +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { BadRequestException, NotFoundException } from '@nestjs/common'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class CheckRoomGuard implements CanActivate { + private tuya: TuyaContext; + constructor( + private readonly configService: ConfigService, + private readonly spaceRepository: SpaceRepository, + private readonly deviceRepository: DeviceRepository, + ) { + const accessKey = this.configService.get('auth-config.ACCESS_KEY'); + const secretKey = this.configService.get('auth-config.SECRET_KEY'); + this.tuya = new TuyaContext({ + baseUrl: 'https://openapi.tuyaeu.com', + accessKey, + secretKey, + }); + } + + async canActivate(context: ExecutionContext): Promise { + const req = context.switchToHttp().getRequest(); + + try { + console.log(req.body); + + if (req.query && req.query.roomUuid) { + const { roomUuid } = req.query; + await this.checkRoomIsFound(roomUuid); + } else if (req.body && req.body.roomUuid && req.body.deviceTuyaUuid) { + const { roomUuid, deviceTuyaUuid } = req.body; + await this.checkRoomIsFound(roomUuid); + await this.checkDeviceIsFoundFromTuya(deviceTuyaUuid); + } else { + throw new BadRequestException('Invalid request parameters'); + } + + return true; + } catch (error) { + this.handleGuardError(error, context); + return false; + } + } + + private async checkRoomIsFound(roomUuid: string) { + const room = await this.spaceRepository.findOne({ + where: { + uuid: roomUuid, + spaceType: { + type: 'room', + }, + }, + }); + if (!room) { + throw new NotFoundException('Room not found'); + } + } + async checkDeviceIsFoundFromTuya(deviceTuyaUuid: string) { + console.log('deviceTuyaUuid: ', deviceTuyaUuid); + + const path = `/v1.1/iot-03/devices/${deviceTuyaUuid}`; + const response = await this.tuya.request({ + method: 'GET', + path, + }); + console.log('checkDeviceIsFoundFromTuya: ', response); + + if (!response.success) { + throw new NotFoundException('Device not found'); + } + } + + private handleGuardError(error: Error, context: ExecutionContext) { + const response = context.switchToHttp().getResponse(); + if (error instanceof NotFoundException) { + response + .status(HttpStatus.NOT_FOUND) + .json({ statusCode: HttpStatus.NOT_FOUND, message: error.message }); + } else if (error instanceof BadRequestException) { + response + .status(HttpStatus.BAD_REQUEST) + .json({ statusCode: HttpStatus.BAD_REQUEST, message: error.message }); + } else { + response.status(HttpStatus.BAD_REQUEST).json({ + statusCode: HttpStatus.BAD_REQUEST, + message: 'Invalid UUID', + }); + } + } +}