From 7e9894b1d3bb8aab73e884355a12e7c6e5d51306 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sat, 2 Nov 2024 19:36:56 +0400 Subject: [PATCH] Fixed scene --- libs/common/src/common.module.ts | 2 +- libs/common/src/constants/controller-route.ts | 41 + .../src/integrations/tuya/interfaces/index.ts | 2 + .../interfaces/tap-to-run-action.interface.ts | 11 + .../interfaces/tuya.response.interface.ts | 5 + .../tuya/{ => services}/tuya.service.ts | 47 ++ .../src/util/buildTypeORMIncludeQuery.ts | 2 - src/community/community.module.ts | 2 +- src/community/services/community.service.ts | 2 +- src/scene/controllers/scene.controller.ts | 101 ++- src/scene/dtos/scene.dto.ts | 2 +- src/scene/interface/scene.interface.ts | 17 + src/scene/scene.module.ts | 2 +- src/scene/services/scene.service.ts | 704 +++++++++--------- src/space/controllers/index.ts | 1 + .../controllers/space-scene.controller.ts | 34 + src/space/services/index.ts | 1 + src/space/services/space-device.service.ts | 7 +- src/space/services/space-scene.service.ts | 55 ++ src/space/services/space.service.ts | 25 + .../subspace/subspace-device.service.ts | 2 +- src/space/space.module.ts | 20 +- 22 files changed, 672 insertions(+), 413 deletions(-) create mode 100644 libs/common/src/integrations/tuya/interfaces/index.ts create mode 100644 libs/common/src/integrations/tuya/interfaces/tap-to-run-action.interface.ts create mode 100644 libs/common/src/integrations/tuya/interfaces/tuya.response.interface.ts rename libs/common/src/integrations/tuya/{ => services}/tuya.service.ts (66%) create mode 100644 src/space/controllers/space-scene.controller.ts create mode 100644 src/space/services/space-scene.service.ts diff --git a/libs/common/src/common.module.ts b/libs/common/src/common.module.ts index 08a8fe2..cbe733d 100644 --- a/libs/common/src/common.module.ts +++ b/libs/common/src/common.module.ts @@ -7,7 +7,7 @@ import { ConfigModule } from '@nestjs/config'; import config from './config'; import { EmailService } from './util/email.service'; import { ErrorMessageService } from 'src/error-message/error-message.service'; -import { TuyaService } from './integrations/tuya/tuya.service'; +import { TuyaService } from './integrations/tuya/services/tuya.service'; @Module({ providers: [CommonService, EmailService, ErrorMessageService, TuyaService], exports: [ diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 2bca1a1..7f84bcc 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -80,6 +80,36 @@ export class ControllerRoute { }; }; + static SCENE = class { + public static readonly ROUTE = 'scene'; + static ACTIONS = class { + public static readonly CREATE_TAP_TO_RUN_SCENE_SUMMARY = + 'Create a Tap-to-Run Scene'; + public static readonly CREATE_TAP_TO_RUN_SCENE_DESCRIPTION = + 'Creates a new Tap-to-Run scene in Tuya and stores the scene in the local database.'; + + public static readonly DELETE_TAP_TO_RUN_SCENE_SUMMARY = + 'Delete a Tap-to-Run Scene'; + public static readonly DELETE_TAP_TO_RUN_SCENE_DESCRIPTION = + 'Deletes a Tap-to-Run scene from Tuya and removes it from the local database.'; + + public static readonly TRIGGER_TAP_TO_RUN_SCENE_SUMMARY = + 'Trigger a Tap-to-Run Scene'; + public static readonly TRIGGER_TAP_TO_RUN_SCENE_DESCRIPTION = + 'Triggers an existing Tap-to-Run scene in Tuya by scene UUID, executing its actions immediately.'; + + public static readonly GET_TAP_TO_RUN_SCENE_SUMMARY = + 'Get Tap-to-Run Scene Details'; + public static readonly GET_TAP_TO_RUN_SCENE_DESCRIPTION = + 'Retrieves detailed information of a specific Tap-to-Run scene identified by the scene UUID.'; + + public static readonly UPDATE_TAP_TO_RUN_SCENE_SUMMARY = + 'Update a Tap-to-Run Scene'; + public static readonly UPDATE_TAP_TO_RUN_SCENE_DESCRIPTION = + 'Updates an existing Tap-to-Run scene in Tuya and updates the scene in the local database, reflecting any new configurations or actions.'; + }; + }; + static SPACE = class { public static readonly ROUTE = '/communities/:communityUuid/spaces'; static ACTIONS = class { @@ -120,6 +150,17 @@ export class ControllerRoute { }; }; + static SPACE_SCENE = class { + public static readonly ROUTE = + '/communities/:communityUuid/spaces/:spaceUuid/scenes'; + static ACTIONS = class { + public static readonly GET_TAP_TO_RUN_SCENE_BY_SPACE_SUMMARY = + 'Retrieve Tap-to-Run Scenes by Space'; + public static readonly GET_TAP_TO_RUN_SCENE_BY_SPACE_DESCRIPTION = + 'Fetches all Tap-to-Run scenes associated with a specified space UUID. An optional query parameter can filter the results to show only scenes marked for the homepage display.'; + }; + }; + static SPACE_USER = class { public static readonly ROUTE = '/communities/:communityUuid/spaces/:spaceUuid/user'; diff --git a/libs/common/src/integrations/tuya/interfaces/index.ts b/libs/common/src/integrations/tuya/interfaces/index.ts new file mode 100644 index 0000000..506fc06 --- /dev/null +++ b/libs/common/src/integrations/tuya/interfaces/index.ts @@ -0,0 +1,2 @@ +export * from './tuya.response.interface'; +export * from './tap-to-run-action.interface'; diff --git a/libs/common/src/integrations/tuya/interfaces/tap-to-run-action.interface.ts b/libs/common/src/integrations/tuya/interfaces/tap-to-run-action.interface.ts new file mode 100644 index 0000000..13b8031 --- /dev/null +++ b/libs/common/src/integrations/tuya/interfaces/tap-to-run-action.interface.ts @@ -0,0 +1,11 @@ +export interface ConvertedExecutorProperty { + function_code?: string; + function_value?: any; + delay_seconds?: number; +} + +export interface ConvertedAction { + entity_id: string; + action_executor: string; + executor_property?: ConvertedExecutorProperty; +} diff --git a/libs/common/src/integrations/tuya/interfaces/tuya.response.interface.ts b/libs/common/src/integrations/tuya/interfaces/tuya.response.interface.ts new file mode 100644 index 0000000..3e3a2a9 --- /dev/null +++ b/libs/common/src/integrations/tuya/interfaces/tuya.response.interface.ts @@ -0,0 +1,5 @@ +export interface TuyaResponseInterface { + success: boolean; + msg?: string; + result: boolean; +} diff --git a/libs/common/src/integrations/tuya/tuya.service.ts b/libs/common/src/integrations/tuya/services/tuya.service.ts similarity index 66% rename from libs/common/src/integrations/tuya/tuya.service.ts rename to libs/common/src/integrations/tuya/services/tuya.service.ts index e37ab4d..80205af 100644 --- a/libs/common/src/integrations/tuya/tuya.service.ts +++ b/libs/common/src/integrations/tuya/services/tuya.service.ts @@ -1,6 +1,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { TuyaContext } from '@tuya/tuya-connector-nodejs'; +import { ConvertedAction, TuyaResponseInterface } from '../interfaces'; @Injectable() export class TuyaService { @@ -86,4 +87,50 @@ export class TuyaService { ); } } + + async addTapToRunScene( + spaceId: string, + sceneName: string, + actions: ConvertedAction[], + decisionExpr: string, + ) { + const path = `/v2.0/cloud/scene/rule`; + const response = await this.tuya.request({ + method: 'POST', + path, + body: { + space_id: spaceId, + name: sceneName, + type: 'scene', + decision_expr: decisionExpr, + actions: actions, + }, + }); + + if (response.success) { + return response; + } else { + throw new HttpException( + `Error fetching scene rule: ${response.msg}`, + HttpStatus.BAD_REQUEST, + ); + } + } + + async triggerScene(sceneId: string): Promise { + const path = `/v2.0/cloud/scene/rule/${sceneId}/actions/trigger`; + const response: TuyaResponseInterface = await this.tuya.request({ + method: 'POST', + path, + }); + + if (!response.success) { + throw new HttpException( + response.msg || 'Error triggering scene', + HttpStatus.BAD_REQUEST, + ); + } + + return response; + } } diff --git a/libs/common/src/util/buildTypeORMIncludeQuery.ts b/libs/common/src/util/buildTypeORMIncludeQuery.ts index dda60df..c2d401e 100644 --- a/libs/common/src/util/buildTypeORMIncludeQuery.ts +++ b/libs/common/src/util/buildTypeORMIncludeQuery.ts @@ -36,8 +36,6 @@ export function buildTypeORMIncludeQuery( } }); - console.log(`Including relations for ${modelName}:`, relations); - return relations; } diff --git a/src/community/community.module.ts b/src/community/community.module.ts index 81c36cf..106d9d1 100644 --- a/src/community/community.module.ts +++ b/src/community/community.module.ts @@ -8,7 +8,7 @@ import { UserSpaceRepository } from '@app/common/modules/user/repositories'; import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; import { SpacePermissionService } from '@app/common/helper/services'; import { CommunityRepository } from '@app/common/modules/community/repositories'; -import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule], diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index 7080c3e..e833416 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -10,7 +10,7 @@ import { PageResponse } from '@app/common/dto/pagination.response.dto'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { CommunityDto } from '@app/common/modules/community/dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; -import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; @Injectable() export class CommunityService { diff --git a/src/scene/controllers/scene.controller.ts b/src/scene/controllers/scene.controller.ts index bb729f8..eaf67ec 100644 --- a/src/scene/controllers/scene.controller.ts +++ b/src/scene/controllers/scene.controller.ts @@ -8,24 +8,24 @@ import { Param, Post, Put, - Query, UseGuards, } from '@nestjs/common'; -import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; import { AddSceneIconDto, AddSceneTapToRunDto, - GetSceneDto, UpdateSceneTapToRunDto, } from '../dtos/scene.dto'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; -import { DeleteSceneParamDto, SceneParamDto, SpaceParamDto } from '../dtos'; +import { SceneParamDto } from '../dtos'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { ControllerRoute } from '@app/common/constants/controller-route'; @ApiTags('Scene Module') @Controller({ version: EnableDisableStatusEnum.ENABLED, - path: 'scene', + path: ControllerRoute.SCENE.ROUTE, }) export class SceneController { constructor(private readonly sceneService: SceneService) {} @@ -33,81 +33,72 @@ export class SceneController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post('tap-to-run') - async addTapToRunScene(@Body() addSceneTapToRunDto: AddSceneTapToRunDto) { - const tapToRunScene = - await this.sceneService.addTapToRunScene(addSceneTapToRunDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'Scene added successfully', - data: tapToRunScene, - }; + @ApiOperation({ + summary: ControllerRoute.SCENE.ACTIONS.CREATE_TAP_TO_RUN_SCENE_SUMMARY, + description: + ControllerRoute.SCENE.ACTIONS.CREATE_TAP_TO_RUN_SCENE_DESCRIPTION, + }) + async addTapToRunScene( + @Body() addSceneTapToRunDto: AddSceneTapToRunDto, + ): Promise { + return await this.sceneService.createScene(addSceneTapToRunDto); } @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Get('tap-to-run/:spaceUuid') - async getTapToRunSceneByUnit( - @Param() param: SpaceParamDto, - @Query() inHomePage: GetSceneDto, - ) { - const tapToRunScenes = await this.sceneService.getTapToRunSceneBySpace( - param.spaceUuid, - inHomePage, - ); - return tapToRunScenes; + @Delete('tap-to-run/:sceneUuid') + @ApiOperation({ + summary: ControllerRoute.SCENE.ACTIONS.DELETE_TAP_TO_RUN_SCENE_SUMMARY, + description: + ControllerRoute.SCENE.ACTIONS.DELETE_TAP_TO_RUN_SCENE_DESCRIPTION, + }) + async deleteTapToRunScene( + @Param() param: SceneParamDto, + ): Promise { + return await this.sceneService.deleteScene(param); } @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Delete('tap-to-run/:spaceUuid/:sceneUuid') - async deleteTapToRunScene(@Param() param: DeleteSceneParamDto) { - await this.sceneService.deleteScene(param); - return { - statusCode: HttpStatus.OK, - message: 'Scene Deleted Successfully', - }; - } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Post('tap-to-run/trigger/:sceneUuid') + @Post('tap-to-run/:sceneUuid/trigger') + @ApiOperation({ + summary: ControllerRoute.SCENE.ACTIONS.TRIGGER_TAP_TO_RUN_SCENE_SUMMARY, + description: + ControllerRoute.SCENE.ACTIONS.TRIGGER_TAP_TO_RUN_SCENE_DESCRIPTION, + }) async triggerTapToRunScene(@Param() param: SceneParamDto) { - await this.sceneService.triggerTapToRunScene(param.sceneUuid); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'Scene trigger successfully', - }; + return await this.sceneService.triggerTapToRunScene(param.sceneUuid); } @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Get('tap-to-run/details/:sceneUuid') - async getTapToRunSceneDetails(@Param() param: SceneParamDto) { - const tapToRunScenes = await this.sceneService.getTapToRunSceneDetails( - param.sceneUuid, - ); - return tapToRunScenes; + @Get('tap-to-run/:sceneUuid') + @ApiOperation({ + summary: ControllerRoute.SCENE.ACTIONS.GET_TAP_TO_RUN_SCENE_SUMMARY, + description: ControllerRoute.SCENE.ACTIONS.GET_TAP_TO_RUN_SCENE_DESCRIPTION, + }) + async getTapToRunSceneDetails( + @Param() param: SceneParamDto, + ): Promise { + return await this.sceneService.getSceneByUuid(param.sceneUuid); } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Put('tap-to-run/:sceneUuid') + @ApiOperation({ + summary: ControllerRoute.SCENE.ACTIONS.UPDATE_TAP_TO_RUN_SCENE_SUMMARY, + description: + ControllerRoute.SCENE.ACTIONS.UPDATE_TAP_TO_RUN_SCENE_DESCRIPTION, + }) async updateTapToRunScene( @Body() updateSceneTapToRunDto: UpdateSceneTapToRunDto, @Param() param: SceneParamDto, ) { - const tapToRunScene = await this.sceneService.updateTapToRunScene( + return await this.sceneService.updateTapToRunScene( updateSceneTapToRunDto, param.sceneUuid, ); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'Scene updated successfully', - data: tapToRunScene, - }; } @ApiBearerAuth() diff --git a/src/scene/dtos/scene.dto.ts b/src/scene/dtos/scene.dto.ts index 03d2517..1062d90 100644 --- a/src/scene/dtos/scene.dto.ts +++ b/src/scene/dtos/scene.dto.ts @@ -36,7 +36,7 @@ class ExecutorProperty { public delaySeconds?: number; } -class Action { +export class Action { @ApiProperty({ description: 'Entity ID', required: true, diff --git a/src/scene/interface/scene.interface.ts b/src/scene/interface/scene.interface.ts index e6508d9..c08c204 100644 --- a/src/scene/interface/scene.interface.ts +++ b/src/scene/interface/scene.interface.ts @@ -1,3 +1,5 @@ +import { Action } from '../dtos'; + export interface AddTapToRunSceneInterface { success: boolean; msg?: string; @@ -25,4 +27,19 @@ export interface SceneDetailsResult { id: string; name: string; type: string; + actions?: any; + status?: string; +} + +export interface SceneDetails { + uuid: string; + sceneTuyaId: string; + name: string; + status: string; + icon?: string; + iconUuid?: string; + showInHome: boolean; + type: string; + actions: Action[]; + spaceId: string; } diff --git a/src/scene/scene.module.ts b/src/scene/scene.module.ts index 5649f2a..680de5f 100644 --- a/src/scene/scene.module.ts +++ b/src/scene/scene.module.ts @@ -12,7 +12,7 @@ import { SceneIconRepository, SceneRepository, } from '@app/common/modules/scene/repositories'; -import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusFirebaseModule], diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index f279fd7..930ee61 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -6,19 +6,19 @@ import { } from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { + Action, AddSceneIconDto, AddSceneTapToRunDto, - DeleteSceneParamDto, GetSceneDto, + SceneParamDto, UpdateSceneTapToRunDto, } from '../dtos'; -import { ConfigService } from '@nestjs/config'; -import { TuyaContext } from '@tuya/tuya-connector-nodejs'; import { convertKeysToSnakeCase } from '@app/common/helper/snakeCaseConverter'; import { DeviceService } from 'src/device/services'; import { AddTapToRunSceneInterface, DeleteTapToRunSceneInterface, + SceneDetails, SceneDetailsResult, } from '../interface/scene.interface'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; @@ -28,354 +28,241 @@ import { SceneRepository, } from '@app/common/modules/scene/repositories'; import { SceneIconType } from '@app/common/constants/secne-icon-type.enum'; -import { SceneEntity } from '@app/common/modules/scene/entities'; -import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; +import { + SceneEntity, + SceneIconEntity, +} from '@app/common/modules/scene/entities'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { HttpStatusCode } from 'axios'; +import { ConvertedAction } from '@app/common/integrations/tuya/interfaces'; @Injectable() export class SceneService { - private tuya: TuyaContext; constructor( - private readonly configService: ConfigService, private readonly spaceRepository: SpaceRepository, private readonly sceneIconRepository: SceneIconRepository, private readonly sceneRepository: SceneRepository, private readonly deviceService: DeviceService, private readonly tuyaService: TuyaService, - ) { - const accessKey = this.configService.get('auth-config.ACCESS_KEY'); - const secretKey = this.configService.get('auth-config.SECRET_KEY'); - const tuyaEuUrl = this.configService.get('tuya-config.TUYA_EU_URL'); - this.tuya = new TuyaContext({ - baseUrl: tuyaEuUrl, - accessKey, - secretKey, - }); - } + ) {} - async addTapToRunScene( + async createScene( + addSceneTapToRunDto: AddSceneTapToRunDto, + ): Promise { + try { + const { spaceUuid } = addSceneTapToRunDto; + + const space = await this.getSpaceByUuid(spaceUuid); + + const scene = await this.create(space.spaceTuyaUuid, addSceneTapToRunDto); + + return new SuccessResponseDto({ + message: `Successfully created new scene with uuid ${scene.uuid}`, + data: scene, + statusCode: HttpStatus.CREATED, + }); + } catch (err) { + console.error( + `Error in createScene for space UUID ${addSceneTapToRunDto.spaceUuid}:`, + err.message, + ); + throw err instanceof HttpException + ? err + : new HttpException( + 'Failed to create scene', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async create( + spaceTuyaUuid: string, + addSceneTapToRunDto: AddSceneTapToRunDto, + ): Promise { + const { iconUuid, showInHomePage, spaceUuid } = addSceneTapToRunDto; + + try { + const [defaultSceneIcon] = await Promise.all([ + this.getDefaultSceneIcon(), + ]); + if (!defaultSceneIcon) { + throw new HttpException( + 'Default scene icon not found', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + + const response = await this.createSceneExternalService( + spaceTuyaUuid, + addSceneTapToRunDto, + ); + + const scene = await this.sceneRepository.save({ + sceneTuyaUuid: response.result.id, + sceneIcon: { uuid: iconUuid || defaultSceneIcon.uuid }, + showInHomePage, + spaceUuid, + }); + + return scene; + } catch (err) { + if (err instanceof HttpException) { + throw err; + } else if (err.message?.includes('tuya')) { + throw new HttpException( + 'API error: Failed to create scene', + HttpStatus.BAD_GATEWAY, + ); + } else { + throw new HttpException( + 'Database error: Failed to save scene', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } + + async createSceneExternalService( + spaceTuyaUuid: string, addSceneTapToRunDto: AddSceneTapToRunDto, - spaceTuyaId = null, - spaceUuid?: string, ) { + const { sceneName, decisionExpr, actions } = addSceneTapToRunDto; try { - let tuyaSpaceId; - if (!spaceTuyaId) { - const space = await this.getSpaceByUuid(addSceneTapToRunDto.spaceUuid); - tuyaSpaceId = space.spaceTuyaUuid; - if (!space) { - throw new BadRequestException( - `Invalid space UUID ${addSceneTapToRunDto.spaceUuid}`, - ); - } - } else { - tuyaSpaceId = spaceTuyaId; + const formattedActions = await this.prepareActions(actions); + + const response = (await this.tuyaService.addTapToRunScene( + spaceTuyaUuid, + sceneName, + formattedActions, + decisionExpr, + )) as AddTapToRunSceneInterface; + + if (!response.result?.id) { + throw new HttpException( + 'Failed to create scene in Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); } - const actions = addSceneTapToRunDto.actions.map((action) => { - return { - ...action, - }; - }); - - const convertedData = convertKeysToSnakeCase(actions); - for (const action of convertedData) { - if (action.action_executor === ActionExecutorEnum.DEVICE_ISSUE) { - const device = await this.deviceService.getDeviceByDeviceUuid( - action.entity_id, - false, - ); - if (device) { - action.entity_id = device.deviceTuyaUuid; - } - } - } - const path = `/v2.0/cloud/scene/rule`; - const response: AddTapToRunSceneInterface = await this.tuya.request({ - method: 'POST', - path, - body: { - space_id: tuyaSpaceId, - name: addSceneTapToRunDto.sceneName, - type: 'scene', - decision_expr: addSceneTapToRunDto.decisionExpr, - actions: convertedData, - }, - }); - if (!response.success) { - throw new HttpException(response.msg, HttpStatus.BAD_REQUEST); - } else { - const defaultSceneIcon = await this.sceneIconRepository.findOne({ - where: { iconType: SceneIconType.Default }, - }); - - await this.sceneRepository.save({ - sceneTuyaUuid: response.result.id, - sceneIcon: { - uuid: addSceneTapToRunDto.iconUuid - ? addSceneTapToRunDto.iconUuid - : defaultSceneIcon.uuid, - }, - showInHomePage: addSceneTapToRunDto.showInHomePage, - spaceUuid: spaceUuid ? spaceUuid : addSceneTapToRunDto.spaceUuid, - }); - } - return { - id: response.result.id, - }; + return response; } catch (err) { - console.log(err); - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException + if (err instanceof HttpException) { + throw err; + } else if (err.message?.includes('tuya')) { + throw new HttpException( + 'API error: Failed to create scene', + HttpStatus.BAD_GATEWAY, + ); } else { throw new HttpException( - err.message || `Scene not found`, - err.status || HttpStatus.NOT_FOUND, + 'An Internal error has been occured', + HttpStatus.INTERNAL_SERVER_ERROR, ); } } } - async getSpaceByUuid(spaceUuid: string) { + async findScenesBySpace(spaceUuid: string, filter: GetSceneDto) { try { - const space = await this.spaceRepository.findOne({ - where: { - uuid: spaceUuid, - }, - relations: ['community'], - }); - if (!space) { - throw new BadRequestException(`Invalid space UUID ${spaceUuid}`); - } - return { - uuid: space.uuid, - createdAt: space.createdAt, - updatedAt: space.updatedAt, - name: space.spaceName, - spaceTuyaUuid: space.community.externalId, - }; - } catch (err) { - if (err instanceof BadRequestException) { - console.log(err); - throw err; // Re-throw BadRequestException - } else { - throw new HttpException( - `Space with id ${spaceUuid} not found`, - HttpStatus.NOT_FOUND, - ); - } - } - } - - async getTapToRunSceneBySpace(spaceUuid: string, inHomePage: GetSceneDto) { - try { - const showInHomePage = inHomePage?.showInHomePage; + await this.getSpaceByUuid(spaceUuid); + const showInHomePage = filter?.showInHomePage; const scenesData = await this.sceneRepository.find({ where: { spaceUuid, - ...(showInHomePage ? { showInHomePage } : {}), + ...(showInHomePage !== undefined ? { showInHomePage } : {}), }, }); + if (!scenesData.length) { + throw new HttpException( + `No scenes found for space UUID ${spaceUuid}`, + HttpStatus.NOT_FOUND, + ); + } + const scenes = await Promise.all( scenesData.map(async (scene) => { - const sceneData = await this.getTapToRunSceneDetails( - scene.uuid, - false, - ); // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { actions, ...rest } = sceneData; - return { - uuid: scene.uuid, - ...rest, - }; + const { actions, ...sceneDetails } = await this.getScene(scene); + + return sceneDetails; }), ); return scenes; } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException( - err.message || 'Scene not found', - err.status || HttpStatus.NOT_FOUND, - ); - } - } - } - - async deleteTapToRunScene(param: DeleteSceneParamDto, spaceTuyaId = null) { - const { spaceUuid, sceneUuid } = param; - try { - let tuyaSpaceId; - if (!spaceTuyaId) { - const space = await this.getSpaceByUuid(spaceUuid); - tuyaSpaceId = space.spaceTuyaUuid; - if (!tuyaSpaceId) { - throw new BadRequestException(`Invalid space UUID ${spaceUuid}`); - } - } else { - tuyaSpaceId = spaceTuyaId; - } - - const path = `/v2.0/cloud/scene/rule?ids=${sceneUuid}&space_id=${tuyaSpaceId}`; - const response: DeleteTapToRunSceneInterface = await this.tuya.request({ - method: 'DELETE', - path, - }); - - console.log(path); - - if (!response.success) { - throw new HttpException('Scene not found', HttpStatus.NOT_FOUND); - } else { - await this.sceneRepository.delete({ sceneTuyaUuid: sceneUuid }); - } - - return response; - } catch (err) { - console.log(err); - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException( - err.message || 'Scene not found', - err.status || HttpStatus.NOT_FOUND, - ); - } - } - } - - async triggerTapToRunScene(sceneId: string) { - try { - const path = `/v2.0/cloud/scene/rule/${sceneId}/actions/trigger`; - const response: DeleteTapToRunSceneInterface = await this.tuya.request({ - method: 'POST', - path, - }); - if (!response.success) { - throw new HttpException(response.msg, HttpStatus.BAD_REQUEST); - } - return response; - } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException( - err.message || 'Scene not found', - err.status || HttpStatus.NOT_FOUND, - ); - } - } - } - - async getTapToRunSceneDetails(sceneId: string, withSpaceId = false) { - const scene = await this.sceneRepository.findOne({ - where: { uuid: sceneId }, - relations: ['sceneIcon'], - }); - - if (!scene) { - throw new HttpException( - `Scene with ${sceneId} is not found`, - HttpStatus.NOT_FOUND, + console.error( + `Error fetching Tap-to-Run scenes for space UUID ${spaceUuid}:`, + err.message, ); - } - try { - const sceneTuyaUuid = scene.sceneTuyaUuid; - const path = `/v2.0/cloud/scene/rule/${sceneTuyaUuid}`; - const response = await this.tuya.request({ - method: 'GET', - path, - }); - if (!response.success) { - throw new HttpException(response.msg, HttpStatus.BAD_REQUEST); - } - const responseData = convertKeysToCamelCase(response.result); - const actions = responseData.actions.map((action) => { - return { - ...action, - }; - }); - - for (const action of actions) { - if (action.actionExecutor === ActionExecutorEnum.DEVICE_ISSUE) { - const device = await this.deviceService.getDeviceByDeviceTuyaUuid( - action.entityId, - ); - - if (device) { - action.entityId = device.uuid; - } - } else if ( - action.actionExecutor !== ActionExecutorEnum.DEVICE_ISSUE && - action.actionExecutor !== ActionExecutorEnum.DELAY - ) { - const sceneDetails = await this.getTapToRunSceneDetailsTuya( - action.entityId, - ); - - if (sceneDetails.id) { - action.name = sceneDetails.name; - action.type = sceneDetails.type; - } - } - } - - return { - id: responseData.id, - name: responseData.name, - status: responseData.status, - icon: scene.sceneIcon?.icon, - iconUuid: scene.sceneIcon?.uuid, - showInHome: scene.showInHomePage, - type: 'tap_to_run', - actions: actions, - ...(withSpaceId && { spaceId: responseData.spaceId }), - }; - } catch (err) { if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException + throw err; } else { - console.log(err); throw new HttpException( - `Scene not found for ${sceneId}`, - HttpStatus.NOT_FOUND, + 'An error occurred while retrieving scenes', + HttpStatus.INTERNAL_SERVER_ERROR, ); } } } - async getTapToRunSceneDetailsTuya( + async triggerTapToRunScene(sceneUuid: string) { + try { + const scene = await this.findScene(sceneUuid); + await this.tuyaService.triggerScene(scene.sceneTuyaUuid); + return new SuccessResponseDto({ + message: `Scene with ID ${sceneUuid} triggered successfully`, + }); + } catch (err) { + if (err instanceof HttpException) { + throw err; + } else { + throw new HttpException( + err.message || 'Scene not found', + err.status || HttpStatus.NOT_FOUND, + ); + } + } + } + + async fetchSceneDetailsFromTuya( sceneId: string, ): Promise { try { - const path = `/v2.0/cloud/scene/rule/${sceneId}`; - const response = await this.tuya.request({ - method: 'GET', - path, - }); - - if (!response.success) { - throw new HttpException(response.msg, HttpStatus.BAD_REQUEST); - } + const response = await this.tuyaService.getSceneRule(sceneId); const camelCaseResponse = convertKeysToCamelCase(response); - const { id, name, type } = camelCaseResponse.result; + const { + id, + name, + type, + status, + actions: tuyaActions = [], + } = camelCaseResponse.result; + + const actions = tuyaActions.map((action) => ({ ...action })); return { id, name, type, + status, + actions, } as SceneDetailsResult; } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException + console.error( + `Error fetching scene details for scene ID ${sceneId}:`, + err, + ); + if (err instanceof HttpException) { + throw err; } else { throw new HttpException( - 'Scene not found for Tuya', - HttpStatus.NOT_FOUND, + 'An error occurred while fetching scene details from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, ); } } @@ -385,23 +272,9 @@ export class SceneService { updateSceneTapToRunDto: UpdateSceneTapToRunDto, sceneUuid: string, ) { - const scene = await this.sceneRepository.findOne({ - where: { uuid: sceneUuid }, - }); - - if (!scene) { - throw new HttpException( - `Scene with ${sceneUuid} is not found`, - HttpStatus.NOT_FOUND, - ); - } - try { - const spaceTuyaId = await this.getTapToRunSceneDetails(sceneUuid, true); - - if (!spaceTuyaId.spaceId) { - throw new HttpException("Scene doesn't exist", HttpStatus.NOT_FOUND); - } + const scene = await this.findScene(sceneUuid); + const space = await this.getSpaceByUuid(scene.spaceUuid); const addSceneTapToRunDto: AddSceneTapToRunDto = { ...updateSceneTapToRunDto, @@ -410,38 +283,38 @@ export class SceneService { showInHomePage: updateSceneTapToRunDto.showInHomePage, }; - const newTapToRunScene = await this.addTapToRunScene( + const createdTuyaSceneResponse = await this.createSceneExternalService( + space.spaceTuyaUuid, addSceneTapToRunDto, - spaceTuyaId.spaceId, - scene.spaceUuid, ); + const newSceneTuyaUuid = createdTuyaSceneResponse.result?.id; - const param: DeleteSceneParamDto = { - spaceUuid: scene.spaceUuid, - sceneUuid: scene.sceneTuyaUuid, - }; - - console.log(param); - - if (newTapToRunScene.id) { - await this.deleteTapToRunScene(param, spaceTuyaId.spaceId); - - await this.sceneRepository.update( - { sceneTuyaUuid: sceneUuid }, - { - sceneTuyaUuid: newTapToRunScene.id, - showInHomePage: addSceneTapToRunDto.showInHomePage, - sceneIcon: { - uuid: addSceneTapToRunDto.iconUuid, - }, - spaceUuid: scene.spaceUuid, - }, + if (!newSceneTuyaUuid) { + throw new HttpException( + `Failed to create a external new scene`, + HttpStatus.BAD_GATEWAY, ); - return newTapToRunScene; } + + await this.delete(scene.sceneTuyaUuid, space.spaceTuyaUuid); + + const updatedScene = await this.sceneRepository.update( + { uuid: sceneUuid }, + { + sceneTuyaUuid: newSceneTuyaUuid, + showInHomePage: addSceneTapToRunDto.showInHomePage, + sceneIcon: { + uuid: addSceneTapToRunDto.iconUuid, + }, + spaceUuid: scene.spaceUuid, + }, + ); + return new SuccessResponseDto({ + data: updatedScene, + message: `Scene with ID ${sceneUuid} updated successfully`, + }); } catch (err) { - console.log(err); - if (err instanceof BadRequestException) { + if (err instanceof HttpException) { throw err; // Re-throw BadRequestException } else { throw new HttpException( @@ -469,6 +342,7 @@ export class SceneService { ); } } + async getAllIcons() { try { const icons = await this.sceneIconRepository.find(); @@ -487,25 +361,99 @@ export class SceneService { } } - async deleteScene(params: DeleteSceneParamDto) { + async getSceneByUuid(sceneUuid: string): Promise { try { - const { sceneUuid, spaceUuid } = params; const scene = await this.findScene(sceneUuid); - const space = await this.getSpaceByUuid(spaceUuid); - if (!space.spaceTuyaUuid) { + const sceneDetails = await this.getScene(scene); + + return new SuccessResponseDto({ + data: sceneDetails, + message: `Scene details for ${sceneUuid} retrieved successfully`, + }); + } catch (error) { + console.error( + `Error fetching scene details for sceneUuid ${sceneUuid}:`, + error, + ); + + if (error instanceof HttpException) { + throw error; + } else { throw new HttpException( - `Invalid space UUID: ${spaceUuid}`, - HttpStatus.BAD_REQUEST, + 'An error occurred while retrieving scene details', + HttpStatus.INTERNAL_SERVER_ERROR, ); } - const response = await this.delete( + } + } + + async getScene(scene: SceneEntity): Promise { + try { + const { actions, name, status } = await this.fetchSceneDetailsFromTuya( scene.sceneTuyaUuid, - space.spaceTuyaUuid, ); - return response; + + for (const action of actions) { + if (action.actionExecutor === ActionExecutorEnum.DEVICE_ISSUE) { + const device = await this.deviceService.getDeviceByDeviceTuyaUuid( + action.entityId, + ); + + if (device) { + action.entityId = device.uuid; + } + } else if ( + action.actionExecutor !== ActionExecutorEnum.DEVICE_ISSUE && + action.actionExecutor !== ActionExecutorEnum.DELAY + ) { + const sceneDetails = await this.fetchSceneDetailsFromTuya( + action.entityId, + ); + + if (sceneDetails.id) { + action.name = sceneDetails.name; + action.type = sceneDetails.type; + } + } + } + return { + uuid: scene.uuid, + sceneTuyaId: scene.sceneTuyaUuid, + name, + status, + icon: scene.sceneIcon?.icon, + iconUuid: scene.sceneIcon?.uuid, + showInHome: scene.showInHomePage, + type: 'tap_to_run', + actions, + spaceId: scene.spaceUuid, + }; + } catch (err) { + if (err instanceof BadRequestException) { + throw err; + } else { + console.log(err); + throw new HttpException( + `An error occurred while retrieving scene details for ${scene.uuid}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } + + async deleteScene(params: SceneParamDto): Promise { + try { + const { sceneUuid } = params; + const scene = await this.findScene(sceneUuid); + const space = await this.getSpaceByUuid(scene.spaceUuid); + + await this.delete(scene.sceneTuyaUuid, space.spaceTuyaUuid); + return new SuccessResponseDto({ + message: `Scene with ID ${sceneUuid} deleted successfully`, + }); } catch (err) { if (err instanceof HttpException) { - throw err; // Re-throw existing HttpException + throw err; } else { throw new HttpException( err.message || `Scene not found for id ${params.sceneUuid}`, @@ -517,9 +465,8 @@ export class SceneService { async findScene(sceneUuid: string): Promise { const scene = await this.sceneRepository.findOne({ - where: { - uuid: sceneUuid, - }, + where: { uuid: sceneUuid }, + relations: ['sceneIcon'], }); if (!scene) { @@ -549,4 +496,73 @@ export class SceneService { } } } + + private async prepareActions(actions: Action[]): Promise { + const convertedData = convertKeysToSnakeCase(actions) as ConvertedAction[]; + + await Promise.all( + convertedData.map(async (action) => { + if (action.action_executor === ActionExecutorEnum.DEVICE_ISSUE) { + const device = await this.deviceService.getDeviceByDeviceUuid( + action.entity_id, + false, + ); + if (device) { + action.entity_id = device.deviceTuyaUuid; + } + } + }), + ); + + return convertedData; + } + + private async getDefaultSceneIcon(): Promise { + const defaultIcon = await this.sceneIconRepository.findOne({ + where: { iconType: SceneIconType.Default }, + }); + return defaultIcon; + } + + async getSpaceByUuid(spaceUuid: string) { + try { + const space = await this.spaceRepository.findOne({ + where: { + uuid: spaceUuid, + }, + relations: ['community'], + }); + + if (!space) { + throw new HttpException( + `Invalid space UUID ${spaceUuid}`, + HttpStatusCode.BadRequest, + ); + } + + if (!space.community.externalId) { + throw new HttpException( + `Space doesn't have any association with tuya${spaceUuid}`, + HttpStatusCode.BadRequest, + ); + } + return { + uuid: space.uuid, + createdAt: space.createdAt, + updatedAt: space.updatedAt, + name: space.spaceName, + spaceTuyaUuid: space.community.externalId, + }; + } catch (err) { + if (err instanceof BadRequestException) { + console.log(err); + throw err; + } else { + throw new HttpException( + `Space with id ${spaceUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + } + } } diff --git a/src/space/controllers/index.ts b/src/space/controllers/index.ts index f3b360e..f9587fb 100644 --- a/src/space/controllers/index.ts +++ b/src/space/controllers/index.ts @@ -1,4 +1,5 @@ export * from './space.controller'; export * from './space-user.controller'; export * from './space-device.controller'; +export * from './space-scene.controller'; export * from './subspace'; diff --git a/src/space/controllers/space-scene.controller.ts b/src/space/controllers/space-scene.controller.ts new file mode 100644 index 0000000..5517362 --- /dev/null +++ b/src/space/controllers/space-scene.controller.ts @@ -0,0 +1,34 @@ +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { SpaceSceneService } from '../services'; +import { GetSceneDto } from '../../scene/dtos'; +import { GetSpaceParam } from '../dtos'; + +@ApiTags('Space Module') +@Controller({ + version: '1', + path: ControllerRoute.SPACE_SCENE.ROUTE, +}) +export class SpaceSceneController { + constructor(private readonly sceneService: SpaceSceneService) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: + ControllerRoute.SPACE_SCENE.ACTIONS.GET_TAP_TO_RUN_SCENE_BY_SPACE_SUMMARY, + description: + ControllerRoute.SPACE_SCENE.ACTIONS + .GET_TAP_TO_RUN_SCENE_BY_SPACE_DESCRIPTION, + }) + @Get() + async getTapToRunSceneByUnit( + @Param() params: GetSpaceParam, + @Query() inHomePage: GetSceneDto, + ): Promise { + return await this.sceneService.getScenes(params, inHomePage); + } +} diff --git a/src/space/services/index.ts b/src/space/services/index.ts index 97cfe42..c4e9405 100644 --- a/src/space/services/index.ts +++ b/src/space/services/index.ts @@ -2,3 +2,4 @@ export * from './space.service'; export * from './space-user.service'; export * from './space-device.service'; export * from './subspace'; +export * from './space-scene.service'; diff --git a/src/space/services/space-device.service.ts b/src/space/services/space-device.service.ts index 9701c6f..1be3ea7 100644 --- a/src/space/services/space-device.service.ts +++ b/src/space/services/space-device.service.ts @@ -1,4 +1,4 @@ -import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; @@ -51,10 +51,7 @@ export class SpaceDeviceService { }); } - private async validateCommunityAndSpace( - communityUuid: string, - spaceUuid: string, - ) { + async validateCommunityAndSpace(communityUuid: string, spaceUuid: string) { const community = await this.communityRepository.findOne({ where: { uuid: communityUuid }, }); diff --git a/src/space/services/space-scene.service.ts b/src/space/services/space-scene.service.ts new file mode 100644 index 0000000..ffc10e0 --- /dev/null +++ b/src/space/services/space-scene.service.ts @@ -0,0 +1,55 @@ +import { + BadRequestException, + HttpException, + HttpStatus, + Injectable, +} from '@nestjs/common'; +import { GetSpaceParam } from '../dtos'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { SpaceService } from './space.service'; +import { SceneService } from '../../scene/services'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { GetSceneDto } from '../../scene/dtos'; + +@Injectable() +export class SpaceSceneService { + constructor( + private readonly spaceSevice: SpaceService, + private readonly sceneSevice: SceneService, + ) {} + + async getScenes( + params: GetSpaceParam, + getSceneDto: GetSceneDto, + ): Promise { + try { + const { spaceUuid, communityUuid } = params; + + await this.spaceSevice.validateCommunityAndSpace( + communityUuid, + spaceUuid, + ); + + const scenes = await this.sceneSevice.findScenesBySpace( + spaceUuid, + getSceneDto, + ); + + return new SuccessResponseDto({ + message: `Scenes retrieved successfully for space ${spaceUuid}`, + data: scenes, + }); + } catch (error) { + console.error('Error retrieving scenes:', error); + + if (error instanceof BadRequestException) { + throw new HttpException(error.message, HttpStatus.BAD_REQUEST); + } else { + throw new HttpException( + 'An error occurred while retrieving scenes', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } +} diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 52267d8..78d45d7 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -357,4 +357,29 @@ export class SpaceService { return rootSpaces; // Return the root spaces with children nested within them } + + async validateCommunityAndSpace(communityUuid: string, spaceUuid: string) { + const community = await this.communityRepository.findOne({ + where: { uuid: communityUuid }, + }); + if (!community) { + this.throwNotFound('Community', communityUuid); + } + + const space = await this.spaceRepository.findOne({ + where: { uuid: spaceUuid, community: { uuid: communityUuid } }, + relations: ['devices', 'devices.productDevice'], + }); + if (!space) { + this.throwNotFound('Space', spaceUuid); + } + return space; + } + + private throwNotFound(entity: string, uuid: string) { + throw new HttpException( + `${entity} with ID ${uuid} not found`, + HttpStatus.NOT_FOUND, + ); + } } diff --git a/src/space/services/subspace/subspace-device.service.ts b/src/space/services/subspace/subspace-device.service.ts index 9b2db68..06464b4 100644 --- a/src/space/services/subspace/subspace-device.service.ts +++ b/src/space/services/subspace/subspace-device.service.ts @@ -9,7 +9,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { DeviceSubSpaceParam, GetSubSpaceParam } from '../../dtos'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; -import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; import { ProductRepository } from '@app/common/modules/product/repositories'; import { GetDeviceDetailsInterface } from '../../../device/interfaces/get.device.interface'; diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 5eb7af8..cf6b766 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -7,9 +7,11 @@ import { SpaceUserController, SubSpaceController, SubSpaceDeviceController, + SpaceSceneController, } from './controllers'; import { SpaceDeviceService, + SpaceSceneService, SpaceService, SpaceUserService, SubspaceDeviceService, @@ -25,8 +27,16 @@ import { UserSpaceRepository, } from '@app/common/modules/user/repositories'; import { DeviceRepository } from '@app/common/modules/device/repositories'; -import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; import { ProductRepository } from '@app/common/modules/product/repositories'; +import { SceneService } from '../scene/services'; +import { + SceneIconRepository, + SceneRepository, +} from '@app/common/modules/scene/repositories'; +import { DeviceService } from 'src/device/services'; +import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service'; +import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories'; @Module({ imports: [ConfigModule, SpaceRepositoryModule], @@ -36,6 +46,7 @@ import { ProductRepository } from '@app/common/modules/product/repositories'; SpaceDeviceController, SubSpaceController, SubSpaceDeviceController, + SpaceSceneController, ], providers: [ SpaceService, @@ -51,6 +62,13 @@ import { ProductRepository } from '@app/common/modules/product/repositories'; UserSpaceRepository, UserRepository, SpaceUserService, + SpaceSceneService, + SceneService, + SceneIconRepository, + SceneRepository, + DeviceService, + DeviceStatusFirebaseService, + DeviceStatusLogRepository, ], exports: [SpaceService], })