mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-07-16 18:56:22 +00:00
Merge branch 'dev'
This commit is contained in:
@ -7,14 +7,24 @@ 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/services/tuya.service';
|
||||
import { SceneDeviceRepository } from './modules/scene-device/repositories';
|
||||
@Module({
|
||||
providers: [CommonService, EmailService, ErrorMessageService],
|
||||
providers: [
|
||||
CommonService,
|
||||
EmailService,
|
||||
ErrorMessageService,
|
||||
TuyaService,
|
||||
SceneDeviceRepository,
|
||||
],
|
||||
exports: [
|
||||
CommonService,
|
||||
TuyaService,
|
||||
HelperModule,
|
||||
AuthModule,
|
||||
EmailService,
|
||||
ErrorMessageService,
|
||||
SceneDeviceRepository,
|
||||
],
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
|
@ -1,4 +1,3 @@
|
||||
// automation.enum.ts
|
||||
export enum ActionExecutorEnum {
|
||||
DEVICE_ISSUE = 'device_issue',
|
||||
DELAY = 'delay',
|
||||
@ -7,3 +6,15 @@ export enum ActionExecutorEnum {
|
||||
export enum EntityTypeEnum {
|
||||
DEVICE_REPORT = 'device_report',
|
||||
}
|
||||
export const AUTOMATION_CONFIG = {
|
||||
DEFAULT_START_TIME: '00:00',
|
||||
DEFAULT_END_TIME: '23:59',
|
||||
DEFAULT_LOOPS: '1111111',
|
||||
DECISION_EXPR: 'and',
|
||||
CONDITION_TYPE: 'device_report',
|
||||
ACTION_EXECUTOR: 'rule_trigger',
|
||||
COMPARATOR: '==',
|
||||
SCENE_STATUS_VALUE: 'scene',
|
||||
};
|
||||
export const AUTOMATION_TYPE = 'automation';
|
||||
export const AUTO_PREFIX = 'Auto_';
|
||||
|
@ -8,4 +8,681 @@ export class ControllerRoute {
|
||||
'Retrieve the list of all regions registered in Syncrow.';
|
||||
};
|
||||
};
|
||||
|
||||
static COMMUNITY = class {
|
||||
public static readonly ROUTE = 'communities';
|
||||
static ACTIONS = class {
|
||||
public static readonly GET_COMMUNITY_BY_ID_SUMMARY =
|
||||
'Get community by community community uuid';
|
||||
|
||||
public static readonly GET_COMMUNITY_BY_ID_DESCRIPTION =
|
||||
'Get community by community community uuid - ( [a-zA-Z0-9]{10} )';
|
||||
|
||||
public static readonly LIST_COMMUNITY_SUMMARY = 'Get list of community';
|
||||
|
||||
public static readonly LIST_COMMUNITY_DESCRIPTION =
|
||||
'Return a list of community';
|
||||
public static readonly UPDATE_COMMUNITY_SUMMARY = 'Update community';
|
||||
|
||||
public static readonly UPDATE_COMMUNITY_DESCRIPTION =
|
||||
'Update community in the database and return updated community';
|
||||
|
||||
public static readonly DELETE_COMMUNITY_SUMMARY = 'Delete community';
|
||||
|
||||
public static readonly DELETE_COMMUNITY_DESCRIPTION =
|
||||
'Delete community matching by community id';
|
||||
|
||||
public static readonly CREATE_COMMUNITY_SUMMARY = 'Create community';
|
||||
|
||||
public static readonly CREATE_COMMUNITY_DESCRIPTION =
|
||||
'Create community in the database and return in model';
|
||||
};
|
||||
};
|
||||
|
||||
static COMMUNITY_SPACE = class {
|
||||
public static readonly ROUTE = 'communities/:communityUuid/space';
|
||||
static ACTIONS = class {
|
||||
public static readonly GET_COMMUNITY_SPACES_HIERARCHY_SUMMARY =
|
||||
'Fetch hierarchical structure of spaces within a community.';
|
||||
|
||||
public static readonly GET_COMMUNITY_SPACES_HIERARCHY_DESCRIPTION =
|
||||
'retrieves all the spaces associated with a given community, organized into a hierarchical structure.';
|
||||
};
|
||||
};
|
||||
|
||||
static USER_COMMUNITY = class {
|
||||
public static readonly ROUTE = '/user/:userUuid/communities';
|
||||
static ACTIONS = class {
|
||||
public static readonly GET_USER_COMMUNITIES_SUMMARY =
|
||||
'Get communities associated with a user by user UUID';
|
||||
public static readonly GET_USER_COMMUNITIES_DESCRIPTION =
|
||||
'This endpoint returns the list of communities a specific user is associated with';
|
||||
|
||||
public static readonly ASSOCIATE_USER_COMMUNITY_SUMMARY =
|
||||
'Associate a user with a community';
|
||||
public static readonly ASSOCIATE_USER_COMMUNITY_DESCRIPTION =
|
||||
'This endpoint associates a user with a community.';
|
||||
|
||||
public static readonly DISASSOCIATE_USER_COMMUNITY_SUMMARY =
|
||||
'Disassociate a user from a community';
|
||||
public static readonly DISASSOCIATE_USER_COMMUNITY_DESCRIPTION =
|
||||
'This endpoint disassociates a user from a community. It removes the relationship between the specified user and the community. If the user is not associated with the community, an error will be returned.';
|
||||
};
|
||||
};
|
||||
|
||||
static USER_SPACE = class {
|
||||
public static readonly ROUTE = '/user/:userUuid/spaces';
|
||||
static ACTIONS = class {
|
||||
public static readonly GET_USER_SPACES_SUMMARY =
|
||||
'Retrieve list of spaces a user belongs to';
|
||||
public static readonly GET_USER_SPACES_DESCRIPTION =
|
||||
'This endpoint retrieves all the spaces that a user is associated with, based on the user ID. It fetches the user spaces by querying the UserSpaceEntity to find the spaces where the user has an association.';
|
||||
public static readonly VERIFY_CODE_AND_ADD_USER_SPACE_SUMMARY =
|
||||
'Verify code and add user space';
|
||||
public static readonly VERIFY_CODE_AND_ADD_USER_SPACE_DESCRIPTION =
|
||||
'This endpoint verifies a provided code and associates the user with a space. It checks the validity of the code and, if valid, links the user to the corresponding space in the system.';
|
||||
};
|
||||
};
|
||||
|
||||
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 {
|
||||
public static readonly CREATE_SPACE_SUMMARY = 'Create a new space';
|
||||
public static readonly CREATE_SPACE_DESCRIPTION =
|
||||
'This endpoint allows you to create a space in a specified community. Optionally, you can specify a parent space to nest the new space under it.';
|
||||
|
||||
public static readonly LIST_SPACE_SUMMARY = 'List spaces in community';
|
||||
public static readonly LIST_SPACE_DESCRIPTION =
|
||||
'List spaces in specified community by community id';
|
||||
|
||||
public static readonly GET_SPACE_SUMMARY = 'Get a space in community';
|
||||
public static readonly GET_SPACE_DESCRIPTION =
|
||||
'Get Space in specified community by community id';
|
||||
|
||||
public static readonly DELETE_SPACE_SUMMARY = 'Delete a space';
|
||||
public static readonly DELETE_SPACE_DESCRIPTION =
|
||||
'Deletes a space by its UUID and community ID. If the space has children, they will also be deleted due to cascade delete.';
|
||||
|
||||
public static readonly UPDATE_SPACE_SUMMARY = 'Update a space';
|
||||
public static readonly UPDATE_SPACE_DESCRIPTION =
|
||||
'Updates a space by its UUID and community ID. You can update the name, parent space, and other properties. If a parent space is provided and not already a parent, its `isParent` flag will be set to true.';
|
||||
|
||||
public static readonly GET_HEIRARCHY_SUMMARY = 'Get space hierarchy';
|
||||
public static readonly GET_HEIRARCHY_DESCRIPTION =
|
||||
'This endpoint retrieves the hierarchical structure of spaces under a given space ID. It returns all the child spaces nested within the specified space, organized by their parent-child relationships. ';
|
||||
|
||||
public static readonly CREATE_INVITATION_CODE_SPACE_SUMMARY =
|
||||
'Generate a new invitation code for a specific space';
|
||||
public static readonly CREATE_INVITATION_CODE_SPACE_DESCRIPTION =
|
||||
'This endpoint generates a new 6-character invitation code for a space identified by its UUID and stores it in the space entity';
|
||||
|
||||
public static readonly GET_COMMUNITY_SPACES_HIERARCHY_SUMMARY =
|
||||
'Fetch hierarchical structure of spaces within a community.';
|
||||
|
||||
public static readonly GET_COMMUNITY_SPACES_HIERARCHY_DESCRIPTION =
|
||||
'retrieves all the spaces associated with a given community, organized into a hierarchical structure.';
|
||||
};
|
||||
};
|
||||
|
||||
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';
|
||||
static ACTIONS = class {
|
||||
public static readonly ASSOCIATE_SPACE_USER_SUMMARY =
|
||||
'Associate a user to a space';
|
||||
public static readonly ASSOCIATE_SPACE_USER_DESCRIPTION =
|
||||
'Associates a user with a given space by their respective UUIDs';
|
||||
|
||||
public static readonly DISSOCIATE_SPACE_USER_SUMMARY =
|
||||
'Disassociate a user from a space';
|
||||
public static readonly DISSOCIATE_SPACE_USER_DESCRIPTION =
|
||||
'Disassociates a user from a space by removing the existing association.';
|
||||
};
|
||||
};
|
||||
|
||||
static SPACE_DEVICES = class {
|
||||
public static readonly ROUTE =
|
||||
'/communities/:communityUuid/spaces/:spaceUuid/devices';
|
||||
static ACTIONS = class {
|
||||
public static readonly LIST_SPACE_DEVICE_SUMMARY =
|
||||
'List devices in a space';
|
||||
public static readonly LIST_SPACE_DEVICE_DESCRIPTION =
|
||||
'Retrieves a list of all devices associated with a specified space.';
|
||||
};
|
||||
};
|
||||
|
||||
static SUBSPACE = class {
|
||||
public static readonly ROUTE =
|
||||
'/communities/:communityUuid/spaces/:spaceUuid/subspaces';
|
||||
static ACTIONS = class {
|
||||
public static readonly CREATE_SUBSPACE_SUMMARY = 'Create Subspace';
|
||||
public static readonly CREATE_SUBSPACE_DESCRIPTION =
|
||||
'Creates a new subspace within a specific space and community.';
|
||||
|
||||
public static readonly LIST_SUBSPACES_SUMMARY = 'List Subspaces';
|
||||
public static readonly LIST_SUBSPACES_DESCRIPTION =
|
||||
'Retrieves a list of subspaces within a specified space and community.';
|
||||
|
||||
public static readonly GET_SUBSPACE_SUMMARY = 'Get Subspace';
|
||||
public static readonly GET_SUBSPACE_DESCRIPTION =
|
||||
'Fetches a specific subspace by UUID within a given space and community';
|
||||
|
||||
public static readonly UPDATE_SUBSPACE_SUMMARY = 'Update Subspace';
|
||||
public static readonly UPDATE_SUBSPACE_DESCRIPTION =
|
||||
'Updates a specific subspace within a given space and community.';
|
||||
|
||||
public static readonly DELETE_SUBSPACE_SUMMARY = 'Delete Subspace';
|
||||
public static readonly DELETE_SUBSPACE_DESCRIPTION =
|
||||
'Deletes a specific subspace within a given space and community.';
|
||||
};
|
||||
};
|
||||
|
||||
static SUBSPACE_DEVICE = class {
|
||||
public static readonly ROUTE =
|
||||
'/communities/:communityUuid/spaces/:spaceUuid/subspaces/:subSpaceUuid/devices';
|
||||
|
||||
static ACTIONS = class {
|
||||
public static readonly LIST_SUBSPACE_DEVICE_SUMMARY =
|
||||
'List devices in a subspace';
|
||||
public static readonly LIST_SUBSPACE_DEVICE_DESCRIPTION =
|
||||
'Retrieves a list of all devices associated with a specified subspace.';
|
||||
|
||||
public static readonly ASSOCIATE_SUBSPACE_DEVICE_SUMMARY =
|
||||
'Associate a device to a subspace';
|
||||
public static readonly ASSOCIATE_SUBSPACE_DEVICE_DESCRIPTION =
|
||||
'Associates a device with a specific subspace, enabling it to be managed within that subspace context.';
|
||||
|
||||
public static readonly DISASSOCIATE_SUBSPACE_DEVICE_SUMMARY =
|
||||
'Disassociate a device from a subspace';
|
||||
public static readonly DISASSOCIATE_SUBSPACE_DEVICE_DESCRIPTION =
|
||||
'Removes the association of a device from a specific subspace, making it no longer managed within that subspace.';
|
||||
};
|
||||
};
|
||||
|
||||
static PRODUCT = class {
|
||||
public static readonly ROUTE = 'products';
|
||||
static ACTIONS = class {
|
||||
public static readonly LIST_PRODUCT_SUMMARY = 'Retrieve all products';
|
||||
public static readonly LIST_PRODUCT_DESCRIPTION =
|
||||
'Fetches a list of all products along with their associated device details';
|
||||
};
|
||||
};
|
||||
static USER = class {
|
||||
public static readonly ROUTE = '/user';
|
||||
|
||||
static ACTIONS = class {
|
||||
public static readonly GET_USER_DETAILS_SUMMARY =
|
||||
'Retrieve user details by user UUID';
|
||||
public static readonly GET_USER_DETAILS_DESCRIPTION =
|
||||
'This endpoint retrieves detailed information for a specific user based on their UUID.';
|
||||
|
||||
public static readonly UPDATE_PROFILE_PICTURE_SUMMARY =
|
||||
'Update profile picture by user UUID';
|
||||
public static readonly UPDATE_PROFILE_PICTURE_DESCRIPTION =
|
||||
'This endpoint updates the profile picture for a user identified by their UUID.';
|
||||
|
||||
public static readonly UPDATE_REGION_SUMMARY =
|
||||
'Update region by user UUID';
|
||||
public static readonly UPDATE_REGION_DESCRIPTION =
|
||||
'This endpoint updates the region information for a user identified by their UUID.';
|
||||
|
||||
public static readonly UPDATE_TIMEZONE_SUMMARY =
|
||||
'Update timezone by user UUID';
|
||||
public static readonly UPDATE_TIMEZONE_DESCRIPTION =
|
||||
'This endpoint updates the timezone information for a user identified by their UUID.';
|
||||
|
||||
public static readonly UPDATE_NAME_SUMMARY = 'Update name by user UUID';
|
||||
public static readonly UPDATE_NAME_DESCRIPTION =
|
||||
'This endpoint updates the name for a user identified by their UUID.';
|
||||
|
||||
public static readonly DELETE_USER_SUMMARY = 'Delete user by UUID';
|
||||
public static readonly DELETE_USER_DESCRIPTION =
|
||||
'This endpoint deletes a user identified by their UUID. Accessible only by users with the Super Admin role.';
|
||||
};
|
||||
};
|
||||
static AUTHENTICATION = class {
|
||||
public static readonly ROUTE = 'authentication';
|
||||
|
||||
static ACTIONS = class {
|
||||
public static readonly SIGN_UP_SUMMARY =
|
||||
'Sign up a new user and return a JWT token';
|
||||
public static readonly SIGN_UP_DESCRIPTION =
|
||||
'This endpoint is used to register a new user by providing the user details. A JWT token will be generated and returned upon successful registration.';
|
||||
|
||||
public static readonly LOGIN_SUMMARY =
|
||||
'Login a user and return an access token';
|
||||
public static readonly LOGIN_DESCRIPTION =
|
||||
'This endpoint allows an existing user to log in using their credentials. Upon successful login, an access token will be returned.';
|
||||
|
||||
public static readonly SEND_OTP_SUMMARY =
|
||||
'Generate and send OTP to the user';
|
||||
public static readonly SEND_OTP_DESCRIPTION =
|
||||
'This endpoint generates and sends an OTP to the user for verification, such as for password reset or account verification.';
|
||||
|
||||
public static readonly VERIFY_OTP_SUMMARY =
|
||||
'Verify the OTP entered by the user';
|
||||
public static readonly VERIFY_OTP_DESCRIPTION =
|
||||
'This endpoint verifies the OTP entered by the user. If the OTP is valid, the process continues (e.g., password reset).';
|
||||
|
||||
public static readonly FORGET_PASSWORD_SUMMARY =
|
||||
'Reset the user password after OTP verification';
|
||||
public static readonly FORGET_PASSWORD_DESCRIPTION =
|
||||
'This endpoint allows users who have forgotten their password to reset it. After verifying the OTP, the user can set a new password.';
|
||||
|
||||
public static readonly USER_LIST_SUMMARY = 'Fetch the list of all users';
|
||||
public static readonly USER_LIST_DESCRIPTION =
|
||||
'This endpoint retrieves a list of all users in the system. Access is restricted to super admins only.';
|
||||
|
||||
public static readonly REFRESH_TOKEN_SUMMARY =
|
||||
'Refresh the user session token';
|
||||
public static readonly REFRESH_TOKEN_DESCRIPTION =
|
||||
'This endpoint allows a user to refresh their session token. The user must provide a valid refresh token to get a new session token.';
|
||||
};
|
||||
};
|
||||
static ROLE = class {
|
||||
public static readonly ROUTE = 'role';
|
||||
|
||||
static ACTIONS = class {
|
||||
public static readonly FETCH_ROLE_TYPES_SUMMARY =
|
||||
'Fetch available role types';
|
||||
public static readonly FETCH_ROLE_TYPES_DESCRIPTION =
|
||||
'This endpoint retrieves all available role types in the system.';
|
||||
|
||||
public static readonly ADD_USER_ROLE_SUMMARY = 'Add a new user role';
|
||||
public static readonly ADD_USER_ROLE_DESCRIPTION =
|
||||
'This endpoint adds a new user role to the system based on the provided role data.';
|
||||
};
|
||||
};
|
||||
static GROUP = class {
|
||||
public static readonly ROUTE = 'group';
|
||||
|
||||
static ACTIONS = class {
|
||||
public static readonly GET_GROUPS_BY_SPACE_UUID_SUMMARY =
|
||||
'Get groups by space UUID';
|
||||
public static readonly GET_GROUPS_BY_SPACE_UUID_DESCRIPTION =
|
||||
'This endpoint retrieves all groups for a specific space, identified by the space UUID.';
|
||||
|
||||
public static readonly GET_UNIT_DEVICES_BY_GROUP_NAME_SUMMARY =
|
||||
'Get devices by group name in a space';
|
||||
public static readonly GET_UNIT_DEVICES_BY_GROUP_NAME_DESCRIPTION =
|
||||
'This endpoint retrieves all devices in a specified group within a space, based on the group name and space UUID.';
|
||||
};
|
||||
};
|
||||
static DEVICE = class {
|
||||
public static readonly ROUTE = 'device';
|
||||
|
||||
static ACTIONS = class {
|
||||
public static readonly ADD_DEVICE_TO_USER_SUMMARY = 'Add device to user';
|
||||
public static readonly ADD_DEVICE_TO_USER_DESCRIPTION =
|
||||
'This endpoint adds a device to a user in the system.';
|
||||
|
||||
public static readonly GET_DEVICES_BY_USER_SUMMARY =
|
||||
'Get devices by user UUID';
|
||||
public static readonly GET_DEVICES_BY_USER_DESCRIPTION =
|
||||
'This endpoint retrieves all devices associated with a specific user.';
|
||||
|
||||
public static readonly GET_DEVICES_BY_SPACE_UUID_SUMMARY =
|
||||
'Get devices by space UUID';
|
||||
public static readonly GET_DEVICES_BY_SPACE_UUID_DESCRIPTION =
|
||||
'This endpoint retrieves all devices associated with a specific space UUID.';
|
||||
|
||||
public static readonly UPDATE_DEVICE_IN_ROOM_SUMMARY =
|
||||
'Update device in Space';
|
||||
public static readonly UPDATE_DEVICE_IN_ROOM_DESCRIPTION =
|
||||
'This endpoint updates the device in a specific space with new details.';
|
||||
|
||||
public static readonly GET_DEVICE_DETAILS_SUMMARY = 'Get device details';
|
||||
public static readonly GET_DEVICE_DETAILS_DESCRIPTION =
|
||||
'This endpoint retrieves details of a specific device by its UUID.';
|
||||
|
||||
public static readonly UPDATE_DEVICE_SUMMARY = 'Update device';
|
||||
public static readonly UPDATE_DEVICE_DESCRIPTION =
|
||||
'This endpoint updates the details of a device by its UUID.';
|
||||
|
||||
public static readonly GET_DEVICE_INSTRUCTION_SUMMARY =
|
||||
'Get device instruction';
|
||||
public static readonly GET_DEVICE_INSTRUCTION_DESCRIPTION =
|
||||
'This endpoint retrieves the instruction details for a specific device.';
|
||||
|
||||
public static readonly GET_DEVICE_STATUS_SUMMARY = 'Get device status';
|
||||
public static readonly GET_DEVICE_STATUS_DESCRIPTION =
|
||||
'This endpoint retrieves the current status of a specific device.';
|
||||
|
||||
public static readonly CONTROL_DEVICE_SUMMARY = 'Control device';
|
||||
public static readonly CONTROL_DEVICE_DESCRIPTION =
|
||||
'This endpoint allows control operations (like power on/off) on a specific device.';
|
||||
|
||||
public static readonly UPDATE_DEVICE_FIRMWARE_SUMMARY =
|
||||
'Update device firmware';
|
||||
public static readonly UPDATE_DEVICE_FIRMWARE_DESCRIPTION =
|
||||
'This endpoint updates the firmware of a specific device to the provided version.';
|
||||
|
||||
public static readonly GET_DEVICES_IN_GATEWAY_SUMMARY =
|
||||
'Get devices in gateway';
|
||||
public static readonly GET_DEVICES_IN_GATEWAY_DESCRIPTION =
|
||||
'This endpoint retrieves all devices associated with a specific gateway.';
|
||||
|
||||
public static readonly GET_ALL_DEVICES_SUMMARY = 'Get all devices';
|
||||
public static readonly GET_ALL_DEVICES_DESCRIPTION =
|
||||
'This endpoint retrieves all devices in the system.';
|
||||
|
||||
public static readonly GET_DEVICE_LOGS_SUMMARY = 'Get device logs';
|
||||
public static readonly GET_DEVICE_LOGS_DESCRIPTION =
|
||||
'This endpoint retrieves the logs for a specific device based on device UUID.';
|
||||
|
||||
public static readonly BATCH_CONTROL_DEVICES_SUMMARY =
|
||||
'Batch control devices';
|
||||
public static readonly BATCH_CONTROL_DEVICES_DESCRIPTION =
|
||||
'This endpoint controls a batch of devices with the specified actions.';
|
||||
|
||||
public static readonly BATCH_STATUS_DEVICES_SUMMARY =
|
||||
'Batch status devices';
|
||||
public static readonly BATCH_STATUS_DEVICES_DESCRIPTION =
|
||||
'This endpoint retrieves the status of a batch of devices.';
|
||||
|
||||
public static readonly BATCH_FACTORY_RESET_DEVICES_SUMMARY =
|
||||
'Batch factory reset devices';
|
||||
public static readonly BATCH_FACTORY_RESET_DEVICES_DESCRIPTION =
|
||||
'This endpoint performs a factory reset on a batch of devices.';
|
||||
|
||||
public static readonly GET_POWER_CLAMP_STATUS_SUMMARY =
|
||||
'Get power clamp status';
|
||||
public static readonly GET_POWER_CLAMP_STATUS_DESCRIPTION =
|
||||
'This endpoint retrieves the status of a specific power clamp device.';
|
||||
|
||||
public static readonly ADD_SCENE_TO_DEVICE_SUMMARY =
|
||||
'Add scene to device (4 Scene and 6 Scene devices only)';
|
||||
public static readonly ADD_SCENE_TO_DEVICE_DESCRIPTION =
|
||||
'This endpoint adds a scene to a specific switch device.';
|
||||
|
||||
public static readonly GET_SCENES_BY_DEVICE_SUMMARY =
|
||||
'Get scenes by device (4 Scene and 6 Scene devices only)';
|
||||
public static readonly GET_SCENES_BY_DEVICE_DESCRIPTION =
|
||||
'This endpoint retrieves all scenes associated with a specific switch device.';
|
||||
public static readonly DELETE_SCENES_BY_SWITCH_NAME_SUMMARY =
|
||||
'Delete scenes by device uuid and switch name (4 Scene and 6 Scene devices only)';
|
||||
public static readonly DELETE_SCENES_BY_SWITCH_NAME_DESCRIPTION =
|
||||
'This endpoint deletes all scenes associated with a specific switch device.';
|
||||
};
|
||||
};
|
||||
|
||||
static DEVICE_PERMISSION = class {
|
||||
public static readonly ROUTE = 'device-permission';
|
||||
|
||||
static ACTIONS = class {
|
||||
public static readonly ADD_PERMISSION_SUMMARY =
|
||||
'Add user permission for device';
|
||||
public static readonly ADD_PERMISSION_DESCRIPTION =
|
||||
'This endpoint adds a user permission for a specific device. Accessible only by users with the Super Admin role.';
|
||||
|
||||
public static readonly EDIT_PERMISSION_SUMMARY =
|
||||
'Edit user permission for device';
|
||||
public static readonly EDIT_PERMISSION_DESCRIPTION =
|
||||
'This endpoint updates a user permission for a specific device. Accessible only by users with the Super Admin role.';
|
||||
|
||||
public static readonly FETCH_PERMISSION_SUMMARY =
|
||||
'Fetch user permission for device';
|
||||
public static readonly FETCH_PERMISSION_DESCRIPTION =
|
||||
'This endpoint retrieves the user permission for a specific device. Accessible only by users with the Super Admin role.';
|
||||
|
||||
public static readonly DELETE_PERMISSION_SUMMARY =
|
||||
'Delete user permission for device';
|
||||
public static readonly DELETE_PERMISSION_DESCRIPTION =
|
||||
'This endpoint deletes the user permission for a specific device. Accessible only by users with the Super Admin role.';
|
||||
};
|
||||
};
|
||||
|
||||
static USER_NOTIFICATION = class {
|
||||
public static readonly ROUTE = 'user-notification/subscription';
|
||||
|
||||
static ACTIONS = class {
|
||||
public static readonly ADD_SUBSCRIPTION_SUMMARY =
|
||||
'Add user notification subscription';
|
||||
public static readonly ADD_SUBSCRIPTION_DESCRIPTION =
|
||||
'This endpoint adds a subscription for user notifications.';
|
||||
|
||||
public static readonly FETCH_SUBSCRIPTIONS_SUMMARY =
|
||||
'Fetch user notification subscriptions';
|
||||
public static readonly FETCH_SUBSCRIPTIONS_DESCRIPTION =
|
||||
'This endpoint retrieves the subscriptions of a specific user based on their UUID.';
|
||||
|
||||
public static readonly UPDATE_SUBSCRIPTION_SUMMARY =
|
||||
'Update user notification subscription';
|
||||
public static readonly UPDATE_SUBSCRIPTION_DESCRIPTION =
|
||||
'This endpoint updates the notification subscription details for a user.';
|
||||
};
|
||||
};
|
||||
|
||||
static AUTOMATION = class {
|
||||
public static readonly ROUTE = 'automation';
|
||||
|
||||
static ACTIONS = class {
|
||||
public static readonly ADD_AUTOMATION_SUMMARY = 'Add automation';
|
||||
public static readonly ADD_AUTOMATION_DESCRIPTION =
|
||||
'This endpoint creates a new automation based on the provided details.';
|
||||
|
||||
public static readonly GET_AUTOMATION_BY_SPACE_SUMMARY =
|
||||
'Get automation by space';
|
||||
public static readonly GET_AUTOMATION_BY_SPACE_DESCRIPTION =
|
||||
'This endpoint retrieves the automations associated with a particular space.';
|
||||
|
||||
public static readonly GET_AUTOMATION_DETAILS_SUMMARY =
|
||||
'Get automation details';
|
||||
public static readonly GET_AUTOMATION_DETAILS_DESCRIPTION =
|
||||
'This endpoint retrieves detailed information about a specific automation.';
|
||||
|
||||
public static readonly DELETE_AUTOMATION_SUMMARY = 'Delete automation';
|
||||
public static readonly DELETE_AUTOMATION_DESCRIPTION =
|
||||
'This endpoint deletes an automation identified by its UUID.';
|
||||
|
||||
public static readonly UPDATE_AUTOMATION_SUMMARY = 'Update automation';
|
||||
public static readonly UPDATE_AUTOMATION_DESCRIPTION =
|
||||
'This endpoint updates the details of an existing automation.';
|
||||
|
||||
public static readonly UPDATE_AUTOMATION_STATUS_SUMMARY =
|
||||
'Update automation status';
|
||||
public static readonly UPDATE_AUTOMATION_STATUS_DESCRIPTION =
|
||||
'This endpoint updates the status of an automation identified by its UUID (enabled/disabled).';
|
||||
};
|
||||
};
|
||||
|
||||
static DOOR_LOCK = class {
|
||||
public static readonly ROUTE = 'door-lock';
|
||||
|
||||
static ACTIONS = class {
|
||||
public static readonly ADD_ONLINE_TEMPORARY_PASSWORD_SUMMARY =
|
||||
'Add online temporary password';
|
||||
public static readonly ADD_ONLINE_TEMPORARY_PASSWORD_DESCRIPTION =
|
||||
'This endpoint allows you to add an online temporary password to a door lock.';
|
||||
|
||||
public static readonly ADD_OFFLINE_ONE_TIME_TEMPORARY_PASSWORD_SUMMARY =
|
||||
'Add offline one-time temporary password';
|
||||
public static readonly ADD_OFFLINE_ONE_TIME_TEMPORARY_PASSWORD_DESCRIPTION =
|
||||
'This endpoint allows you to add an offline one-time temporary password to a door lock.';
|
||||
|
||||
public static readonly ADD_OFFLINE_MULTIPLE_TIME_TEMPORARY_PASSWORD_SUMMARY =
|
||||
'Add offline multiple-time temporary password';
|
||||
public static readonly ADD_OFFLINE_MULTIPLE_TIME_TEMPORARY_PASSWORD_DESCRIPTION =
|
||||
'This endpoint allows you to add an offline multiple-time temporary password to a door lock.';
|
||||
|
||||
public static readonly GET_ONLINE_TEMPORARY_PASSWORDS_SUMMARY =
|
||||
'Get online temporary passwords';
|
||||
public static readonly GET_ONLINE_TEMPORARY_PASSWORDS_DESCRIPTION =
|
||||
'This endpoint retrieves the list of online temporary passwords for a door lock.';
|
||||
|
||||
public static readonly DELETE_ONLINE_TEMPORARY_PASSWORD_SUMMARY =
|
||||
'Delete online temporary password';
|
||||
public static readonly DELETE_ONLINE_TEMPORARY_PASSWORD_DESCRIPTION =
|
||||
'This endpoint deletes an online temporary password for a door lock.';
|
||||
|
||||
public static readonly GET_OFFLINE_ONE_TIME_TEMPORARY_PASSWORDS_SUMMARY =
|
||||
'Get offline one-time temporary passwords';
|
||||
public static readonly GET_OFFLINE_ONE_TIME_TEMPORARY_PASSWORDS_DESCRIPTION =
|
||||
'This endpoint retrieves the list of offline one-time temporary passwords for a door lock.';
|
||||
|
||||
public static readonly GET_OFFLINE_MULTIPLE_TIME_TEMPORARY_PASSWORDS_SUMMARY =
|
||||
'Get offline multiple-time temporary passwords';
|
||||
public static readonly GET_OFFLINE_MULTIPLE_TIME_TEMPORARY_PASSWORDS_DESCRIPTION =
|
||||
'This endpoint retrieves the list of offline multiple-time temporary passwords for a door lock.';
|
||||
|
||||
public static readonly UPDATE_OFFLINE_TEMPORARY_PASSWORD_SUMMARY =
|
||||
'Update offline temporary password';
|
||||
public static readonly UPDATE_OFFLINE_TEMPORARY_PASSWORD_DESCRIPTION =
|
||||
'This endpoint updates an offline temporary password for a door lock.';
|
||||
|
||||
public static readonly OPEN_DOOR_LOCK_SUMMARY = 'Open door lock';
|
||||
public static readonly OPEN_DOOR_LOCK_DESCRIPTION =
|
||||
'This endpoint allows you to open a door lock.';
|
||||
};
|
||||
};
|
||||
static TIMEZONE = class {
|
||||
public static readonly ROUTE = 'timezone';
|
||||
|
||||
static ACTIONS = class {
|
||||
public static readonly GET_ALL_TIME_ZONES_SUMMARY = 'Get all time zones';
|
||||
public static readonly GET_ALL_TIME_ZONES_DESCRIPTION =
|
||||
'This endpoint retrieves all available time zones.';
|
||||
};
|
||||
};
|
||||
static VISITOR_PASSWORD = class {
|
||||
public static readonly ROUTE = 'visitor-password';
|
||||
|
||||
static ACTIONS = class {
|
||||
public static readonly ADD_ONLINE_TEMP_PASSWORD_MULTIPLE_TIME_SUMMARY =
|
||||
'Add online temporary passwords (multiple-time)';
|
||||
public static readonly ADD_ONLINE_TEMP_PASSWORD_MULTIPLE_TIME_DESCRIPTION =
|
||||
'This endpoint adds multiple online temporary passwords for door locks.';
|
||||
|
||||
public static readonly ADD_ONLINE_TEMP_PASSWORD_ONE_TIME_SUMMARY =
|
||||
'Add online temporary password (one-time)';
|
||||
public static readonly ADD_ONLINE_TEMP_PASSWORD_ONE_TIME_DESCRIPTION =
|
||||
'This endpoint adds a one-time online temporary password for a door lock.';
|
||||
|
||||
public static readonly ADD_OFFLINE_TEMP_PASSWORD_ONE_TIME_SUMMARY =
|
||||
'Add offline temporary password (one-time)';
|
||||
public static readonly ADD_OFFLINE_TEMP_PASSWORD_ONE_TIME_DESCRIPTION =
|
||||
'This endpoint adds a one-time offline temporary password for a door lock.';
|
||||
|
||||
public static readonly ADD_OFFLINE_TEMP_PASSWORD_MULTIPLE_TIME_SUMMARY =
|
||||
'Add offline temporary passwords (multiple-time)';
|
||||
public static readonly ADD_OFFLINE_TEMP_PASSWORD_MULTIPLE_TIME_DESCRIPTION =
|
||||
'This endpoint adds multiple offline temporary passwords for door locks.';
|
||||
|
||||
public static readonly GET_VISITOR_PASSWORD_SUMMARY =
|
||||
'Get visitor passwords';
|
||||
public static readonly GET_VISITOR_PASSWORD_DESCRIPTION =
|
||||
'This endpoint retrieves all visitor passwords.';
|
||||
|
||||
public static readonly GET_VISITOR_DEVICES_SUMMARY =
|
||||
'Get visitor devices';
|
||||
public static readonly GET_VISITOR_DEVICES_DESCRIPTION =
|
||||
'This endpoint retrieves all devices associated with visitor passwords.';
|
||||
};
|
||||
};
|
||||
static SCHEDULE = class {
|
||||
public static readonly ROUTE = 'schedule';
|
||||
|
||||
static ACTIONS = class {
|
||||
public static readonly ADD_DEVICE_SCHEDULE_SUMMARY =
|
||||
'Add device schedule';
|
||||
public static readonly ADD_DEVICE_SCHEDULE_DESCRIPTION =
|
||||
'This endpoint allows you to add a schedule for a specific device.';
|
||||
|
||||
public static readonly GET_DEVICE_SCHEDULE_BY_CATEGORY_SUMMARY =
|
||||
'Get device schedule by category';
|
||||
public static readonly GET_DEVICE_SCHEDULE_BY_CATEGORY_DESCRIPTION =
|
||||
'This endpoint retrieves the schedule for a specific device based on the given category.';
|
||||
|
||||
public static readonly DELETE_DEVICE_SCHEDULE_SUMMARY =
|
||||
'Delete device schedule';
|
||||
public static readonly DELETE_DEVICE_SCHEDULE_DESCRIPTION =
|
||||
'This endpoint deletes a specific schedule for a device.';
|
||||
|
||||
public static readonly ENABLE_DEVICE_SCHEDULE_SUMMARY =
|
||||
'Enable device schedule';
|
||||
public static readonly ENABLE_DEVICE_SCHEDULE_DESCRIPTION =
|
||||
'This endpoint enables a device schedule for a specific device.';
|
||||
|
||||
public static readonly UPDATE_DEVICE_SCHEDULE_SUMMARY =
|
||||
'Update device schedule';
|
||||
public static readonly UPDATE_DEVICE_SCHEDULE_DESCRIPTION =
|
||||
'This endpoint updates the schedule for a specific device.';
|
||||
};
|
||||
};
|
||||
static DEVICE_STATUS_FIREBASE = class {
|
||||
public static readonly ROUTE = 'device-status-firebase';
|
||||
|
||||
static ACTIONS = class {
|
||||
public static readonly ADD_DEVICE_STATUS_SUMMARY =
|
||||
'Add device status to Firebase';
|
||||
public static readonly ADD_DEVICE_STATUS_DESCRIPTION =
|
||||
'This endpoint adds a device status in Firebase based on the provided device UUID.';
|
||||
|
||||
public static readonly GET_DEVICE_STATUS_SUMMARY =
|
||||
'Get device status from Firebase';
|
||||
public static readonly GET_DEVICE_STATUS_DESCRIPTION =
|
||||
'This endpoint retrieves a device status from Firebase using the device UUID.';
|
||||
};
|
||||
};
|
||||
static DEVICE_MESSAGES_SUBSCRIPTION = class {
|
||||
public static readonly ROUTE = 'device-messages/subscription';
|
||||
|
||||
static ACTIONS = class {
|
||||
public static readonly ADD_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY =
|
||||
'Add device messages subscription';
|
||||
public static readonly ADD_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION =
|
||||
'This endpoint adds a subscription for device messages.';
|
||||
|
||||
public static readonly GET_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY =
|
||||
'Get device messages subscription';
|
||||
public static readonly GET_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION =
|
||||
'This endpoint fetches a user’s subscription for a specific device.';
|
||||
|
||||
public static readonly DELETE_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY =
|
||||
'Delete device messages subscription';
|
||||
public static readonly DELETE_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION =
|
||||
'This endpoint deletes a user’s subscription for device messages.';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
5
libs/common/src/constants/direction.enum.ts
Normal file
5
libs/common/src/constants/direction.enum.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export enum Direction {
|
||||
LEFT = 'left',
|
||||
RIGHT = 'right',
|
||||
DOWN = 'down',
|
||||
}
|
@ -16,4 +16,6 @@ export enum ProductType {
|
||||
GD = 'GD',
|
||||
CUR = 'CUR',
|
||||
PC = 'PC',
|
||||
FOUR_S = '4S',
|
||||
SIX_S = '6S',
|
||||
}
|
||||
|
8
libs/common/src/constants/scene-switch-type.enum.ts
Normal file
8
libs/common/src/constants/scene-switch-type.enum.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export enum SceneSwitchesTypeEnum {
|
||||
SCENE_1 = 'scene_1',
|
||||
SCENE_2 = 'scene_2',
|
||||
SCENE_3 = 'scene_3',
|
||||
SCENE_4 = 'scene_4',
|
||||
SCENE_5 = 'scene_5',
|
||||
SCENE_6 = 'scene_6',
|
||||
}
|
@ -8,8 +8,11 @@ import { UserOtpEntity } from '../modules/user/entities';
|
||||
import { ProductEntity } from '../modules/product/entities';
|
||||
import { DeviceEntity } from '../modules/device/entities';
|
||||
import { PermissionTypeEntity } from '../modules/permission/entities';
|
||||
import { SpaceEntity } from '../modules/space/entities';
|
||||
import { SpaceTypeEntity } from '../modules/space/entities';
|
||||
import {
|
||||
SpaceEntity,
|
||||
SpaceLinkEntity,
|
||||
SubspaceEntity,
|
||||
} from '../modules/space/entities';
|
||||
import { UserSpaceEntity } from '../modules/user/entities';
|
||||
import { DeviceUserPermissionEntity } from '../modules/device/entities';
|
||||
import { UserRoleEntity } from '../modules/user/entities';
|
||||
@ -19,8 +22,11 @@ import { DeviceNotificationEntity } from '../modules/device/entities';
|
||||
import { RegionEntity } from '../modules/region/entities';
|
||||
import { TimeZoneEntity } from '../modules/timezone/entities';
|
||||
import { VisitorPasswordEntity } from '../modules/visitor-password/entities';
|
||||
import { CommunityEntity } from '../modules/community/entities';
|
||||
import { DeviceStatusLogEntity } from '../modules/device-status-log/entities';
|
||||
import { SceneEntity, SceneIconEntity } from '../modules/scene/entities';
|
||||
import { SceneDeviceEntity } from '../modules/scene-device/entities';
|
||||
import { SpaceProductEntity } from '../modules/space/entities/space-product.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -43,8 +49,11 @@ import { SceneEntity, SceneIconEntity } from '../modules/scene/entities';
|
||||
DeviceUserPermissionEntity,
|
||||
DeviceEntity,
|
||||
PermissionTypeEntity,
|
||||
CommunityEntity,
|
||||
SpaceEntity,
|
||||
SpaceTypeEntity,
|
||||
SpaceLinkEntity,
|
||||
SubspaceEntity,
|
||||
SpaceProductEntity,
|
||||
UserSpaceEntity,
|
||||
DeviceUserPermissionEntity,
|
||||
UserRoleEntity,
|
||||
@ -57,6 +66,7 @@ import { SceneEntity, SceneIconEntity } from '../modules/scene/entities';
|
||||
DeviceStatusLogEntity,
|
||||
SceneEntity,
|
||||
SceneIconEntity,
|
||||
SceneDeviceEntity,
|
||||
],
|
||||
namingStrategy: new SnakeNamingStrategy(),
|
||||
synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))),
|
||||
|
23
libs/common/src/dto/base.response.dto.ts
Normal file
23
libs/common/src/dto/base.response.dto.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { WithOptional } from '../type/optional.type';
|
||||
|
||||
export class BaseResponseDto {
|
||||
statusCode?: number;
|
||||
|
||||
message: string;
|
||||
|
||||
error?: string;
|
||||
|
||||
data?: any;
|
||||
|
||||
success?: boolean;
|
||||
|
||||
static wrap({
|
||||
data,
|
||||
statusCode = 200,
|
||||
message = 'Success',
|
||||
success = true,
|
||||
error = undefined,
|
||||
}: WithOptional<BaseResponseDto, 'message'>) {
|
||||
return { data, statusCode, success, message, error };
|
||||
}
|
||||
}
|
66
libs/common/src/dto/pagination.request.dto.ts
Normal file
66
libs/common/src/dto/pagination.request.dto.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { IsDate, IsOptional } from 'class-validator';
|
||||
import { IsPageRequestParam } from '../validators/is-page-request-param.validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsSizeRequestParam } from '../validators/is-size-request-param.validator';
|
||||
import { Transform } from 'class-transformer';
|
||||
import { parseToDate } from '../util/parseToDate';
|
||||
|
||||
export class PaginationRequestGetListDto {
|
||||
@IsOptional()
|
||||
@IsPageRequestParam({
|
||||
message: 'Page must be bigger than 0',
|
||||
})
|
||||
@ApiProperty({
|
||||
name: 'page',
|
||||
required: false,
|
||||
description: 'Page request',
|
||||
})
|
||||
page?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsSizeRequestParam({
|
||||
message: 'Size must not be negative',
|
||||
})
|
||||
@ApiProperty({
|
||||
name: 'size',
|
||||
required: false,
|
||||
description: 'Size request',
|
||||
})
|
||||
size?: number;
|
||||
|
||||
@IsOptional()
|
||||
@ApiProperty({
|
||||
name: 'name',
|
||||
required: false,
|
||||
description: 'Name to be filtered',
|
||||
})
|
||||
name?: string;
|
||||
|
||||
@ApiProperty({
|
||||
name: 'from',
|
||||
required: false,
|
||||
type: Number,
|
||||
description: `Start time in UNIX timestamp format to filter`,
|
||||
example: 1674172800000,
|
||||
})
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => parseToDate(value))
|
||||
@IsDate({
|
||||
message: `From must be in UNIX timestamp format in order to parse to Date instance`,
|
||||
})
|
||||
from?: Date;
|
||||
|
||||
@ApiProperty({
|
||||
name: 'to',
|
||||
required: false,
|
||||
type: Number,
|
||||
description: `End time in UNIX timestamp format to filter`,
|
||||
example: 1674259200000,
|
||||
})
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => parseToDate(value))
|
||||
@IsDate({
|
||||
message: `To must be in UNIX timestamp format in order to parse to Date instance`,
|
||||
})
|
||||
to?: Date;
|
||||
}
|
62
libs/common/src/dto/pagination.response.dto.ts
Normal file
62
libs/common/src/dto/pagination.response.dto.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { BaseResponseDto } from './base.response.dto';
|
||||
|
||||
export interface PageResponseDto {
|
||||
// Original paging information from the request ( or default )
|
||||
page: number;
|
||||
size: number;
|
||||
|
||||
// Useful for display (N Records found)
|
||||
totalItem: number;
|
||||
|
||||
// Use for display N Pages ( 0... N )
|
||||
totalPage: number;
|
||||
|
||||
// Has next is false when cursor is at last page
|
||||
hasNext: boolean;
|
||||
|
||||
// Has previous is false when cursor is at first page
|
||||
hasPrevious: boolean;
|
||||
}
|
||||
|
||||
export class PageResponse<T> implements BaseResponseDto, PageResponseDto {
|
||||
code?: number;
|
||||
|
||||
message: string;
|
||||
|
||||
data: Array<T>;
|
||||
|
||||
page: number;
|
||||
|
||||
size: number;
|
||||
|
||||
totalItem: number;
|
||||
|
||||
totalPage: number;
|
||||
|
||||
hasNext: boolean;
|
||||
|
||||
hasPrevious: boolean;
|
||||
|
||||
constructor(
|
||||
baseResponseDto: BaseResponseDto,
|
||||
pageResponseDto: PageResponseDto,
|
||||
) {
|
||||
if (baseResponseDto.statusCode) {
|
||||
this.code = baseResponseDto.statusCode;
|
||||
} else {
|
||||
this.code = 200;
|
||||
}
|
||||
|
||||
if (baseResponseDto.data) {
|
||||
this.data = baseResponseDto.data;
|
||||
}
|
||||
|
||||
this.message = baseResponseDto.message;
|
||||
this.page = pageResponseDto.page;
|
||||
this.size = pageResponseDto.size;
|
||||
this.totalItem = pageResponseDto.totalItem;
|
||||
this.totalPage = pageResponseDto.totalPage;
|
||||
this.hasNext = pageResponseDto.hasNext;
|
||||
this.hasPrevious = pageResponseDto.hasPrevious;
|
||||
}
|
||||
}
|
30
libs/common/src/dto/success.response.dto.ts
Normal file
30
libs/common/src/dto/success.response.dto.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { BaseResponseDto } from './base.response.dto';
|
||||
|
||||
export class SuccessResponseDto<Type> implements BaseResponseDto {
|
||||
@ApiProperty({
|
||||
example: 200,
|
||||
})
|
||||
statusCode: number;
|
||||
|
||||
data: Type;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'Success message',
|
||||
})
|
||||
message: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: true,
|
||||
description: 'Indicates that the operation was successful',
|
||||
})
|
||||
success: boolean;
|
||||
|
||||
constructor(input: BaseResponseDto) {
|
||||
if (input.statusCode) this.statusCode = input.statusCode;
|
||||
else this.statusCode = 200;
|
||||
if (input.message) this.message = input.message;
|
||||
if (input.data) this.data = input.data;
|
||||
this.success = true;
|
||||
}
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
import { Controller, Post, Param } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { AddDeviceStatusDto } from '../dtos/add.devices-status.dto';
|
||||
import { DeviceStatusFirebaseService } from '../services/devices-status.service';
|
||||
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
|
||||
import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||
|
||||
@ApiTags('Device Status Firebase Module')
|
||||
@Controller({
|
||||
version: EnableDisableStatusEnum.ENABLED,
|
||||
path: 'device-status-firebase',
|
||||
path: ControllerRoute.DEVICE_STATUS_FIREBASE.ROUTE,
|
||||
})
|
||||
export class DeviceStatusFirebaseController {
|
||||
constructor(
|
||||
@ -16,6 +17,13 @@ export class DeviceStatusFirebaseController {
|
||||
|
||||
@ApiBearerAuth()
|
||||
@Post(':deviceTuyaUuid')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
ControllerRoute.DEVICE_STATUS_FIREBASE.ACTIONS.ADD_DEVICE_STATUS_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DEVICE_STATUS_FIREBASE.ACTIONS
|
||||
.ADD_DEVICE_STATUS_DESCRIPTION,
|
||||
})
|
||||
async addDeviceStatus(
|
||||
@Param('deviceTuyaUuid') deviceTuyaUuid: string,
|
||||
): Promise<AddDeviceStatusDto> {
|
||||
|
@ -10,19 +10,27 @@ import { DeviceMessagesService } from './services/device.messages.service';
|
||||
import { DeviceRepositoryModule } from '../modules/device/device.repository.module';
|
||||
import { DeviceNotificationRepository } from '../modules/device/repositories';
|
||||
import { DeviceStatusFirebaseModule } from '../firebase/devices-status/devices-status.module';
|
||||
import { CommunityPermissionService } from './services/community.permission.service';
|
||||
import { CommunityRepository } from '../modules/community/repositories';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [
|
||||
HelperHashService,
|
||||
SpacePermissionService,
|
||||
CommunityPermissionService,
|
||||
SpaceRepository,
|
||||
TuyaWebSocketService,
|
||||
OneSignalService,
|
||||
DeviceMessagesService,
|
||||
DeviceNotificationRepository,
|
||||
CommunityRepository,
|
||||
],
|
||||
exports: [
|
||||
HelperHashService,
|
||||
SpacePermissionService,
|
||||
CommunityPermissionService,
|
||||
],
|
||||
exports: [HelperHashService, SpacePermissionService],
|
||||
controllers: [],
|
||||
imports: [
|
||||
SpaceRepositoryModule,
|
||||
|
@ -0,0 +1,27 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||
|
||||
@Injectable()
|
||||
export class CommunityPermissionService {
|
||||
constructor(private readonly communityRepository: CommunityRepository) {}
|
||||
|
||||
async checkUserPermission(communityUuid: string): Promise<void> {
|
||||
try {
|
||||
const communityData = await this.communityRepository.findOne({
|
||||
where: {
|
||||
uuid: communityUuid,
|
||||
},
|
||||
relations: ['users'],
|
||||
});
|
||||
|
||||
if (!communityData) {
|
||||
throw new BadRequestException(
|
||||
'You do not have permission to access this community',
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
throw new BadRequestException(err.message || 'Invalid UUID');
|
||||
}
|
||||
}
|
||||
}
|
@ -9,27 +9,23 @@ export class SpacePermissionService {
|
||||
async checkUserPermission(
|
||||
spaceUuid: string,
|
||||
userUuid: string,
|
||||
type: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const spaceData = await this.spaceRepository.findOne({
|
||||
where: {
|
||||
uuid: spaceUuid,
|
||||
spaceType: {
|
||||
type: type,
|
||||
},
|
||||
userSpaces: {
|
||||
user: {
|
||||
uuid: userUuid,
|
||||
},
|
||||
},
|
||||
},
|
||||
relations: ['spaceType', 'userSpaces', 'userSpaces.user'],
|
||||
relations: ['userSpaces', 'userSpaces.user'],
|
||||
});
|
||||
|
||||
if (!spaceData) {
|
||||
throw new BadRequestException(
|
||||
`You do not have permission to access this ${type}`,
|
||||
`You do not have permission to access this space`,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
|
@ -0,0 +1,9 @@
|
||||
import { TuyaResponseInterface } from './tuya.response.interface';
|
||||
|
||||
export interface AddTuyaResponseInterface extends TuyaResponseInterface {
|
||||
result: {
|
||||
id: string;
|
||||
};
|
||||
t?: number;
|
||||
tid?: string;
|
||||
}
|
3
libs/common/src/integrations/tuya/interfaces/index.ts
Normal file
3
libs/common/src/integrations/tuya/interfaces/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './tuya.response.interface';
|
||||
export * from './tap-to-run-action.interface';
|
||||
export * from './automation.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 | { id: string };
|
||||
}
|
251
libs/common/src/integrations/tuya/services/tuya.service.ts
Normal file
251
libs/common/src/integrations/tuya/services/tuya.service.ts
Normal file
@ -0,0 +1,251 @@
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { TuyaContext } from '@tuya/tuya-connector-nodejs';
|
||||
import {
|
||||
AddTuyaResponseInterface,
|
||||
ConvertedAction,
|
||||
TuyaResponseInterface,
|
||||
} from '../interfaces';
|
||||
|
||||
@Injectable()
|
||||
export class TuyaService {
|
||||
private tuya: TuyaContext;
|
||||
|
||||
constructor(private readonly configService: ConfigService) {
|
||||
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 createSpace({ name }: { name: string }) {
|
||||
const path = '/v2.0/cloud/space/creation';
|
||||
const body = { name };
|
||||
|
||||
const response = await this.tuya.request({
|
||||
method: 'POST',
|
||||
path,
|
||||
body,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
return response.result as string;
|
||||
} else {
|
||||
throw new HttpException(
|
||||
'Error creating space in Tuya',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getDeviceDetails(deviceId: string) {
|
||||
const path = `/v1.1/iot-03/devices/${deviceId}`;
|
||||
const response = await this.tuya.request({
|
||||
method: 'GET',
|
||||
path,
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
throw new HttpException(
|
||||
`Error fetching device details: ${response.msg}`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
return response.result;
|
||||
}
|
||||
|
||||
async deleteSceneRule(sceneId: string, spaceId: string) {
|
||||
const path = `/v2.0/cloud/scene/rule?ids=${sceneId}&space_id=${spaceId}`;
|
||||
const response = await this.tuya.request({
|
||||
method: 'DELETE',
|
||||
path,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
return response;
|
||||
} else {
|
||||
throw new HttpException(
|
||||
`Error deleting scene rule: ${response.msg}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getSceneRule(sceneId: string) {
|
||||
const path = `/v2.0/cloud/scene/rule/${sceneId}`;
|
||||
const response = await this.tuya.request({
|
||||
method: 'GET',
|
||||
path,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
return response;
|
||||
} else {
|
||||
throw new HttpException(
|
||||
`Error fetching scene rule: ${response.msg}`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 updateTapToRunScene(
|
||||
sceneTuyaUuid: string,
|
||||
spaceId: string,
|
||||
sceneName: string,
|
||||
actions: ConvertedAction[],
|
||||
decisionExpr: string,
|
||||
) {
|
||||
const path = `/v2.0/cloud/scene/rule/${sceneTuyaUuid}`;
|
||||
const response = await this.tuya.request({
|
||||
method: 'PUT',
|
||||
path,
|
||||
body: {
|
||||
space_id: spaceId,
|
||||
name: sceneName,
|
||||
type: 'scene',
|
||||
decision_expr: decisionExpr,
|
||||
conditions: [],
|
||||
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;
|
||||
}
|
||||
|
||||
async createAutomation(
|
||||
spaceId: string,
|
||||
automationName: string,
|
||||
effectiveTime: any,
|
||||
decisionExpr: string,
|
||||
conditions: any[],
|
||||
actions: any[],
|
||||
) {
|
||||
const path = `/v2.0/cloud/scene/rule`;
|
||||
|
||||
const response: AddTuyaResponseInterface = await this.tuya.request({
|
||||
method: 'POST',
|
||||
path,
|
||||
body: {
|
||||
space_id: spaceId,
|
||||
name: automationName,
|
||||
effective_time: {
|
||||
...effectiveTime,
|
||||
timezone_id: 'Asia/Dubai',
|
||||
},
|
||||
type: 'automation',
|
||||
decision_expr: decisionExpr,
|
||||
conditions: conditions,
|
||||
actions: actions,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
throw new HttpException(response.msg, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
async deleteAutomation(spaceId: string, automationUuid: string) {
|
||||
const path = `/v2.0/cloud/scene/rule?ids=${automationUuid}&space_id=${spaceId}`;
|
||||
const response = await this.tuya.request({
|
||||
method: 'DELETE',
|
||||
path,
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
throw new HttpException(
|
||||
'Failed to delete automation',
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
async updateAutomationState(
|
||||
spaceId: string,
|
||||
automationUuid: string,
|
||||
isEnable: boolean,
|
||||
) {
|
||||
const path = `/v2.0/cloud/scene/rule/state?space_id=${spaceId}`;
|
||||
|
||||
try {
|
||||
const response: TuyaResponseInterface = await this.tuya.request({
|
||||
method: 'PUT',
|
||||
path,
|
||||
body: {
|
||||
ids: automationUuid,
|
||||
is_enable: isEnable,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
throw new HttpException('Automation not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Failed to update automation state',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
145
libs/common/src/models/typeOrmCustom.model.ts
Normal file
145
libs/common/src/models/typeOrmCustom.model.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import { FindManyOptions, Repository } from 'typeorm';
|
||||
import { InternalServerErrorException } from '@nestjs/common';
|
||||
import { BaseResponseDto } from '../dto/base.response.dto';
|
||||
import { PageResponseDto } from '../dto/pagination.response.dto';
|
||||
import { buildTypeORMSortQuery } from '../util/buildTypeORMSortQuery';
|
||||
import { buildTypeORMIncludeQuery } from '../util/buildTypeORMIncludeQuery';
|
||||
import { buildTypeORMWhereClause } from '../util/buildTypeORMWhereClause';
|
||||
import { getPaginationResponseDto } from '../util/getPaginationResponseDto';
|
||||
|
||||
export interface TypeORMCustomModelFindAllQuery {
|
||||
page: number | undefined;
|
||||
size: number | undefined;
|
||||
sort?: string;
|
||||
modelName?: string;
|
||||
include?: string;
|
||||
where?: { [key: string]: unknown };
|
||||
select?: string[];
|
||||
includeDisable?: boolean | string;
|
||||
}
|
||||
interface CustomFindAllQuery {
|
||||
page?: number;
|
||||
size?: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface FindAllQueryWithDefaults extends CustomFindAllQuery {
|
||||
page: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
function getDefaultQueryOptions(
|
||||
query: Partial<TypeORMCustomModelFindAllQuery>,
|
||||
): FindManyOptions & FindAllQueryWithDefaults {
|
||||
const { page, size, includeDisable, modelName, ...rest } = query;
|
||||
|
||||
// Set default if undefined or null
|
||||
const returnPage = page ? Number(page) : 1;
|
||||
const returnSize = size ? Number(size) : 10;
|
||||
const returnIncludeDisable =
|
||||
includeDisable === true || includeDisable === 'true';
|
||||
|
||||
// Return query with defaults and ensure modelName is passed through
|
||||
return {
|
||||
skip: (returnPage - 1) * returnSize,
|
||||
take: returnSize,
|
||||
where: {
|
||||
...rest,
|
||||
},
|
||||
page: returnPage,
|
||||
size: returnSize,
|
||||
includeDisable: returnIncludeDisable,
|
||||
modelName: modelName || query.modelName, // Ensure modelName is passed through
|
||||
};
|
||||
}
|
||||
|
||||
export interface TypeORMCustomModelFindAllQueryWithDefault
|
||||
extends TypeORMCustomModelFindAllQuery {
|
||||
page: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export type TypeORMCustomModelFindAllResponse = {
|
||||
baseResponseDto: BaseResponseDto;
|
||||
paginationResponseDto: PageResponseDto;
|
||||
};
|
||||
|
||||
export function TypeORMCustomModel(repository: Repository<any>) {
|
||||
return Object.assign(repository, {
|
||||
findAll: async function (
|
||||
query: Partial<TypeORMCustomModelFindAllQuery>,
|
||||
): Promise<TypeORMCustomModelFindAllResponse> {
|
||||
// Extract values from the query
|
||||
const {
|
||||
page = 1,
|
||||
size = 10,
|
||||
sort,
|
||||
modelName,
|
||||
include,
|
||||
where,
|
||||
select,
|
||||
} = getDefaultQueryOptions(query);
|
||||
|
||||
// Ensure modelName is set before proceeding
|
||||
if (!modelName) {
|
||||
console.error(
|
||||
'modelName is missing after getDefaultQueryOptions:',
|
||||
query,
|
||||
);
|
||||
throw new InternalServerErrorException(
|
||||
`[TypeORMCustomModel] Cannot findAll with unknown modelName`,
|
||||
);
|
||||
}
|
||||
|
||||
const skip = (page - 1) * size;
|
||||
const order = buildTypeORMSortQuery(sort);
|
||||
const relations = buildTypeORMIncludeQuery(modelName, include);
|
||||
|
||||
// Use the where clause directly, without wrapping it under 'where'
|
||||
const whereClause = buildTypeORMWhereClause({ where });
|
||||
console.log('Final where clause:', whereClause);
|
||||
|
||||
// Ensure the whereClause is passed directly to findAndCount
|
||||
const [data, count] = await repository.findAndCount({
|
||||
where: whereClause,
|
||||
take: size,
|
||||
skip: skip,
|
||||
order: order,
|
||||
select: select,
|
||||
relations: relations,
|
||||
});
|
||||
|
||||
const paginationResponseDto = getPaginationResponseDto(count, page, size);
|
||||
const baseResponseDto: BaseResponseDto = {
|
||||
data,
|
||||
message: getResponseMessage(modelName, { where }),
|
||||
};
|
||||
|
||||
return { baseResponseDto, paginationResponseDto };
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function getResponseMessage(
|
||||
modelName: string,
|
||||
query?: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
where?: any;
|
||||
},
|
||||
): string {
|
||||
if (!query) {
|
||||
return `Success get list ${modelName}`;
|
||||
}
|
||||
|
||||
const { where } = query;
|
||||
if (modelName === 'user' && where && where?.community) {
|
||||
const {
|
||||
some: { communityId },
|
||||
} = where.community;
|
||||
if (typeof communityId === 'string') {
|
||||
return `Success get list ${modelName} belong to community`;
|
||||
}
|
||||
}
|
||||
|
||||
return `Success get list ${modelName}`;
|
||||
}
|
19
libs/common/src/modules/community/dtos/community.dto.ts
Normal file
19
libs/common/src/modules/community/dtos/community.dto.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
|
||||
|
||||
export class CommunityDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public uuid: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public name: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
public description?: string;
|
||||
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
public regionId: string;
|
||||
}
|
1
libs/common/src/modules/community/dtos/index.ts
Normal file
1
libs/common/src/modules/community/dtos/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './community.dto';
|
@ -0,0 +1,34 @@
|
||||
import { Column, Entity, OneToMany, Unique } from 'typeorm';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { CommunityDto } from '../dtos';
|
||||
import { SpaceEntity } from '../../space/entities';
|
||||
|
||||
@Entity({ name: 'community' })
|
||||
@Unique(['name'])
|
||||
export class CommunityEntity extends AbstractEntity<CommunityDto> {
|
||||
@Column({
|
||||
type: 'uuid',
|
||||
default: () => 'gen_random_uuid()',
|
||||
nullable: false,
|
||||
})
|
||||
public uuid: string;
|
||||
|
||||
@Column({
|
||||
length: 255,
|
||||
nullable: false,
|
||||
})
|
||||
name: string;
|
||||
|
||||
@Column({ length: 255, nullable: true })
|
||||
description: string;
|
||||
|
||||
@OneToMany(() => SpaceEntity, (space) => space.community)
|
||||
spaces: SpaceEntity[];
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
length: 255,
|
||||
nullable: true,
|
||||
})
|
||||
externalId: string;
|
||||
}
|
1
libs/common/src/modules/community/entities/index.ts
Normal file
1
libs/common/src/modules/community/entities/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './community.entity';
|
@ -0,0 +1,10 @@
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CommunityEntity } from '../entities';
|
||||
|
||||
@Injectable()
|
||||
export class CommunityRepository extends Repository<CommunityEntity> {
|
||||
constructor(private dataSource: DataSource) {
|
||||
super(CommunityEntity, dataSource.createEntityManager());
|
||||
}
|
||||
}
|
1
libs/common/src/modules/community/repositories/index.ts
Normal file
1
libs/common/src/modules/community/repositories/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './community.repository';
|
@ -1,11 +1,20 @@
|
||||
import { Column, Entity, ManyToOne, OneToMany, Unique, Index } from 'typeorm';
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
Unique,
|
||||
Index,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { DeviceDto, DeviceUserPermissionDto } from '../dtos/device.dto';
|
||||
import { SpaceEntity } from '../../space/entities';
|
||||
import { SpaceEntity, SubspaceEntity } from '../../space/entities';
|
||||
import { ProductEntity } from '../../product/entities';
|
||||
import { UserEntity } from '../../user/entities';
|
||||
import { DeviceNotificationDto } from '../dtos';
|
||||
import { PermissionTypeEntity } from '../../permission/entities';
|
||||
import { SceneDeviceEntity } from '../../scene-device/entities';
|
||||
|
||||
@Entity({ name: 'device' })
|
||||
@Unique(['deviceTuyaUuid'])
|
||||
@ -42,7 +51,7 @@ export class DeviceEntity extends AbstractEntity<DeviceDto> {
|
||||
)
|
||||
deviceUserNotification: DeviceNotificationEntity[];
|
||||
|
||||
@ManyToOne(() => SpaceEntity, (space) => space.devicesSpaceEntity, {
|
||||
@ManyToOne(() => SpaceEntity, (space) => space.devices, {
|
||||
nullable: true,
|
||||
})
|
||||
spaceDevice: SpaceEntity;
|
||||
@ -52,10 +61,19 @@ export class DeviceEntity extends AbstractEntity<DeviceDto> {
|
||||
})
|
||||
productDevice: ProductEntity;
|
||||
|
||||
@ManyToOne(() => SubspaceEntity, (subspace) => subspace.devices, {
|
||||
nullable: true,
|
||||
})
|
||||
@JoinColumn({ name: 'subspace_id' })
|
||||
subspace: SubspaceEntity;
|
||||
|
||||
@Index()
|
||||
@Column({ nullable: false })
|
||||
uuid: string;
|
||||
|
||||
@OneToMany(() => SceneDeviceEntity, (sceneDevice) => sceneDevice.device, {})
|
||||
sceneDevices: SceneDeviceEntity[];
|
||||
|
||||
constructor(partial: Partial<DeviceEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
|
@ -2,6 +2,7 @@ import { Column, Entity, OneToMany } from 'typeorm';
|
||||
import { ProductDto } from '../dtos';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { DeviceEntity } from '../../device/entities';
|
||||
import { SpaceProductEntity } from '../../space/entities/space-product.entity';
|
||||
|
||||
@Entity({ name: 'product' })
|
||||
export class ProductEntity extends AbstractEntity<ProductDto> {
|
||||
@ -16,11 +17,19 @@ export class ProductEntity extends AbstractEntity<ProductDto> {
|
||||
})
|
||||
public prodId: string;
|
||||
|
||||
@Column({
|
||||
nullable: true,
|
||||
})
|
||||
public name: string;
|
||||
|
||||
@Column({
|
||||
nullable: false,
|
||||
})
|
||||
public prodType: string;
|
||||
|
||||
@OneToMany(() => SpaceProductEntity, (spaceProduct) => spaceProduct.product)
|
||||
spaceProducts: SpaceProductEntity[];
|
||||
|
||||
@OneToMany(
|
||||
() => DeviceEntity,
|
||||
(devicesProductEntity) => devicesProductEntity.productDevice,
|
||||
|
1
libs/common/src/modules/scene-device/dtos/index.ts
Normal file
1
libs/common/src/modules/scene-device/dtos/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './scene-device.dto';
|
@ -0,0 +1,20 @@
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class SceneDeviceDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public uuid: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public deviceUuid: string;
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public sceneUuid: string;
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public switchName: string;
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public automationTuyaUuid: string;
|
||||
}
|
1
libs/common/src/modules/scene-device/entities/index.ts
Normal file
1
libs/common/src/modules/scene-device/entities/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './scene-device.entity';
|
@ -0,0 +1,51 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, Unique } from 'typeorm';
|
||||
import { SceneDeviceDto } from '../dtos';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { SceneSwitchesTypeEnum } from '@app/common/constants/scene-switch-type.enum';
|
||||
import { DeviceEntity } from '../../device/entities';
|
||||
import { SceneEntity } from '../../scene/entities';
|
||||
|
||||
@Entity({ name: 'scene-device' })
|
||||
@Unique(['device', 'switchName'])
|
||||
export class SceneDeviceEntity extends AbstractEntity<SceneDeviceDto> {
|
||||
@Column({
|
||||
type: 'uuid',
|
||||
default: () => 'gen_random_uuid()',
|
||||
nullable: false,
|
||||
})
|
||||
public uuid: string;
|
||||
|
||||
@ManyToOne(() => DeviceEntity, (device) => device.sceneDevices, {
|
||||
nullable: false,
|
||||
})
|
||||
@JoinColumn({ name: 'device_uuid' })
|
||||
device: DeviceEntity;
|
||||
|
||||
@Column({
|
||||
nullable: false,
|
||||
})
|
||||
sceneUuid: string;
|
||||
|
||||
@Column({
|
||||
nullable: false,
|
||||
type: 'enum',
|
||||
enum: SceneSwitchesTypeEnum,
|
||||
})
|
||||
switchName: SceneSwitchesTypeEnum;
|
||||
|
||||
@Column({
|
||||
nullable: false,
|
||||
})
|
||||
automationTuyaUuid: string;
|
||||
|
||||
@ManyToOne(() => SceneEntity, (scene) => scene.sceneDevices, {
|
||||
nullable: false,
|
||||
})
|
||||
@JoinColumn({ name: 'scene_uuid' })
|
||||
scene: SceneEntity;
|
||||
|
||||
constructor(partial: Partial<SceneDeviceEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
export * from './scene-device.repository';
|
@ -0,0 +1,10 @@
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { SceneDeviceEntity } from '../entities';
|
||||
|
||||
@Injectable()
|
||||
export class SceneDeviceRepository extends Repository<SceneDeviceEntity> {
|
||||
constructor(private dataSource: DataSource) {
|
||||
super(SceneDeviceEntity, dataSource.createEntityManager());
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { SceneDeviceEntity } from './entities';
|
||||
|
||||
@Module({
|
||||
providers: [],
|
||||
exports: [],
|
||||
controllers: [],
|
||||
imports: [TypeOrmModule.forFeature([SceneDeviceEntity])],
|
||||
})
|
||||
export class SceneDeviceRepositoryModule {}
|
@ -1,7 +1,9 @@
|
||||
import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
|
||||
import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
|
||||
import { SceneDto, SceneIconDto } from '../dtos';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { SceneIconType } from '@app/common/constants/secne-icon-type.enum';
|
||||
import { SceneDeviceEntity } from '../../scene-device/entities';
|
||||
import { SpaceEntity } from '../../space/entities';
|
||||
|
||||
// Define SceneIconEntity before SceneEntity
|
||||
@Entity({ name: 'scene-icon' })
|
||||
@ -44,20 +46,27 @@ export class SceneEntity extends AbstractEntity<SceneDto> {
|
||||
nullable: false,
|
||||
})
|
||||
sceneTuyaUuid: string;
|
||||
@Column({
|
||||
nullable: false,
|
||||
})
|
||||
unitUuid: string;
|
||||
|
||||
@Column({
|
||||
nullable: false,
|
||||
})
|
||||
showInHomePage: boolean;
|
||||
|
||||
@ManyToOne(() => SpaceEntity, (space) => space.scenes, {
|
||||
nullable: false,
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'space_uuid' })
|
||||
space: SpaceEntity;
|
||||
|
||||
@ManyToOne(() => SceneIconEntity, (icon) => icon.scenesIconEntity, {
|
||||
nullable: false,
|
||||
})
|
||||
sceneIcon: SceneIconEntity;
|
||||
|
||||
@OneToMany(() => SceneDeviceEntity, (sceneDevice) => sceneDevice.scene)
|
||||
sceneDevices: SceneDeviceEntity[];
|
||||
|
||||
constructor(partial: Partial<SceneEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
|
@ -1 +1,2 @@
|
||||
export * from './space.dto';
|
||||
export * from './subspace.dto';
|
||||
|
@ -21,13 +21,3 @@ export class SpaceDto {
|
||||
@IsNotEmpty()
|
||||
public invitationCode: string;
|
||||
}
|
||||
|
||||
export class SpaceTypeDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public uuid: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public type: string;
|
||||
}
|
||||
|
29
libs/common/src/modules/space/dtos/subspace.dto.ts
Normal file
29
libs/common/src/modules/space/dtos/subspace.dto.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { SpaceDto } from './space.dto';
|
||||
import { DeviceDto } from '../../device/dtos';
|
||||
|
||||
export class SubspaceDto {
|
||||
@ApiProperty({
|
||||
example: 'd7a44e8a-32d5-4f39-ae2e-013f1245aead',
|
||||
description: 'UUID of the subspace',
|
||||
})
|
||||
uuid: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'Meeting Room 1',
|
||||
description: 'Name of the subspace',
|
||||
})
|
||||
subspaceName: string;
|
||||
|
||||
@ApiProperty({
|
||||
type: () => SpaceDto,
|
||||
description: 'The space to which this subspace belongs',
|
||||
})
|
||||
space: SpaceDto;
|
||||
|
||||
@ApiProperty({
|
||||
example: [],
|
||||
description: 'List of devices assigned to this subspace, if any',
|
||||
})
|
||||
devices: DeviceDto[];
|
||||
}
|
@ -1 +1,3 @@
|
||||
export * from './space.entity';
|
||||
export * from './subspace.entity';
|
||||
export * from './space-link.entity';
|
||||
|
26
libs/common/src/modules/space/entities/space-link.entity.ts
Normal file
26
libs/common/src/modules/space/entities/space-link.entity.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { SpaceEntity } from './space.entity';
|
||||
import { Direction } from '@app/common/constants/direction.enum';
|
||||
|
||||
@Entity({ name: 'space-link' })
|
||||
export class SpaceLinkEntity extends AbstractEntity {
|
||||
@ManyToOne(() => SpaceEntity, { nullable: false, onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'start_space_id' })
|
||||
public startSpace: SpaceEntity;
|
||||
|
||||
@ManyToOne(() => SpaceEntity, { nullable: false, onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'end_space_id' })
|
||||
public endSpace: SpaceEntity;
|
||||
|
||||
@Column({
|
||||
nullable: false,
|
||||
enum: Object.values(Direction),
|
||||
})
|
||||
direction: string;
|
||||
|
||||
constructor(partial: Partial<SpaceLinkEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
import { Column, Entity, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { SpaceEntity } from './space.entity';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { ProductEntity } from '../../product/entities';
|
||||
|
||||
@Entity({ name: 'space-product' })
|
||||
export class SpaceProductEntity extends AbstractEntity<SpaceProductEntity> {
|
||||
@ManyToOne(() => SpaceEntity, (space) => space.spaceProducts, {
|
||||
nullable: false,
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'space_uuid' })
|
||||
space: SpaceEntity;
|
||||
|
||||
@ManyToOne(() => ProductEntity, (product) => product.spaceProducts, {
|
||||
nullable: false,
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'product_uuid' })
|
||||
product: ProductEntity;
|
||||
|
||||
@Column({
|
||||
nullable: false,
|
||||
type: 'int',
|
||||
})
|
||||
productCount: number;
|
||||
|
||||
constructor(partial: Partial<SpaceProductEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
}
|
||||
}
|
@ -1,30 +1,20 @@
|
||||
import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm';
|
||||
import { SpaceDto, SpaceTypeDto } from '../dtos';
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
Unique,
|
||||
} from 'typeorm';
|
||||
import { SpaceDto } from '../dtos';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { UserSpaceEntity } from '../../user/entities';
|
||||
import { DeviceEntity } from '../../device/entities';
|
||||
|
||||
@Entity({ name: 'space-type' })
|
||||
export class SpaceTypeEntity extends AbstractEntity<SpaceTypeDto> {
|
||||
@Column({
|
||||
type: 'uuid',
|
||||
default: () => 'gen_random_uuid()',
|
||||
nullable: false,
|
||||
})
|
||||
public uuid: string;
|
||||
|
||||
@Column({
|
||||
nullable: false,
|
||||
})
|
||||
type: string;
|
||||
|
||||
@OneToMany(() => SpaceEntity, (space) => space.spaceType)
|
||||
spaces: SpaceEntity[];
|
||||
constructor(partial: Partial<SpaceTypeEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
}
|
||||
}
|
||||
import { CommunityEntity } from '../../community/entities';
|
||||
import { SubspaceEntity } from './subspace.entity';
|
||||
import { SpaceLinkEntity } from './space-link.entity';
|
||||
import { SpaceProductEntity } from './space-product.entity';
|
||||
import { SceneEntity } from '../../scene/entities';
|
||||
|
||||
@Entity({ name: 'space' })
|
||||
@Unique(['invitationCode'])
|
||||
@ -45,6 +35,13 @@ export class SpaceEntity extends AbstractEntity<SpaceDto> {
|
||||
})
|
||||
public spaceName: string;
|
||||
|
||||
@ManyToOne(() => CommunityEntity, (community) => community.spaces, {
|
||||
nullable: false,
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'community_id' })
|
||||
community: CommunityEntity;
|
||||
|
||||
@Column({
|
||||
nullable: true,
|
||||
})
|
||||
@ -52,21 +49,54 @@ export class SpaceEntity extends AbstractEntity<SpaceDto> {
|
||||
@ManyToOne(() => SpaceEntity, (space) => space.children, { nullable: true })
|
||||
parent: SpaceEntity;
|
||||
|
||||
@OneToMany(() => SpaceEntity, (space) => space.parent)
|
||||
children: SpaceEntity[];
|
||||
@ManyToOne(() => SpaceTypeEntity, (spaceType) => spaceType.spaces, {
|
||||
@OneToMany(() => SpaceEntity, (space) => space.parent, {
|
||||
nullable: false,
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
spaceType: SpaceTypeEntity;
|
||||
children: SpaceEntity[];
|
||||
|
||||
@OneToMany(() => UserSpaceEntity, (userSpace) => userSpace.space)
|
||||
userSpaces: UserSpaceEntity[];
|
||||
|
||||
@OneToMany(() => SubspaceEntity, (subspace) => subspace.space, {
|
||||
nullable: true,
|
||||
})
|
||||
subspaces?: SubspaceEntity[];
|
||||
|
||||
// Position columns
|
||||
@Column({ type: 'float', nullable: false, default: 0 })
|
||||
public x: number; // X coordinate for position
|
||||
|
||||
@Column({ type: 'float', nullable: false, default: 0 })
|
||||
public y: number; // Y coordinate for position
|
||||
|
||||
@OneToMany(
|
||||
() => DeviceEntity,
|
||||
(devicesSpaceEntity) => devicesSpaceEntity.spaceDevice,
|
||||
)
|
||||
devicesSpaceEntity: DeviceEntity[];
|
||||
devices: DeviceEntity[];
|
||||
|
||||
@OneToMany(() => SpaceLinkEntity, (connection) => connection.startSpace, {
|
||||
nullable: true,
|
||||
})
|
||||
public outgoingConnections: SpaceLinkEntity[];
|
||||
|
||||
@OneToMany(() => SpaceLinkEntity, (connection) => connection.endSpace, {
|
||||
nullable: true,
|
||||
})
|
||||
public incomingConnections: SpaceLinkEntity[];
|
||||
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: 'text',
|
||||
})
|
||||
public icon: string;
|
||||
|
||||
@OneToMany(() => SpaceProductEntity, (spaceProduct) => spaceProduct.space)
|
||||
spaceProducts: SpaceProductEntity[];
|
||||
|
||||
@OneToMany(() => SceneEntity, (scene) => scene.space)
|
||||
scenes: SceneEntity[];
|
||||
|
||||
constructor(partial: Partial<SpaceEntity>) {
|
||||
super();
|
||||
|
37
libs/common/src/modules/space/entities/subspace.entity.ts
Normal file
37
libs/common/src/modules/space/entities/subspace.entity.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { DeviceEntity } from '../../device/entities';
|
||||
import { SpaceEntity } from './space.entity';
|
||||
import { SubspaceDto } from '../dtos';
|
||||
|
||||
@Entity({ name: 'subspace' })
|
||||
export class SubspaceEntity extends AbstractEntity<SubspaceDto> {
|
||||
@Column({
|
||||
type: 'uuid',
|
||||
default: () => 'gen_random_uuid()',
|
||||
nullable: false,
|
||||
})
|
||||
public uuid: string;
|
||||
|
||||
@Column({
|
||||
nullable: false,
|
||||
})
|
||||
public subspaceName: string;
|
||||
|
||||
@ManyToOne(() => SpaceEntity, (space) => space.subspaces, {
|
||||
nullable: false,
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'space_id' })
|
||||
space: SpaceEntity;
|
||||
|
||||
@OneToMany(() => DeviceEntity, (device) => device.subspace, {
|
||||
nullable: true,
|
||||
})
|
||||
devices: DeviceEntity[];
|
||||
|
||||
constructor(partial: Partial<SubspaceEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { SpaceEntity, SpaceTypeEntity } from '../entities';
|
||||
import { SpaceProductEntity } from '../entities/space-product.entity';
|
||||
import { SpaceEntity, SpaceLinkEntity, SubspaceEntity } from '../entities';
|
||||
|
||||
@Injectable()
|
||||
export class SpaceRepository extends Repository<SpaceEntity> {
|
||||
@ -8,10 +9,22 @@ export class SpaceRepository extends Repository<SpaceEntity> {
|
||||
super(SpaceEntity, dataSource.createEntityManager());
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SpaceTypeRepository extends Repository<SpaceTypeEntity> {
|
||||
export class SubspaceRepository extends Repository<SubspaceEntity> {
|
||||
constructor(private dataSource: DataSource) {
|
||||
super(SpaceTypeEntity, dataSource.createEntityManager());
|
||||
super(SubspaceEntity, dataSource.createEntityManager());
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SpaceLinkRepository extends Repository<SpaceLinkEntity> {
|
||||
constructor(private dataSource: DataSource) {
|
||||
super(SpaceLinkEntity, dataSource.createEntityManager());
|
||||
}
|
||||
}
|
||||
@Injectable()
|
||||
export class SpaceProductRepository extends Repository<SpaceProductEntity> {
|
||||
constructor(private dataSource: DataSource) {
|
||||
super(SpaceProductEntity, dataSource.createEntityManager());
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { SpaceEntity, SpaceTypeEntity } from './entities';
|
||||
import { SpaceEntity, SubspaceEntity } from './entities';
|
||||
|
||||
@Module({
|
||||
providers: [],
|
||||
exports: [],
|
||||
controllers: [],
|
||||
imports: [TypeOrmModule.forFeature([SpaceEntity, SpaceTypeEntity])],
|
||||
imports: [TypeOrmModule.forFeature([SpaceEntity, SubspaceEntity])],
|
||||
})
|
||||
export class SpaceRepositoryModule {}
|
||||
|
@ -115,6 +115,7 @@ export class UserEntity extends AbstractEntity<UserDto> {
|
||||
(visitorPassword) => visitorPassword.user,
|
||||
)
|
||||
public visitorPasswords: VisitorPasswordEntity[];
|
||||
|
||||
constructor(partial: Partial<UserEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
|
@ -7,8 +7,6 @@ import { ConfigModule } from '@nestjs/config';
|
||||
import { RoleTypeRepositoryModule } from '../modules/role-type/role.type.repository.module';
|
||||
import { RoleTypeRepository } from '../modules/role-type/repositories';
|
||||
import { RoleTypeSeeder } from './services/role.type.seeder';
|
||||
import { SpaceTypeRepository } from '../modules/space/repositories';
|
||||
import { SpaceTypeSeeder } from './services/space.type.seeder';
|
||||
import { SpaceRepositoryModule } from '../modules/space/space.repository.module';
|
||||
import { SuperAdminSeeder } from './services/supper.admin.seeder';
|
||||
import { UserRepository } from '../modules/user/repositories';
|
||||
@ -25,11 +23,9 @@ import { SceneIconRepository } from '../modules/scene/repositories';
|
||||
providers: [
|
||||
PermissionTypeSeeder,
|
||||
RoleTypeSeeder,
|
||||
SpaceTypeSeeder,
|
||||
SeederService,
|
||||
PermissionTypeRepository,
|
||||
RoleTypeRepository,
|
||||
SpaceTypeRepository,
|
||||
SuperAdminSeeder,
|
||||
UserRepository,
|
||||
UserRoleRepository,
|
||||
|
@ -14,8 +14,6 @@ export class SceneIconSeeder {
|
||||
});
|
||||
|
||||
if (defaultSceneIconData.length <= 0) {
|
||||
console.log('Creating default scene icon...');
|
||||
|
||||
await this.createDefaultSceneIcon();
|
||||
}
|
||||
} catch (err) {
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PermissionTypeSeeder } from './permission.type.seeder';
|
||||
import { RoleTypeSeeder } from './role.type.seeder';
|
||||
import { SpaceTypeSeeder } from './space.type.seeder';
|
||||
import { SuperAdminSeeder } from './supper.admin.seeder';
|
||||
import { RegionSeeder } from './regions.seeder';
|
||||
import { TimeZoneSeeder } from './timezone.seeder';
|
||||
@ -11,7 +10,6 @@ export class SeederService {
|
||||
constructor(
|
||||
private readonly permissionTypeSeeder: PermissionTypeSeeder,
|
||||
private readonly roleTypeSeeder: RoleTypeSeeder,
|
||||
private readonly spaceTypeSeeder: SpaceTypeSeeder,
|
||||
private readonly regionSeeder: RegionSeeder,
|
||||
private readonly timeZoneSeeder: TimeZoneSeeder,
|
||||
private readonly superAdminSeeder: SuperAdminSeeder,
|
||||
@ -21,7 +19,6 @@ export class SeederService {
|
||||
async seed() {
|
||||
await this.permissionTypeSeeder.addPermissionTypeDataIfNotFound();
|
||||
await this.roleTypeSeeder.addRoleTypeDataIfNotFound();
|
||||
await this.spaceTypeSeeder.addSpaceTypeDataIfNotFound();
|
||||
await this.regionSeeder.addRegionDataIfNotFound();
|
||||
await this.timeZoneSeeder.addTimeZoneDataIfNotFound();
|
||||
await this.superAdminSeeder.createSuperAdminIfNotFound();
|
||||
|
@ -1,52 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { SpaceType } from '../../constants/space-type.enum';
|
||||
import { SpaceTypeRepository } from '../../modules/space/repositories';
|
||||
|
||||
@Injectable()
|
||||
export class SpaceTypeSeeder {
|
||||
constructor(private readonly spaceTypeRepository: SpaceTypeRepository) {}
|
||||
|
||||
async addSpaceTypeDataIfNotFound(): Promise<void> {
|
||||
try {
|
||||
const existingSpaceTypes = await this.spaceTypeRepository.find();
|
||||
|
||||
const spaceTypeNames = existingSpaceTypes.map((pt) => pt.type);
|
||||
|
||||
const missingSpaceTypes = [];
|
||||
if (!spaceTypeNames.includes(SpaceType.COMMUNITY)) {
|
||||
missingSpaceTypes.push(SpaceType.COMMUNITY);
|
||||
}
|
||||
if (!spaceTypeNames.includes(SpaceType.BUILDING)) {
|
||||
missingSpaceTypes.push(SpaceType.BUILDING);
|
||||
}
|
||||
if (!spaceTypeNames.includes(SpaceType.FLOOR)) {
|
||||
missingSpaceTypes.push(SpaceType.FLOOR);
|
||||
}
|
||||
if (!spaceTypeNames.includes(SpaceType.UNIT)) {
|
||||
missingSpaceTypes.push(SpaceType.UNIT);
|
||||
}
|
||||
if (!spaceTypeNames.includes(SpaceType.ROOM)) {
|
||||
missingSpaceTypes.push(SpaceType.ROOM);
|
||||
}
|
||||
if (missingSpaceTypes.length > 0) {
|
||||
await this.addSpaceTypeData(missingSpaceTypes);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error while checking space type data:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private async addSpaceTypeData(spaceTypes: string[]): Promise<void> {
|
||||
try {
|
||||
const spaceTypeEntities = spaceTypes.map((type) => ({
|
||||
type,
|
||||
}));
|
||||
|
||||
await this.spaceTypeRepository.save(spaceTypeEntities);
|
||||
} catch (err) {
|
||||
console.error('Error while adding space type data:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
2
libs/common/src/type/optional.type.ts
Normal file
2
libs/common/src/type/optional.type.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export type WithOptional<T, K extends keyof T> = Omit<T, K> & Partial<T>;
|
||||
export type WithRequired<T, K extends keyof T> = Omit<T, K> & Required<T>;
|
43
libs/common/src/util/buildTypeORMIncludeQuery.ts
Normal file
43
libs/common/src/util/buildTypeORMIncludeQuery.ts
Normal file
@ -0,0 +1,43 @@
|
||||
type TypeORMIncludeQuery = string[];
|
||||
|
||||
const mappingInclude: { [key: string]: any } = {
|
||||
roles: {
|
||||
role: true,
|
||||
},
|
||||
users: {
|
||||
user: true,
|
||||
},
|
||||
community: {
|
||||
community: true,
|
||||
},
|
||||
space: {
|
||||
space: true,
|
||||
},
|
||||
subspace: {
|
||||
subspace: true,
|
||||
},
|
||||
};
|
||||
|
||||
export function buildTypeORMIncludeQuery(
|
||||
modelName: string,
|
||||
includeParam?: string,
|
||||
): TypeORMIncludeQuery | undefined {
|
||||
if (includeParam) {
|
||||
const relations: TypeORMIncludeQuery = [];
|
||||
const fieldsToInclude: string[] = includeParam.split(',');
|
||||
|
||||
fieldsToInclude.forEach((field: string) => {
|
||||
if (mappingInclude[field]) {
|
||||
relations.push(field); // Push mapped field
|
||||
} else {
|
||||
console.warn(
|
||||
`Field ${field} not found in mappingInclude for ${modelName}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return relations;
|
||||
}
|
||||
|
||||
return undefined; // If no includes, return undefined
|
||||
}
|
18
libs/common/src/util/buildTypeORMSortQuery.ts
Normal file
18
libs/common/src/util/buildTypeORMSortQuery.ts
Normal file
@ -0,0 +1,18 @@
|
||||
type TypeORMSortQuery = { [key: string]: 'ASC' | 'DESC' };
|
||||
|
||||
export function buildTypeORMSortQuery(
|
||||
sortParam: string | undefined,
|
||||
): TypeORMSortQuery {
|
||||
// sortParam format: userId:asc,createdDate:desc
|
||||
if (!sortParam) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const conditions: string[] = sortParam.split(',');
|
||||
|
||||
return conditions.reduce((acc: TypeORMSortQuery, condition) => {
|
||||
const [field, direction] = condition.split(':').map((str) => str.trim());
|
||||
acc[field] = direction.toUpperCase() === 'DESC' ? 'DESC' : 'ASC';
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
26
libs/common/src/util/buildTypeORMWhereClause.ts
Normal file
26
libs/common/src/util/buildTypeORMWhereClause.ts
Normal file
@ -0,0 +1,26 @@
|
||||
export function buildTypeORMWhereClause({ where }) {
|
||||
if (!where) return {};
|
||||
|
||||
// Remove extra nesting if `where` is wrapped within an additional `where` property
|
||||
const condition = where.where ? where.where : where;
|
||||
|
||||
const convertToNestedObject = (condition: any): any => {
|
||||
const result = {};
|
||||
for (const [key, value] of Object.entries(condition)) {
|
||||
if (key.includes('.')) {
|
||||
const [parentKey, childKey] = key.split('.');
|
||||
result[parentKey] = {
|
||||
...(result[parentKey] || {}),
|
||||
[childKey]: value,
|
||||
};
|
||||
} else {
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
return Array.isArray(condition)
|
||||
? condition.map((item) => convertToNestedObject(item))
|
||||
: convertToNestedObject(condition);
|
||||
}
|
21
libs/common/src/util/getPaginationResponseDto.ts
Normal file
21
libs/common/src/util/getPaginationResponseDto.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { PageResponseDto } from '../dto/pagination.response.dto';
|
||||
|
||||
export function getPaginationResponseDto(
|
||||
count: number,
|
||||
page: number,
|
||||
size: number,
|
||||
): PageResponseDto {
|
||||
const totalItem = count;
|
||||
const totalPage = Math.ceil(totalItem / size);
|
||||
const hasNext = page < totalPage ? true : false;
|
||||
const hasPrevious = page === 1 || page > totalPage ? false : true;
|
||||
|
||||
return {
|
||||
hasNext,
|
||||
hasPrevious,
|
||||
page,
|
||||
size,
|
||||
totalItem,
|
||||
totalPage,
|
||||
};
|
||||
}
|
4
libs/common/src/util/parseToDate.ts
Normal file
4
libs/common/src/util/parseToDate.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export function parseToDate(value: unknown): Date {
|
||||
const valueInNumber = Number(value);
|
||||
return new Date(valueInNumber);
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { ValidateBy, ValidationOptions } from 'class-validator';
|
||||
|
||||
export function IsPageRequestParam(
|
||||
validationOptions?: ValidationOptions,
|
||||
): PropertyDecorator {
|
||||
return ValidateBy(
|
||||
{
|
||||
name: 'IsPageRequestParam',
|
||||
validator: {
|
||||
validate(value) {
|
||||
return IsPageParam(value); // you can return a Promise<boolean> here as well, if you want to make async validation
|
||||
},
|
||||
},
|
||||
},
|
||||
validationOptions,
|
||||
);
|
||||
}
|
||||
|
||||
function IsPageParam(value: any): boolean {
|
||||
return !isNaN(Number(value)) && value > 0;
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { ValidateBy, ValidationOptions } from 'class-validator';
|
||||
|
||||
export function IsSizeRequestParam(
|
||||
validationOptions?: ValidationOptions,
|
||||
): PropertyDecorator {
|
||||
return ValidateBy(
|
||||
{
|
||||
name: 'IsSizeRequestParam',
|
||||
validator: {
|
||||
validate(value) {
|
||||
return IsSizeParam(value); // you can return a Promise<boolean> here as well, if you want to make async validation
|
||||
},
|
||||
},
|
||||
},
|
||||
validationOptions,
|
||||
);
|
||||
}
|
||||
|
||||
function IsSizeParam(value: any): boolean {
|
||||
return !isNaN(Number(value)) && value > -1;
|
||||
}
|
54
libs/common/src/validators/is-sort-param.validator.ts
Normal file
54
libs/common/src/validators/is-sort-param.validator.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { ValidateBy, ValidationOptions } from 'class-validator';
|
||||
|
||||
export function IsSortParam(
|
||||
validationOptions?: ValidationOptions,
|
||||
allowedFieldName?: string[],
|
||||
): PropertyDecorator {
|
||||
return ValidateBy(
|
||||
{
|
||||
name: 'IsSortParam',
|
||||
validator: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
|
||||
validate(value) {
|
||||
return IsValidMultipleSortParam(value, allowedFieldName); // you can return a Promise<boolean> here as well, if you want to make async validation
|
||||
},
|
||||
},
|
||||
},
|
||||
validationOptions,
|
||||
);
|
||||
}
|
||||
|
||||
function IsValidMultipleSortParam(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
|
||||
value: any,
|
||||
allowedFieldName?: string[],
|
||||
): boolean {
|
||||
if (typeof value !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const conditions: string[] = value.split(',');
|
||||
const isValid: boolean = conditions.every((condition) => {
|
||||
const combination: string[] = condition.split(':');
|
||||
if (combination.length !== 2) {
|
||||
return false;
|
||||
}
|
||||
const field = combination[0].trim();
|
||||
const direction = combination[1].trim();
|
||||
|
||||
if (!field) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (allowedFieldName?.length && !allowedFieldName.includes(field)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!['asc', 'desc'].includes(direction.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
return isValid;
|
||||
}
|
@ -3,14 +3,10 @@ import { ConfigModule } from '@nestjs/config';
|
||||
import config from './config';
|
||||
import { AuthenticationModule } from './auth/auth.module';
|
||||
import { UserModule } from './users/user.module';
|
||||
import { RoomModule } from './room/room.module';
|
||||
import { GroupModule } from './group/group.module';
|
||||
import { DeviceModule } from './device/device.module';
|
||||
import { UserDevicePermissionModule } from './user-device-permission/user-device-permission.module';
|
||||
import { CommunityModule } from './community/community.module';
|
||||
import { BuildingModule } from './building/building.module';
|
||||
import { FloorModule } from './floor/floor.module';
|
||||
import { UnitModule } from './unit/unit.module';
|
||||
import { RoleModule } from './role/role.module';
|
||||
import { SeederModule } from '@app/common/seed/seeder.module';
|
||||
import { UserNotificationModule } from './user-notification/user-notification.module';
|
||||
@ -24,6 +20,8 @@ import { RegionModule } from './region/region.module';
|
||||
import { TimeZoneModule } from './timezone/timezone.module';
|
||||
import { VisitorPasswordModule } from './vistor-password/visitor-password.module';
|
||||
import { ScheduleModule } from './schedule/schedule.module';
|
||||
import { SpaceModule } from './space/space.module';
|
||||
import { ProductModule } from './product';
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
@ -33,11 +31,9 @@ import { ScheduleModule } from './schedule/schedule.module';
|
||||
UserModule,
|
||||
RoleModule,
|
||||
CommunityModule,
|
||||
BuildingModule,
|
||||
FloorModule,
|
||||
UnitModule,
|
||||
RoomModule,
|
||||
RoomModule,
|
||||
|
||||
SpaceModule,
|
||||
|
||||
GroupModule,
|
||||
DeviceModule,
|
||||
DeviceMessagesSubscriptionModule,
|
||||
@ -51,6 +47,7 @@ import { ScheduleModule } from './schedule/schedule.module';
|
||||
TimeZoneModule,
|
||||
VisitorPasswordModule,
|
||||
ScheduleModule,
|
||||
ProductModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { UserAuthService } from '../services/user-auth.service';
|
||||
import { UserSignUpDto } from '../dtos/user-auth.dto';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { ResponseMessage } from '../../../libs/common/src/response/response.decorator';
|
||||
import { UserLoginDto } from '../dtos/user-login.dto';
|
||||
import { ForgetPasswordDto, UserOtpDto, VerifyOtpDto } from '../dtos';
|
||||
@ -18,17 +18,22 @@ import { RefreshTokenGuard } from '@app/common/guards/jwt-refresh.auth.guard';
|
||||
import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard';
|
||||
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
|
||||
import { OtpType } from '@app/common/constants/otp-type.enum';
|
||||
import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||
|
||||
@Controller({
|
||||
version: EnableDisableStatusEnum.ENABLED,
|
||||
path: 'authentication',
|
||||
path: ControllerRoute.AUTHENTICATION.ROUTE,
|
||||
})
|
||||
@ApiTags('Auth')
|
||||
@ApiTags('Authentication Module')
|
||||
export class UserAuthController {
|
||||
constructor(private readonly userAuthService: UserAuthService) {}
|
||||
|
||||
@ResponseMessage('User Registered Successfully')
|
||||
@Post('user/signup')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.AUTHENTICATION.ACTIONS.SIGN_UP_SUMMARY,
|
||||
description: ControllerRoute.AUTHENTICATION.ACTIONS.SIGN_UP_DESCRIPTION,
|
||||
})
|
||||
async signUp(@Body() userSignUpDto: UserSignUpDto) {
|
||||
const signupUser = await this.userAuthService.signUp(userSignUpDto);
|
||||
return {
|
||||
@ -41,8 +46,12 @@ export class UserAuthController {
|
||||
};
|
||||
}
|
||||
|
||||
@ResponseMessage('user logged in successfully')
|
||||
@ResponseMessage('User Logged in Successfully')
|
||||
@Post('user/login')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.AUTHENTICATION.ACTIONS.LOGIN_SUMMARY,
|
||||
description: ControllerRoute.AUTHENTICATION.ACTIONS.LOGIN_DESCRIPTION,
|
||||
})
|
||||
async userLogin(@Body() data: UserLoginDto) {
|
||||
const accessToken = await this.userAuthService.userLogin(data);
|
||||
return {
|
||||
@ -53,6 +62,10 @@ export class UserAuthController {
|
||||
}
|
||||
|
||||
@Post('user/send-otp')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.AUTHENTICATION.ACTIONS.SEND_OTP_SUMMARY,
|
||||
description: ControllerRoute.AUTHENTICATION.ACTIONS.SEND_OTP_DESCRIPTION,
|
||||
})
|
||||
async sendOtp(@Body() otpDto: UserOtpDto) {
|
||||
const otpCode = await this.userAuthService.generateOTP(otpDto);
|
||||
return {
|
||||
@ -60,11 +73,15 @@ export class UserAuthController {
|
||||
data: {
|
||||
...otpCode,
|
||||
},
|
||||
message: 'Otp Send Successfully',
|
||||
message: 'Otp Sent Successfully',
|
||||
};
|
||||
}
|
||||
|
||||
@Post('user/verify-otp')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.AUTHENTICATION.ACTIONS.VERIFY_OTP_SUMMARY,
|
||||
description: ControllerRoute.AUTHENTICATION.ACTIONS.VERIFY_OTP_DESCRIPTION,
|
||||
})
|
||||
async verifyOtp(@Body() verifyOtpDto: VerifyOtpDto) {
|
||||
await this.userAuthService.verifyOTP(verifyOtpDto);
|
||||
return {
|
||||
@ -75,6 +92,11 @@ export class UserAuthController {
|
||||
}
|
||||
|
||||
@Post('user/forget-password')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.AUTHENTICATION.ACTIONS.FORGET_PASSWORD_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.AUTHENTICATION.ACTIONS.FORGET_PASSWORD_DESCRIPTION,
|
||||
})
|
||||
async forgetPassword(@Body() forgetPasswordDto: ForgetPasswordDto) {
|
||||
const otpResult = await this.userAuthService.verifyOTP(
|
||||
{
|
||||
@ -102,6 +124,10 @@ export class UserAuthController {
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(SuperAdminRoleGuard)
|
||||
@Get('user')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.AUTHENTICATION.ACTIONS.USER_LIST_SUMMARY,
|
||||
description: ControllerRoute.AUTHENTICATION.ACTIONS.USER_LIST_DESCRIPTION,
|
||||
})
|
||||
async userList() {
|
||||
const userList = await this.userAuthService.userList();
|
||||
return {
|
||||
@ -114,6 +140,11 @@ export class UserAuthController {
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(RefreshTokenGuard)
|
||||
@Get('refresh-token')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.AUTHENTICATION.ACTIONS.REFRESH_TOKEN_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.AUTHENTICATION.ACTIONS.REFRESH_TOKEN_DESCRIPTION,
|
||||
})
|
||||
async refreshToken(@Req() req) {
|
||||
const refreshToken = await this.userAuthService.refreshToken(
|
||||
req.user.uuid,
|
||||
|
@ -8,16 +8,28 @@ import { DeviceService } from 'src/device/services';
|
||||
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
||||
import { ProductRepository } from '@app/common/modules/product/repositories';
|
||||
import { DeviceStatusFirebaseModule } from '@app/common/firebase/devices-status/devices-status.module';
|
||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||
import { SceneService } from 'src/scene/services';
|
||||
import {
|
||||
SceneIconRepository,
|
||||
SceneRepository,
|
||||
} from '@app/common/modules/scene/repositories';
|
||||
import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusFirebaseModule],
|
||||
controllers: [AutomationController],
|
||||
providers: [
|
||||
AutomationService,
|
||||
TuyaService,
|
||||
SpaceRepository,
|
||||
DeviceService,
|
||||
DeviceRepository,
|
||||
ProductRepository,
|
||||
SceneService,
|
||||
SceneIconRepository,
|
||||
SceneRepository,
|
||||
SceneDeviceRepository,
|
||||
],
|
||||
exports: [AutomationService],
|
||||
})
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
Put,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
||||
import {
|
||||
AddAutomationDto,
|
||||
UpdateAutomationDto,
|
||||
@ -18,11 +18,13 @@ import {
|
||||
} from '../dtos/automation.dto';
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
|
||||
import { AutomationParamDto, SpaceParamDto } from '../dtos';
|
||||
import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||
|
||||
@ApiTags('Automation Module')
|
||||
@Controller({
|
||||
version: EnableDisableStatusEnum.ENABLED,
|
||||
path: 'automation',
|
||||
path: ControllerRoute.AUTOMATION.ROUTE,
|
||||
})
|
||||
export class AutomationController {
|
||||
constructor(private readonly automationService: AutomationService) {}
|
||||
@ -30,6 +32,10 @@ export class AutomationController {
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post()
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.AUTOMATION.ACTIONS.ADD_AUTOMATION_SUMMARY,
|
||||
description: ControllerRoute.AUTOMATION.ACTIONS.ADD_AUTOMATION_DESCRIPTION,
|
||||
})
|
||||
async addAutomation(@Body() addAutomationDto: AddAutomationDto) {
|
||||
const automation =
|
||||
await this.automationService.addAutomation(addAutomationDto);
|
||||
@ -40,45 +46,68 @@ export class AutomationController {
|
||||
data: automation,
|
||||
};
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get(':unitUuid')
|
||||
async getAutomationByUnit(@Param('unitUuid') unitUuid: string) {
|
||||
const automation =
|
||||
await this.automationService.getAutomationByUnit(unitUuid);
|
||||
@Get(':spaceUuid')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.AUTOMATION.ACTIONS.GET_AUTOMATION_BY_SPACE_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.AUTOMATION.ACTIONS.GET_AUTOMATION_BY_SPACE_DESCRIPTION,
|
||||
})
|
||||
async getAutomationBySpace(@Param() param: SpaceParamDto) {
|
||||
const automation = await this.automationService.getAutomationBySpace(
|
||||
param.spaceUuid,
|
||||
);
|
||||
return automation;
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('details/:automationId')
|
||||
async getAutomationDetails(@Param('automationId') automationId: string) {
|
||||
const automation =
|
||||
await this.automationService.getAutomationDetails(automationId);
|
||||
@Get('details/:automationUuid')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.AUTOMATION.ACTIONS.GET_AUTOMATION_DETAILS_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.AUTOMATION.ACTIONS.GET_AUTOMATION_DETAILS_DESCRIPTION,
|
||||
})
|
||||
async getAutomationDetails(@Param() param: AutomationParamDto) {
|
||||
const automation = await this.automationService.getAutomationDetails(
|
||||
param.automationUuid,
|
||||
);
|
||||
return automation;
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Delete(':unitUuid/:automationId')
|
||||
async deleteAutomation(
|
||||
@Param('unitUuid') unitUuid: string,
|
||||
@Param('automationId') automationId: string,
|
||||
) {
|
||||
await this.automationService.deleteAutomation(unitUuid, automationId);
|
||||
@Delete(':automationUuid')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.AUTOMATION.ACTIONS.DELETE_AUTOMATION_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.AUTOMATION.ACTIONS.DELETE_AUTOMATION_DESCRIPTION,
|
||||
})
|
||||
async deleteAutomation(@Param() param: AutomationParamDto) {
|
||||
await this.automationService.deleteAutomation(param);
|
||||
return {
|
||||
statusCode: HttpStatus.OK,
|
||||
message: 'Automation Deleted Successfully',
|
||||
};
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Put(':automationId')
|
||||
@Put(':automationUuid')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.AUTOMATION.ACTIONS.UPDATE_AUTOMATION_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.AUTOMATION.ACTIONS.UPDATE_AUTOMATION_DESCRIPTION,
|
||||
})
|
||||
async updateAutomation(
|
||||
@Body() updateAutomationDto: UpdateAutomationDto,
|
||||
@Param('automationId') automationId: string,
|
||||
@Param() param: AutomationParamDto,
|
||||
) {
|
||||
const automation = await this.automationService.updateAutomation(
|
||||
updateAutomationDto,
|
||||
automationId,
|
||||
param.automationUuid,
|
||||
);
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
@ -87,16 +116,23 @@ export class AutomationController {
|
||||
data: automation,
|
||||
};
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Put('status/:automationId')
|
||||
@Put('status/:automationUuid')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
ControllerRoute.AUTOMATION.ACTIONS.UPDATE_AUTOMATION_STATUS_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.AUTOMATION.ACTIONS.UPDATE_AUTOMATION_STATUS_DESCRIPTION,
|
||||
})
|
||||
async updateAutomationStatus(
|
||||
@Body() updateAutomationStatusDto: UpdateAutomationStatusDto,
|
||||
@Param('automationId') automationId: string,
|
||||
@Param() param: AutomationParamDto,
|
||||
) {
|
||||
await this.automationService.updateAutomationStatus(
|
||||
updateAutomationStatusDto,
|
||||
automationId,
|
||||
param.automationUuid,
|
||||
);
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
class EffectiveTime {
|
||||
export class EffectiveTime {
|
||||
@ApiProperty({ description: 'Start time', required: true })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ -114,10 +114,10 @@ class Action {
|
||||
}
|
||||
|
||||
export class AddAutomationDto {
|
||||
@ApiProperty({ description: 'Unit ID', required: true })
|
||||
@ApiProperty({ description: 'Space ID', required: true })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public unitUuid: string;
|
||||
public spaceUuid: string;
|
||||
|
||||
@ApiProperty({ description: 'Automation name', required: true })
|
||||
@IsString()
|
||||
@ -197,10 +197,10 @@ export class UpdateAutomationDto {
|
||||
}
|
||||
}
|
||||
export class UpdateAutomationStatusDto {
|
||||
@ApiProperty({ description: 'Unit uuid', required: true })
|
||||
@ApiProperty({ description: 'Space uuid', required: true })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public unitUuid: string;
|
||||
public spaceUuid: string;
|
||||
|
||||
@ApiProperty({ description: 'Is enable', required: true })
|
||||
@IsBoolean()
|
||||
|
12
src/automation/dtos/automation.param.dto.ts
Normal file
12
src/automation/dtos/automation.param.dto.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class AutomationParamDto {
|
||||
@ApiProperty({
|
||||
description: 'TuyaId of the automation',
|
||||
example: 'SfFi2Tbn09btes84',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
automationUuid: string;
|
||||
}
|
19
src/automation/dtos/delete.automation.param.dto.ts
Normal file
19
src/automation/dtos/delete.automation.param.dto.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString, IsUUID } from 'class-validator';
|
||||
|
||||
export class DeleteAutomationParamDto {
|
||||
@ApiProperty({
|
||||
description: 'UUID of the Space',
|
||||
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
|
||||
})
|
||||
@IsUUID()
|
||||
spaceUuid: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'TuyaId of the automation',
|
||||
example: 'SfFi2Tbn09btes84',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
automationUuid: string;
|
||||
}
|
@ -1 +1,4 @@
|
||||
export * from './automation.dto';
|
||||
export * from './space.param.dto';
|
||||
export * from './automation.param.dto';
|
||||
export * from './delete.automation.param.dto';
|
||||
|
11
src/automation/dtos/space.param.dto.ts
Normal file
11
src/automation/dtos/space.param.dto.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsUUID } from 'class-validator';
|
||||
|
||||
export class SpaceParamDto {
|
||||
@ApiProperty({
|
||||
description: 'UUID of the space',
|
||||
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
|
||||
})
|
||||
@IsUUID()
|
||||
spaceUuid: string;
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
import { EffectiveTime } from '../dtos';
|
||||
|
||||
export interface AddAutomationInterface {
|
||||
success: boolean;
|
||||
msg?: string;
|
||||
@ -5,7 +7,7 @@ export interface AddAutomationInterface {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
export interface GetAutomationByUnitInterface {
|
||||
export interface GetAutomationBySpaceInterface {
|
||||
success: boolean;
|
||||
msg?: string;
|
||||
result: {
|
||||
@ -48,3 +50,12 @@ export interface AutomationDetailsResult {
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface AddAutomationParams {
|
||||
actions: Action[];
|
||||
conditions: Condition[];
|
||||
automationName: string;
|
||||
effectiveTime: EffectiveTime;
|
||||
decisionExpr: string;
|
||||
spaceTuyaId: string;
|
||||
}
|
||||
|
@ -7,27 +7,32 @@ import {
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import {
|
||||
AddAutomationDto,
|
||||
AutomationParamDto,
|
||||
UpdateAutomationDto,
|
||||
UpdateAutomationStatusDto,
|
||||
} from '../dtos';
|
||||
import { GetUnitByUuidInterface } from 'src/unit/interface/unit.interface';
|
||||
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 {
|
||||
AddAutomationInterface,
|
||||
Action,
|
||||
AddAutomationParams,
|
||||
AutomationDetailsResult,
|
||||
AutomationResponseData,
|
||||
DeleteAutomationInterface,
|
||||
GetAutomationByUnitInterface,
|
||||
Condition,
|
||||
GetAutomationBySpaceInterface,
|
||||
} from '../interface/automation.interface';
|
||||
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
import {
|
||||
ActionExecutorEnum,
|
||||
AUTO_PREFIX,
|
||||
AUTOMATION_TYPE,
|
||||
EntityTypeEnum,
|
||||
} from '@app/common/constants/automation.enum';
|
||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||
import { ConvertedAction } from '@app/common/integrations/tuya/interfaces';
|
||||
import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories';
|
||||
|
||||
@Injectable()
|
||||
export class AutomationService {
|
||||
@ -36,6 +41,8 @@ export class AutomationService {
|
||||
private readonly configService: ConfigService,
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
private readonly deviceService: DeviceService,
|
||||
private readonly tuyaService: TuyaService,
|
||||
private readonly sceneDeviceRepository: SceneDeviceRepository,
|
||||
) {
|
||||
const accessKey = this.configService.get<string>('auth-config.ACCESS_KEY');
|
||||
const secretKey = this.configService.get<string>('auth-config.SECRET_KEY');
|
||||
@ -47,77 +54,28 @@ export class AutomationService {
|
||||
});
|
||||
}
|
||||
|
||||
async addAutomation(addAutomationDto: AddAutomationDto, spaceTuyaId = null) {
|
||||
async addAutomation(addAutomationDto: AddAutomationDto) {
|
||||
try {
|
||||
let unitSpaceTuyaId;
|
||||
if (!spaceTuyaId) {
|
||||
const unitDetails = await this.getUnitByUuid(addAutomationDto.unitUuid);
|
||||
|
||||
unitSpaceTuyaId = unitDetails.spaceTuyaUuid;
|
||||
if (!unitDetails) {
|
||||
throw new BadRequestException('Invalid unit UUID');
|
||||
}
|
||||
} else {
|
||||
unitSpaceTuyaId = spaceTuyaId;
|
||||
}
|
||||
|
||||
const actions = addAutomationDto.actions.map((action) =>
|
||||
convertKeysToSnakeCase(action),
|
||||
);
|
||||
const conditions = addAutomationDto.conditions.map((condition) =>
|
||||
convertKeysToSnakeCase(condition),
|
||||
);
|
||||
|
||||
for (const action of actions) {
|
||||
if (action.action_executor === ActionExecutorEnum.DEVICE_ISSUE) {
|
||||
const device = await this.deviceService.getDeviceByDeviceUuid(
|
||||
action.entity_id,
|
||||
false,
|
||||
);
|
||||
if (device) {
|
||||
action.entity_id = device.deviceTuyaUuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const condition of conditions) {
|
||||
if (condition.entity_type === EntityTypeEnum.DEVICE_REPORT) {
|
||||
const device = await this.deviceService.getDeviceByDeviceUuid(
|
||||
condition.entity_id,
|
||||
false,
|
||||
);
|
||||
if (device) {
|
||||
condition.entity_id = device.deviceTuyaUuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const path = `/v2.0/cloud/scene/rule`;
|
||||
const response: AddAutomationInterface = await this.tuya.request({
|
||||
method: 'POST',
|
||||
path,
|
||||
body: {
|
||||
space_id: unitSpaceTuyaId,
|
||||
name: addAutomationDto.automationName,
|
||||
effective_time: {
|
||||
...addAutomationDto.effectiveTime,
|
||||
timezone_id: 'Asia/Dubai',
|
||||
},
|
||||
type: 'automation',
|
||||
decision_expr: addAutomationDto.decisionExpr,
|
||||
conditions: conditions,
|
||||
actions: actions,
|
||||
},
|
||||
const {
|
||||
automationName,
|
||||
effectiveTime,
|
||||
decisionExpr,
|
||||
actions,
|
||||
conditions,
|
||||
} = addAutomationDto;
|
||||
const space = await this.getSpaceByUuid(addAutomationDto.spaceUuid);
|
||||
const response = await this.add({
|
||||
automationName,
|
||||
effectiveTime,
|
||||
decisionExpr,
|
||||
actions,
|
||||
conditions,
|
||||
spaceTuyaId: space.spaceTuyaUuid,
|
||||
});
|
||||
if (!response.success) {
|
||||
throw new HttpException(response.msg, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
return {
|
||||
id: response.result.id,
|
||||
};
|
||||
return response;
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
throw err;
|
||||
} else {
|
||||
throw new HttpException(
|
||||
err.message || 'Automation not found',
|
||||
@ -126,45 +84,70 @@ export class AutomationService {
|
||||
}
|
||||
}
|
||||
}
|
||||
async getUnitByUuid(unitUuid: string): Promise<GetUnitByUuidInterface> {
|
||||
|
||||
async add(params: AddAutomationParams) {
|
||||
try {
|
||||
const unit = await this.spaceRepository.findOne({
|
||||
const formattedActions = await this.prepareActions(params.actions);
|
||||
const formattedCondition = await this.prepareConditions(
|
||||
params.conditions,
|
||||
);
|
||||
|
||||
const response = await this.tuyaService.createAutomation(
|
||||
params.spaceTuyaId,
|
||||
params.automationName,
|
||||
params.effectiveTime,
|
||||
params.decisionExpr,
|
||||
formattedCondition,
|
||||
formattedActions,
|
||||
);
|
||||
|
||||
return {
|
||||
id: response?.result.id,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Failed to add automation',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getSpaceByUuid(spaceUuid: string) {
|
||||
try {
|
||||
const space = await this.spaceRepository.findOne({
|
||||
where: {
|
||||
uuid: unitUuid,
|
||||
spaceType: {
|
||||
type: SpaceType.UNIT,
|
||||
},
|
||||
uuid: spaceUuid,
|
||||
},
|
||||
relations: ['spaceType'],
|
||||
relations: ['community'],
|
||||
});
|
||||
if (!unit || !unit.spaceType || unit.spaceType.type !== SpaceType.UNIT) {
|
||||
throw new BadRequestException('Invalid unit UUID');
|
||||
if (!space) {
|
||||
throw new BadRequestException(`Invalid space UUID ${spaceUuid}`);
|
||||
}
|
||||
return {
|
||||
uuid: unit.uuid,
|
||||
createdAt: unit.createdAt,
|
||||
updatedAt: unit.updatedAt,
|
||||
name: unit.spaceName,
|
||||
type: unit.spaceType.type,
|
||||
spaceTuyaUuid: unit.spaceTuyaUuid,
|
||||
uuid: space.uuid,
|
||||
createdAt: space.createdAt,
|
||||
updatedAt: space.updatedAt,
|
||||
name: space.spaceName,
|
||||
spaceTuyaUuid: space.community.externalId,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Unit not found', HttpStatus.NOT_FOUND);
|
||||
throw new HttpException('Space not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
async getAutomationByUnit(unitUuid: string) {
|
||||
|
||||
async getAutomationBySpace(spaceUuid: string) {
|
||||
try {
|
||||
const unit = await this.getUnitByUuid(unitUuid);
|
||||
if (!unit.spaceTuyaUuid) {
|
||||
throw new BadRequestException('Invalid unit UUID');
|
||||
const space = await this.getSpaceByUuid(spaceUuid);
|
||||
if (!space.spaceTuyaUuid) {
|
||||
throw new BadRequestException(`Invalid space UUID ${spaceUuid}`);
|
||||
}
|
||||
|
||||
const path = `/v2.0/cloud/scene/rule?space_id=${unit.spaceTuyaUuid}&type=automation`;
|
||||
const response: GetAutomationByUnitInterface = await this.tuya.request({
|
||||
const path = `/v2.0/cloud/scene/rule?space_id=${space.spaceTuyaUuid}&type=automation`;
|
||||
const response: GetAutomationBySpaceInterface = await this.tuya.request({
|
||||
method: 'GET',
|
||||
path,
|
||||
});
|
||||
@ -173,14 +156,16 @@ export class AutomationService {
|
||||
throw new HttpException(response.msg, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
return response.result.list.map((item) => {
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
status: item.status,
|
||||
type: 'automation',
|
||||
};
|
||||
});
|
||||
return response.result.list
|
||||
.filter((item) => item.name && !item.name.startsWith(AUTO_PREFIX))
|
||||
.map((item) => {
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
status: item.status,
|
||||
type: AUTOMATION_TYPE,
|
||||
};
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
@ -192,11 +177,12 @@ export class AutomationService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getTapToRunSceneDetailsTuya(
|
||||
sceneId: string,
|
||||
sceneUuid: string,
|
||||
): Promise<AutomationDetailsResult> {
|
||||
try {
|
||||
const path = `/v2.0/cloud/scene/rule/${sceneId}`;
|
||||
const path = `/v2.0/cloud/scene/rule/${sceneUuid}`;
|
||||
const response = await this.tuya.request({
|
||||
method: 'GET',
|
||||
path,
|
||||
@ -224,9 +210,9 @@ export class AutomationService {
|
||||
}
|
||||
}
|
||||
}
|
||||
async getAutomationDetails(automationId: string, withSpaceId = false) {
|
||||
async getAutomationDetails(automationUuid: string, withSpaceId = false) {
|
||||
try {
|
||||
const path = `/v2.0/cloud/scene/rule/${automationId}`;
|
||||
const path = `/v2.0/cloud/scene/rule/${automationUuid}`;
|
||||
const response = await this.tuya.request({
|
||||
method: 'GET',
|
||||
path,
|
||||
@ -252,6 +238,8 @@ export class AutomationService {
|
||||
|
||||
if (device) {
|
||||
action.entityId = device.uuid;
|
||||
action.productUuid = device.productDevice.uuid;
|
||||
action.productType = device.productDevice.prodType;
|
||||
}
|
||||
} else if (
|
||||
action.actionExecutor !== ActionExecutorEnum.DEVICE_ISSUE &&
|
||||
@ -280,6 +268,8 @@ export class AutomationService {
|
||||
|
||||
if (device) {
|
||||
condition.entityId = device.uuid;
|
||||
condition.productUuid = device.productDevice.uuid;
|
||||
condition.productType = device.productDevice.prodType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -312,37 +302,64 @@ export class AutomationService {
|
||||
}
|
||||
}
|
||||
|
||||
async deleteAutomation(
|
||||
unitUuid: string,
|
||||
automationId: string,
|
||||
spaceTuyaId = null,
|
||||
) {
|
||||
async deleteAutomation(param: AutomationParamDto) {
|
||||
try {
|
||||
let unitSpaceTuyaId;
|
||||
if (!spaceTuyaId) {
|
||||
const unitDetails = await this.getUnitByUuid(unitUuid);
|
||||
unitSpaceTuyaId = unitDetails.spaceTuyaUuid;
|
||||
if (!unitSpaceTuyaId) {
|
||||
throw new BadRequestException('Invalid unit UUID');
|
||||
}
|
||||
} else {
|
||||
unitSpaceTuyaId = spaceTuyaId;
|
||||
}
|
||||
const { automationUuid } = param;
|
||||
|
||||
const path = `/v2.0/cloud/scene/rule?ids=${automationId}&space_id=${unitSpaceTuyaId}`;
|
||||
const response: DeleteAutomationInterface = await this.tuya.request({
|
||||
method: 'DELETE',
|
||||
path,
|
||||
const automation = await this.getAutomationDetails(automationUuid, true);
|
||||
|
||||
if (!automation && !automation.spaceId) {
|
||||
throw new HttpException(
|
||||
`Invalid automationid ${automationUuid}`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
const existingSceneDevice = await this.sceneDeviceRepository.findOne({
|
||||
where: { automationTuyaUuid: automationUuid },
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
throw new HttpException('Automation not found', HttpStatus.NOT_FOUND);
|
||||
if (existingSceneDevice) {
|
||||
await this.sceneDeviceRepository.delete({
|
||||
automationTuyaUuid: automationUuid,
|
||||
});
|
||||
}
|
||||
|
||||
const response = this.tuyaService.deleteAutomation(
|
||||
automation.spaceId,
|
||||
automationUuid,
|
||||
);
|
||||
return response;
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
if (err instanceof HttpException) {
|
||||
throw err;
|
||||
} else {
|
||||
throw new HttpException(
|
||||
err.message || 'Automation not found',
|
||||
err.status || HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async delete(tuyaSpaceId: string, automationUuid: string) {
|
||||
try {
|
||||
const existingSceneDevice = await this.sceneDeviceRepository.findOne({
|
||||
where: { automationTuyaUuid: automationUuid },
|
||||
});
|
||||
|
||||
if (existingSceneDevice) {
|
||||
await this.sceneDeviceRepository.delete({
|
||||
automationTuyaUuid: automationUuid,
|
||||
});
|
||||
}
|
||||
const response = await this.tuyaService.deleteAutomation(
|
||||
tuyaSpaceId,
|
||||
automationUuid,
|
||||
);
|
||||
return response;
|
||||
} catch (err) {
|
||||
if (err instanceof HttpException) {
|
||||
throw err;
|
||||
} else {
|
||||
throw new HttpException(
|
||||
err.message || 'Automation not found',
|
||||
@ -354,26 +371,30 @@ export class AutomationService {
|
||||
|
||||
async updateAutomation(
|
||||
updateAutomationDto: UpdateAutomationDto,
|
||||
automationId: string,
|
||||
automationUuid: string,
|
||||
) {
|
||||
const { actions, conditions, automationName, effectiveTime, decisionExpr } =
|
||||
updateAutomationDto;
|
||||
try {
|
||||
const spaceTuyaId = await this.getAutomationDetails(automationId, true);
|
||||
if (!spaceTuyaId.spaceId) {
|
||||
const automation = await this.getAutomationDetails(automationUuid, true);
|
||||
if (!automation.spaceId) {
|
||||
throw new HttpException(
|
||||
"Automation doesn't exist",
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
const addAutomation = {
|
||||
...updateAutomationDto,
|
||||
unitUuid: null,
|
||||
};
|
||||
const newAutomation = await this.addAutomation(
|
||||
addAutomation,
|
||||
spaceTuyaId.spaceId,
|
||||
);
|
||||
|
||||
const newAutomation = await this.add({
|
||||
actions,
|
||||
conditions,
|
||||
automationName,
|
||||
effectiveTime,
|
||||
decisionExpr,
|
||||
spaceTuyaId: automation.spaceId,
|
||||
});
|
||||
|
||||
if (newAutomation.id) {
|
||||
await this.deleteAutomation(null, automationId, spaceTuyaId.spaceId);
|
||||
await this.delete(automation.spaceId, automationUuid);
|
||||
return newAutomation;
|
||||
}
|
||||
} catch (err) {
|
||||
@ -389,29 +410,23 @@ export class AutomationService {
|
||||
}
|
||||
async updateAutomationStatus(
|
||||
updateAutomationStatusDto: UpdateAutomationStatusDto,
|
||||
automationId: string,
|
||||
automationUuid: string,
|
||||
) {
|
||||
const { isEnable, spaceUuid } = updateAutomationStatusDto;
|
||||
try {
|
||||
const unitDetails = await this.getUnitByUuid(
|
||||
updateAutomationStatusDto.unitUuid,
|
||||
const space = await this.getSpaceByUuid(spaceUuid);
|
||||
if (!space.spaceTuyaUuid) {
|
||||
throw new HttpException(
|
||||
`Invalid space UUID ${spaceUuid}`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const response = await this.tuyaService.updateAutomationState(
|
||||
space.spaceTuyaUuid,
|
||||
automationUuid,
|
||||
isEnable,
|
||||
);
|
||||
if (!unitDetails.spaceTuyaUuid) {
|
||||
throw new BadRequestException('Invalid unit UUID');
|
||||
}
|
||||
|
||||
const path = `/v2.0/cloud/scene/rule/state?space_id=${unitDetails.spaceTuyaUuid}`;
|
||||
const response: DeleteAutomationInterface = await this.tuya.request({
|
||||
method: 'PUT',
|
||||
path,
|
||||
body: {
|
||||
ids: automationId,
|
||||
is_enable: updateAutomationStatusDto.isEnable,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
throw new HttpException('Automation not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (err) {
|
||||
@ -425,4 +440,42 @@ export class AutomationService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 prepareConditions(conditions: Condition[]) {
|
||||
const convertedData = convertKeysToSnakeCase(conditions);
|
||||
await Promise.all(
|
||||
convertedData.map(async (condition) => {
|
||||
if (condition.entity_type === EntityTypeEnum.DEVICE_REPORT) {
|
||||
const device = await this.deviceService.getDeviceByDeviceUuid(
|
||||
condition.entity_id,
|
||||
false,
|
||||
);
|
||||
if (device) {
|
||||
condition.entity_id = device.deviceTuyaUuid;
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
return convertedData;
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { BuildingService } from './services/building.service';
|
||||
import { BuildingController } from './controllers/building.controller';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { SpaceTypeRepository } from '@app/common/modules/space/repositories';
|
||||
import { UserSpaceRepository } from '@app/common/modules/user/repositories';
|
||||
import { UserRepository } from '@app/common/modules/user/repositories';
|
||||
import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule],
|
||||
controllers: [BuildingController],
|
||||
providers: [
|
||||
BuildingService,
|
||||
SpaceRepository,
|
||||
SpaceTypeRepository,
|
||||
UserSpaceRepository,
|
||||
UserRepository,
|
||||
],
|
||||
exports: [BuildingService],
|
||||
})
|
||||
export class BuildingModule {}
|
@ -1,106 +0,0 @@
|
||||
import { BuildingService } from '../services/building.service';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
HttpStatus,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { AddBuildingDto, AddUserBuildingDto } from '../dtos/add.building.dto';
|
||||
import { GetBuildingChildDto } from '../dtos/get.building.dto';
|
||||
import { UpdateBuildingNameDto } from '../dtos/update.building.dto';
|
||||
import { CheckCommunityTypeGuard } from 'src/guards/community.type.guard';
|
||||
import { CheckUserBuildingGuard } from 'src/guards/user.building.guard';
|
||||
import { AdminRoleGuard } from 'src/guards/admin.role.guard';
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
import { BuildingPermissionGuard } from 'src/guards/building.permission.guard';
|
||||
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
|
||||
@ApiTags('Building Module')
|
||||
@Controller({
|
||||
version: EnableDisableStatusEnum.ENABLED,
|
||||
path: SpaceType.BUILDING,
|
||||
})
|
||||
export class BuildingController {
|
||||
constructor(private readonly buildingService: BuildingService) {}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, CheckCommunityTypeGuard)
|
||||
@Post()
|
||||
async addBuilding(@Body() addBuildingDto: AddBuildingDto) {
|
||||
const building = await this.buildingService.addBuilding(addBuildingDto);
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'Building added successfully',
|
||||
data: building,
|
||||
};
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, BuildingPermissionGuard)
|
||||
@Get(':buildingUuid')
|
||||
async getBuildingByUuid(@Param('buildingUuid') buildingUuid: string) {
|
||||
const building = await this.buildingService.getBuildingByUuid(buildingUuid);
|
||||
return building;
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, BuildingPermissionGuard)
|
||||
@Get('child/:buildingUuid')
|
||||
async getBuildingChildByUuid(
|
||||
@Param('buildingUuid') buildingUuid: string,
|
||||
@Query() query: GetBuildingChildDto,
|
||||
) {
|
||||
const building = await this.buildingService.getBuildingChildByUuid(
|
||||
buildingUuid,
|
||||
query,
|
||||
);
|
||||
return building;
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, BuildingPermissionGuard)
|
||||
@Get('parent/:buildingUuid')
|
||||
async getBuildingParentByUuid(@Param('buildingUuid') buildingUuid: string) {
|
||||
const building =
|
||||
await this.buildingService.getBuildingParentByUuid(buildingUuid);
|
||||
return building;
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AdminRoleGuard, CheckUserBuildingGuard)
|
||||
@Post('user')
|
||||
async addUserBuilding(@Body() addUserBuildingDto: AddUserBuildingDto) {
|
||||
await this.buildingService.addUserBuilding(addUserBuildingDto);
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'user building added successfully',
|
||||
};
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('user/:userUuid')
|
||||
async getBuildingsByUserId(@Param('userUuid') userUuid: string) {
|
||||
return await this.buildingService.getBuildingsByUserId(userUuid);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, BuildingPermissionGuard)
|
||||
@Put(':buildingUuid')
|
||||
async renameBuildingByUuid(
|
||||
@Param('buildingUuid') buildingUuid: string,
|
||||
@Body() updateBuildingDto: UpdateBuildingNameDto,
|
||||
) {
|
||||
const building = await this.buildingService.renameBuildingByUuid(
|
||||
buildingUuid,
|
||||
updateBuildingDto,
|
||||
);
|
||||
return building;
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './building.controller';
|
@ -1,42 +0,0 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class AddBuildingDto {
|
||||
@ApiProperty({
|
||||
description: 'buildingName',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public buildingName: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'communityUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public communityUuid: string;
|
||||
constructor(dto: Partial<AddBuildingDto>) {
|
||||
Object.assign(this, dto);
|
||||
}
|
||||
}
|
||||
export class AddUserBuildingDto {
|
||||
@ApiProperty({
|
||||
description: 'buildingUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public buildingUuid: string;
|
||||
@ApiProperty({
|
||||
description: 'userUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public userUuid: string;
|
||||
constructor(dto: Partial<AddUserBuildingDto>) {
|
||||
Object.assign(this, dto);
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
import { BooleanValues } from '@app/common/constants/boolean-values.enum';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Transform } from 'class-transformer';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
|
||||
export class GetBuildingDto {
|
||||
@ApiProperty({
|
||||
description: 'buildingUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public buildingUuid: string;
|
||||
}
|
||||
|
||||
export class GetBuildingChildDto {
|
||||
@ApiProperty({ example: 1, description: 'Page number', required: true })
|
||||
@IsInt({ message: 'Page must be a number' })
|
||||
@Min(1, { message: 'Page must not be less than 1' })
|
||||
@IsNotEmpty()
|
||||
public page: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: 10,
|
||||
description: 'Number of items per page',
|
||||
required: true,
|
||||
})
|
||||
@IsInt({ message: 'Page size must be a number' })
|
||||
@Min(1, { message: 'Page size must not be less than 1' })
|
||||
@IsNotEmpty()
|
||||
public pageSize: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: true,
|
||||
description: 'Flag to determine whether to fetch full hierarchy',
|
||||
required: false,
|
||||
default: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@Transform((value) => {
|
||||
return value.obj.includeSubSpaces === BooleanValues.TRUE;
|
||||
})
|
||||
public includeSubSpaces: boolean = false;
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './add.building.dto';
|
@ -1,16 +0,0 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class UpdateBuildingNameDto {
|
||||
@ApiProperty({
|
||||
description: 'buildingName',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public buildingName: string;
|
||||
|
||||
constructor(dto: Partial<UpdateBuildingNameDto>) {
|
||||
Object.assign(this, dto);
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
export interface GetBuildingByUuidInterface {
|
||||
uuid: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface BuildingChildInterface {
|
||||
uuid: string;
|
||||
name: string;
|
||||
type: string;
|
||||
totalCount?: number;
|
||||
children?: BuildingChildInterface[];
|
||||
}
|
||||
export interface BuildingParentInterface {
|
||||
uuid: string;
|
||||
name: string;
|
||||
type: string;
|
||||
parent?: BuildingParentInterface;
|
||||
}
|
||||
export interface RenameBuildingByUuidInterface {
|
||||
uuid: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
export interface GetBuildingByUserUuidInterface {
|
||||
uuid: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
@ -1,317 +0,0 @@
|
||||
import { GetBuildingChildDto } from '../dtos/get.building.dto';
|
||||
import { SpaceTypeRepository } from '../../../libs/common/src/modules/space/repositories/space.repository';
|
||||
import {
|
||||
Injectable,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { AddBuildingDto, AddUserBuildingDto } from '../dtos';
|
||||
import {
|
||||
BuildingChildInterface,
|
||||
BuildingParentInterface,
|
||||
GetBuildingByUserUuidInterface,
|
||||
GetBuildingByUuidInterface,
|
||||
RenameBuildingByUuidInterface,
|
||||
} from '../interface/building.interface';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities';
|
||||
import { UpdateBuildingNameDto } from '../dtos/update.building.dto';
|
||||
import { UserSpaceRepository } from '@app/common/modules/user/repositories';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
import { CommonErrorCodes } from '@app/common/constants/error-codes.enum';
|
||||
|
||||
@Injectable()
|
||||
export class BuildingService {
|
||||
constructor(
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
private readonly spaceTypeRepository: SpaceTypeRepository,
|
||||
private readonly userSpaceRepository: UserSpaceRepository,
|
||||
) {}
|
||||
|
||||
async addBuilding(addBuildingDto: AddBuildingDto) {
|
||||
try {
|
||||
const spaceType = await this.spaceTypeRepository.findOne({
|
||||
where: {
|
||||
type: SpaceType.BUILDING,
|
||||
},
|
||||
});
|
||||
|
||||
if (!spaceType) {
|
||||
throw new BadRequestException('Invalid building UUID');
|
||||
}
|
||||
const building = await this.spaceRepository.save({
|
||||
spaceName: addBuildingDto.buildingName,
|
||||
parent: { uuid: addBuildingDto.communityUuid },
|
||||
spaceType: { uuid: spaceType.uuid },
|
||||
});
|
||||
return building;
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Building not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getBuildingByUuid(
|
||||
buildingUuid: string,
|
||||
): Promise<GetBuildingByUuidInterface> {
|
||||
try {
|
||||
const building = await this.spaceRepository.findOne({
|
||||
where: {
|
||||
uuid: buildingUuid,
|
||||
spaceType: {
|
||||
type: SpaceType.BUILDING,
|
||||
},
|
||||
},
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
if (
|
||||
!building ||
|
||||
!building.spaceType ||
|
||||
building.spaceType.type !== SpaceType.BUILDING
|
||||
) {
|
||||
throw new BadRequestException('Invalid building UUID');
|
||||
}
|
||||
return {
|
||||
uuid: building.uuid,
|
||||
createdAt: building.createdAt,
|
||||
updatedAt: building.updatedAt,
|
||||
name: building.spaceName,
|
||||
type: building.spaceType.type,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Building not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
async getBuildingChildByUuid(
|
||||
buildingUuid: string,
|
||||
getBuildingChildDto: GetBuildingChildDto,
|
||||
): Promise<BuildingChildInterface> {
|
||||
try {
|
||||
const { includeSubSpaces, page, pageSize } = getBuildingChildDto;
|
||||
|
||||
const space = await this.spaceRepository.findOneOrFail({
|
||||
where: { uuid: buildingUuid },
|
||||
relations: ['children', 'spaceType'],
|
||||
});
|
||||
if (
|
||||
!space ||
|
||||
!space.spaceType ||
|
||||
space.spaceType.type !== SpaceType.BUILDING
|
||||
) {
|
||||
throw new BadRequestException('Invalid building UUID');
|
||||
}
|
||||
|
||||
const totalCount = await this.spaceRepository.count({
|
||||
where: { parent: { uuid: space.uuid } },
|
||||
});
|
||||
|
||||
const children = await this.buildHierarchy(
|
||||
space,
|
||||
includeSubSpaces,
|
||||
page,
|
||||
pageSize,
|
||||
);
|
||||
|
||||
return {
|
||||
uuid: space.uuid,
|
||||
name: space.spaceName,
|
||||
type: space.spaceType.type,
|
||||
totalCount,
|
||||
children,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Building not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async buildHierarchy(
|
||||
space: SpaceEntity,
|
||||
includeSubSpaces: boolean,
|
||||
page: number,
|
||||
pageSize: number,
|
||||
): Promise<BuildingChildInterface[]> {
|
||||
const children = await this.spaceRepository.find({
|
||||
where: { parent: { uuid: space.uuid } },
|
||||
relations: ['spaceType'],
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
});
|
||||
|
||||
if (!children || children.length === 0 || !includeSubSpaces) {
|
||||
return children
|
||||
.filter(
|
||||
(child) =>
|
||||
child.spaceType.type !== SpaceType.BUILDING &&
|
||||
child.spaceType.type !== SpaceType.COMMUNITY,
|
||||
) // Filter remaining building and community types
|
||||
.map((child) => ({
|
||||
uuid: child.uuid,
|
||||
name: child.spaceName,
|
||||
type: child.spaceType.type,
|
||||
}));
|
||||
}
|
||||
|
||||
const childHierarchies = await Promise.all(
|
||||
children
|
||||
.filter(
|
||||
(child) =>
|
||||
child.spaceType.type !== SpaceType.BUILDING &&
|
||||
child.spaceType.type !== SpaceType.COMMUNITY,
|
||||
) // Filter remaining building and community types
|
||||
.map(async (child) => ({
|
||||
uuid: child.uuid,
|
||||
name: child.spaceName,
|
||||
type: child.spaceType.type,
|
||||
children: await this.buildHierarchy(child, true, 1, pageSize),
|
||||
})),
|
||||
);
|
||||
|
||||
return childHierarchies;
|
||||
}
|
||||
|
||||
async getBuildingParentByUuid(
|
||||
buildingUuid: string,
|
||||
): Promise<BuildingParentInterface> {
|
||||
try {
|
||||
const building = await this.spaceRepository.findOne({
|
||||
where: {
|
||||
uuid: buildingUuid,
|
||||
spaceType: {
|
||||
type: SpaceType.BUILDING,
|
||||
},
|
||||
},
|
||||
relations: ['spaceType', 'parent', 'parent.spaceType'],
|
||||
});
|
||||
if (
|
||||
!building ||
|
||||
!building.spaceType ||
|
||||
building.spaceType.type !== SpaceType.BUILDING
|
||||
) {
|
||||
throw new BadRequestException('Invalid building UUID');
|
||||
}
|
||||
return {
|
||||
uuid: building.uuid,
|
||||
name: building.spaceName,
|
||||
type: building.spaceType.type,
|
||||
parent: {
|
||||
uuid: building.parent.uuid,
|
||||
name: building.parent.spaceName,
|
||||
type: building.parent.spaceType.type,
|
||||
},
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Building not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getBuildingsByUserId(
|
||||
userUuid: string,
|
||||
): Promise<GetBuildingByUserUuidInterface[]> {
|
||||
try {
|
||||
const buildings = await this.userSpaceRepository.find({
|
||||
relations: ['space', 'space.spaceType'],
|
||||
where: {
|
||||
user: { uuid: userUuid },
|
||||
space: { spaceType: { type: SpaceType.BUILDING } },
|
||||
},
|
||||
});
|
||||
|
||||
if (buildings.length === 0) {
|
||||
throw new HttpException(
|
||||
'this user has no buildings',
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
const spaces = buildings.map((building) => ({
|
||||
uuid: building.space.uuid,
|
||||
name: building.space.spaceName,
|
||||
type: building.space.spaceType.type,
|
||||
}));
|
||||
|
||||
return spaces;
|
||||
} catch (err) {
|
||||
if (err instanceof HttpException) {
|
||||
throw err;
|
||||
} else {
|
||||
throw new HttpException('user not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
async addUserBuilding(addUserBuildingDto: AddUserBuildingDto) {
|
||||
try {
|
||||
await this.userSpaceRepository.save({
|
||||
user: { uuid: addUserBuildingDto.userUuid },
|
||||
space: { uuid: addUserBuildingDto.buildingUuid },
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.code === CommonErrorCodes.DUPLICATE_ENTITY) {
|
||||
throw new HttpException(
|
||||
'User already belongs to this building',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
throw new HttpException(
|
||||
err.message || 'Internal Server Error',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
async renameBuildingByUuid(
|
||||
buildingUuid: string,
|
||||
updateBuildingNameDto: UpdateBuildingNameDto,
|
||||
): Promise<RenameBuildingByUuidInterface> {
|
||||
try {
|
||||
const building = await this.spaceRepository.findOneOrFail({
|
||||
where: { uuid: buildingUuid },
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
|
||||
if (
|
||||
!building ||
|
||||
!building.spaceType ||
|
||||
building.spaceType.type !== SpaceType.BUILDING
|
||||
) {
|
||||
throw new BadRequestException('Invalid building UUID');
|
||||
}
|
||||
|
||||
await this.spaceRepository.update(
|
||||
{ uuid: buildingUuid },
|
||||
{ spaceName: updateBuildingNameDto.buildingName },
|
||||
);
|
||||
|
||||
// Fetch the updated building
|
||||
const updatedBuilding = await this.spaceRepository.findOneOrFail({
|
||||
where: { uuid: buildingUuid },
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
|
||||
return {
|
||||
uuid: updatedBuilding.uuid,
|
||||
name: updatedBuilding.spaceName,
|
||||
type: updatedBuilding.spaceType.type,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Building not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './building.service';
|
@ -4,11 +4,11 @@ import { CommunityController } from './controllers/community.controller';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { SpaceTypeRepository } from '@app/common/modules/space/repositories';
|
||||
import { UserSpaceRepository } from '@app/common/modules/user/repositories';
|
||||
import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module';
|
||||
import { UserRepository } from '@app/common/modules/user/repositories';
|
||||
import { SpacePermissionService } from '@app/common/helper/services';
|
||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule],
|
||||
@ -16,9 +16,9 @@ import { SpacePermissionService } from '@app/common/helper/services';
|
||||
providers: [
|
||||
CommunityService,
|
||||
SpaceRepository,
|
||||
SpaceTypeRepository,
|
||||
UserSpaceRepository,
|
||||
UserRepository,
|
||||
TuyaService,
|
||||
CommunityRepository,
|
||||
SpacePermissionService,
|
||||
],
|
||||
exports: [CommunityService, SpacePermissionService],
|
||||
|
@ -2,31 +2,28 @@ import { CommunityService } from '../services/community.service';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpStatus,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import {
|
||||
AddCommunityDto,
|
||||
AddUserCommunityDto,
|
||||
} from '../dtos/add.community.dto';
|
||||
import { GetCommunityChildDto } from '../dtos/get.community.dto';
|
||||
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
||||
import { AddCommunityDto } from '../dtos/add.community.dto';
|
||||
import { GetCommunityParams } from '../dtos/get.community.dto';
|
||||
import { UpdateCommunityNameDto } from '../dtos/update.community.dto';
|
||||
import { AdminRoleGuard } from 'src/guards/admin.role.guard';
|
||||
// import { CheckUserCommunityGuard } from 'src/guards/user.community.guard';
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
// import { CommunityPermissionGuard } from 'src/guards/community.permission.guard';
|
||||
import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { PaginationRequestGetListDto } from '@app/common/dto/pagination.request.dto';
|
||||
|
||||
@ApiTags('Community Module')
|
||||
@Controller({
|
||||
version: EnableDisableStatusEnum.ENABLED,
|
||||
path: SpaceType.COMMUNITY,
|
||||
version: '1',
|
||||
path: ControllerRoute.COMMUNITY.ROUTE,
|
||||
})
|
||||
export class CommunityController {
|
||||
constructor(private readonly communityService: CommunityService) {}
|
||||
@ -34,73 +31,70 @@ export class CommunityController {
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post()
|
||||
async addCommunity(@Body() addCommunityDto: AddCommunityDto) {
|
||||
const community = await this.communityService.addCommunity(addCommunityDto);
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'Community added successfully',
|
||||
data: community,
|
||||
};
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.COMMUNITY.ACTIONS.CREATE_COMMUNITY_SUMMARY,
|
||||
description: ControllerRoute.COMMUNITY.ACTIONS.CREATE_COMMUNITY_DESCRIPTION,
|
||||
})
|
||||
async createCommunity(
|
||||
@Body() addCommunityDto: AddCommunityDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
return await this.communityService.createCommunity(addCommunityDto);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get(':communityUuid')
|
||||
async getCommunityByUuid(@Param('communityUuid') communityUuid: string) {
|
||||
const community =
|
||||
await this.communityService.getCommunityByUuid(communityUuid);
|
||||
return community;
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.COMMUNITY.ACTIONS.GET_COMMUNITY_BY_ID_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.COMMUNITY.ACTIONS.GET_COMMUNITY_BY_ID_DESCRIPTION,
|
||||
})
|
||||
@Get('/:communityUuid')
|
||||
async getCommunityByUuid(
|
||||
@Param() params: GetCommunityParams,
|
||||
): Promise<BaseResponseDto> {
|
||||
return await this.communityService.getCommunityById(params.communityUuid);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.COMMUNITY.ACTIONS.LIST_COMMUNITY_SUMMARY,
|
||||
description: ControllerRoute.COMMUNITY.ACTIONS.LIST_COMMUNITY_DESCRIPTION,
|
||||
})
|
||||
@Get()
|
||||
async getCommunities() {
|
||||
const communities = await this.communityService.getCommunities();
|
||||
return communities;
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('child/:communityUuid')
|
||||
async getCommunityChildByUuid(
|
||||
@Param('communityUuid') communityUuid: string,
|
||||
@Query() query: GetCommunityChildDto,
|
||||
) {
|
||||
const community = await this.communityService.getCommunityChildByUuid(
|
||||
communityUuid,
|
||||
query,
|
||||
);
|
||||
return community;
|
||||
async getCommunities(
|
||||
@Query() query: PaginationRequestGetListDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
return this.communityService.getCommunities(query);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('user/:userUuid')
|
||||
async getCommunitiesByUserId(@Param('userUuid') userUuid: string) {
|
||||
return await this.communityService.getCommunitiesByUserId(userUuid);
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AdminRoleGuard)
|
||||
@Post('user')
|
||||
async addUserCommunity(@Body() addUserCommunityDto: AddUserCommunityDto) {
|
||||
await this.communityService.addUserCommunity(addUserCommunityDto);
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'user community added successfully',
|
||||
};
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Put(':communityUuid')
|
||||
async renameCommunityByUuid(
|
||||
@Param('communityUuid') communityUuid: string,
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.COMMUNITY.ACTIONS.UPDATE_COMMUNITY_SUMMARY,
|
||||
description: ControllerRoute.COMMUNITY.ACTIONS.UPDATE_COMMUNITY_DESCRIPTION,
|
||||
})
|
||||
@Put('/:communityUuid')
|
||||
async updateCommunity(
|
||||
@Param() param: GetCommunityParams,
|
||||
@Body() updateCommunityDto: UpdateCommunityNameDto,
|
||||
) {
|
||||
const community = await this.communityService.renameCommunityByUuid(
|
||||
communityUuid,
|
||||
return this.communityService.updateCommunity(
|
||||
param.communityUuid,
|
||||
updateCommunityDto,
|
||||
);
|
||||
return community;
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Delete('/:communityUuid')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.COMMUNITY.ACTIONS.DELETE_COMMUNITY_SUMMARY,
|
||||
description: ControllerRoute.COMMUNITY.ACTIONS.DELETE_COMMUNITY_DESCRIPTION,
|
||||
})
|
||||
async deleteCommunity(
|
||||
@Param() param: GetCommunityParams,
|
||||
): Promise<BaseResponseDto> {
|
||||
return this.communityService.deleteCommunity(param.communityUuid);
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,30 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
|
||||
|
||||
export class AddCommunityDto {
|
||||
@ApiProperty({
|
||||
description: 'communityName',
|
||||
description: 'The name of the community',
|
||||
example: 'Community A',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public communityName: string;
|
||||
public name: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'A description of the community',
|
||||
example: 'This is a community for developers.',
|
||||
required: false,
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
public description?: string;
|
||||
|
||||
constructor(dto: Partial<AddCommunityDto>) {
|
||||
Object.assign(this, dto);
|
||||
}
|
||||
}
|
||||
|
||||
export class AddUserCommunityDto {
|
||||
@ApiProperty({
|
||||
description: 'communityUuid',
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
|
||||
@ -20,6 +21,18 @@ export class GetCommunityDto {
|
||||
public communityUuid: string;
|
||||
}
|
||||
|
||||
export class GetCommunityParams {
|
||||
@ApiProperty({
|
||||
description: 'Community id of the specific community',
|
||||
required: true,
|
||||
name: 'communityUuid',
|
||||
})
|
||||
@IsUUID()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public communityUuid: string;
|
||||
}
|
||||
|
||||
export class GetCommunityChildDto {
|
||||
@ApiProperty({ example: 1, description: 'Page number', required: true })
|
||||
@IsInt({ message: 'Page must be a number' })
|
||||
|
@ -3,12 +3,12 @@ import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class UpdateCommunityNameDto {
|
||||
@ApiProperty({
|
||||
description: 'communityName',
|
||||
description: 'community name',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public communityName: string;
|
||||
public name: string;
|
||||
|
||||
constructor(dto: Partial<UpdateCommunityNameDto>) {
|
||||
Object.assign(this, dto);
|
||||
|
@ -1,278 +1,172 @@
|
||||
import { GetCommunityChildDto } from './../dtos/get.community.dto';
|
||||
import { SpaceTypeRepository } from './../../../libs/common/src/modules/space/repositories/space.repository';
|
||||
import {
|
||||
Injectable,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { AddCommunityDto, AddUserCommunityDto } from '../dtos';
|
||||
import {
|
||||
CommunityChildInterface,
|
||||
GetCommunitiesInterface,
|
||||
GetCommunityByUserUuidInterface,
|
||||
GetCommunityByUuidInterface,
|
||||
RenameCommunityByUuidInterface,
|
||||
} from '../interface/community.interface';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities';
|
||||
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
|
||||
import { AddCommunityDto } from '../dtos';
|
||||
import { UpdateCommunityNameDto } from '../dtos/update.community.dto';
|
||||
import { UserSpaceRepository } from '@app/common/modules/user/repositories';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
import { CommonErrorCodes } from '@app/common/constants/error-codes.enum';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import {
|
||||
TypeORMCustomModel,
|
||||
TypeORMCustomModelFindAllQuery,
|
||||
} from '@app/common/models/typeOrmCustom.model';
|
||||
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/services/tuya.service';
|
||||
|
||||
@Injectable()
|
||||
export class CommunityService {
|
||||
constructor(
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
private readonly spaceTypeRepository: SpaceTypeRepository,
|
||||
private readonly userSpaceRepository: UserSpaceRepository,
|
||||
private readonly communityRepository: CommunityRepository,
|
||||
private readonly tuyaService: TuyaService,
|
||||
) {}
|
||||
|
||||
async addCommunity(addCommunityDto: AddCommunityDto) {
|
||||
try {
|
||||
const spaceType = await this.spaceTypeRepository.findOne({
|
||||
where: {
|
||||
type: SpaceType.COMMUNITY,
|
||||
},
|
||||
});
|
||||
async createCommunity(dto: AddCommunityDto): Promise<BaseResponseDto> {
|
||||
const { name, description } = dto;
|
||||
|
||||
const community = await this.spaceRepository.save({
|
||||
spaceName: addCommunityDto.communityName,
|
||||
spaceType: { uuid: spaceType.uuid },
|
||||
});
|
||||
return community;
|
||||
} catch (err) {
|
||||
throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getCommunityByUuid(
|
||||
communityUuid: string,
|
||||
): Promise<GetCommunityByUuidInterface> {
|
||||
try {
|
||||
const community = await this.spaceRepository.findOne({
|
||||
where: {
|
||||
uuid: communityUuid,
|
||||
spaceType: {
|
||||
type: SpaceType.COMMUNITY,
|
||||
},
|
||||
},
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
if (
|
||||
!community ||
|
||||
!community.spaceType ||
|
||||
community.spaceType.type !== SpaceType.COMMUNITY
|
||||
) {
|
||||
throw new BadRequestException('Invalid community UUID');
|
||||
}
|
||||
return {
|
||||
uuid: community.uuid,
|
||||
createdAt: community.createdAt,
|
||||
updatedAt: community.updatedAt,
|
||||
name: community.spaceName,
|
||||
type: community.spaceType.type,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Community not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
async getCommunities(): Promise<GetCommunitiesInterface> {
|
||||
try {
|
||||
const community = await this.spaceRepository.find({
|
||||
where: { spaceType: { type: SpaceType.COMMUNITY } },
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
return community.map((community) => ({
|
||||
uuid: community.uuid,
|
||||
createdAt: community.createdAt,
|
||||
updatedAt: community.updatedAt,
|
||||
name: community.spaceName,
|
||||
type: community.spaceType.type,
|
||||
}));
|
||||
} catch (err) {
|
||||
throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
async getCommunityChildByUuid(
|
||||
communityUuid: string,
|
||||
getCommunityChildDto: GetCommunityChildDto,
|
||||
): Promise<CommunityChildInterface> {
|
||||
try {
|
||||
const { includeSubSpaces, page, pageSize } = getCommunityChildDto;
|
||||
|
||||
const space = await this.spaceRepository.findOneOrFail({
|
||||
where: { uuid: communityUuid },
|
||||
relations: ['children', 'spaceType'],
|
||||
});
|
||||
|
||||
if (
|
||||
!space ||
|
||||
!space.spaceType ||
|
||||
space.spaceType.type !== SpaceType.COMMUNITY
|
||||
) {
|
||||
throw new BadRequestException('Invalid community UUID');
|
||||
}
|
||||
const totalCount = await this.spaceRepository.count({
|
||||
where: { parent: { uuid: space.uuid } },
|
||||
});
|
||||
const children = await this.buildHierarchy(
|
||||
space,
|
||||
includeSubSpaces,
|
||||
page,
|
||||
pageSize,
|
||||
const existingCommunity = await this.communityRepository.findOneBy({
|
||||
name,
|
||||
});
|
||||
if (existingCommunity) {
|
||||
throw new HttpException(
|
||||
`A community with the name '${name}' already exists.`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
return {
|
||||
uuid: space.uuid,
|
||||
name: space.spaceName,
|
||||
type: space.spaceType.type,
|
||||
totalCount,
|
||||
children,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Community not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async buildHierarchy(
|
||||
space: SpaceEntity,
|
||||
includeSubSpaces: boolean,
|
||||
page: number,
|
||||
pageSize: number,
|
||||
): Promise<CommunityChildInterface[]> {
|
||||
const children = await this.spaceRepository.find({
|
||||
where: { parent: { uuid: space.uuid } },
|
||||
relations: ['spaceType'],
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
// Create the new community entity
|
||||
const community = this.communityRepository.create({
|
||||
name: name,
|
||||
description: description,
|
||||
});
|
||||
|
||||
if (!children || children.length === 0 || !includeSubSpaces) {
|
||||
return children
|
||||
.filter((child) => child.spaceType.type !== SpaceType.COMMUNITY) // Filter remaining community type
|
||||
.map((child) => ({
|
||||
uuid: child.uuid,
|
||||
name: child.spaceName,
|
||||
type: child.spaceType.type,
|
||||
}));
|
||||
// Save the community to the database
|
||||
try {
|
||||
const externalId = await this.createTuyaSpace(name);
|
||||
community.externalId = externalId;
|
||||
await this.communityRepository.save(community);
|
||||
return new SuccessResponseDto({
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
data: community,
|
||||
message: 'Community created successfully',
|
||||
});
|
||||
} catch (error) {
|
||||
throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
const childHierarchies = await Promise.all(
|
||||
children
|
||||
.filter((child) => child.spaceType.type !== SpaceType.COMMUNITY) // Filter remaining community type
|
||||
.map(async (child) => ({
|
||||
uuid: child.uuid,
|
||||
name: child.spaceName,
|
||||
type: child.spaceType.type,
|
||||
children: await this.buildHierarchy(child, true, 1, pageSize),
|
||||
})),
|
||||
);
|
||||
|
||||
return childHierarchies;
|
||||
}
|
||||
|
||||
async getCommunitiesByUserId(
|
||||
userUuid: string,
|
||||
): Promise<GetCommunityByUserUuidInterface[]> {
|
||||
async getCommunityById(communityUuid: string): Promise<BaseResponseDto> {
|
||||
const community = await this.communityRepository.findOneBy({
|
||||
uuid: communityUuid,
|
||||
});
|
||||
|
||||
// If the community is not found, throw a 404 NotFoundException
|
||||
if (!community) {
|
||||
throw new HttpException(
|
||||
`Community with ID ${communityUuid} not found.`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
// Return a success response
|
||||
return new SuccessResponseDto({
|
||||
data: community,
|
||||
message: 'Community fetched successfully',
|
||||
});
|
||||
}
|
||||
|
||||
async getCommunities(
|
||||
pageable: Partial<TypeORMCustomModelFindAllQuery>,
|
||||
): Promise<BaseResponseDto> {
|
||||
pageable.modelName = 'community';
|
||||
|
||||
const customModel = TypeORMCustomModel(this.communityRepository);
|
||||
|
||||
const { baseResponseDto, paginationResponseDto } =
|
||||
await customModel.findAll(pageable);
|
||||
|
||||
return new PageResponse<CommunityDto>(
|
||||
baseResponseDto,
|
||||
paginationResponseDto,
|
||||
);
|
||||
}
|
||||
|
||||
async updateCommunity(
|
||||
communityUuid: string,
|
||||
updateCommunityDto: UpdateCommunityNameDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
const community = await this.communityRepository.findOne({
|
||||
where: { uuid: communityUuid },
|
||||
});
|
||||
|
||||
// If the community doesn't exist, throw a 404 error
|
||||
if (!community) {
|
||||
throw new HttpException(
|
||||
`Community with ID ${communityUuid} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const communities = await this.userSpaceRepository.find({
|
||||
relations: ['space', 'space.spaceType'],
|
||||
where: {
|
||||
user: { uuid: userUuid },
|
||||
space: { spaceType: { type: SpaceType.COMMUNITY } },
|
||||
},
|
||||
const { name } = updateCommunityDto;
|
||||
|
||||
community.name = name;
|
||||
|
||||
const updatedCommunity = await this.communityRepository.save(community);
|
||||
|
||||
return new SuccessResponseDto<CommunityDto>({
|
||||
message: 'Success update Community',
|
||||
data: updatedCommunity,
|
||||
});
|
||||
|
||||
if (communities.length === 0) {
|
||||
throw new HttpException(
|
||||
'this user has no communities',
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
} catch (err) {
|
||||
// Catch and handle any errors
|
||||
if (err instanceof HttpException) {
|
||||
throw err; // If it's an HttpException, rethrow it
|
||||
} else {
|
||||
// Throw a generic 404 error if anything else goes wrong
|
||||
throw new HttpException('Community not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
const spaces = communities.map((community) => ({
|
||||
uuid: community.space.uuid,
|
||||
name: community.space.spaceName,
|
||||
type: community.space.spaceType.type,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return spaces;
|
||||
async deleteCommunity(communityUuid: string): Promise<BaseResponseDto> {
|
||||
const community = await this.communityRepository.findOne({
|
||||
where: { uuid: communityUuid },
|
||||
});
|
||||
|
||||
// If the community is not found, throw an error
|
||||
if (!community) {
|
||||
throw new HttpException(
|
||||
`Community with ID ${communityUuid} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
try {
|
||||
await this.communityRepository.remove(community);
|
||||
|
||||
return new SuccessResponseDto({
|
||||
message: `Community with ID ${communityUuid} has been successfully deleted`,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof HttpException) {
|
||||
throw err;
|
||||
} else {
|
||||
throw new HttpException('user not found', HttpStatus.NOT_FOUND);
|
||||
throw new HttpException(
|
||||
'An error occurred while deleting the community',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async addUserCommunity(addUserCommunityDto: AddUserCommunityDto) {
|
||||
private async createTuyaSpace(name: string): Promise<string> {
|
||||
try {
|
||||
await this.userSpaceRepository.save({
|
||||
user: { uuid: addUserCommunityDto.userUuid },
|
||||
space: { uuid: addUserCommunityDto.communityUuid },
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.code === CommonErrorCodes.DUPLICATE_ENTITY) {
|
||||
throw new HttpException(
|
||||
'User already belongs to this community',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
const response = await this.tuyaService.createSpace({ name });
|
||||
return response;
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
err.message || 'Internal Server Error',
|
||||
'Failed to create a Tuya space',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
async renameCommunityByUuid(
|
||||
communityUuid: string,
|
||||
updateCommunityDto: UpdateCommunityNameDto,
|
||||
): Promise<RenameCommunityByUuidInterface> {
|
||||
try {
|
||||
const community = await this.spaceRepository.findOneOrFail({
|
||||
where: { uuid: communityUuid },
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
|
||||
if (
|
||||
!community ||
|
||||
!community.spaceType ||
|
||||
community.spaceType.type !== SpaceType.COMMUNITY
|
||||
) {
|
||||
throw new BadRequestException('Invalid community UUID');
|
||||
}
|
||||
|
||||
await this.spaceRepository.update(
|
||||
{ uuid: communityUuid },
|
||||
{ spaceName: updateCommunityDto.communityName },
|
||||
);
|
||||
|
||||
// Fetch the updated community
|
||||
const updatedCommunity = await this.spaceRepository.findOneOrFail({
|
||||
where: { uuid: communityUuid },
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
|
||||
return {
|
||||
uuid: updatedCommunity.uuid,
|
||||
name: updatedCommunity.spaceName,
|
||||
type: updatedCommunity.spaceType.type,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Community not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,17 +8,18 @@ import {
|
||||
Post,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { DeviceMessagesSubscriptionService } from '../services/device-messages.service';
|
||||
import { DeviceMessagesAddDto } from '../dtos/device-messages.dto';
|
||||
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
|
||||
import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||
|
||||
@ApiTags('Device Messages Status Module')
|
||||
@Controller({
|
||||
version: EnableDisableStatusEnum.ENABLED,
|
||||
path: 'device-messages/subscription',
|
||||
path: ControllerRoute.DEVICE_MESSAGES_SUBSCRIPTION.ROUTE,
|
||||
})
|
||||
export class DeviceMessagesSubscriptionController {
|
||||
constructor(
|
||||
@ -28,6 +29,14 @@ export class DeviceMessagesSubscriptionController {
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post()
|
||||
@ApiOperation({
|
||||
summary:
|
||||
ControllerRoute.DEVICE_MESSAGES_SUBSCRIPTION.ACTIONS
|
||||
.ADD_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DEVICE_MESSAGES_SUBSCRIPTION.ACTIONS
|
||||
.ADD_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION,
|
||||
})
|
||||
async addDeviceMessagesSubscription(
|
||||
@Body() deviceMessagesAddDto: DeviceMessagesAddDto,
|
||||
) {
|
||||
@ -45,6 +54,14 @@ export class DeviceMessagesSubscriptionController {
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get(':deviceUuid/user/:userUuid')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
ControllerRoute.DEVICE_MESSAGES_SUBSCRIPTION.ACTIONS
|
||||
.GET_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DEVICE_MESSAGES_SUBSCRIPTION.ACTIONS
|
||||
.GET_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION,
|
||||
})
|
||||
async getDeviceMessagesSubscription(
|
||||
@Param('deviceUuid') deviceUuid: string,
|
||||
@Param('userUuid') userUuid: string,
|
||||
@ -60,9 +77,18 @@ export class DeviceMessagesSubscriptionController {
|
||||
data: deviceDetails,
|
||||
};
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Delete()
|
||||
@ApiOperation({
|
||||
summary:
|
||||
ControllerRoute.DEVICE_MESSAGES_SUBSCRIPTION.ACTIONS
|
||||
.DELETE_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DEVICE_MESSAGES_SUBSCRIPTION.ACTIONS
|
||||
.DELETE_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION,
|
||||
})
|
||||
async deleteDeviceMessagesSubscription(
|
||||
@Body() deviceMessagesAddDto: DeviceMessagesAddDto,
|
||||
) {
|
||||
|
@ -10,38 +10,48 @@ import {
|
||||
UseGuards,
|
||||
Req,
|
||||
Put,
|
||||
Delete,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { AddDeviceDto, UpdateDeviceInRoomDto } from '../dtos/add.device.dto';
|
||||
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
||||
import {
|
||||
GetDeviceByRoomUuidDto,
|
||||
GetDeviceLogsDto,
|
||||
} from '../dtos/get.device.dto';
|
||||
AddDeviceDto,
|
||||
AddSceneToFourSceneDeviceDto,
|
||||
UpdateDeviceDto,
|
||||
UpdateDeviceInSpaceDto,
|
||||
} from '../dtos/add.device.dto';
|
||||
import { GetDeviceLogsDto } from '../dtos/get.device.dto';
|
||||
import {
|
||||
ControlDeviceDto,
|
||||
BatchControlDevicesDto,
|
||||
BatchStatusDevicesDto,
|
||||
BatchFactoryResetDevicesDto,
|
||||
GetSceneFourSceneDeviceDto,
|
||||
} from '../dtos/control.device.dto';
|
||||
import { CheckRoomGuard } from 'src/guards/room.guard';
|
||||
import { CheckUserHavePermission } from 'src/guards/user.device.permission.guard';
|
||||
import { CheckUserHaveControllablePermission } from 'src/guards/user.device.controllable.permission.guard';
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
import { CheckDeviceGuard } from 'src/guards/device.guard';
|
||||
import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard';
|
||||
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
import { CheckFourAndSixSceneDeviceTypeGuard } from 'src/guards/scene.device.type.guard';
|
||||
import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { DeviceSceneParamDto } from '../dtos/device.param.dto';
|
||||
import { DeleteSceneFromSceneDeviceDto } from '../dtos/delete.device.dto';
|
||||
|
||||
@ApiTags('Device Module')
|
||||
@Controller({
|
||||
version: EnableDisableStatusEnum.ENABLED,
|
||||
path: 'device',
|
||||
path: ControllerRoute.DEVICE.ROUTE,
|
||||
})
|
||||
export class DeviceController {
|
||||
constructor(private readonly deviceService: DeviceService) {}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(SuperAdminRoleGuard, CheckDeviceGuard)
|
||||
@Post()
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.DEVICE.ACTIONS.ADD_DEVICE_TO_USER_SUMMARY,
|
||||
description: ControllerRoute.DEVICE.ACTIONS.ADD_DEVICE_TO_USER_DESCRIPTION,
|
||||
})
|
||||
async addDeviceUser(@Body() addDeviceDto: AddDeviceDto) {
|
||||
const device = await this.deviceService.addDeviceUser(addDeviceDto);
|
||||
|
||||
@ -55,36 +65,38 @@ export class DeviceController {
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('user/:userUuid')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICES_BY_USER_SUMMARY,
|
||||
description: ControllerRoute.DEVICE.ACTIONS.GET_DEVICES_BY_USER_DESCRIPTION,
|
||||
})
|
||||
async getDevicesByUser(@Param('userUuid') userUuid: string) {
|
||||
return await this.deviceService.getDevicesByUser(userUuid);
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, CheckRoomGuard)
|
||||
@Get(SpaceType.ROOM)
|
||||
async getDevicesByRoomId(
|
||||
@Query() getDeviceByRoomUuidDto: GetDeviceByRoomUuidDto,
|
||||
@Req() req: any,
|
||||
) {
|
||||
const userUuid = req.user.uuid;
|
||||
return await this.deviceService.getDevicesByRoomId(
|
||||
getDeviceByRoomUuidDto,
|
||||
userUuid,
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('unit/:unitUuid')
|
||||
async getDevicesByUnitId(@Param('unitUuid') unitUuid: string) {
|
||||
return await this.deviceService.getDevicesByUnitId(unitUuid);
|
||||
@Get('space/:spaceUuid')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICES_BY_SPACE_UUID_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DEVICE.ACTIONS.GET_DEVICES_BY_SPACE_UUID_DESCRIPTION,
|
||||
})
|
||||
async getDevicesByUnitId(@Param('spaceUuid') spaceUuid: string) {
|
||||
return await this.deviceService.getDevicesBySpaceUuid(spaceUuid);
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, CheckRoomGuard)
|
||||
@Put('room')
|
||||
@Put('space')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.DEVICE.ACTIONS.UPDATE_DEVICE_IN_ROOM_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DEVICE.ACTIONS.UPDATE_DEVICE_IN_ROOM_DESCRIPTION,
|
||||
})
|
||||
async updateDeviceInRoom(
|
||||
@Body() updateDeviceInRoomDto: UpdateDeviceInRoomDto,
|
||||
@Body() updateDeviceInSpaceDto: UpdateDeviceInSpaceDto,
|
||||
) {
|
||||
const device = await this.deviceService.updateDeviceInRoom(
|
||||
updateDeviceInRoomDto,
|
||||
const device = await this.deviceService.updateDeviceInSpace(
|
||||
updateDeviceInSpaceDto,
|
||||
);
|
||||
|
||||
return {
|
||||
@ -96,8 +108,12 @@ export class DeviceController {
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, CheckUserHavePermission)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get(':deviceUuid')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_DETAILS_SUMMARY,
|
||||
description: ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_DETAILS_DESCRIPTION,
|
||||
})
|
||||
async getDeviceDetailsByDeviceId(
|
||||
@Param('deviceUuid') deviceUuid: string,
|
||||
@Req() req: any,
|
||||
@ -109,23 +125,60 @@ export class DeviceController {
|
||||
);
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, CheckUserHavePermission)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Put(':deviceUuid')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.DEVICE.ACTIONS.UPDATE_DEVICE_SUMMARY,
|
||||
description: ControllerRoute.DEVICE.ACTIONS.UPDATE_DEVICE_DESCRIPTION,
|
||||
})
|
||||
async updateDevice(
|
||||
@Param('deviceUuid') deviceUuid: string,
|
||||
@Body() updateDeviceDto: UpdateDeviceDto,
|
||||
) {
|
||||
const device = await this.deviceService.updateDevice(
|
||||
deviceUuid,
|
||||
updateDeviceDto,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'device updated successfully',
|
||||
data: device,
|
||||
};
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get(':deviceUuid/functions')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_INSTRUCTION_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_INSTRUCTION_DESCRIPTION,
|
||||
})
|
||||
async getDeviceInstructionByDeviceId(
|
||||
@Param('deviceUuid') deviceUuid: string,
|
||||
) {
|
||||
return await this.deviceService.getDeviceInstructionByDeviceId(deviceUuid);
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, CheckUserHavePermission)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get(':deviceUuid/functions/status')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_STATUS_SUMMARY,
|
||||
description: ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_STATUS_DESCRIPTION,
|
||||
})
|
||||
async getDevicesInstructionStatus(@Param('deviceUuid') deviceUuid: string) {
|
||||
return await this.deviceService.getDevicesInstructionStatus(deviceUuid);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, CheckUserHaveControllablePermission)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post(':deviceUuid/control')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.DEVICE.ACTIONS.CONTROL_DEVICE_SUMMARY,
|
||||
description: ControllerRoute.DEVICE.ACTIONS.CONTROL_DEVICE_DESCRIPTION,
|
||||
})
|
||||
async controlDevice(
|
||||
@Body() controlDeviceDto: ControlDeviceDto,
|
||||
@Param('deviceUuid') deviceUuid: string,
|
||||
@ -135,6 +188,11 @@ export class DeviceController {
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post(':deviceUuid/firmware/:firmwareVersion')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.DEVICE.ACTIONS.UPDATE_DEVICE_FIRMWARE_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DEVICE.ACTIONS.UPDATE_DEVICE_FIRMWARE_DESCRIPTION,
|
||||
})
|
||||
async updateDeviceFirmware(
|
||||
@Param('deviceUuid') deviceUuid: string,
|
||||
@Param('firmwareVersion') firmwareVersion: number,
|
||||
@ -147,18 +205,32 @@ export class DeviceController {
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('gateway/:gatewayUuid/devices')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICES_IN_GATEWAY_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DEVICE.ACTIONS.GET_DEVICES_IN_GATEWAY_DESCRIPTION,
|
||||
})
|
||||
async getDevicesInGateway(@Param('gatewayUuid') gatewayUuid: string) {
|
||||
return await this.deviceService.getDevicesInGateway(gatewayUuid);
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get()
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.DEVICE.ACTIONS.GET_ALL_DEVICES_SUMMARY,
|
||||
description: ControllerRoute.DEVICE.ACTIONS.GET_ALL_DEVICES_DESCRIPTION,
|
||||
})
|
||||
async getAllDevices() {
|
||||
return await this.deviceService.getAllDevices();
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('report-logs/:deviceUuid')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_LOGS_SUMMARY,
|
||||
description: ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_LOGS_DESCRIPTION,
|
||||
})
|
||||
async getBuildingChildByUuid(
|
||||
@Param('deviceUuid') deviceUuid: string,
|
||||
@Query() query: GetDeviceLogsDto,
|
||||
@ -168,6 +240,11 @@ export class DeviceController {
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post('control/batch')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.DEVICE.ACTIONS.BATCH_CONTROL_DEVICES_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DEVICE.ACTIONS.BATCH_CONTROL_DEVICES_DESCRIPTION,
|
||||
})
|
||||
async batchControlDevices(
|
||||
@Body() batchControlDevicesDto: BatchControlDevicesDto,
|
||||
) {
|
||||
@ -176,6 +253,11 @@ export class DeviceController {
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('status/batch')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.DEVICE.ACTIONS.BATCH_STATUS_DEVICES_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DEVICE.ACTIONS.BATCH_STATUS_DEVICES_DESCRIPTION,
|
||||
})
|
||||
async batchStatusDevices(
|
||||
@Query() batchStatusDevicesDto: BatchStatusDevicesDto,
|
||||
) {
|
||||
@ -184,6 +266,11 @@ export class DeviceController {
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post('factory/reset/:deviceUuid')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.DEVICE.ACTIONS.BATCH_FACTORY_RESET_DEVICES_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DEVICE.ACTIONS.BATCH_FACTORY_RESET_DEVICES_DESCRIPTION,
|
||||
})
|
||||
async batchFactoryResetDevices(
|
||||
@Body() batchFactoryResetDevicesDto: BatchFactoryResetDevicesDto,
|
||||
) {
|
||||
@ -194,6 +281,11 @@ export class DeviceController {
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get(':powerClampUuid/power-clamp/status')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.DEVICE.ACTIONS.GET_POWER_CLAMP_STATUS_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DEVICE.ACTIONS.GET_POWER_CLAMP_STATUS_DESCRIPTION,
|
||||
})
|
||||
async getPowerClampInstructionStatus(
|
||||
@Param('powerClampUuid') powerClampUuid: string,
|
||||
) {
|
||||
@ -201,4 +293,59 @@ export class DeviceController {
|
||||
powerClampUuid,
|
||||
);
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, CheckFourAndSixSceneDeviceTypeGuard)
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.DEVICE.ACTIONS.ADD_SCENE_TO_DEVICE_SUMMARY,
|
||||
description: ControllerRoute.DEVICE.ACTIONS.ADD_SCENE_TO_DEVICE_DESCRIPTION,
|
||||
})
|
||||
@Post(':deviceUuid/scenes')
|
||||
async addSceneToSceneDevice(
|
||||
@Param('deviceUuid') deviceUuid: string,
|
||||
@Body() addSceneToFourSceneDeviceDto: AddSceneToFourSceneDeviceDto,
|
||||
) {
|
||||
const device = await this.deviceService.addSceneToSceneDevice(
|
||||
deviceUuid,
|
||||
addSceneToFourSceneDeviceDto,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: `scene added successfully to device ${deviceUuid}`,
|
||||
data: device,
|
||||
};
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, CheckFourAndSixSceneDeviceTypeGuard)
|
||||
@Get(':deviceUuid/scenes')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.DEVICE.ACTIONS.GET_SCENES_BY_DEVICE_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DEVICE.ACTIONS.GET_SCENES_BY_DEVICE_DESCRIPTION,
|
||||
})
|
||||
async getScenesBySceneDevice(
|
||||
@Param('deviceUuid') deviceUuid: string,
|
||||
@Query() getSceneFourSceneDeviceDto: GetSceneFourSceneDeviceDto,
|
||||
) {
|
||||
return await this.deviceService.getScenesBySceneDevice(
|
||||
deviceUuid,
|
||||
getSceneFourSceneDeviceDto,
|
||||
);
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Delete(':deviceUuid/scenes')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
ControllerRoute.DEVICE.ACTIONS.DELETE_SCENES_BY_SWITCH_NAME_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DEVICE.ACTIONS.DELETE_SCENES_BY_SWITCH_NAME_DESCRIPTION,
|
||||
})
|
||||
async deleteSceneFromSceneDevice(
|
||||
@Param() param: DeviceSceneParamDto,
|
||||
@Query() query: DeleteSceneFromSceneDeviceDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
return await this.deviceService.deleteSceneFromSceneDevice(param, query);
|
||||
}
|
||||
}
|
||||
|
@ -11,9 +11,18 @@ import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { DeviceUserPermissionRepository } from '@app/common/modules/device/repositories';
|
||||
import { UserRepository } from '@app/common/modules/user/repositories';
|
||||
import { DeviceStatusFirebaseModule } from '@app/common/firebase/devices-status/devices-status.module';
|
||||
import { SpaceModule } from 'src/space/space.module';
|
||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||
import { SceneService } from 'src/scene/services';
|
||||
import {
|
||||
SceneIconRepository,
|
||||
SceneRepository,
|
||||
} from '@app/common/modules/scene/repositories';
|
||||
import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories';
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule,
|
||||
SpaceModule,
|
||||
ProductRepositoryModule,
|
||||
DeviceRepositoryModule,
|
||||
DeviceStatusFirebaseModule,
|
||||
@ -27,6 +36,11 @@ import { DeviceStatusFirebaseModule } from '@app/common/firebase/devices-status/
|
||||
SpaceRepository,
|
||||
DeviceRepository,
|
||||
UserRepository,
|
||||
TuyaService,
|
||||
SceneService,
|
||||
SceneIconRepository,
|
||||
SceneRepository,
|
||||
SceneDeviceRepository,
|
||||
],
|
||||
exports: [DeviceService],
|
||||
})
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { SceneSwitchesTypeEnum } from '@app/common/constants/scene-switch-type.enum';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class AddDeviceDto {
|
||||
@ApiProperty({
|
||||
@ -18,7 +19,7 @@ export class AddDeviceDto {
|
||||
@IsNotEmpty()
|
||||
public userUuid: string;
|
||||
}
|
||||
export class UpdateDeviceInRoomDto {
|
||||
export class UpdateDeviceInSpaceDto {
|
||||
@ApiProperty({
|
||||
description: 'deviceUuid',
|
||||
required: true,
|
||||
@ -28,10 +29,43 @@ export class UpdateDeviceInRoomDto {
|
||||
public deviceUuid: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'roomUuid',
|
||||
description: 'spaceUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public roomUuid: string;
|
||||
public spaceUuid: string;
|
||||
}
|
||||
export class AddSceneToFourSceneDeviceDto {
|
||||
@ApiProperty({
|
||||
description: 'switchName',
|
||||
required: true,
|
||||
})
|
||||
@IsEnum(SceneSwitchesTypeEnum)
|
||||
@IsNotEmpty()
|
||||
switchName: SceneSwitchesTypeEnum;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'sceneUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public sceneUuid: string;
|
||||
@ApiProperty({
|
||||
description: 'spaceUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public spaceUuid: string;
|
||||
}
|
||||
export class UpdateDeviceDto {
|
||||
@ApiProperty({
|
||||
description: 'deviceName',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public deviceName: string;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsArray, IsNotEmpty, IsString } from 'class-validator';
|
||||
import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class ControlDeviceDto {
|
||||
@ApiProperty({
|
||||
@ -54,3 +54,12 @@ export class BatchFactoryResetDevicesDto {
|
||||
@IsNotEmpty()
|
||||
public devicesUuid: [string];
|
||||
}
|
||||
export class GetSceneFourSceneDeviceDto {
|
||||
@ApiProperty({
|
||||
description: 'switchName',
|
||||
required: false,
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
public switchName?: string;
|
||||
}
|
||||
|
@ -1,16 +1,12 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class UpdateRoomNameDto {
|
||||
export class DeleteSceneFromSceneDeviceDto {
|
||||
@ApiProperty({
|
||||
description: 'roomName',
|
||||
description: 'switchName',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public roomName: string;
|
||||
|
||||
constructor(dto: Partial<UpdateRoomNameDto>) {
|
||||
Object.assign(this, dto);
|
||||
}
|
||||
public switchName: string;
|
||||
}
|
11
src/device/dtos/device.param.dto.ts
Normal file
11
src/device/dtos/device.param.dto.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsUUID } from 'class-validator';
|
||||
|
||||
export class DeviceSceneParamDto {
|
||||
@ApiProperty({
|
||||
description: 'UUID of the device',
|
||||
example: 'b3a37332-9c03-4ce2-ac94-bea75382b365',
|
||||
})
|
||||
@IsUUID()
|
||||
deviceUuid: string;
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class GetDeviceByRoomUuidDto {
|
||||
export class GetDeviceBySpaceUuidDto {
|
||||
@ApiProperty({
|
||||
description: 'roomUuid',
|
||||
description: 'spaceUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public roomUuid: string;
|
||||
public spaceUuid: string;
|
||||
}
|
||||
export class GetDeviceLogsDto {
|
||||
@ApiProperty({
|
||||
|
@ -1,6 +1,6 @@
|
||||
export interface GetDeviceDetailsInterface {
|
||||
activeTime: number;
|
||||
assetId: string;
|
||||
assetId?: string;
|
||||
category: string;
|
||||
categoryName: string;
|
||||
createTime: number;
|
||||
@ -13,6 +13,7 @@ export interface GetDeviceDetailsInterface {
|
||||
lon: string;
|
||||
model: string;
|
||||
name: string;
|
||||
battery?: number;
|
||||
nodeId: string;
|
||||
online: boolean;
|
||||
productId?: string;
|
||||
@ -23,6 +24,18 @@ export interface GetDeviceDetailsInterface {
|
||||
uuid: string;
|
||||
productType: string;
|
||||
productUuid: string;
|
||||
spaces?: SpaceInterface[];
|
||||
community?: CommunityInterface;
|
||||
}
|
||||
|
||||
export interface SpaceInterface {
|
||||
uuid: string;
|
||||
spaceName: string;
|
||||
}
|
||||
|
||||
export interface CommunityInterface {
|
||||
uuid: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface addDeviceInRoomInterface {
|
||||
@ -77,3 +90,9 @@ export interface GetPowerClampFunctionsStatusInterface {
|
||||
success: boolean;
|
||||
msg: string;
|
||||
}
|
||||
export interface GetMacAddressInterface {
|
||||
uuid: string;
|
||||
mac: string;
|
||||
sn: string;
|
||||
id: string;
|
||||
}
|
||||
|
@ -5,22 +5,30 @@ import {
|
||||
HttpStatus,
|
||||
NotFoundException,
|
||||
BadRequestException,
|
||||
forwardRef,
|
||||
Inject,
|
||||
} from '@nestjs/common';
|
||||
import { TuyaContext } from '@tuya/tuya-connector-nodejs';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { AddDeviceDto, UpdateDeviceInRoomDto } from '../dtos/add.device.dto';
|
||||
import {
|
||||
AddDeviceDto,
|
||||
AddSceneToFourSceneDeviceDto,
|
||||
UpdateDeviceDto,
|
||||
UpdateDeviceInSpaceDto,
|
||||
} from '../dtos/add.device.dto';
|
||||
import {
|
||||
DeviceInstructionResponse,
|
||||
GetDeviceDetailsFunctionsInterface,
|
||||
GetDeviceDetailsFunctionsStatusInterface,
|
||||
GetDeviceDetailsInterface,
|
||||
GetMacAddressInterface,
|
||||
GetPowerClampFunctionsStatusInterface,
|
||||
controlDeviceInterface,
|
||||
getDeviceLogsInterface,
|
||||
updateDeviceFirmwareInterface,
|
||||
} from '../interfaces/get.device.interface';
|
||||
import {
|
||||
GetDeviceByRoomUuidDto,
|
||||
GetDeviceBySpaceUuidDto,
|
||||
GetDeviceLogsDto,
|
||||
} from '../dtos/get.device.dto';
|
||||
import {
|
||||
@ -28,6 +36,7 @@ import {
|
||||
BatchFactoryResetDevicesDto,
|
||||
BatchStatusDevicesDto,
|
||||
ControlDeviceDto,
|
||||
GetSceneFourSceneDeviceDto,
|
||||
} from '../dtos/control.device.dto';
|
||||
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
|
||||
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
||||
@ -39,6 +48,17 @@ import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status
|
||||
import { DeviceStatuses } from '@app/common/constants/device-status.enum';
|
||||
import { CommonErrorCodes } from '@app/common/constants/error-codes.enum';
|
||||
import { BatteryStatus } from '@app/common/constants/battery-status.enum';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities';
|
||||
import { SceneService } from 'src/scene/services';
|
||||
import { AddAutomationDto } from 'src/automation/dtos';
|
||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||
import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories';
|
||||
import { SceneSwitchesTypeEnum } from '@app/common/constants/scene-switch-type.enum';
|
||||
import { AUTOMATION_CONFIG } from '@app/common/constants/automation.enum';
|
||||
import { DeviceSceneParamDto } from '../dtos/device.param.dto';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||
import { DeleteSceneFromSceneDeviceDto } from '../dtos/delete.device.dto';
|
||||
|
||||
@Injectable()
|
||||
export class DeviceService {
|
||||
@ -46,9 +66,13 @@ export class DeviceService {
|
||||
constructor(
|
||||
private readonly configService: ConfigService,
|
||||
private readonly deviceRepository: DeviceRepository,
|
||||
private readonly sceneDeviceRepository: SceneDeviceRepository,
|
||||
private readonly productRepository: ProductRepository,
|
||||
private readonly deviceStatusFirebaseService: DeviceStatusFirebaseService,
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
@Inject(forwardRef(() => SceneService))
|
||||
private readonly sceneService: SceneService,
|
||||
private readonly tuyaService: TuyaService,
|
||||
) {
|
||||
const accessKey = this.configService.get<string>('auth-config.ACCESS_KEY');
|
||||
const secretKey = this.configService.get<string>('auth-config.SECRET_KEY');
|
||||
@ -63,13 +87,18 @@ export class DeviceService {
|
||||
deviceUuid: string,
|
||||
withProductDevice: boolean = true,
|
||||
) {
|
||||
return await this.deviceRepository.findOne({
|
||||
where: {
|
||||
uuid: deviceUuid,
|
||||
},
|
||||
...(withProductDevice && { relations: ['productDevice'] }),
|
||||
const relations = ['subspace'];
|
||||
|
||||
if (withProductDevice) {
|
||||
relations.push('productDevice');
|
||||
}
|
||||
|
||||
return this.deviceRepository.findOne({
|
||||
where: { uuid: deviceUuid },
|
||||
relations,
|
||||
});
|
||||
}
|
||||
|
||||
async getDeviceByDeviceTuyaUuid(deviceTuyaUuid: string) {
|
||||
return await this.deviceRepository.findOne({
|
||||
where: {
|
||||
@ -78,6 +107,7 @@ export class DeviceService {
|
||||
relations: ['productDevice'],
|
||||
});
|
||||
}
|
||||
|
||||
async addDeviceUser(addDeviceDto: AddDeviceDto) {
|
||||
try {
|
||||
const device = await this.getDeviceDetailsByDeviceIdTuya(
|
||||
@ -117,12 +147,13 @@ export class DeviceService {
|
||||
);
|
||||
} else {
|
||||
throw new HttpException(
|
||||
error.message || 'Failed to add device in room',
|
||||
error.message || 'Failed to add device in space',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getDevicesByUser(
|
||||
userUuid: string,
|
||||
): Promise<GetDeviceDetailsInterface[]> {
|
||||
@ -145,38 +176,48 @@ export class DeviceService {
|
||||
'permission.permissionType',
|
||||
],
|
||||
});
|
||||
const devicesData = await Promise.all(
|
||||
devices.map(async (device) => {
|
||||
const safeFetchDeviceDetails = async (device: any) => {
|
||||
try {
|
||||
const tuyaDetails = await this.getDeviceDetailsByDeviceIdTuya(
|
||||
device.deviceTuyaUuid,
|
||||
);
|
||||
|
||||
return {
|
||||
haveRoom: device.spaceDevice ? true : false,
|
||||
haveRoom: !!device.spaceDevice,
|
||||
productUuid: device.productDevice.uuid,
|
||||
productType: device.productDevice.prodType,
|
||||
permissionType: device.permission[0].permissionType.type,
|
||||
...(await this.getDeviceDetailsByDeviceIdTuya(
|
||||
device.deviceTuyaUuid,
|
||||
)),
|
||||
...tuyaDetails,
|
||||
uuid: device.uuid,
|
||||
} as GetDeviceDetailsInterface;
|
||||
}),
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`Skipping device with deviceTuyaUuid: ${device.deviceTuyaUuid} due to error.`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const devicesData = await Promise.all(
|
||||
devices.map(safeFetchDeviceDetails),
|
||||
);
|
||||
|
||||
return devicesData;
|
||||
return devicesData.filter(Boolean); // Remove null or undefined entries
|
||||
} catch (error) {
|
||||
// Handle the error here
|
||||
console.error('Error fetching devices by user:', error);
|
||||
throw new HttpException(
|
||||
'User does not have any devices',
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
async getDevicesByRoomId(
|
||||
getDeviceByRoomUuidDto: GetDeviceByRoomUuidDto,
|
||||
async getDevicesBySpaceId(
|
||||
getDeviceBySpaceUuidDto: GetDeviceBySpaceUuidDto,
|
||||
userUuid: string,
|
||||
): Promise<GetDeviceDetailsInterface[]> {
|
||||
try {
|
||||
const devices = await this.deviceRepository.find({
|
||||
where: {
|
||||
spaceDevice: { uuid: getDeviceByRoomUuidDto.roomUuid },
|
||||
spaceDevice: { uuid: getDeviceBySpaceUuidDto.spaceUuid },
|
||||
isActive: true,
|
||||
permission: {
|
||||
userUuid,
|
||||
@ -211,22 +252,22 @@ export class DeviceService {
|
||||
} catch (error) {
|
||||
// Handle the error here
|
||||
throw new HttpException(
|
||||
'Error fetching devices by room',
|
||||
'Error fetching devices by space',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
async updateDeviceInRoom(updateDeviceInRoomDto: UpdateDeviceInRoomDto) {
|
||||
async updateDeviceInSpace(updateDeviceInSpaceDto: UpdateDeviceInSpaceDto) {
|
||||
try {
|
||||
await this.deviceRepository.update(
|
||||
{ uuid: updateDeviceInRoomDto.deviceUuid },
|
||||
{ uuid: updateDeviceInSpaceDto.deviceUuid },
|
||||
{
|
||||
spaceDevice: { uuid: updateDeviceInRoomDto.roomUuid },
|
||||
spaceDevice: { uuid: updateDeviceInSpaceDto.spaceUuid },
|
||||
},
|
||||
);
|
||||
const device = await this.deviceRepository.findOne({
|
||||
where: {
|
||||
uuid: updateDeviceInRoomDto.deviceUuid,
|
||||
uuid: updateDeviceInSpaceDto.deviceUuid,
|
||||
},
|
||||
relations: ['spaceDevice', 'spaceDevice.parent'],
|
||||
});
|
||||
@ -239,15 +280,16 @@ export class DeviceService {
|
||||
|
||||
return {
|
||||
uuid: device.uuid,
|
||||
roomUuid: device.spaceDevice.uuid,
|
||||
spaceUuid: device.spaceDevice.uuid,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
'Failed to add device in room',
|
||||
'Failed to add device in space',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async transferDeviceInSpacesTuya(
|
||||
deviceId: string,
|
||||
spaceId: string,
|
||||
@ -268,6 +310,26 @@ export class DeviceService {
|
||||
);
|
||||
}
|
||||
}
|
||||
async updateDeviceNameTuya(
|
||||
deviceId: string,
|
||||
deviceName: string,
|
||||
): Promise<controlDeviceInterface> {
|
||||
try {
|
||||
const path = `/v2.0/cloud/thing/${deviceId}/attribute`;
|
||||
const response = await this.tuya.request({
|
||||
method: 'POST',
|
||||
path,
|
||||
body: { type: 1, data: deviceName },
|
||||
});
|
||||
|
||||
return response as controlDeviceInterface;
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
'Error updating device name from Tuya',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
async controlDevice(controlDeviceDto: ControlDeviceDto, deviceUuid: string) {
|
||||
try {
|
||||
const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid, false);
|
||||
@ -295,6 +357,7 @@ export class DeviceService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async factoryResetDeviceTuya(
|
||||
deviceUuid: string,
|
||||
): Promise<controlDeviceInterface> {
|
||||
@ -337,6 +400,7 @@ export class DeviceService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async batchControlDevices(batchControlDevicesDto: BatchControlDevicesDto) {
|
||||
const { devicesUuid } = batchControlDevicesDto;
|
||||
|
||||
@ -511,6 +575,9 @@ export class DeviceService {
|
||||
const response = await this.getDeviceDetailsByDeviceIdTuya(
|
||||
deviceDetails.deviceTuyaUuid,
|
||||
);
|
||||
const macAddress = await this.getMacAddressByDeviceIdTuya(
|
||||
deviceDetails.deviceTuyaUuid,
|
||||
);
|
||||
|
||||
return {
|
||||
...response,
|
||||
@ -518,6 +585,8 @@ export class DeviceService {
|
||||
productUuid: deviceDetails.productDevice.uuid,
|
||||
productType: deviceDetails.productDevice.prodType,
|
||||
permissionType: userDevicePermission,
|
||||
macAddress: macAddress.mac,
|
||||
subspace: deviceDetails.subspace ? deviceDetails.subspace : {},
|
||||
};
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
@ -526,6 +595,27 @@ export class DeviceService {
|
||||
);
|
||||
}
|
||||
}
|
||||
async updateDevice(deviceUuid: string, updateDeviceDto: UpdateDeviceDto) {
|
||||
try {
|
||||
const device = await this.getDeviceByDeviceUuid(deviceUuid);
|
||||
if (device.deviceTuyaUuid) {
|
||||
await this.updateDeviceNameTuya(
|
||||
device.deviceTuyaUuid,
|
||||
updateDeviceDto.deviceName,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
uuid: device.uuid,
|
||||
deviceName: updateDeviceDto.deviceName,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
'Error updating device',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
async getDeviceDetailsByDeviceIdTuya(
|
||||
deviceId: string,
|
||||
): Promise<GetDeviceDetailsInterface> {
|
||||
@ -558,17 +648,33 @@ export class DeviceService {
|
||||
);
|
||||
}
|
||||
}
|
||||
async getMacAddressByDeviceIdTuya(
|
||||
deviceId: string,
|
||||
): Promise<GetMacAddressInterface> {
|
||||
try {
|
||||
const path = `/v1.0/devices/factory-infos?device_ids=${deviceId}`;
|
||||
const response = await this.tuya.request({
|
||||
method: 'GET',
|
||||
path,
|
||||
});
|
||||
|
||||
return response.result[0];
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
'Error fetching mac address device from Tuya',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
async getDeviceInstructionByDeviceId(
|
||||
deviceUuid: string,
|
||||
): Promise<DeviceInstructionResponse> {
|
||||
const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid);
|
||||
|
||||
if (!deviceDetails) {
|
||||
throw new NotFoundException('Device Not Found');
|
||||
}
|
||||
try {
|
||||
const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid);
|
||||
|
||||
if (!deviceDetails) {
|
||||
throw new NotFoundException('Device Not Found');
|
||||
}
|
||||
|
||||
const response = await this.getDeviceInstructionByDeviceIdTuya(
|
||||
deviceDetails.deviceTuyaUuid,
|
||||
);
|
||||
@ -778,22 +884,22 @@ export class DeviceService {
|
||||
);
|
||||
}
|
||||
}
|
||||
async getDevicesByUnitId(unitUuid: string) {
|
||||
async getDevicesBySpaceUuid(SpaceUuid: string) {
|
||||
try {
|
||||
const spaces = await this.spaceRepository.find({
|
||||
where: {
|
||||
parent: {
|
||||
uuid: unitUuid,
|
||||
uuid: SpaceUuid,
|
||||
},
|
||||
devicesSpaceEntity: {
|
||||
devices: {
|
||||
isActive: true,
|
||||
},
|
||||
},
|
||||
relations: ['devicesSpaceEntity', 'devicesSpaceEntity.productDevice'],
|
||||
relations: ['devices', 'devices.productDevice'],
|
||||
});
|
||||
|
||||
const devices = spaces.flatMap((space) => {
|
||||
return space.devicesSpaceEntity.map((device) => device);
|
||||
return space.devices.map((device) => device);
|
||||
});
|
||||
|
||||
const devicesData = await Promise.all(
|
||||
@ -814,7 +920,7 @@ export class DeviceService {
|
||||
return devicesData;
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
'This unit does not have any devices',
|
||||
'This space does not have any devices',
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
@ -825,11 +931,13 @@ export class DeviceService {
|
||||
where: { isActive: true },
|
||||
relations: [
|
||||
'spaceDevice.parent',
|
||||
'spaceDevice.community',
|
||||
'productDevice',
|
||||
'permission',
|
||||
'permission.permissionType',
|
||||
],
|
||||
});
|
||||
|
||||
const devicesData = await Promise.allSettled(
|
||||
devices.map(async (device) => {
|
||||
let battery = null;
|
||||
@ -874,20 +982,24 @@ export class DeviceService {
|
||||
battery = batteryStatus.value;
|
||||
}
|
||||
}
|
||||
const spaceDevice = device?.spaceDevice;
|
||||
const parentDevice = spaceDevice?.parent;
|
||||
|
||||
const spaceHierarchy = await this.getFullSpaceHierarchy(
|
||||
device?.spaceDevice,
|
||||
);
|
||||
const orderedHierarchy = spaceHierarchy.reverse();
|
||||
|
||||
return {
|
||||
room: {
|
||||
uuid: spaceDevice?.uuid,
|
||||
name: spaceDevice?.spaceName,
|
||||
},
|
||||
unit: {
|
||||
uuid: parentDevice?.uuid,
|
||||
name: parentDevice?.spaceName,
|
||||
},
|
||||
spaces: orderedHierarchy.map((space) => ({
|
||||
uuid: space.uuid,
|
||||
spaceName: space.spaceName,
|
||||
})),
|
||||
productUuid: device.productDevice.uuid,
|
||||
productType: device.productDevice.prodType,
|
||||
permissionType: device.permission[0].permissionType.type,
|
||||
community: {
|
||||
uuid: device.spaceDevice.community.uuid,
|
||||
name: device.spaceDevice.community.name,
|
||||
},
|
||||
// permissionType: device.permission[0].permissionType.type,
|
||||
...(await this.getDeviceDetailsByDeviceIdTuya(
|
||||
device.deviceTuyaUuid,
|
||||
)),
|
||||
@ -913,6 +1025,7 @@ export class DeviceService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getDeviceLogs(deviceUuid: string, query: GetDeviceLogsDto) {
|
||||
try {
|
||||
const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid);
|
||||
@ -966,6 +1079,40 @@ export class DeviceService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getFullSpaceHierarchy(
|
||||
space: SpaceEntity,
|
||||
): Promise<{ uuid: string; spaceName: string }[]> {
|
||||
try {
|
||||
// Fetch only the relevant spaces, starting with the target space
|
||||
const targetSpace = await this.spaceRepository.findOne({
|
||||
where: { uuid: space.uuid },
|
||||
relations: ['parent', 'children'],
|
||||
});
|
||||
|
||||
// Fetch only the ancestors of the target space
|
||||
const ancestors = await this.fetchAncestors(targetSpace);
|
||||
|
||||
// Optionally, fetch descendants if required
|
||||
const descendants = await this.fetchDescendants(targetSpace);
|
||||
|
||||
const fullHierarchy = [...ancestors, targetSpace, ...descendants].map(
|
||||
(space) => ({
|
||||
uuid: space.uuid,
|
||||
spaceName: space.spaceName,
|
||||
}),
|
||||
);
|
||||
|
||||
return fullHierarchy;
|
||||
} catch (error) {
|
||||
console.error('Error fetching space hierarchy:', error.message);
|
||||
throw new HttpException(
|
||||
'Error fetching space hierarchy',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getPowerClampInstructionStatus(powerClampUuid: string) {
|
||||
try {
|
||||
const deviceDetails = await this.getDeviceByDeviceUuid(powerClampUuid);
|
||||
@ -1043,4 +1190,276 @@ export class DeviceService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchAncestors(space: SpaceEntity): Promise<SpaceEntity[]> {
|
||||
const ancestors: SpaceEntity[] = [];
|
||||
|
||||
let currentSpace = space;
|
||||
while (currentSpace && currentSpace.parent) {
|
||||
// Fetch the parent space
|
||||
const parent = await this.spaceRepository.findOne({
|
||||
where: { uuid: currentSpace.parent.uuid },
|
||||
relations: ['parent'], // To continue fetching upwards
|
||||
});
|
||||
|
||||
if (parent) {
|
||||
ancestors.push(parent);
|
||||
currentSpace = parent;
|
||||
} else {
|
||||
currentSpace = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the ancestors in reverse order to have the root at the start
|
||||
return ancestors.reverse();
|
||||
}
|
||||
|
||||
private async fetchDescendants(space: SpaceEntity): Promise<SpaceEntity[]> {
|
||||
const descendants: SpaceEntity[] = [];
|
||||
|
||||
// Fetch the immediate children of the current space
|
||||
const children = await this.spaceRepository.find({
|
||||
where: { parent: { uuid: space.uuid } },
|
||||
relations: ['children'], // To continue fetching downwards
|
||||
});
|
||||
|
||||
for (const child of children) {
|
||||
// Add the child to the descendants list
|
||||
descendants.push(child);
|
||||
|
||||
// Recursively fetch the child's descendants
|
||||
const childDescendants = await this.fetchDescendants(child);
|
||||
descendants.push(...childDescendants);
|
||||
}
|
||||
|
||||
return descendants;
|
||||
}
|
||||
|
||||
async addSceneToSceneDevice(
|
||||
deviceUuid: string,
|
||||
addSceneToFourSceneDeviceDto: AddSceneToFourSceneDeviceDto,
|
||||
) {
|
||||
try {
|
||||
const { spaceUuid, sceneUuid, switchName } = addSceneToFourSceneDeviceDto;
|
||||
|
||||
if (!spaceUuid || !sceneUuid || !switchName) {
|
||||
throw new BadRequestException('Missing required fields in DTO');
|
||||
}
|
||||
|
||||
const [sceneData, spaceData, deviceData] = await Promise.all([
|
||||
this.sceneService.findScene(sceneUuid),
|
||||
this.sceneService.getSpaceByUuid(spaceUuid),
|
||||
this.getDeviceByDeviceUuid(deviceUuid),
|
||||
]);
|
||||
|
||||
const shortUuid = deviceUuid.slice(0, 6); // First 6 characters of the UUID
|
||||
const timestamp = Math.floor(Date.now() / 1000); // Current timestamp in seconds
|
||||
const automationName = `Auto_${shortUuid}_${timestamp}`;
|
||||
|
||||
const addAutomationData: AddAutomationDto = {
|
||||
spaceUuid: spaceData.spaceTuyaUuid,
|
||||
automationName,
|
||||
decisionExpr: AUTOMATION_CONFIG.DECISION_EXPR,
|
||||
effectiveTime: {
|
||||
start: AUTOMATION_CONFIG.DEFAULT_START_TIME,
|
||||
end: AUTOMATION_CONFIG.DEFAULT_END_TIME,
|
||||
loops: AUTOMATION_CONFIG.DEFAULT_LOOPS,
|
||||
},
|
||||
conditions: [
|
||||
{
|
||||
code: 1,
|
||||
entityId: deviceData.deviceTuyaUuid,
|
||||
entityType: AUTOMATION_CONFIG.CONDITION_TYPE,
|
||||
expr: {
|
||||
comparator: AUTOMATION_CONFIG.COMPARATOR,
|
||||
statusCode: switchName,
|
||||
statusValue: AUTOMATION_CONFIG.SCENE_STATUS_VALUE,
|
||||
},
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
actionExecutor: AUTOMATION_CONFIG.ACTION_EXECUTOR,
|
||||
entityId: sceneData.sceneTuyaUuid,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const automation = await this.tuyaService.createAutomation(
|
||||
addAutomationData.spaceUuid,
|
||||
addAutomationData.automationName,
|
||||
addAutomationData.effectiveTime,
|
||||
addAutomationData.decisionExpr,
|
||||
addAutomationData.conditions,
|
||||
addAutomationData.actions,
|
||||
);
|
||||
|
||||
if (automation.success) {
|
||||
const existingSceneDevice = await this.sceneDeviceRepository.findOne({
|
||||
where: {
|
||||
device: { uuid: deviceUuid },
|
||||
switchName: switchName,
|
||||
},
|
||||
relations: ['scene', 'device'],
|
||||
});
|
||||
|
||||
if (existingSceneDevice) {
|
||||
await this.tuyaService.deleteAutomation(
|
||||
spaceData.spaceTuyaUuid,
|
||||
existingSceneDevice.automationTuyaUuid,
|
||||
);
|
||||
|
||||
existingSceneDevice.automationTuyaUuid = automation.result.id;
|
||||
existingSceneDevice.scene = sceneData;
|
||||
existingSceneDevice.device = deviceData;
|
||||
existingSceneDevice.switchName = switchName;
|
||||
|
||||
return await this.sceneDeviceRepository.save(existingSceneDevice);
|
||||
} else {
|
||||
const sceneDevice = await this.sceneDeviceRepository.save({
|
||||
scene: sceneData,
|
||||
device: deviceData,
|
||||
automationTuyaUuid: automation.result.id,
|
||||
switchName: switchName,
|
||||
});
|
||||
return sceneDevice;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
const errorMessage = err.message || 'Error creating automation';
|
||||
const errorStatus = err.status || HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
throw new HttpException(errorMessage, errorStatus);
|
||||
}
|
||||
}
|
||||
|
||||
async getScenesBySceneDevice(
|
||||
deviceUuid: string,
|
||||
getSceneFourSceneDeviceDto: GetSceneFourSceneDeviceDto,
|
||||
): Promise<any> {
|
||||
try {
|
||||
if (getSceneFourSceneDeviceDto.switchName) {
|
||||
// Query for a single record directly when switchName is provided
|
||||
const sceneDevice = await this.sceneDeviceRepository.findOne({
|
||||
where: {
|
||||
device: { uuid: deviceUuid },
|
||||
switchName:
|
||||
getSceneFourSceneDeviceDto.switchName as SceneSwitchesTypeEnum,
|
||||
},
|
||||
relations: ['device', 'scene'],
|
||||
});
|
||||
|
||||
if (!sceneDevice) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const sceneDetails = await this.sceneService.getSceneByUuid(
|
||||
sceneDevice.scene.uuid,
|
||||
);
|
||||
|
||||
return {
|
||||
switchSceneUuid: sceneDevice.uuid,
|
||||
switchName: sceneDevice.switchName,
|
||||
createdAt: sceneDevice.createdAt,
|
||||
updatedAt: sceneDevice.updatedAt,
|
||||
deviceUuid: sceneDevice.device.uuid,
|
||||
scene: sceneDetails.data,
|
||||
};
|
||||
}
|
||||
|
||||
// Query for multiple records if switchName is not provided
|
||||
const sceneDevices = await this.sceneDeviceRepository.find({
|
||||
where: { device: { uuid: deviceUuid } },
|
||||
relations: ['device', 'scene'],
|
||||
});
|
||||
|
||||
if (!sceneDevices.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const results = await Promise.all(
|
||||
sceneDevices.map(async (sceneDevice) => {
|
||||
const sceneDetails = await this.sceneService.getSceneByUuid(
|
||||
sceneDevice.scene.uuid,
|
||||
);
|
||||
|
||||
return {
|
||||
switchSceneUuid: sceneDevice.uuid,
|
||||
switchName: sceneDevice.switchName,
|
||||
createdAt: sceneDevice.createdAt,
|
||||
updatedAt: sceneDevice.updatedAt,
|
||||
deviceUuid: sceneDevice.device.uuid,
|
||||
scene: sceneDetails.data,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
return results;
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Failed to fetch scenes for device',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
async deleteSceneFromSceneDevice(
|
||||
params: DeviceSceneParamDto,
|
||||
query: DeleteSceneFromSceneDeviceDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
const { deviceUuid } = params;
|
||||
const { switchName } = query;
|
||||
|
||||
try {
|
||||
const existingSceneDevice = await this.sceneDeviceRepository.findOne({
|
||||
where: {
|
||||
device: { uuid: deviceUuid },
|
||||
switchName: switchName as SceneSwitchesTypeEnum,
|
||||
},
|
||||
relations: ['scene.space.community'],
|
||||
});
|
||||
|
||||
if (!existingSceneDevice) {
|
||||
throw new HttpException(
|
||||
`No scene found for device with UUID ${deviceUuid} and switch name ${switchName}`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const deleteResult = await this.sceneDeviceRepository.delete({
|
||||
device: { uuid: deviceUuid },
|
||||
switchName: switchName as SceneSwitchesTypeEnum,
|
||||
});
|
||||
|
||||
if (deleteResult.affected === 0) {
|
||||
throw new HttpException(
|
||||
`Failed to delete Switch Scene with device ID ${deviceUuid} and switch name ${switchName}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
const tuyaAutomationResult = await this.tuyaService.deleteAutomation(
|
||||
existingSceneDevice.scene.space.community.externalId,
|
||||
existingSceneDevice.automationTuyaUuid,
|
||||
);
|
||||
|
||||
if (!tuyaAutomationResult.success) {
|
||||
throw new HttpException(
|
||||
`Failed to delete Tuya automation for Switch Scene with ID ${existingSceneDevice.automationTuyaUuid}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
return new SuccessResponseDto({
|
||||
message: `Switch Scene with device ID ${deviceUuid} and switch name ${switchName} deleted successfully`,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw new HttpException(
|
||||
error.message || `An unexpected error occurred`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,23 +10,31 @@ import {
|
||||
UseGuards,
|
||||
Put,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
||||
import { AddDoorLockOnlineDto } from '../dtos/add.online-temp.dto';
|
||||
import { AddDoorLockOfflineTempMultipleTimeDto } from '../dtos/add.offline-temp.dto';
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
import { UpdateDoorLockOfflineTempDto } from '../dtos/update.offline-temp.dto';
|
||||
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
|
||||
import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||
|
||||
@ApiTags('Door Lock Module')
|
||||
@Controller({
|
||||
version: EnableDisableStatusEnum.ENABLED,
|
||||
path: 'door-lock',
|
||||
path: ControllerRoute.DOOR_LOCK.ROUTE,
|
||||
})
|
||||
export class DoorLockController {
|
||||
constructor(private readonly doorLockService: DoorLockService) {}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post('temporary-password/online/:doorLockUuid')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
ControllerRoute.DOOR_LOCK.ACTIONS.ADD_ONLINE_TEMPORARY_PASSWORD_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DOOR_LOCK.ACTIONS
|
||||
.ADD_ONLINE_TEMPORARY_PASSWORD_DESCRIPTION,
|
||||
})
|
||||
async addOnlineTemporaryPassword(
|
||||
@Body() addDoorLockDto: AddDoorLockOnlineDto,
|
||||
@Param('doorLockUuid') doorLockUuid: string,
|
||||
@ -40,15 +48,24 @@ export class DoorLockController {
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'online temporary password added successfully',
|
||||
message: 'Online temporary password added successfully',
|
||||
data: {
|
||||
id: temporaryPassword.id,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post('temporary-password/offline/one-time/:doorLockUuid')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
ControllerRoute.DOOR_LOCK.ACTIONS
|
||||
.ADD_OFFLINE_ONE_TIME_TEMPORARY_PASSWORD_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DOOR_LOCK.ACTIONS
|
||||
.ADD_OFFLINE_ONE_TIME_TEMPORARY_PASSWORD_DESCRIPTION,
|
||||
})
|
||||
async addOfflineOneTimeTemporaryPassword(
|
||||
@Param('doorLockUuid') doorLockUuid: string,
|
||||
) {
|
||||
@ -60,13 +77,22 @@ export class DoorLockController {
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'offline temporary password added successfully',
|
||||
message: 'Offline one-time temporary password added successfully',
|
||||
data: temporaryPassword,
|
||||
};
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post('temporary-password/offline/multiple-time/:doorLockUuid')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
ControllerRoute.DOOR_LOCK.ACTIONS
|
||||
.ADD_OFFLINE_MULTIPLE_TIME_TEMPORARY_PASSWORD_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DOOR_LOCK.ACTIONS
|
||||
.ADD_OFFLINE_MULTIPLE_TIME_TEMPORARY_PASSWORD_DESCRIPTION,
|
||||
})
|
||||
async addOfflineMultipleTimeTemporaryPassword(
|
||||
@Body()
|
||||
addDoorLockOfflineTempMultipleTimeDto: AddDoorLockOfflineTempMultipleTimeDto,
|
||||
@ -81,13 +107,21 @@ export class DoorLockController {
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'offline temporary password added successfully',
|
||||
message: 'Offline multiple-time temporary password added successfully',
|
||||
data: temporaryPassword,
|
||||
};
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('temporary-password/online/:doorLockUuid')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
ControllerRoute.DOOR_LOCK.ACTIONS.GET_ONLINE_TEMPORARY_PASSWORDS_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DOOR_LOCK.ACTIONS
|
||||
.GET_ONLINE_TEMPORARY_PASSWORDS_DESCRIPTION,
|
||||
})
|
||||
async getOnlineTemporaryPasswords(
|
||||
@Param('doorLockUuid') doorLockUuid: string,
|
||||
) {
|
||||
@ -95,9 +129,18 @@ export class DoorLockController {
|
||||
doorLockUuid,
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Delete('temporary-password/online/:doorLockUuid/:passwordId')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
ControllerRoute.DOOR_LOCK.ACTIONS
|
||||
.DELETE_ONLINE_TEMPORARY_PASSWORD_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DOOR_LOCK.ACTIONS
|
||||
.DELETE_ONLINE_TEMPORARY_PASSWORD_DESCRIPTION,
|
||||
})
|
||||
async deleteDoorLockPassword(
|
||||
@Param('doorLockUuid') doorLockUuid: string,
|
||||
@Param('passwordId') passwordId: string,
|
||||
@ -105,12 +148,21 @@ export class DoorLockController {
|
||||
await this.doorLockService.deleteDoorLockPassword(doorLockUuid, passwordId);
|
||||
return {
|
||||
statusCode: HttpStatus.OK,
|
||||
message: 'Temporary Password deleted Successfully',
|
||||
message: 'Temporary password deleted successfully',
|
||||
};
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('temporary-password/offline/one-time/:doorLockUuid')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
ControllerRoute.DOOR_LOCK.ACTIONS
|
||||
.GET_OFFLINE_ONE_TIME_TEMPORARY_PASSWORDS_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DOOR_LOCK.ACTIONS
|
||||
.GET_OFFLINE_ONE_TIME_TEMPORARY_PASSWORDS_DESCRIPTION,
|
||||
})
|
||||
async getOfflineOneTimeTemporaryPasswords(
|
||||
@Param('doorLockUuid') doorLockUuid: string,
|
||||
) {
|
||||
@ -118,9 +170,18 @@ export class DoorLockController {
|
||||
doorLockUuid,
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('temporary-password/offline/multiple-time/:doorLockUuid')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
ControllerRoute.DOOR_LOCK.ACTIONS
|
||||
.GET_OFFLINE_MULTIPLE_TIME_TEMPORARY_PASSWORDS_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DOOR_LOCK.ACTIONS
|
||||
.GET_OFFLINE_MULTIPLE_TIME_TEMPORARY_PASSWORDS_DESCRIPTION,
|
||||
})
|
||||
async getOfflineMultipleTimeTemporaryPasswords(
|
||||
@Param('doorLockUuid') doorLockUuid: string,
|
||||
) {
|
||||
@ -132,9 +193,16 @@ export class DoorLockController {
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Put('temporary-password/:doorLockUuid/offline/:passwordId')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
ControllerRoute.DOOR_LOCK.ACTIONS
|
||||
.UPDATE_OFFLINE_TEMPORARY_PASSWORD_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.DOOR_LOCK.ACTIONS
|
||||
.UPDATE_OFFLINE_TEMPORARY_PASSWORD_DESCRIPTION,
|
||||
})
|
||||
async updateOfflineTemporaryPassword(
|
||||
@Body()
|
||||
updateDoorLockOfflineTempDto: UpdateDoorLockOfflineTempDto,
|
||||
@Body() updateDoorLockOfflineTempDto: UpdateDoorLockOfflineTempDto,
|
||||
@Param('doorLockUuid') doorLockUuid: string,
|
||||
@Param('passwordId') passwordId: string,
|
||||
) {
|
||||
@ -148,20 +216,25 @@ export class DoorLockController {
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'offline temporary password updated successfully',
|
||||
message: 'Offline temporary password updated successfully',
|
||||
data: temporaryPassword,
|
||||
};
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post('open/:doorLockUuid')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.DOOR_LOCK.ACTIONS.OPEN_DOOR_LOCK_SUMMARY,
|
||||
description: ControllerRoute.DOOR_LOCK.ACTIONS.OPEN_DOOR_LOCK_DESCRIPTION,
|
||||
})
|
||||
async openDoorLock(@Param('doorLockUuid') doorLockUuid: string) {
|
||||
await this.doorLockService.openDoorLock(doorLockUuid);
|
||||
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'door lock opened successfully',
|
||||
message: 'Door lock opened successfully',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,13 @@ import { ProductRepository } from '@app/common/modules/product/repositories';
|
||||
import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories';
|
||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||
import { SceneService } from 'src/scene/services';
|
||||
import {
|
||||
SceneIconRepository,
|
||||
SceneRepository,
|
||||
} from '@app/common/modules/scene/repositories';
|
||||
import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories';
|
||||
@Module({
|
||||
imports: [ConfigModule, DeviceRepositoryModule],
|
||||
controllers: [DoorLockController],
|
||||
@ -24,6 +31,11 @@ import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log
|
||||
DeviceStatusFirebaseService,
|
||||
SpaceRepository,
|
||||
DeviceStatusLogRepository,
|
||||
TuyaService,
|
||||
SceneService,
|
||||
SceneIconRepository,
|
||||
SceneRepository,
|
||||
SceneDeviceRepository,
|
||||
],
|
||||
exports: [DoorLockService],
|
||||
})
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user