mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-07-16 10:46:17 +00:00
Fixed scene
This commit is contained in:
@ -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: [
|
||||
|
@ -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';
|
||||
|
2
libs/common/src/integrations/tuya/interfaces/index.ts
Normal file
2
libs/common/src/integrations/tuya/interfaces/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './tuya.response.interface';
|
||||
export * from './tap-to-run-action.interface';
|
@ -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;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
export interface TuyaResponseInterface {
|
||||
success: boolean;
|
||||
msg?: string;
|
||||
result: boolean;
|
||||
}
|
@ -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<TuyaResponseInterface> {
|
||||
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;
|
||||
}
|
||||
}
|
@ -36,8 +36,6 @@ export function buildTypeORMIncludeQuery(
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`Including relations for ${modelName}:`, relations);
|
||||
|
||||
return relations;
|
||||
}
|
||||
|
||||
|
@ -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],
|
||||
|
@ -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 {
|
||||
|
@ -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<BaseResponseDto> {
|
||||
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<BaseResponseDto> {
|
||||
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<BaseResponseDto> {
|
||||
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()
|
||||
|
@ -36,7 +36,7 @@ class ExecutorProperty {
|
||||
public delaySeconds?: number;
|
||||
}
|
||||
|
||||
class Action {
|
||||
export class Action {
|
||||
@ApiProperty({
|
||||
description: 'Entity ID',
|
||||
required: true,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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],
|
||||
|
@ -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<string>('auth-config.ACCESS_KEY');
|
||||
const secretKey = this.configService.get<string>('auth-config.SECRET_KEY');
|
||||
const tuyaEuUrl = this.configService.get<string>('tuya-config.TUYA_EU_URL');
|
||||
this.tuya = new TuyaContext({
|
||||
baseUrl: tuyaEuUrl,
|
||||
accessKey,
|
||||
secretKey,
|
||||
});
|
||||
}
|
||||
) {}
|
||||
|
||||
async addTapToRunScene(
|
||||
async createScene(
|
||||
addSceneTapToRunDto: AddSceneTapToRunDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
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<SceneEntity> {
|
||||
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<SceneDetailsResult> {
|
||||
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<BaseResponseDto> {
|
||||
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<SceneDetails> {
|
||||
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<BaseResponseDto> {
|
||||
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<SceneEntity> {
|
||||
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<ConvertedAction[]> {
|
||||
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<SceneIconEntity> {
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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';
|
||||
|
34
src/space/controllers/space-scene.controller.ts
Normal file
34
src/space/controllers/space-scene.controller.ts
Normal file
@ -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<BaseResponseDto> {
|
||||
return await this.sceneService.getScenes(params, inHomePage);
|
||||
}
|
||||
}
|
@ -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';
|
||||
|
@ -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 },
|
||||
});
|
||||
|
55
src/space/services/space-scene.service.ts
Normal file
55
src/space/services/space-scene.service.ts
Normal file
@ -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<BaseResponseDto> {
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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],
|
||||
})
|
||||
|
Reference in New Issue
Block a user