get devices

This commit is contained in:
hannathkadher
2024-10-30 19:07:26 +04:00
parent 295013fd15
commit adacc60a40
15 changed files with 306 additions and 69 deletions

View File

@ -136,6 +136,17 @@ export class ControllerRoute {
};
};
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';

View File

@ -35,4 +35,21 @@ export class TuyaService {
);
}
}
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;
}
}

View File

@ -42,7 +42,7 @@ export class DeviceEntity extends AbstractEntity<DeviceDto> {
)
deviceUserNotification: DeviceNotificationEntity[];
@ManyToOne(() => SpaceEntity, (space) => space.devicesSpaceEntity, {
@ManyToOne(() => SpaceEntity, (space) => space.devices, {
nullable: true,
})
spaceDevice: SpaceEntity;

View File

@ -81,7 +81,7 @@ export class SpaceEntity extends AbstractEntity<SpaceDto> {
() => DeviceEntity,
(devicesSpaceEntity) => devicesSpaceEntity.spaceDevice,
)
devicesSpaceEntity: DeviceEntity[];
devices: DeviceEntity[];
constructor(partial: Partial<SpaceEntity>) {
super();

View File

@ -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()

View File

@ -50,11 +50,15 @@ export class AutomationService {
try {
let unitSpaceTuyaId;
if (!spaceTuyaId) {
const unitDetails = await this.getUnitByUuid(addAutomationDto.unitUuid);
const unitDetails = await this.getUnitByUuid(
addAutomationDto.spaceUuid,
);
unitSpaceTuyaId = unitDetails.spaceTuyaUuid;
if (!unitDetails) {
throw new BadRequestException('Invalid unit UUID');
throw new BadRequestException(
`Invalid space UUID ${addAutomationDto.spaceUuid}`,
);
}
} else {
unitSpaceTuyaId = spaceTuyaId;
@ -125,22 +129,23 @@ export class AutomationService {
}
}
}
async getUnitByUuid(unitUuid: string) {
async getUnitByUuid(spaceUuid: string) {
try {
const unit = await this.spaceRepository.findOne({
const space = await this.spaceRepository.findOne({
where: {
uuid: unitUuid,
uuid: spaceUuid,
},
relations: ['community'],
});
if (!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,
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) {
@ -150,11 +155,11 @@ export class AutomationService {
}
}
}
async getAutomationByUnit(unitUuid: string) {
async getAutomationByUnit(spaceUuid: string) {
try {
const unit = await this.getUnitByUuid(unitUuid);
const unit = await this.getUnitByUuid(spaceUuid);
if (!unit.spaceTuyaUuid) {
throw new BadRequestException('Invalid unit UUID');
throw new BadRequestException(`Invalid space UUID ${spaceUuid}`);
}
const path = `/v2.0/cloud/scene/rule?space_id=${unit.spaceTuyaUuid}&type=automation`;
@ -311,10 +316,10 @@ export class AutomationService {
const { automationUuid, spaceUuid } = param;
let unitSpaceTuyaId;
if (!spaceTuyaId) {
const unitDetails = await this.getUnitByUuid(spaceUuid);
unitSpaceTuyaId = unitDetails.spaceTuyaUuid;
const space = await this.getUnitByUuid(spaceUuid);
unitSpaceTuyaId = space.spaceTuyaUuid;
if (!unitSpaceTuyaId) {
throw new BadRequestException('Invalid unit UUID');
throw new BadRequestException(`Invalid space UUID ${spaceUuid}`);
}
} else {
unitSpaceTuyaId = spaceTuyaId;
@ -357,7 +362,7 @@ export class AutomationService {
}
const addAutomation = {
...updateAutomationDto,
unitUuid: null,
spaceUuid: null,
};
const newAutomation = await this.addAutomation(
addAutomation,
@ -387,14 +392,16 @@ export class AutomationService {
automationUuid: string,
) {
try {
const unitDetails = await this.getUnitByUuid(
updateAutomationStatusDto.unitUuid,
const space = await this.getUnitByUuid(
updateAutomationStatusDto.spaceUuid,
);
if (!space.spaceTuyaUuid) {
throw new BadRequestException(
`Invalid space UUID ${updateAutomationStatusDto.spaceUuid}`,
);
if (!unitDetails.spaceTuyaUuid) {
throw new BadRequestException('Invalid unit UUID');
}
const path = `/v2.0/cloud/scene/rule/state?space_id=${unitDetails.spaceTuyaUuid}`;
const path = `/v2.0/cloud/scene/rule/state?space_id=${space.spaceTuyaUuid}`;
const response: DeleteAutomationInterface = await this.tuya.request({
method: 'PUT',
path,

View File

@ -792,15 +792,15 @@ export class DeviceService {
parent: {
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(

View File

@ -35,13 +35,11 @@ export class GroupService {
uuid: unitUuid,
},
},
relations: ['devicesSpaceEntity', 'devicesSpaceEntity.productDevice'],
relations: ['devices', 'devices.productDevice'],
});
const groupNames = spaces.flatMap((space) => {
return space.devicesSpaceEntity.map(
(device) => device.productDevice.prodType,
);
return space.devices.map((device) => device.productDevice.prodType);
});
const uniqueGroupNames = [...new Set(groupNames)];
@ -82,7 +80,7 @@ export class GroupService {
parent: {
uuid: unitUuid,
},
devicesSpaceEntity: {
devices: {
productDevice: {
prodType: groupName,
},
@ -95,18 +93,18 @@ export class GroupService {
},
},
relations: [
'devicesSpaceEntity',
'devicesSpaceEntity.productDevice',
'devicesSpaceEntity.spaceDevice',
'devicesSpaceEntity.permission',
'devicesSpaceEntity.permission.permissionType',
'devices',
'devices.productDevice',
'devices.spaceDevice',
'devices.permission',
'devices.permission.permissionType',
],
});
const devices = await Promise.all(
spaces.flatMap(async (space) => {
return await Promise.all(
space.devicesSpaceEntity.map(async (device) => {
space.devices.map(async (device) => {
const deviceDetails = await this.getDeviceDetailsByDeviceIdTuya(
device.deviceTuyaUuid,
);

View File

@ -33,13 +33,13 @@ export class CheckUnitTypeGuard implements CanActivate {
}
}
async checkFloorIsFloorType(unitUuid: string) {
const unitData = await this.spaceRepository.findOne({
where: { uuid: unitUuid },
async checkFloorIsFloorType(spaceUuid: string) {
const space = await this.spaceRepository.findOne({
where: { uuid: spaceUuid },
});
if (!unitData) {
throw new BadRequestException('Invalid unit UUID');
if (!space) {
throw new BadRequestException(`Invalid space UUID ${spaceUuid}`);
}
}

View File

@ -64,12 +64,12 @@ class Action {
export class AddSceneTapToRunDto {
@ApiProperty({
description: 'Unit UUID',
description: 'Space UUID',
required: true,
})
@IsString()
@IsNotEmpty()
public unitUuid: string;
public spaceUuid: string;
@ApiProperty({
description: 'Scene name',

View File

@ -48,12 +48,12 @@ export class SceneService {
try {
let unitSpaceTuyaId;
if (!spaceTuyaId) {
const unitDetails = await this.getSpaceByUuid(
addSceneTapToRunDto.unitUuid,
const space = await this.getSpaceByUuid(addSceneTapToRunDto.spaceUuid);
unitSpaceTuyaId = space.spaceTuyaUuid;
if (!space) {
throw new BadRequestException(
`Invalid space UUID ${addSceneTapToRunDto.spaceUuid}`,
);
unitSpaceTuyaId = unitDetails.spaceTuyaUuid;
if (!unitDetails) {
throw new BadRequestException('Invalid unit UUID');
}
} else {
unitSpaceTuyaId = spaceTuyaId;
@ -112,6 +112,7 @@ export class SceneService {
where: {
uuid: spaceUuid,
},
relations: ['community'],
});
if (!space) {
throw new BadRequestException(`Invalid space UUID ${spaceUuid}`);
@ -121,10 +122,11 @@ export class SceneService {
createdAt: space.createdAt,
updatedAt: space.updatedAt,
name: space.spaceName,
spaceTuyaUuid: space.spaceTuyaUuid,
spaceTuyaUuid: space.community.externalId,
};
} catch (err) {
if (err instanceof BadRequestException) {
console.log(err);
throw err; // Re-throw BadRequestException
} else {
throw new HttpException(
@ -136,12 +138,12 @@ export class SceneService {
}
async getTapToRunSceneByUnit(spaceUuid: string) {
try {
const unit = await this.getSpaceByUuid(spaceUuid);
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=scene`;
const path = `/v2.0/cloud/scene/rule?space_id=${space.spaceTuyaUuid}&type=scene`;
const response: GetTapToRunSceneByUnitInterface = await this.tuya.request(
{
method: 'GET',
@ -177,10 +179,10 @@ export class SceneService {
try {
let unitSpaceTuyaId;
if (!spaceTuyaId) {
const unitDetails = await this.getSpaceByUuid(spaceUuid);
unitSpaceTuyaId = unitDetails.spaceTuyaUuid;
const space = await this.getSpaceByUuid(spaceUuid);
unitSpaceTuyaId = space.spaceTuyaUuid;
if (!unitSpaceTuyaId) {
throw new BadRequestException('Invalid unit UUID');
throw new BadRequestException(`Invalid space UUID ${spaceUuid}`);
}
} else {
unitSpaceTuyaId = spaceTuyaId;
@ -331,7 +333,7 @@ export class SceneService {
}
const addSceneTapToRunDto: AddSceneTapToRunDto = {
...updateSceneTapToRunDto,
unitUuid: null,
spaceUuid: null,
};
const newTapToRunScene = await this.addTapToRunScene(
addSceneTapToRunDto,

View File

@ -0,0 +1,30 @@
import { ControllerRoute } from '@app/common/constants/controller-route';
import { Controller, Get, Param, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { SubspaceDeviceService } from '../services/space-device.service';
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
import { GetSpaceParam } from '../dtos';
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
@ApiTags('Space Module')
@Controller({
version: '1',
path: ControllerRoute.SPACE_DEVICES.ROUTE,
})
export class SubSpaceDeviceController {
constructor(private readonly spaceDeviceService: SubspaceDeviceService) {}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@ApiOperation({
summary: ControllerRoute.SPACE_DEVICES.ACTIONS.LIST_SPACE_DEVICE_SUMMARY,
description:
ControllerRoute.SPACE_DEVICES.ACTIONS.LIST_SPACE_DEVICE_DESCRIPTION,
})
@Get()
async listDevicesInSpace(
@Param() params: GetSpaceParam,
): Promise<BaseResponseDto> {
return await this.spaceDeviceService.listDevicesInSpace(params);
}
}

View File

@ -0,0 +1,112 @@
import { TuyaService } from '@app/common/integrations/tuya/tuya.service';
import { CommunityRepository } from '@app/common/modules/community/repositories';
import { SpaceRepository } from '@app/common/modules/space/repositories';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { GetDeviceDetailsInterface } from 'src/device/interfaces/get.device.interface';
import { GetSpaceParam } from '../dtos';
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
import { ProductRepository } from '@app/common/modules/product/repositories';
@Injectable()
export class SubspaceDeviceService {
constructor(
private readonly spaceRepository: SpaceRepository,
private readonly tuyaService: TuyaService,
private readonly productRepository: ProductRepository,
private readonly communityRepository: CommunityRepository,
) {}
async listDevicesInSpace(params: GetSpaceParam): Promise<BaseResponseDto> {
const { spaceUuid, communityUuid } = params;
const space = await this.validateCommunityAndSpace(
communityUuid,
spaceUuid,
);
const detailedDevices = await Promise.all(
space.devices.map(async (device) => {
const tuyaDetails = await this.getDeviceDetailsByDeviceIdTuya(
device.deviceTuyaUuid,
);
return {
uuid: device.uuid,
deviceTuyaUuid: device.deviceTuyaUuid,
productUuid: device.productDevice.uuid,
productType: device.productDevice.prodType,
isActive: device.isActive,
createdAt: device.createdAt,
updatedAt: device.updatedAt,
...tuyaDetails,
};
}),
);
return new SuccessResponseDto({
data: detailedDevices,
message: 'Successfully retrieved list of devices',
});
}
private async validateCommunityAndSpace(
communityUuid: string,
spaceUuid: string,
) {
const community = await this.communityRepository.findOne({
where: { uuid: communityUuid },
});
if (!community) {
this.throwNotFound('Community', communityUuid);
}
const space = await this.spaceRepository.findOne({
where: { uuid: spaceUuid, community: { uuid: communityUuid } },
relations: ['devices', 'devices.productDevice'],
});
if (!space) {
this.throwNotFound('Space', spaceUuid);
}
return space;
}
private throwNotFound(entity: string, uuid: string) {
throw new HttpException(
`${entity} with ID ${uuid} not found`,
HttpStatus.NOT_FOUND,
);
}
private async getDeviceDetailsByDeviceIdTuya(
deviceId: string,
): Promise<GetDeviceDetailsInterface> {
try {
// Fetch details from TuyaService
const tuyaDeviceDetails =
await this.tuyaService.getDeviceDetails(deviceId);
// Convert keys to camel case
const camelCaseResponse = convertKeysToCamelCase(tuyaDeviceDetails);
const product = await this.productRepository.findOne({
where: {
prodId: camelCaseResponse.productId,
},
});
// Exclude specific keys and add `productUuid`
const { ...rest } = camelCaseResponse;
return {
...rest,
productUuid: product?.uuid,
} as GetDeviceDetailsInterface;
} catch (error) {
throw new HttpException(
'Error fetching device details from Tuya',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}

View File

@ -8,6 +8,10 @@ import {
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { DeviceSubSpaceParam, GetSubSpaceParam } from '../../dtos';
import { CommunityRepository } from '@app/common/modules/community/repositories';
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
import { TuyaService } from '@app/common/integrations/tuya/tuya.service';
import { ProductRepository } from '@app/common/modules/product/repositories';
import { GetDeviceDetailsInterface } from '../../../device/interfaces/get.device.interface';
@Injectable()
export class SubspaceDeviceService {
@ -16,6 +20,8 @@ export class SubspaceDeviceService {
private readonly communityRepository: CommunityRepository,
private readonly subspaceRepository: SubspaceRepository,
private readonly deviceRepository: DeviceRepository,
private readonly tuyaService: TuyaService,
private readonly productRepository: ProductRepository,
) {}
async listDevicesInSubspace(
@ -26,12 +32,31 @@ export class SubspaceDeviceService {
await this.validateCommunityAndSpace(communityUuid, spaceUuid);
const subspace = await this.findSubspaceWithDevices(subSpaceUuid);
const detailedDevices = await Promise.all(
subspace.devices.map(async (device) => {
const tuyaDetails = await this.getDeviceDetailsByDeviceIdTuya(
device.deviceTuyaUuid,
);
return {
uuid: device.uuid,
deviceTuyaUuid: device.deviceTuyaUuid,
productUuid: device.productDevice.uuid,
productType: device.productDevice.prodType,
isActive: device.isActive,
createdAt: device.createdAt,
updatedAt: device.updatedAt,
...tuyaDetails,
};
}),
);
return new SuccessResponseDto({
data: subspace.devices,
data: detailedDevices,
message: 'Successfully retrieved list of devices',
});
}
async associateDeviceToSubspace(params: DeviceSubSpaceParam) {
const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid } = params;
@ -91,7 +116,7 @@ export class SubspaceDeviceService {
private async findSubspaceWithDevices(subSpaceUuid: string) {
const subspace = await this.subspaceRepository.findOne({
where: { uuid: subSpaceUuid },
relations: ['devices'],
relations: ['devices', 'devices.productDevice'],
});
if (!subspace) {
this.throwNotFound('Subspace', subSpaceUuid);
@ -125,4 +150,35 @@ export class SubspaceDeviceService {
HttpStatus.NOT_FOUND,
);
}
private async getDeviceDetailsByDeviceIdTuya(
deviceId: string,
): Promise<GetDeviceDetailsInterface> {
try {
// Fetch details from TuyaService
const tuyaDeviceDetails =
await this.tuyaService.getDeviceDetails(deviceId);
// Convert keys to camel case
const camelCaseResponse = convertKeysToCamelCase(tuyaDeviceDetails);
const product = await this.productRepository.findOne({
where: {
prodId: camelCaseResponse.productId,
},
});
// Exclude specific keys and add `productUuid`
const { ...rest } = camelCaseResponse;
return {
...rest,
productUuid: product?.uuid,
} as GetDeviceDetailsInterface;
} catch (error) {
throw new HttpException(
'Error fetching device details from Tuya',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}

View File

@ -23,6 +23,8 @@ import {
UserSpaceRepository,
} from '@app/common/modules/user/repositories';
import { DeviceRepository } from '@app/common/modules/device/repositories';
import { TuyaService } from '@app/common/integrations/tuya/tuya.service';
import { ProductRepository } from '@app/common/modules/product/repositories';
@Module({
imports: [ConfigModule, SpaceRepositoryModule],
@ -34,6 +36,8 @@ import { DeviceRepository } from '@app/common/modules/device/repositories';
],
providers: [
SpaceService,
TuyaService,
ProductRepository,
SubSpaceService,
SubspaceDeviceService,
SpaceRepository,