diff --git a/src/app.module.ts b/src/app.module.ts index b890f1a..1da5880 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 { GroupModule } from './group/group.module'; @Module({ imports: [ ConfigModule.forRoot({ @@ -15,6 +16,7 @@ import { RoomModule } from './room/room.module'; UserModule, HomeModule, RoomModule, + GroupModule, ], controllers: [AuthenticationController], }) diff --git a/src/group/controllers/group.controller.ts b/src/group/controllers/group.controller.ts new file mode 100644 index 0000000..be0c7a3 --- /dev/null +++ b/src/group/controllers/group.controller.ts @@ -0,0 +1,91 @@ +import { GroupService } from '../services/group.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 { 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'; + +@ApiTags('Group Module') +@Controller({ + version: '1', + path: 'group', +}) +export class GroupController { + constructor(private readonly groupService: GroupService) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get() + async getGroupsByHomeId(@Query() getGroupsDto: GetGroupDto) { + try { + return await this.groupService.getGroupsByHomeId(getGroupsDto); + } catch (err) { + throw new Error(err); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get(':groupId') + async getGroupsByGroupId(@Param('groupId') groupId: number) { + try { + return await this.groupService.getGroupsByGroupId(groupId); + } catch (err) { + throw new Error(err); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post() + async addGroup(@Body() addGroupDto: AddGroupDto) { + try { + return await this.groupService.addGroup(addGroupDto); + } catch (err) { + throw new Error(err); + } + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post('control') + async controlGroup(@Body() controlGroupDto: ControlGroupDto) { + try { + return await this.groupService.controlGroup(controlGroupDto); + } catch (err) { + throw new Error(err); + } + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Put('rename') + async renameGroup(@Body() renameGroupDto: RenameGroupDto) { + try { + return await this.groupService.renameGroup(renameGroupDto); + } catch (err) { + throw new Error(err); + } + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Delete(':groupId') + async deleteGroup(@Param('groupId') groupId: number) { + try { + return await this.groupService.deleteGroup(groupId); + } catch (err) { + throw new Error(err); + } + } +} diff --git a/src/group/controllers/index.ts b/src/group/controllers/index.ts new file mode 100644 index 0000000..daf2953 --- /dev/null +++ b/src/group/controllers/index.ts @@ -0,0 +1 @@ +export * from './group.controller'; diff --git a/src/group/dtos/add.group.dto.ts b/src/group/dtos/add.group.dto.ts new file mode 100644 index 0000000..b91f793 --- /dev/null +++ b/src/group/dtos/add.group.dto.ts @@ -0,0 +1,36 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString, IsNumberString } from 'class-validator'; + +export class AddGroupDto { + @ApiProperty({ + description: 'groupName', + required: true, + }) + @IsString() + @IsNotEmpty() + public groupName: 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/group/dtos/control.group.dto.ts b/src/group/dtos/control.group.dto.ts new file mode 100644 index 0000000..33a6870 --- /dev/null +++ b/src/group/dtos/control.group.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsObject, IsNumberString } from 'class-validator'; + +export class ControlGroupDto { + @ApiProperty({ + description: 'groupId', + required: true, + }) + @IsNumberString() + @IsNotEmpty() + public groupId: string; + + @ApiProperty({ + description: 'example {"switch_1":true,"add_ele":300}', + required: true, + }) + @IsObject() + @IsNotEmpty() + public properties: object; +} diff --git a/src/group/dtos/get.group.dto.ts b/src/group/dtos/get.group.dto.ts new file mode 100644 index 0000000..aad234b --- /dev/null +++ b/src/group/dtos/get.group.dto.ts @@ -0,0 +1,28 @@ +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/index.ts b/src/group/dtos/index.ts new file mode 100644 index 0000000..61cffa2 --- /dev/null +++ b/src/group/dtos/index.ts @@ -0,0 +1 @@ +export * from './add.group.dto'; diff --git a/src/group/dtos/rename.group.dto copy.ts b/src/group/dtos/rename.group.dto copy.ts new file mode 100644 index 0000000..a85f41b --- /dev/null +++ b/src/group/dtos/rename.group.dto copy.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString, IsNumberString } from 'class-validator'; + +export class RenameGroupDto { + @ApiProperty({ + description: 'groupId', + required: true, + }) + @IsNumberString() + @IsNotEmpty() + public groupId: string; + + @ApiProperty({ + description: 'groupName', + required: true, + }) + @IsString() + @IsNotEmpty() + public groupName: string; +} diff --git a/src/group/group.module.ts b/src/group/group.module.ts new file mode 100644 index 0000000..3969d39 --- /dev/null +++ b/src/group/group.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { GroupService } from './services/group.service'; +import { GroupController } from './controllers/group.controller'; +import { ConfigModule } from '@nestjs/config'; +@Module({ + imports: [ConfigModule], + controllers: [GroupController], + providers: [GroupService], + exports: [GroupService], +}) +export class GroupModule {} diff --git a/src/group/interfaces/get.group.interface.ts b/src/group/interfaces/get.group.interface.ts new file mode 100644 index 0000000..970c343 --- /dev/null +++ b/src/group/interfaces/get.group.interface.ts @@ -0,0 +1,26 @@ +export class GetGroupDetailsInterface { + result: { + id: string; + name: string; + }; +} +export class GetGroupsInterface { + result: { + count: number; + data_list: []; + }; +} + +export class addGroupInterface { + success: boolean; + msg: string; + result: { + id: string; + }; +} + +export class controlGroupInterface { + success: boolean; + result: boolean; + msg: string; +} diff --git a/src/group/services/group.service.ts b/src/group/services/group.service.ts new file mode 100644 index 0000000..07fbacf --- /dev/null +++ b/src/group/services/group.service.ts @@ -0,0 +1,245 @@ +import { Injectable, HttpException, HttpStatus } 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, + 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'; + +@Injectable() +export class GroupService { + 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 getGroupsByHomeId(getGroupDto: GetGroupDto) { + 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, + }, + }); + return response as unknown as GetGroupsInterface; + } catch (error) { + throw new HttpException( + 'Error fetching groups ', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async addGroup(addGroupDto: AddGroupDto) { + const response = await this.addGroupTuya(addGroupDto); + + if (response.success) { + return { + success: true, + groupId: response.result.id, + }; + } else { + throw new HttpException( + response.msg || 'Unknown error', + HttpStatus.BAD_REQUEST, + ); + } + } + async addGroupTuya(addGroupDto: AddGroupDto): Promise { + 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, + }, + }); + + return response as addGroupInterface; + } catch (error) { + throw new HttpException( + 'Error adding group', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async controlGroup(controlGroupDto: ControlGroupDto) { + const response = await this.controlGroupTuya(controlGroupDto); + + if (response.success) { + return response; + } else { + throw new HttpException( + response.msg || 'Unknown error', + HttpStatus.BAD_REQUEST, + ); + } + } + async controlGroupTuya( + controlGroupDto: ControlGroupDto, + ): Promise { + try { + const path = `/v2.0/cloud/thing/group/properties`; + const response = await this.tuya.request({ + method: 'POST', + path, + body: { + group_id: controlGroupDto.groupId, + properties: controlGroupDto.properties, + }, + }); + + return response as controlGroupInterface; + } catch (error) { + throw new HttpException( + 'Error control group', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + 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( + renameGroupDto: RenameGroupDto, + ): Promise { + try { + const path = `/v2.0/cloud/thing/group/${renameGroupDto.groupId}/${renameGroupDto.groupName}`; + const response = await this.tuya.request({ + method: 'PUT', + path, + }); + + 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, + }; + } catch (error) { + throw new HttpException( + 'Error fetching group', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async getGroupsByGroupIdTuya( + groupId: number, + ): Promise { + try { + const path = `/v2.0/cloud/thing/group/${groupId}`; + const response = await this.tuya.request({ + method: 'GET', + path, + }); + return response as GetGroupDetailsInterface; + } catch (error) { + throw new HttpException( + 'Error fetching group ', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/group/services/index.ts b/src/group/services/index.ts new file mode 100644 index 0000000..ee9dd3f --- /dev/null +++ b/src/group/services/index.ts @@ -0,0 +1 @@ +export * from './group.service';