From 5f18d1f4d5c93aeb526f03c1becf6feedc450f95 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 13 Mar 2024 11:17:22 +0300 Subject: [PATCH] finshed get devices by room id --- src/app.module.ts | 2 + src/device/controllers/device.controller.ts | 91 +++++++ src/device/controllers/index.ts | 1 + src/device/device.module.ts | 11 + src/device/dtos/add.device.dto.ts | 36 +++ src/device/dtos/control.device.dto.ts | 20 ++ src/device/dtos/get.device.dto.ts | 20 ++ src/device/dtos/index.ts | 4 + src/device/dtos/rename.device.dto copy.ts | 20 ++ src/device/interfaces/get.device.interface.ts | 25 ++ src/device/services/device.service.ts | 242 ++++++++++++++++++ src/device/services/index.ts | 1 + 12 files changed, 473 insertions(+) create mode 100644 src/device/controllers/device.controller.ts create mode 100644 src/device/controllers/index.ts create mode 100644 src/device/device.module.ts create mode 100644 src/device/dtos/add.device.dto.ts create mode 100644 src/device/dtos/control.device.dto.ts create mode 100644 src/device/dtos/get.device.dto.ts create mode 100644 src/device/dtos/index.ts create mode 100644 src/device/dtos/rename.device.dto copy.ts create mode 100644 src/device/interfaces/get.device.interface.ts create mode 100644 src/device/services/device.service.ts create mode 100644 src/device/services/index.ts diff --git a/src/app.module.ts b/src/app.module.ts index b890f1a..0af9133 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -6,6 +6,7 @@ import { AuthenticationController } from './auth/controllers/authentication.cont import { UserModule } from './users/user.module'; import { HomeModule } from './home/home.module'; import { RoomModule } from './room/room.module'; +import { DeviceModule } from './device/device.module'; @Module({ imports: [ ConfigModule.forRoot({ @@ -15,6 +16,7 @@ import { RoomModule } from './room/room.module'; UserModule, HomeModule, RoomModule, + DeviceModule, ], controllers: [AuthenticationController], }) diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts new file mode 100644 index 0000000..ffedef3 --- /dev/null +++ b/src/device/controllers/device.controller.ts @@ -0,0 +1,91 @@ +import { DeviceService } from '../services/device.service'; +import { + Body, + Controller, + Get, + Post, + UseGuards, + Query, + Param, + Put, + Delete, +} from '@nestjs/common'; +import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; +import { AddDeviceDto } from '../dtos/add.device.dto'; +import { GetDeviceDto } from '../dtos/get.device.dto'; +import { ControlDeviceDto } from '../dtos/control.device.dto'; +import { RenameDeviceDto } from '../dtos/rename.device.dto copy'; + +@ApiTags('Device Module') +@Controller({ + version: '1', + path: 'device', +}) +export class DeviceController { + constructor(private readonly deviceService: DeviceService) {} + + // @ApiBearerAuth() + // @UseGuards(JwtAuthGuard) + @Get('room') + async getDevicesByRoomId(@Query() getDevicesDto: GetDeviceDto) { + try { + return await this.deviceService.getDevicesByRoomId(getDevicesDto); + } catch (err) { + throw new Error(err); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get(':deviceId') + async getDevicesByDeviceId(@Param('deviceId') deviceId: number) { + try { + return await this.deviceService.getDevicesByDeviceId(deviceId); + } catch (err) { + throw new Error(err); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post() + async addDevice(@Body() addDeviceDto: AddDeviceDto) { + try { + return await this.deviceService.addDevice(addDeviceDto); + } catch (err) { + throw new Error(err); + } + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post('control') + async controlDevice(@Body() controlDeviceDto: ControlDeviceDto) { + try { + return await this.deviceService.controlDevice(controlDeviceDto); + } catch (err) { + throw new Error(err); + } + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Put('rename') + async renameDevice(@Body() renameDeviceDto: RenameDeviceDto) { + try { + return await this.deviceService.renameDevice(renameDeviceDto); + } catch (err) { + throw new Error(err); + } + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Delete(':deviceId') + async deleteDevice(@Param('deviceId') deviceId: number) { + try { + return await this.deviceService.deleteDevice(deviceId); + } catch (err) { + throw new Error(err); + } + } +} diff --git a/src/device/controllers/index.ts b/src/device/controllers/index.ts new file mode 100644 index 0000000..8afa190 --- /dev/null +++ b/src/device/controllers/index.ts @@ -0,0 +1 @@ +export * from './device.controller'; diff --git a/src/device/device.module.ts b/src/device/device.module.ts new file mode 100644 index 0000000..8bac0cf --- /dev/null +++ b/src/device/device.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { DeviceService } from './services/device.service'; +import { DeviceController } from './controllers/device.controller'; +import { ConfigModule } from '@nestjs/config'; +@Module({ + imports: [ConfigModule], + controllers: [DeviceController], + providers: [DeviceService], + exports: [DeviceService], +}) +export class DeviceModule {} diff --git a/src/device/dtos/add.device.dto.ts b/src/device/dtos/add.device.dto.ts new file mode 100644 index 0000000..bdb7eb6 --- /dev/null +++ b/src/device/dtos/add.device.dto.ts @@ -0,0 +1,36 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString, IsNumberString } from 'class-validator'; + +export class AddDeviceDto { + @ApiProperty({ + description: 'deviceName', + required: true, + }) + @IsString() + @IsNotEmpty() + public deviceName: string; + + @ApiProperty({ + description: 'homeId', + required: true, + }) + @IsNumberString() + @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; +} diff --git a/src/device/dtos/control.device.dto.ts b/src/device/dtos/control.device.dto.ts new file mode 100644 index 0000000..1164973 --- /dev/null +++ b/src/device/dtos/control.device.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsObject, IsNumberString } from 'class-validator'; + +export class ControlDeviceDto { + @ApiProperty({ + description: 'deviceId', + required: true, + }) + @IsNumberString() + @IsNotEmpty() + public deviceId: string; + + @ApiProperty({ + description: 'example {"switch_1":true,"add_ele":300}', + required: true, + }) + @IsObject() + @IsNotEmpty() + public properties: object; +} diff --git a/src/device/dtos/get.device.dto.ts b/src/device/dtos/get.device.dto.ts new file mode 100644 index 0000000..217ab46 --- /dev/null +++ b/src/device/dtos/get.device.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsNumberString } from 'class-validator'; + +export class GetDeviceDto { + @ApiProperty({ + description: 'roomId', + required: true, + }) + @IsNumberString() + @IsNotEmpty() + public roomId: string; + + @ApiProperty({ + description: 'pageSize', + required: true, + }) + @IsNumberString() + @IsNotEmpty() + public pageSize: number; +} diff --git a/src/device/dtos/index.ts b/src/device/dtos/index.ts new file mode 100644 index 0000000..3409fea --- /dev/null +++ b/src/device/dtos/index.ts @@ -0,0 +1,4 @@ +export * from './add.device.dto'; +export * from './control.device.dto'; +export * from './get.device.dto'; +export * from './rename.device.dto copy'; diff --git a/src/device/dtos/rename.device.dto copy.ts b/src/device/dtos/rename.device.dto copy.ts new file mode 100644 index 0000000..e32a6bb --- /dev/null +++ b/src/device/dtos/rename.device.dto copy.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString, IsNumberString } from 'class-validator'; + +export class RenameDeviceDto { + @ApiProperty({ + description: 'deviceId', + required: true, + }) + @IsNumberString() + @IsNotEmpty() + public deviceId: string; + + @ApiProperty({ + description: 'deviceName', + required: true, + }) + @IsString() + @IsNotEmpty() + public deviceName: string; +} diff --git a/src/device/interfaces/get.device.interface.ts b/src/device/interfaces/get.device.interface.ts new file mode 100644 index 0000000..75a2145 --- /dev/null +++ b/src/device/interfaces/get.device.interface.ts @@ -0,0 +1,25 @@ +export class GetDeviceDetailsInterface { + result: { + id: string; + name: string; + }; +} +export class GetDevicesInterface { + success: boolean; + msg: string; + result: []; +} + +export class addDeviceInterface { + success: boolean; + msg: string; + result: { + id: string; + }; +} + +export class controlDeviceInterface { + success: boolean; + result: boolean; + msg: string; +} diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts new file mode 100644 index 0000000..9a80bfb --- /dev/null +++ b/src/device/services/device.service.ts @@ -0,0 +1,242 @@ +import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { TuyaContext } from '@tuya/tuya-connector-nodejs'; +import { ConfigService } from '@nestjs/config'; +import { AddDeviceDto } from '../dtos/add.device.dto'; +import { + GetDeviceDetailsInterface, + GetDevicesInterface, + addDeviceInterface, + controlDeviceInterface, +} from '../interfaces/get.device.interface'; +import { GetDeviceDto } from '../dtos/get.device.dto'; +import { ControlDeviceDto } from '../dtos/control.device.dto'; +import { RenameDeviceDto } from '../dtos/rename.device.dto copy'; + +@Injectable() +export class DeviceService { + private tuya: TuyaContext; + constructor(private readonly configService: ConfigService) { + const accessKey = this.configService.get('auth-config.ACCESS_KEY'); + const secretKey = this.configService.get('auth-config.SECRET_KEY'); + // const clientId = this.configService.get('auth-config.CLIENT_ID'); + this.tuya = new TuyaContext({ + baseUrl: 'https://openapi.tuyaeu.com', + accessKey, + secretKey, + }); + } + + async getDevicesByRoomId(getDeviceDto: GetDeviceDto) { + try { + const response = await this.getDevicesTuya(getDeviceDto); + + return { + success: response.success, + devices: response.result, + msg: response.msg, + }; + } catch (error) { + throw new HttpException( + 'Error fetching devices', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async getDevicesTuya( + getDeviceDto: GetDeviceDto, + ): Promise { + try { + const path = `/v2.0/cloud/thing/space/device`; + const response = await this.tuya.request({ + method: 'GET', + path, + query: { + space_ids: getDeviceDto.roomId, + page_size: getDeviceDto.pageSize, + }, + }); + return response as unknown as GetDevicesInterface; + } catch (error) { + throw new HttpException( + 'Error fetching devices ', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async addDevice(addDeviceDto: AddDeviceDto) { + const response = await this.addDeviceTuya(addDeviceDto); + + if (response.success) { + return { + success: true, + deviceId: response.result.id, + }; + } else { + throw new HttpException( + response.msg || 'Unknown error', + HttpStatus.BAD_REQUEST, + ); + } + } + async addDeviceTuya(addDeviceDto: AddDeviceDto): Promise { + try { + const path = `/v2.0/cloud/thing/device`; + const response = await this.tuya.request({ + method: 'POST', + path, + body: { + space_id: addDeviceDto.homeId, + name: addDeviceDto.deviceName, + product_id: addDeviceDto.productId, + device_ids: addDeviceDto.deviceIds, + }, + }); + + return response as addDeviceInterface; + } catch (error) { + throw new HttpException( + 'Error adding device', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async controlDevice(controlDeviceDto: ControlDeviceDto) { + const response = await this.controlDeviceTuya(controlDeviceDto); + + if (response.success) { + return response; + } else { + throw new HttpException( + response.msg || 'Unknown error', + HttpStatus.BAD_REQUEST, + ); + } + } + async controlDeviceTuya( + controlDeviceDto: ControlDeviceDto, + ): Promise { + try { + const path = `/v2.0/cloud/thing/device/properties`; + const response = await this.tuya.request({ + method: 'POST', + path, + body: { + device_id: controlDeviceDto.deviceId, + properties: controlDeviceDto.properties, + }, + }); + + return response as controlDeviceInterface; + } catch (error) { + throw new HttpException( + 'Error control device', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async renameDevice(renameDeviceDto: RenameDeviceDto) { + const response = await this.renameDeviceTuya(renameDeviceDto); + + 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 renameDeviceTuya( + renameDeviceDto: RenameDeviceDto, + ): Promise { + try { + const path = `/v2.0/cloud/thing/device/${renameDeviceDto.deviceId}/${renameDeviceDto.deviceName}`; + const response = await this.tuya.request({ + method: 'PUT', + path, + }); + + return response as controlDeviceInterface; + } catch (error) { + throw new HttpException( + 'Error rename device', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async deleteDevice(deviceId: number) { + const response = await this.deleteDeviceTuya(deviceId); + + 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 deleteDeviceTuya(deviceId: number): Promise { + try { + const path = `/v2.0/cloud/thing/device/${deviceId}`; + const response = await this.tuya.request({ + method: 'DELETE', + path, + }); + + return response as controlDeviceInterface; + } catch (error) { + throw new HttpException( + 'Error delete device', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async getDevicesByDeviceId(deviceId: number) { + try { + const response = await this.getDevicesByDeviceIdTuya(deviceId); + + return { + deviceId: response.result.id, + deviceName: response.result.name, + }; + } catch (error) { + throw new HttpException( + 'Error fetching device', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async getDevicesByDeviceIdTuya( + deviceId: number, + ): Promise { + try { + const path = `/v2.0/cloud/thing/device/${deviceId}`; + const response = await this.tuya.request({ + method: 'GET', + path, + }); + return response as GetDeviceDetailsInterface; + } catch (error) { + throw new HttpException( + 'Error fetching device ', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/device/services/index.ts b/src/device/services/index.ts new file mode 100644 index 0000000..9101752 --- /dev/null +++ b/src/device/services/index.ts @@ -0,0 +1 @@ +export * from './device.service';