From 6b881ce01e3528b0746b9a3623114ba2f664556d Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 11 May 2024 22:25:43 +0300 Subject: [PATCH 1/3] Add ProductType enum and implement getDevicesInGetaway method --- .../constants/permission-type.enum copy.ts | 8 ++ src/device/controllers/device.controller.ts | 13 +++ src/device/services/device.service.ts | 91 ++++++++++++++----- 3 files changed, 89 insertions(+), 23 deletions(-) create mode 100644 libs/common/src/constants/permission-type.enum copy.ts diff --git a/libs/common/src/constants/permission-type.enum copy.ts b/libs/common/src/constants/permission-type.enum copy.ts new file mode 100644 index 0000000..f0d24ad --- /dev/null +++ b/libs/common/src/constants/permission-type.enum copy.ts @@ -0,0 +1,8 @@ +export enum ProductType { + AC = 'AC', + GW = 'GW', + CPS = 'CPS', + DL = 'DL', + WPS = 'WPS', + TH_G = '3G', +} diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index a3bbadd..986f23a 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -150,4 +150,17 @@ export class DeviceController { ); } } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('getaway/:gatewayUuid/devices') + async getDevicesInGetaway(@Param('gatewayUuid') gatewayUuid: string) { + try { + return await this.deviceService.getDevicesInGetaway(gatewayUuid); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 39bc0ce..310c2e6 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -26,6 +26,7 @@ import { ControlDeviceDto } from '../dtos/control.device.dto'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { DeviceRepository } from '@app/common/modules/device/repositories'; import { GroupDeviceRepository } from '@app/common/modules/group-device/repositories'; +import { ProductType } from '@app/common/constants/permission-type.enum copy'; @Injectable() export class DeviceService { @@ -160,11 +161,9 @@ export class DeviceService { async controlDevice(controlDeviceDto: ControlDeviceDto) { try { - const deviceDetails = await this.deviceRepository.findOne({ - where: { - uuid: controlDeviceDto.deviceUuid, - }, - }); + const deviceDetails = await this.getDeviceByDeviceUuid( + controlDeviceDto.deviceUuid, + ); if (!deviceDetails || !deviceDetails.deviceTuyaUuid) { throw new NotFoundException('Device Not Found'); @@ -213,11 +212,7 @@ export class DeviceService { async getDeviceDetailsByDeviceId(deviceUuid: string) { try { - const deviceDetails = await this.deviceRepository.findOne({ - where: { - uuid: deviceUuid, - }, - }); + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); if (!deviceDetails) { throw new NotFoundException('Device Not Found'); @@ -256,7 +251,7 @@ export class DeviceService { }); // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { productName, productId, ...rest } = camelCaseResponse.result; + const { productName, productId, id, ...rest } = camelCaseResponse.result; return { ...rest, productUuid: product.uuid, @@ -273,12 +268,7 @@ export class DeviceService { deviceUuid: string, ): Promise { try { - const deviceDetails = await this.deviceRepository.findOne({ - where: { - uuid: deviceUuid, - }, - relations: ['productDevice'], - }); + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); if (!deviceDetails) { throw new NotFoundException('Device Not Found'); @@ -323,12 +313,7 @@ export class DeviceService { } async getDevicesInstructionStatus(deviceUuid: string) { try { - const deviceDetails = await this.deviceRepository.findOne({ - where: { - uuid: deviceUuid, - }, - relations: ['productDevice'], - }); + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); if (!deviceDetails) { throw new NotFoundException('Device Not Found'); @@ -370,4 +355,64 @@ export class DeviceService { ); } } + async getDevicesInGetaway(gatewayUuid: string) { + try { + const deviceDetails = await this.getDeviceByDeviceUuid(gatewayUuid); + + if (!deviceDetails) { + throw new NotFoundException('Device Not Found'); + } else if (deviceDetails.productDevice.prodType !== ProductType.GW) { + throw new NotFoundException('This is not a gateway device'); + } + + const response = await this.getDevicesInGetawayTuya( + deviceDetails.deviceTuyaUuid, + ); + + return { + uuid: deviceDetails.uuid, + productUuid: deviceDetails.productDevice.uuid, + productType: deviceDetails.productDevice.prodType, + device: response, + }; + } catch (error) { + throw new HttpException( + error.message || 'Device Not Found', + HttpStatus.NOT_FOUND, + ); + } + } + async getDevicesInGetawayTuya( + deviceId: string, + ): Promise { + try { + const path = `/v1.0/devices/${deviceId}/sub-devices`; + const response: any = await this.tuya.request({ + method: 'GET', + path, + }); + const camelCaseResponse = response.result.map((device: any) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { product_id, category, id, ...rest } = device; + const camelCaseDevice = convertKeysToCamelCase({ ...rest }); + return camelCaseDevice as GetDeviceDetailsInterface[]; + }); + + return camelCaseResponse; + } catch (error) { + throw new HttpException( + 'Error fetching device details from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + private async getDeviceByDeviceUuid(deviceUuid: string) { + return await this.deviceRepository.findOne({ + where: { + uuid: deviceUuid, + }, + relations: ['productDevice'], + }); + } } From cd40dc897be123305049d155b03c2de97b3fbf28 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 18 May 2024 20:23:17 +0300 Subject: [PATCH 2/3] Add endpoint to update device firmware --- ...type.enum copy.ts => product-type.enum.ts} | 0 src/device/controllers/device.controller.ts | 19 ++++++ src/device/interfaces/get.device.interface.ts | 5 ++ src/device/services/device.service.ts | 58 +++++++++++++++++-- 4 files changed, 78 insertions(+), 4 deletions(-) rename libs/common/src/constants/{permission-type.enum copy.ts => product-type.enum.ts} (100%) diff --git a/libs/common/src/constants/permission-type.enum copy.ts b/libs/common/src/constants/product-type.enum.ts similarity index 100% rename from libs/common/src/constants/permission-type.enum copy.ts rename to libs/common/src/constants/product-type.enum.ts diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index 986f23a..e5ac98c 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -152,6 +152,25 @@ export class DeviceController { } @ApiBearerAuth() @UseGuards(JwtAuthGuard) + @Post(':deviceUuid/firmware/:firmwareVersion') + async updateDeviceFirmware( + @Param('deviceUuid') deviceUuid: string, + @Param('firmwareVersion') firmwareVersion: number, + ) { + try { + return await this.deviceService.updateDeviceFirmware( + deviceUuid, + firmwareVersion, + ); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) @Get('getaway/:gatewayUuid/devices') async getDevicesInGetaway(@Param('gatewayUuid') gatewayUuid: string) { try { diff --git a/src/device/interfaces/get.device.interface.ts b/src/device/interfaces/get.device.interface.ts index f7012f7..526c199 100644 --- a/src/device/interfaces/get.device.interface.ts +++ b/src/device/interfaces/get.device.interface.ts @@ -59,3 +59,8 @@ export interface DeviceInstructionResponse { dataType: string; }[]; } +export interface updateDeviceFirmwareInterface { + success: boolean; + result: boolean; + msg: string; +} diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 310c2e6..7302250 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -4,6 +4,7 @@ import { HttpException, HttpStatus, NotFoundException, + BadRequestException, } from '@nestjs/common'; import { TuyaContext } from '@tuya/tuya-connector-nodejs'; import { ConfigService } from '@nestjs/config'; @@ -17,6 +18,7 @@ import { GetDeviceDetailsFunctionsStatusInterface, GetDeviceDetailsInterface, controlDeviceInterface, + updateDeviceFirmwareInterface, } from '../interfaces/get.device.interface'; import { GetDeviceByGroupIdDto, @@ -26,7 +28,7 @@ import { ControlDeviceDto } from '../dtos/control.device.dto'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { DeviceRepository } from '@app/common/modules/device/repositories'; import { GroupDeviceRepository } from '@app/common/modules/group-device/repositories'; -import { ProductType } from '@app/common/constants/permission-type.enum copy'; +import { ProductType } from '@app/common/constants/product-type.enum'; @Injectable() export class DeviceService { @@ -163,6 +165,7 @@ export class DeviceService { try { const deviceDetails = await this.getDeviceByDeviceUuid( controlDeviceDto.deviceUuid, + false, ); if (!deviceDetails || !deviceDetails.deviceTuyaUuid) { @@ -362,7 +365,7 @@ export class DeviceService { if (!deviceDetails) { throw new NotFoundException('Device Not Found'); } else if (deviceDetails.productDevice.prodType !== ProductType.GW) { - throw new NotFoundException('This is not a gateway device'); + throw new BadRequestException('This is not a gateway device'); } const response = await this.getDevicesInGetawayTuya( @@ -407,12 +410,59 @@ export class DeviceService { } } - private async getDeviceByDeviceUuid(deviceUuid: string) { + private async getDeviceByDeviceUuid( + deviceUuid: string, + withProductDevice: boolean = true, + ) { return await this.deviceRepository.findOne({ where: { uuid: deviceUuid, }, - relations: ['productDevice'], + ...(withProductDevice && { relations: ['productDevice'] }), }); } + + async updateDeviceFirmware(deviceUuid: string, firmwareVersion: number) { + try { + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid, false); + + if (!deviceDetails || !deviceDetails.deviceTuyaUuid) { + throw new NotFoundException('Device Not Found'); + } + const response = await this.updateDeviceFirmwareTuya( + deviceDetails.deviceTuyaUuid, + firmwareVersion, + ); + + 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 updateDeviceFirmwareTuya( + deviceUuid: string, + firmwareVersion: number, + ): Promise { + try { + const path = `/v2.0/cloud/thing/${deviceUuid}/firmware/${firmwareVersion}`; + const response = await this.tuya.request({ + method: 'POST', + path, + }); + + return response as updateDeviceFirmwareInterface; + } catch (error) { + throw new HttpException( + 'Error updating device firmware from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } From cc006ff3c0be6da5840575564b8c260d33af0a4a Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 21 May 2024 16:20:05 +0300 Subject: [PATCH 3/3] resolve conflict with dev branch --- src/device/services/device.service.ts | 56 +++++++++++++-------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 8d3ae3d..fdb75b8 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -4,6 +4,7 @@ import { HttpException, HttpStatus, NotFoundException, + BadRequestException, } from '@nestjs/common'; import { TuyaContext } from '@tuya/tuya-connector-nodejs'; import { ConfigService } from '@nestjs/config'; @@ -17,6 +18,7 @@ import { GetDeviceDetailsFunctionsStatusInterface, GetDeviceDetailsInterface, controlDeviceInterface, + updateDeviceFirmwareInterface, } from '../interfaces/get.device.interface'; import { GetDeviceByGroupIdDto, @@ -26,9 +28,9 @@ import { ControlDeviceDto } from '../dtos/control.device.dto'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { DeviceRepository } from '@app/common/modules/device/repositories'; import { GroupDeviceRepository } from '@app/common/modules/group-device/repositories'; -import { ProductType } from '@app/common/constants/product-type.enum'; import { PermissionType } from '@app/common/constants/permission-type.enum'; import { In } from 'typeorm'; +import { ProductType } from '@app/common/constants/product-type.enum'; @Injectable() export class DeviceService { @@ -47,7 +49,17 @@ export class DeviceService { secretKey, }); } - + private async getDeviceByDeviceUuid( + deviceUuid: string, + withProductDevice: boolean = true, + ) { + return await this.deviceRepository.findOne({ + where: { + uuid: deviceUuid, + }, + ...(withProductDevice && { relations: ['productDevice'] }), + }); + } async getDevicesByRoomId( getDeviceByRoomUuidDto: GetDeviceByRoomUuidDto, userUuid: string, @@ -251,12 +263,6 @@ export class DeviceService { deviceUuid, ); - const deviceDetails = await this.deviceRepository.findOne({ - where: { - uuid: deviceUuid, - }, - relations: ['productDevice'], - }); const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); if (!deviceDetails) { @@ -405,6 +411,18 @@ export class DeviceService { ); } } + private async getUserDevicePermission(userUuid: string, deviceUuid: string) { + const device = await this.deviceRepository.findOne({ + where: { + uuid: deviceUuid, + permission: { + userUuid: userUuid, + }, + }, + relations: ['permission', 'permission.permissionType'], + }); + return device.permission[0].permissionType.type; + } async getDevicesInGetaway(gatewayUuid: string) { try { const deviceDetails = await this.getDeviceByDeviceUuid(gatewayUuid); @@ -456,19 +474,6 @@ export class DeviceService { ); } } - - private async getDeviceByDeviceUuid( - deviceUuid: string, - withProductDevice: boolean = true, - ) { - return await this.deviceRepository.findOne({ - where: { - uuid: deviceUuid, - }, - ...(withProductDevice && { relations: ['productDevice'] }), - }); - } - async updateDeviceFirmware(deviceUuid: string, firmwareVersion: number) { try { const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid, false); @@ -512,13 +517,4 @@ export class DeviceService { ); } } - private async getUserDevicePermission(userUuid: string, deviceUuid: string) { - const device = await this.deviceRepository.findOne({ - where: { - uuid: deviceUuid, - }, - relations: ['permission', 'permission.permissionType'], - }); - return device.permission[0].permissionType.type; - } }