diff --git a/src/app.module.ts b/src/app.module.ts index 1da5880..b3c0106 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -7,6 +7,7 @@ import { UserModule } from './users/user.module'; import { HomeModule } from './home/home.module'; import { RoomModule } from './room/room.module'; import { GroupModule } from './group/group.module'; +import { DeviceModule } from './device/device.module'; @Module({ imports: [ ConfigModule.forRoot({ @@ -17,6 +18,7 @@ import { GroupModule } from './group/group.module'; HomeModule, RoomModule, GroupModule, + 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..936b8e1 --- /dev/null +++ b/src/device/controllers/device.controller.ts @@ -0,0 +1,117 @@ +import { DeviceService } from '../services/device.service'; +import { + Body, + Controller, + Get, + Post, + UseGuards, + Query, + Param, +} from '@nestjs/common'; +import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; +import { + AddDeviceInGroupDto, + AddDeviceInRoomDto, +} from '../dtos/add.device.dto'; +import { + GetDeviceByGroupIdDto, + GetDeviceByRoomIdDto, +} from '../dtos/get.device.dto'; +import { ControlDeviceDto } from '../dtos/control.device.dto'; + +@ApiTags('Device Module') +@Controller({ + version: '1', + path: 'device', +}) +export class DeviceController { + constructor(private readonly deviceService: DeviceService) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('room') + async getDevicesByRoomId( + @Query() getDeviceByRoomIdDto: GetDeviceByRoomIdDto, + ) { + try { + return await this.deviceService.getDevicesByRoomId(getDeviceByRoomIdDto); + } catch (err) { + throw new Error(err); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('group') + async getDevicesByGroupId( + @Query() getDeviceByGroupIdDto: GetDeviceByGroupIdDto, + ) { + try { + return await this.deviceService.getDevicesByGroupId( + getDeviceByGroupIdDto, + ); + } catch (err) { + throw new Error(err); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get(':deviceId') + async getDevicesByDeviceId(@Param('deviceId') deviceId: string) { + try { + return await this.deviceService.getDevicesByDeviceId(deviceId); + } catch (err) { + throw new Error(err); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get(':deviceId/functions') + async getDevicesInstructionByDeviceId(@Param('deviceId') deviceId: string) { + try { + return await this.deviceService.getDevicesInstructionByDeviceId(deviceId); + } catch (err) { + throw new Error(err); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get(':deviceId/functions/status') + async getDevicesInstructionStatus(@Param('deviceId') deviceId: string) { + try { + return await this.deviceService.getDevicesInstructionStatus(deviceId); + } catch (err) { + throw new Error(err); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post('room') + async addDeviceInRoom(@Body() addDeviceInRoomDto: AddDeviceInRoomDto) { + try { + return await this.deviceService.addDeviceInRoom(addDeviceInRoomDto); + } catch (err) { + throw new Error(err); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post('group') + async addDeviceInGroup(@Body() addDeviceInGroupDto: AddDeviceInGroupDto) { + try { + return await this.deviceService.addDeviceInGroup(addDeviceInGroupDto); + } 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); + } + } +} 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..8b1dfe5 --- /dev/null +++ b/src/device/dtos/add.device.dto.ts @@ -0,0 +1,45 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString, IsNumberString } from 'class-validator'; + +export class AddDeviceInRoomDto { + @ApiProperty({ + description: 'deviceId', + required: true, + }) + @IsString() + @IsNotEmpty() + public deviceId: string; + + @ApiProperty({ + description: 'roomId', + required: true, + }) + @IsNumberString() + @IsNotEmpty() + public roomId: string; +} +export class AddDeviceInGroupDto { + @ApiProperty({ + description: 'deviceId', + required: true, + }) + @IsString() + @IsNotEmpty() + public deviceId: string; + + @ApiProperty({ + description: 'homeId', + required: true, + }) + @IsNumberString() + @IsNotEmpty() + public homeId: string; + + @ApiProperty({ + description: 'groupId', + required: true, + }) + @IsNumberString() + @IsNotEmpty() + public groupId: 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..660cdaf --- /dev/null +++ b/src/device/dtos/control.device.dto.ts @@ -0,0 +1,26 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class ControlDeviceDto { + @ApiProperty({ + description: 'deviceId', + required: true, + }) + @IsString() + @IsNotEmpty() + public deviceId: string; + + @ApiProperty({ + description: 'code', + required: true, + }) + @IsString() + @IsNotEmpty() + public code: string; + @ApiProperty({ + description: 'value', + required: true, + }) + @IsNotEmpty() + public value: any; +} diff --git a/src/device/dtos/get.device.dto.ts b/src/device/dtos/get.device.dto.ts new file mode 100644 index 0000000..d49a714 --- /dev/null +++ b/src/device/dtos/get.device.dto.ts @@ -0,0 +1,44 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsNumberString } from 'class-validator'; + +export class GetDeviceByRoomIdDto { + @ApiProperty({ + description: 'roomId', + required: true, + }) + @IsNumberString() + @IsNotEmpty() + public roomId: string; + + @ApiProperty({ + description: 'pageSize', + required: true, + }) + @IsNumberString() + @IsNotEmpty() + public pageSize: number; +} +export class GetDeviceByGroupIdDto { + @ApiProperty({ + description: 'groupId', + required: true, + }) + @IsNumberString() + @IsNotEmpty() + public groupId: 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/device/dtos/index.ts b/src/device/dtos/index.ts new file mode 100644 index 0000000..1a0b6b3 --- /dev/null +++ b/src/device/dtos/index.ts @@ -0,0 +1,3 @@ +export * from './add.device.dto'; +export * from './control.device.dto'; +export * from './get.device.dto'; diff --git a/src/device/interfaces/get.device.interface.ts b/src/device/interfaces/get.device.interface.ts new file mode 100644 index 0000000..e6f1361 --- /dev/null +++ b/src/device/interfaces/get.device.interface.ts @@ -0,0 +1,44 @@ +export class GetDeviceDetailsInterface { + result: object; + success: boolean; + msg: string; +} +export class GetDevicesByRoomIdInterface { + success: boolean; + msg: string; + result: []; +} + +export class GetDevicesByGroupIdInterface { + success: boolean; + msg: string; + result: { + count: number; + data_list: []; + }; +} + +export class addDeviceInRoomInterface { + success: boolean; + msg: string; + result: boolean; +} + +export class controlDeviceInterface { + success: boolean; + result: boolean; + msg: string; +} +export class GetDeviceDetailsFunctionsInterface { + result: { + category: string; + functions: []; + }; + success: boolean; + msg: string; +} +export class GetDeviceDetailsFunctionsStatusInterface { + result: [{ id: string; status: [] }]; + success: 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..ce13b30 --- /dev/null +++ b/src/device/services/device.service.ts @@ -0,0 +1,338 @@ +import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { TuyaContext } from '@tuya/tuya-connector-nodejs'; +import { ConfigService } from '@nestjs/config'; +import { + AddDeviceInGroupDto, + AddDeviceInRoomDto, +} from '../dtos/add.device.dto'; +import { + GetDeviceDetailsFunctionsInterface, + GetDeviceDetailsFunctionsStatusInterface, + GetDeviceDetailsInterface, + GetDevicesByGroupIdInterface, + GetDevicesByRoomIdInterface, + addDeviceInRoomInterface, + controlDeviceInterface, +} from '../interfaces/get.device.interface'; +import { + GetDeviceByGroupIdDto, + GetDeviceByRoomIdDto, +} from '../dtos/get.device.dto'; +import { ControlDeviceDto } from '../dtos/control.device.dto'; + +@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(getDeviceByRoomIdDto: GetDeviceByRoomIdDto) { + try { + const response = await this.getDevicesByRoomIdTuya(getDeviceByRoomIdDto); + + return { + success: response.success, + devices: response.result, + msg: response.msg, + }; + } catch (error) { + throw new HttpException( + 'Error fetching devices', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async getDevicesByRoomIdTuya( + getDeviceByRoomIdDto: GetDeviceByRoomIdDto, + ): Promise { + try { + const path = `/v2.0/cloud/thing/space/device`; + const response = await this.tuya.request({ + method: 'GET', + path, + query: { + space_ids: getDeviceByRoomIdDto.roomId, + page_size: getDeviceByRoomIdDto.pageSize, + }, + }); + return response as GetDevicesByRoomIdInterface; + } catch (error) { + throw new HttpException( + 'Error fetching devices ', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async getDevicesByGroupId(getDeviceByGroupIdDto: GetDeviceByGroupIdDto) { + try { + const response = await this.getDevicesByGroupIdTuya( + getDeviceByGroupIdDto, + ); + + return { + success: response.success, + devices: response.result.data_list, + count: response.result.count, + msg: response.msg, + }; + } catch (error) { + throw new HttpException( + 'Error fetching devices', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async getDevicesByGroupIdTuya( + getDeviceByGroupIdDto: GetDeviceByGroupIdDto, + ): Promise { + try { + const path = `/v2.0/cloud/thing/group`; + const response = await this.tuya.request({ + method: 'GET', + path, + query: { + space_id: getDeviceByGroupIdDto.groupId, + page_size: getDeviceByGroupIdDto.pageSize, + page_no: getDeviceByGroupIdDto.pageNo, + }, + }); + return response as GetDevicesByGroupIdInterface; + } catch (error) { + throw new HttpException( + 'Error fetching devices ', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async addDeviceInRoom(addDeviceInRoomDto: AddDeviceInRoomDto) { + const response = await this.addDeviceInRoomTuya(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 addDeviceInRoomTuya( + 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', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async addDeviceInGroup(addDeviceInGroupDto: AddDeviceInGroupDto) { + const response = await this.addDeviceInGroupTuya(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 addDeviceInGroupTuya( + addDeviceInGroupDto: AddDeviceInGroupDto, + ): Promise { + try { + const path = `/v2.0/cloud/thing/group/${addDeviceInGroupDto.groupId}/device`; + const response = await this.tuya.request({ + method: 'PUT', + path, + body: { + space_id: addDeviceInGroupDto.homeId, + device_ids: addDeviceInGroupDto.deviceId, + }, + }); + + return response as addDeviceInRoomInterface; + } 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 = `/v1.0/iot-03/devices/${controlDeviceDto.deviceId}/commands`; + const response = await this.tuya.request({ + method: 'POST', + path, + body: { + commands: [ + { code: controlDeviceDto.code, value: controlDeviceDto.value }, + ], + }, + }); + + return response as controlDeviceInterface; + } catch (error) { + throw new HttpException( + 'Error control device', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async getDevicesByDeviceId(deviceId: string) { + try { + const response = await this.getDevicesByDeviceIdTuya(deviceId); + + return { + success: response.success, + result: response.result, + msg: response.msg, + }; + } catch (error) { + throw new HttpException( + 'Error fetching device', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async getDevicesByDeviceIdTuya( + deviceId: string, + ): Promise { + try { + const path = `/v1.1/iot-03/devices/${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, + ); + } + } + async getDevicesInstructionByDeviceId(deviceId: string) { + try { + const response = await this.getDevicesInstructionByDeviceIdTuya(deviceId); + + return { + success: response.success, + result: { + category: response.result.category, + function: response.result.functions, + }, + msg: response.msg, + }; + } catch (error) { + throw new HttpException( + 'Error fetching device functions', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async getDevicesInstructionByDeviceIdTuya( + deviceId: string, + ): Promise { + try { + const path = `/v1.0/iot-03/devices/${deviceId}/functions`; + const response = await this.tuya.request({ + method: 'GET', + path, + }); + return response as GetDeviceDetailsFunctionsInterface; + } catch (error) { + throw new HttpException( + 'Error fetching device functions', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async getDevicesInstructionStatus(deviceId: string) { + try { + const response = await this.getDevicesInstructionStatusTuya(deviceId); + + return { + result: response.result, + success: response.success, + msg: response.msg, + }; + } catch (error) { + throw new HttpException( + 'Error fetching device functions', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async getDevicesInstructionStatusTuya( + deviceId: string, + ): Promise { + try { + const path = `/v1.0/iot-03/devices/status`; + const response = await this.tuya.request({ + method: 'GET', + path, + query: { + device_ids: deviceId, + }, + }); + return response as unknown as GetDeviceDetailsFunctionsStatusInterface; + } catch (error) { + throw new HttpException( + 'Error fetching device functions', + 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';