diff --git a/libs/common/src/constants/automation.enum.ts b/libs/common/src/constants/automation.enum.ts index 7e431eb..e9bb6a8 100644 --- a/libs/common/src/constants/automation.enum.ts +++ b/libs/common/src/constants/automation.enum.ts @@ -7,3 +7,13 @@ export enum ActionExecutorEnum { export enum EntityTypeEnum { DEVICE_REPORT = 'device_report', } +export const AUTOMATION_CONFIG = { + DEFAULT_START_TIME: '00:00', + DEFAULT_END_TIME: '23:59', + DEFAULT_LOOPS: '1111111', + DECISION_EXPR: 'and', + CONDITION_TYPE: 'device_report', + ACTION_EXECUTOR: 'rule_trigger', + COMPARATOR: '==', + SCENE_STATUS_VALUE: 'scene', +}; diff --git a/libs/common/src/constants/four-scene.enum.ts b/libs/common/src/constants/four-scene.enum.ts deleted file mode 100644 index 7f43d70..0000000 --- a/libs/common/src/constants/four-scene.enum.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum FourSceneSwitchesEnum { - Scene_1 = 'scene_1', - Scene_2 = 'scene_2', - Scene_3 = 'scene_3', - Scene_4 = 'scene_4', -} diff --git a/libs/common/src/constants/product-type.enum.ts b/libs/common/src/constants/product-type.enum.ts index 2f518fb..d368865 100644 --- a/libs/common/src/constants/product-type.enum.ts +++ b/libs/common/src/constants/product-type.enum.ts @@ -16,4 +16,6 @@ export enum ProductType { GD = 'GD', CUR = 'CUR', PC = 'PC', + FOUR_S = '4S', + SIX_S = '6S', } diff --git a/libs/common/src/constants/scene-switch-type.enum.ts b/libs/common/src/constants/scene-switch-type.enum.ts new file mode 100644 index 0000000..62175d5 --- /dev/null +++ b/libs/common/src/constants/scene-switch-type.enum.ts @@ -0,0 +1,8 @@ +export enum SceneSwitchesTypeEnum { + SCENE_1 = 'scene_1', + SCENE_2 = 'scene_2', + SCENE_3 = 'scene_3', + SCENE_4 = 'scene_4', + SCENE_5 = 'scene_5', + SCENE_6 = 'scene_6', +} diff --git a/libs/common/src/modules/scene-device/entities/scene-device.entity.ts b/libs/common/src/modules/scene-device/entities/scene-device.entity.ts index 27882d6..9814fdf 100644 --- a/libs/common/src/modules/scene-device/entities/scene-device.entity.ts +++ b/libs/common/src/modules/scene-device/entities/scene-device.entity.ts @@ -1,7 +1,7 @@ import { Column, Entity, JoinColumn, ManyToOne, Unique } from 'typeorm'; import { SceneDeviceDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { FourSceneSwitchesEnum } from '@app/common/constants/four-scene.enum'; +import { SceneSwitchesTypeEnum } from '@app/common/constants/scene-switch-type.enum'; import { DeviceEntity } from '../../device/entities'; import { SceneEntity } from '../../scene/entities'; @@ -29,9 +29,9 @@ export class SceneDeviceEntity extends AbstractEntity { @Column({ nullable: false, type: 'enum', - enum: FourSceneSwitchesEnum, + enum: SceneSwitchesTypeEnum, }) - switchName: FourSceneSwitchesEnum; + switchName: SceneSwitchesTypeEnum; @Column({ nullable: false, diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index 564b9fc..28392aa 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -31,6 +31,7 @@ import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { CheckDeviceGuard } from 'src/guards/device.guard'; import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { CheckFourAndSixSceneDeviceTypeGuard } from 'src/guards/scene.device.type.guard'; @ApiTags('Device Module') @Controller({ @@ -211,13 +212,13 @@ export class DeviceController { ); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Post('four-scene/:deviceUuid') - async addSceneToFourSceneDevice( + @UseGuards(JwtAuthGuard, CheckFourAndSixSceneDeviceTypeGuard) + @Post(':deviceUuid/scenes') + async addSceneToSceneDevice( @Param('deviceUuid') deviceUuid: string, @Body() addSceneToFourSceneDeviceDto: AddSceneToFourSceneDeviceDto, ) { - const device = await this.deviceService.addSceneToFourSceneDevice( + const device = await this.deviceService.addSceneToSceneDevice( deviceUuid, addSceneToFourSceneDeviceDto, ); @@ -230,21 +231,15 @@ export class DeviceController { }; } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Get('four-scene/switch/:deviceUuid') - async getSceneFourSceneDevice( + @UseGuards(JwtAuthGuard, CheckFourAndSixSceneDeviceTypeGuard) + @Get(':deviceUuid/scenes') + async getScenesBySceneDevice( @Param('deviceUuid') deviceUuid: string, @Query() getSceneFourSceneDeviceDto: GetSceneFourSceneDeviceDto, ) { - return await this.deviceService.getSceneFourSceneDevice( + return await this.deviceService.getScenesBySceneDevice( deviceUuid, getSceneFourSceneDeviceDto, ); } - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Get('four-scene/:deviceUuid') - async getScenesFourSceneDevice(@Param('deviceUuid') deviceUuid: string) { - return await this.deviceService.getScenesFourSceneDevice(deviceUuid); - } } diff --git a/src/device/dtos/add.device.dto.ts b/src/device/dtos/add.device.dto.ts index bdc76f6..69f42c4 100644 --- a/src/device/dtos/add.device.dto.ts +++ b/src/device/dtos/add.device.dto.ts @@ -1,4 +1,4 @@ -import { FourSceneSwitchesEnum } from '@app/common/constants/four-scene.enum'; +import { SceneSwitchesTypeEnum } from '@app/common/constants/scene-switch-type.enum'; import { ApiProperty } from '@nestjs/swagger'; import { IsEnum, IsNotEmpty, IsString } from 'class-validator'; @@ -41,9 +41,9 @@ export class AddSceneToFourSceneDeviceDto { description: 'switchName', required: true, }) - @IsEnum(FourSceneSwitchesEnum) + @IsEnum(SceneSwitchesTypeEnum) @IsNotEmpty() - switchName: FourSceneSwitchesEnum; + switchName: SceneSwitchesTypeEnum; @ApiProperty({ description: 'sceneUuid', diff --git a/src/device/dtos/control.device.dto.ts b/src/device/dtos/control.device.dto.ts index 7a1e900..1303640 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 { IsArray, IsNotEmpty, IsString } from 'class-validator'; +import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class ControlDeviceDto { @ApiProperty({ @@ -57,9 +57,9 @@ export class BatchFactoryResetDevicesDto { export class GetSceneFourSceneDeviceDto { @ApiProperty({ description: 'switchName', - required: true, + required: false, }) @IsString() - @IsNotEmpty() - public switchName: string; + @IsOptional() + public switchName?: string; } diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 1e1d5f0..eeca08c 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -53,7 +53,8 @@ import { SceneService } from 'src/scene/services'; import { AddAutomationDto } from 'src/automation/dtos'; import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; -import { FourSceneSwitchesEnum } from '@app/common/constants/four-scene.enum'; +import { SceneSwitchesTypeEnum } from '@app/common/constants/scene-switch-type.enum'; +import { AUTOMATION_CONFIG } from '@app/common/constants/automation.enum'; @Injectable() export class DeviceService { @@ -1220,7 +1221,8 @@ export class DeviceService { return descendants; } - async addSceneToFourSceneDevice( + + async addSceneToSceneDevice( deviceUuid: string, addSceneToFourSceneDeviceDto: AddSceneToFourSceneDeviceDto, ) { @@ -1237,7 +1239,6 @@ export class DeviceService { this.getDeviceByDeviceUuid(deviceUuid), ]); - // Generate a shorter automation name (e.g., "Auto_ABC123_169") const shortUuid = deviceUuid.slice(0, 6); // First 6 characters of the UUID const timestamp = Math.floor(Date.now() / 1000); // Current timestamp in seconds const automationName = `Auto_${shortUuid}_${timestamp}`; @@ -1245,33 +1246,32 @@ export class DeviceService { const addAutomationData: AddAutomationDto = { spaceUuid: spaceData.spaceTuyaUuid, automationName, - decisionExpr: 'and', + decisionExpr: AUTOMATION_CONFIG.DECISION_EXPR, effectiveTime: { - start: '00:00', - end: '23:59', - loops: '1111111', + start: AUTOMATION_CONFIG.DEFAULT_START_TIME, + end: AUTOMATION_CONFIG.DEFAULT_END_TIME, + loops: AUTOMATION_CONFIG.DEFAULT_LOOPS, }, conditions: [ { code: 1, entityId: deviceData.deviceTuyaUuid, - entityType: 'device_report', + entityType: AUTOMATION_CONFIG.CONDITION_TYPE, expr: { - comparator: '==', + comparator: AUTOMATION_CONFIG.COMPARATOR, statusCode: switchName, - statusValue: 'scene', + statusValue: AUTOMATION_CONFIG.SCENE_STATUS_VALUE, }, }, ], actions: [ { - actionExecutor: 'rule_trigger', + actionExecutor: AUTOMATION_CONFIG.ACTION_EXECUTOR, entityId: sceneData.sceneTuyaUuid, }, ], }; - // Create automation const automation = await this.tuyaService.createAutomation( addAutomationData.spaceUuid, addAutomationData.automationName, @@ -1318,39 +1318,44 @@ export class DeviceService { throw new HttpException(errorMessage, errorStatus); } } - async getSceneFourSceneDevice( + + async getScenesBySceneDevice( deviceUuid: string, getSceneFourSceneDeviceDto: GetSceneFourSceneDeviceDto, - ) { + ): Promise { try { - const sceneDevice = await this.sceneDeviceRepository.findOne({ - where: { - device: { uuid: deviceUuid }, - switchName: - getSceneFourSceneDeviceDto.switchName as FourSceneSwitchesEnum, // Cast the string to the enum - }, - relations: ['device', 'scene'], - }); - if (sceneDevice.uuid) { - const SceneDetails = await this.sceneService.getSceneByUuid( + if (getSceneFourSceneDeviceDto.switchName) { + // Query for a single record directly when switchName is provided + const sceneDevice = await this.sceneDeviceRepository.findOne({ + where: { + device: { uuid: deviceUuid }, + switchName: + getSceneFourSceneDeviceDto.switchName as SceneSwitchesTypeEnum, + }, + relations: ['device', 'scene'], + }); + + if (!sceneDevice) { + throw new HttpException( + `No scene found for device with UUID ${deviceUuid} and switch name ${getSceneFourSceneDeviceDto.switchName}`, + HttpStatus.NOT_FOUND, + ); + } + + const sceneDetails = await this.sceneService.getSceneByUuid( sceneDevice.scene.uuid, ); + return { switchName: sceneDevice.switchName, createdAt: sceneDevice.createdAt, updatedAt: sceneDevice.updatedAt, deviceUuid: sceneDevice.device.uuid, - scene: { - ...SceneDetails.data, - }, + scene: sceneDetails.data, }; } - } catch (error) { - throw new HttpException('Scene device not found', HttpStatus.NOT_FOUND); - } - } - async getScenesFourSceneDevice(deviceUuid: string): Promise { - try { + + // Query for multiple records if switchName is not provided const sceneDevices = await this.sceneDeviceRepository.find({ where: { device: { uuid: deviceUuid } }, relations: ['device', 'scene'], @@ -1358,37 +1363,28 @@ export class DeviceService { if (!sceneDevices.length) { throw new HttpException( - 'No scenes found for the device', + `No scenes found for device with UUID ${deviceUuid}`, HttpStatus.NOT_FOUND, ); } const results = await Promise.all( sceneDevices.map(async (sceneDevice) => { - if (!sceneDevice.scene?.uuid) return null; + const sceneDetails = await this.sceneService.getSceneByUuid( + sceneDevice.scene.uuid, + ); - try { - const sceneDetails = await this.sceneService.getSceneByUuid( - sceneDevice.scene.uuid, - ); - - return { - switchName: sceneDevice.switchName, - createdAt: sceneDevice.createdAt, - updatedAt: sceneDevice.updatedAt, - deviceUuid: sceneDevice.device.uuid, - scene: sceneDetails.data, - }; - } catch (error) { - throw new HttpException( - 'Failed to fetch scene details', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return { + switchName: sceneDevice.switchName, + createdAt: sceneDevice.createdAt, + updatedAt: sceneDevice.updatedAt, + deviceUuid: sceneDevice.device.uuid, + scene: sceneDetails.data, + }; }), ); - return results.filter(Boolean); + return results; } catch (error) { throw new HttpException( error.message || 'Failed to fetch scenes for device', diff --git a/src/guards/scene.device.type.guard.ts b/src/guards/scene.device.type.guard.ts new file mode 100644 index 0000000..777e60e --- /dev/null +++ b/src/guards/scene.device.type.guard.ts @@ -0,0 +1,42 @@ +import { ProductType } from '@app/common/constants/product-type.enum'; +import { + Injectable, + CanActivate, + ExecutionContext, + BadRequestException, + HttpException, +} from '@nestjs/common'; +import { DeviceService } from 'src/device/services'; + +@Injectable() +export class CheckFourAndSixSceneDeviceTypeGuard implements CanActivate { + constructor(private readonly deviceService: DeviceService) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const deviceUuid = request.params.deviceUuid; + + if (!deviceUuid) { + throw new BadRequestException('Device UUID is required'); + } + + try { + const deviceDetails = + await this.deviceService.getDeviceByDeviceUuid(deviceUuid); + + if ( + deviceDetails.productDevice.prodType !== ProductType.FOUR_S && + deviceDetails.productDevice.prodType !== ProductType.SIX_S + ) { + throw new BadRequestException('The device type is not supported'); + } + + return true; + } catch (error) { + throw new HttpException( + error.message || 'An error occurred', + error.status || 500, + ); + } + } +}