diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index bc399b6..8e7e6d6 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -18,7 +18,10 @@ import { GetDeviceByRoomUuidDto, GetDeviceLogsDto, } from '../dtos/get.device.dto'; -import { ControlDeviceDto } from '../dtos/control.device.dto'; +import { + ControlDeviceDto, + BatchControlDevicesDto, +} from '../dtos/control.device.dto'; import { CheckRoomGuard } from 'src/guards/room.guard'; import { CheckUserHavePermission } from 'src/guards/user.device.permission.guard'; import { CheckUserHaveControllablePermission } from 'src/guards/user.device.controllable.permission.guard'; @@ -255,4 +258,21 @@ export class DeviceController { ); } } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post('control/batch') + async batchControlDevices( + @Body() batchControlDevicesDto: BatchControlDevicesDto, + ) { + try { + return await this.deviceService.batchControlDevices( + batchControlDevicesDto, + ); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } diff --git a/src/device/dtos/control.device.dto.ts b/src/device/dtos/control.device.dto.ts index ab2125d..9bb1d40 100644 --- a/src/device/dtos/control.device.dto.ts +++ b/src/device/dtos/control.device.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; +import { IsArray, IsNotEmpty, IsString } from 'class-validator'; export class ControlDeviceDto { @ApiProperty({ @@ -16,3 +16,25 @@ export class ControlDeviceDto { @IsNotEmpty() public value: any; } +export class BatchControlDevicesDto { + @ApiProperty({ + description: 'devicesUuid', + required: true, + }) + @IsArray() + @IsNotEmpty() + public devicesUuid: [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/services/device.service.ts b/src/device/services/device.service.ts index 0641d6f..93433a3 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -22,7 +22,10 @@ import { GetDeviceByRoomUuidDto, GetDeviceLogsDto, } from '../dtos/get.device.dto'; -import { ControlDeviceDto } from '../dtos/control.device.dto'; +import { + BatchControlDevicesDto, + ControlDeviceDto, +} from '../dtos/control.device.dto'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { DeviceRepository } from '@app/common/modules/device/repositories'; import { PermissionType } from '@app/common/constants/permission-type.enum'; @@ -308,7 +311,85 @@ export class DeviceService { ); } } + async batchControlDevices(batchControlDevicesDto: BatchControlDevicesDto) { + const { devicesUuid } = batchControlDevicesDto; + try { + // Check if all devices have the same product UUID + await this.checkAllDevicesHaveSameProductUuid(devicesUuid); + + // Perform all operations concurrently + const results = await Promise.allSettled( + devicesUuid.map(async (deviceUuid) => { + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); + const result = await this.controlDeviceTuya( + deviceDetails.deviceTuyaUuid, + batchControlDevicesDto, + ); + return { deviceUuid, result }; + }), + ); + + // Separate successful and failed operations + const successResults = []; + const failedResults = []; + + for (const result of results) { + if (result.status === 'fulfilled') { + const { deviceUuid, result: operationResult } = result.value; + + if (operationResult.success) { + // Add to success results if operationResult.success is true + successResults.push({ deviceUuid, result: operationResult }); + } else { + // Add to failed results if operationResult.success is false + failedResults.push({ deviceUuid, error: operationResult.msg }); + } + } else { + // Add to failed results if promise is rejected + failedResults.push({ + deviceUuid: devicesUuid[results.indexOf(result)], + error: result.reason.message, + }); + } + } + + return { successResults, failedResults }; + } catch (error) { + throw new HttpException( + error.message || 'Device Not Found', + error.status || HttpStatus.NOT_FOUND, + ); + } + } + + async checkAllDevicesHaveSameProductUuid(deviceUuids: string[]) { + const firstDevice = await this.deviceRepository.findOne({ + where: { uuid: deviceUuids[0] }, + relations: ['productDevice'], + }); + + if (!firstDevice) { + throw new BadRequestException('First device not found'); + } + + const firstProductType = firstDevice.productDevice.prodType; + + for (let i = 1; i < deviceUuids.length; i++) { + const device = await this.deviceRepository.findOne({ + where: { uuid: deviceUuids[i] }, + relations: ['productDevice'], + }); + + if (!device) { + throw new BadRequestException(`Device ${deviceUuids[i]} not found`); + } + + if (device.productDevice.prodType !== firstProductType) { + throw new BadRequestException(`Devices have different product types`); + } + } + } async getDeviceDetailsByDeviceId(deviceUuid: string, userUuid: string) { try { const userDevicePermission = await this.getUserDevicePermission(