Refactor Device Module

This commit is contained in:
faris Aljohari
2024-04-27 23:27:40 +03:00
parent 165dfb575d
commit 8c790b0d96
14 changed files with 527 additions and 478 deletions

View File

@ -1,26 +1,19 @@
import { Column, Entity, OneToMany } from 'typeorm'; import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm';
import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { DeviceDto } from '../dtos/device.dto'; import { DeviceDto } from '../dtos/device.dto';
import { DeviceUserPermissionEntity } from './device-user-type.entity'; import { DeviceUserPermissionEntity } from './device-user-type.entity';
import { GroupDeviceEntity } from '../../group-device/entities'; import { GroupDeviceEntity } from '../../group-device/entities';
import { SpaceEntity } from '../../space/entities';
import { ProductEntity } from '../../product/entities';
@Entity({ name: 'device' }) @Entity({ name: 'device' })
@Unique(['spaceDevice', 'deviceTuyaUuid'])
export class DeviceEntity extends AbstractEntity<DeviceDto> { export class DeviceEntity extends AbstractEntity<DeviceDto> {
@Column({
nullable: false,
})
public spaceUuid: string;
@Column({ @Column({
nullable: false, nullable: false,
}) })
deviceTuyaUuid: string; deviceTuyaUuid: string;
@Column({
nullable: false,
})
public productUuid: string;
@Column({ @Column({
nullable: true, nullable: true,
default: true, default: true,
@ -44,6 +37,15 @@ export class DeviceEntity extends AbstractEntity<DeviceDto> {
) )
userGroupDevices: GroupDeviceEntity[]; userGroupDevices: GroupDeviceEntity[];
@ManyToOne(() => SpaceEntity, (space) => space.devicesSpaceEntity, {
nullable: false,
})
spaceDevice: SpaceEntity;
@ManyToOne(() => ProductEntity, (product) => product.devicesProductEntity, {
nullable: false,
})
productDevice: ProductEntity;
constructor(partial: Partial<DeviceEntity>) { constructor(partial: Partial<DeviceEntity>) {
super(); super();
Object.assign(this, partial); Object.assign(this, partial);

View File

@ -1,6 +1,7 @@
import { Column, Entity } from 'typeorm'; import { Column, Entity, OneToMany } from 'typeorm';
import { ProductDto } from '../dtos'; import { ProductDto } from '../dtos';
import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { DeviceEntity } from '../../device/entities';
@Entity({ name: 'product' }) @Entity({ name: 'product' })
export class ProductEntity extends AbstractEntity<ProductDto> { export class ProductEntity extends AbstractEntity<ProductDto> {
@ -20,6 +21,11 @@ export class ProductEntity extends AbstractEntity<ProductDto> {
}) })
public prodType: string; public prodType: string;
@OneToMany(
() => DeviceEntity,
(devicesProductEntity) => devicesProductEntity.productDevice,
)
devicesProductEntity: DeviceEntity[];
constructor(partial: Partial<ProductEntity>) { constructor(partial: Partial<ProductEntity>) {
super(); super();
Object.assign(this, partial); Object.assign(this, partial);

View File

@ -3,6 +3,7 @@ import { SpaceDto } from '../dtos';
import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { SpaceTypeEntity } from '../../space-type/entities'; import { SpaceTypeEntity } from '../../space-type/entities';
import { UserSpaceEntity } from '../../user-space/entities'; import { UserSpaceEntity } from '../../user-space/entities';
import { DeviceEntity } from '../../device/entities';
@Entity({ name: 'space' }) @Entity({ name: 'space' })
export class SpaceEntity extends AbstractEntity<SpaceDto> { export class SpaceEntity extends AbstractEntity<SpaceDto> {
@ -30,6 +31,12 @@ export class SpaceEntity extends AbstractEntity<SpaceDto> {
@OneToMany(() => UserSpaceEntity, (userSpace) => userSpace.space) @OneToMany(() => UserSpaceEntity, (userSpace) => userSpace.space)
userSpaces: UserSpaceEntity[]; userSpaces: UserSpaceEntity[];
@OneToMany(
() => DeviceEntity,
(devicesSpaceEntity) => devicesSpaceEntity.spaceDevice,
)
devicesSpaceEntity: DeviceEntity[];
constructor(partial: Partial<SpaceEntity>) { constructor(partial: Partial<SpaceEntity>) {
super(); super();
Object.assign(this, partial); Object.assign(this, partial);

View File

@ -1,5 +1,15 @@
import { DeviceService } from '../services/device.service'; import { DeviceService } from '../services/device.service';
import { Body, Controller, Get, Post, Query, Param } from '@nestjs/common'; import {
Body,
Controller,
Get,
Post,
Query,
Param,
HttpException,
HttpStatus,
UseGuards,
} from '@nestjs/common';
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
import { import {
AddDeviceInGroupDto, AddDeviceInGroupDto,
@ -7,11 +17,12 @@ import {
} from '../dtos/add.device.dto'; } from '../dtos/add.device.dto';
import { import {
GetDeviceByGroupIdDto, GetDeviceByGroupIdDto,
GetDeviceByRoomIdDto, GetDeviceByRoomUuidDto,
} from '../dtos/get.device.dto'; } from '../dtos/get.device.dto';
import { ControlDeviceDto } from '../dtos/control.device.dto'; import { ControlDeviceDto } from '../dtos/control.device.dto';
import { AuthGuardWithRoles } from 'src/guards/device.permission.guard'; import { CheckRoomGuard } from 'src/guards/room.guard';
import { PermissionType } from '@app/common/constants/permission-type.enum'; import { CheckGroupGuard } from 'src/guards/group.guard';
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
@ApiTags('Device Module') @ApiTags('Device Module')
@Controller({ @Controller({
@ -22,19 +33,38 @@ export class DeviceController {
constructor(private readonly deviceService: DeviceService) {} constructor(private readonly deviceService: DeviceService) {}
@ApiBearerAuth() @ApiBearerAuth()
@AuthGuardWithRoles(PermissionType.READ) @UseGuards(JwtAuthGuard, CheckRoomGuard)
@Get('room') @Get('room')
async getDevicesByRoomId( async getDevicesByRoomId(
@Query() getDeviceByRoomIdDto: GetDeviceByRoomIdDto, @Query() getDeviceByRoomUuidDto: GetDeviceByRoomUuidDto,
) { ) {
try { try {
return await this.deviceService.getDevicesByRoomId(getDeviceByRoomIdDto); return await this.deviceService.getDevicesByRoomId(
} catch (err) { getDeviceByRoomUuidDto,
throw new Error(err); );
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard, CheckRoomGuard)
@Post('room')
async addDeviceInRoom(@Body() addDeviceInRoomDto: AddDeviceInRoomDto) {
try {
return await this.deviceService.addDeviceInRoom(addDeviceInRoomDto);
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
} }
} }
@ApiBearerAuth() @ApiBearerAuth()
@AuthGuardWithRoles(PermissionType.READ) @UseGuards(JwtAuthGuard, CheckGroupGuard)
@Get('group') @Get('group')
async getDevicesByGroupId( async getDevicesByGroupId(
@Query() getDeviceByGroupIdDto: GetDeviceByGroupIdDto, @Query() getDeviceByGroupIdDto: GetDeviceByGroupIdDto,
@ -43,68 +73,81 @@ export class DeviceController {
return await this.deviceService.getDevicesByGroupId( return await this.deviceService.getDevicesByGroupId(
getDeviceByGroupIdDto, getDeviceByGroupIdDto,
); );
} catch (err) { } catch (error) {
throw new Error(err); throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
} }
} }
@ApiBearerAuth() @ApiBearerAuth()
@AuthGuardWithRoles(PermissionType.READ) @UseGuards(JwtAuthGuard, CheckGroupGuard)
@Get(':deviceId')
async getDeviceDetailsByDeviceId(@Param('deviceId') deviceId: string) {
try {
return await this.deviceService.getDeviceDetailsByDeviceId(deviceId);
} catch (err) {
throw new Error(err);
}
}
@ApiBearerAuth()
@AuthGuardWithRoles(PermissionType.READ)
@Get(':deviceId/functions')
async getDeviceInstructionByDeviceId(@Param('deviceId') deviceId: string) {
try {
return await this.deviceService.getDeviceInstructionByDeviceId(deviceId);
} catch (err) {
throw new Error(err);
}
}
@ApiBearerAuth()
@AuthGuardWithRoles(PermissionType.READ)
@Get(':deviceId/functions/status')
async getDevicesInstructionStatus(@Param('deviceId') deviceId: string) {
try {
return await this.deviceService.getDevicesInstructionStatus(deviceId);
} catch (err) {
throw new Error(err);
}
}
@ApiBearerAuth()
@AuthGuardWithRoles(PermissionType.CONTROLLABLE)
@Post('room')
async addDeviceInRoom(@Body() addDeviceInRoomDto: AddDeviceInRoomDto) {
try {
return await this.deviceService.addDeviceInRoom(addDeviceInRoomDto);
} catch (err) {
throw new Error(err);
}
}
@ApiBearerAuth()
@AuthGuardWithRoles(PermissionType.CONTROLLABLE)
@Post('group') @Post('group')
async addDeviceInGroup(@Body() addDeviceInGroupDto: AddDeviceInGroupDto) { async addDeviceInGroup(@Body() addDeviceInGroupDto: AddDeviceInGroupDto) {
try { try {
return await this.deviceService.addDeviceInGroup(addDeviceInGroupDto); return await this.deviceService.addDeviceInGroup(addDeviceInGroupDto);
} catch (err) { } catch (error) {
throw new Error(err); throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
} }
} }
@ApiBearerAuth() @ApiBearerAuth()
@AuthGuardWithRoles(PermissionType.CONTROLLABLE) @UseGuards(JwtAuthGuard)
@Get(':deviceUuid')
async getDeviceDetailsByDeviceId(@Param('deviceUuid') deviceUuid: string) {
try {
return await this.deviceService.getDeviceDetailsByDeviceId(deviceUuid);
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get(':deviceUuid/functions')
async getDeviceInstructionByDeviceId(
@Param('deviceUuid') deviceUuid: string,
) {
try {
return await this.deviceService.getDeviceInstructionByDeviceId(
deviceUuid,
);
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get(':deviceUuid/functions/status')
async getDevicesInstructionStatus(@Param('deviceUuid') deviceUuid: string) {
try {
return await this.deviceService.getDevicesInstructionStatus(deviceUuid);
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Post('control') @Post('control')
async controlDevice(@Body() controlDeviceDto: ControlDeviceDto) { async controlDevice(@Body() controlDeviceDto: ControlDeviceDto) {
try { try {
return await this.deviceService.controlDevice(controlDeviceDto); return await this.deviceService.controlDevice(controlDeviceDto);
} catch (err) { } catch (error) {
throw new Error(err); throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
} }
} }
} }

View File

@ -12,8 +12,15 @@ import {
import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; import { PermissionTypeRepository } from '@app/common/modules/permission/repositories';
import { SpaceRepository } from '@app/common/modules/space/repositories'; import { SpaceRepository } from '@app/common/modules/space/repositories';
import { GroupDeviceRepository } from '@app/common/modules/group-device/repositories'; import { GroupDeviceRepository } from '@app/common/modules/group-device/repositories';
import { GroupRepository } from '@app/common/modules/group/repositories';
import { GroupRepositoryModule } from '@app/common/modules/group/group.repository.module';
@Module({ @Module({
imports: [ConfigModule, ProductRepositoryModule, DeviceRepositoryModule], imports: [
ConfigModule,
ProductRepositoryModule,
DeviceRepositoryModule,
GroupRepositoryModule,
],
controllers: [DeviceController], controllers: [DeviceController],
providers: [ providers: [
DeviceService, DeviceService,
@ -23,6 +30,7 @@ import { GroupDeviceRepository } from '@app/common/modules/group-device/reposito
SpaceRepository, SpaceRepository,
DeviceRepository, DeviceRepository,
GroupDeviceRepository, GroupDeviceRepository,
GroupRepository,
], ],
exports: [DeviceService], exports: [DeviceService],
}) })

View File

@ -1,45 +1,37 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString, IsNumberString } from 'class-validator'; import { IsNotEmpty, IsString } from 'class-validator';
export class AddDeviceInRoomDto { export class AddDeviceInRoomDto {
@ApiProperty({ @ApiProperty({
description: 'deviceId', description: 'deviceTuyaUuid',
required: true, required: true,
}) })
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
public deviceId: string; public deviceTuyaUuid: string;
@ApiProperty({ @ApiProperty({
description: 'roomId', description: 'roomUuid',
required: true, required: true,
}) })
@IsNumberString() @IsString()
@IsNotEmpty() @IsNotEmpty()
public roomId: string; public roomUuid: string;
} }
export class AddDeviceInGroupDto { export class AddDeviceInGroupDto {
@ApiProperty({ @ApiProperty({
description: 'deviceId', description: 'deviceUuid',
required: true, required: true,
}) })
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
public deviceId: string; public deviceUuid: string;
@ApiProperty({ @ApiProperty({
description: 'homeId', description: 'groupUuid',
required: true, required: true,
}) })
@IsNumberString() @IsString()
@IsNotEmpty() @IsNotEmpty()
public homeId: string; public groupUuid: string;
@ApiProperty({
description: 'groupId',
required: true,
})
@IsNumberString()
@IsNotEmpty()
public groupId: string;
} }

View File

@ -3,12 +3,12 @@ import { IsNotEmpty, IsString } from 'class-validator';
export class ControlDeviceDto { export class ControlDeviceDto {
@ApiProperty({ @ApiProperty({
description: 'deviceId', description: 'deviceUuid',
required: true, required: true,
}) })
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
public deviceId: string; public deviceUuid: string;
@ApiProperty({ @ApiProperty({
description: 'code', description: 'code',

View File

@ -1,44 +1,21 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsNumberString } from 'class-validator'; import { IsNotEmpty, IsString } from 'class-validator';
export class GetDeviceByRoomIdDto { export class GetDeviceByRoomUuidDto {
@ApiProperty({ @ApiProperty({
description: 'roomId', description: 'roomUuid',
required: true, required: true,
}) })
@IsNumberString() @IsString()
@IsNotEmpty() @IsNotEmpty()
public roomId: string; public roomUuid: string;
@ApiProperty({
description: 'pageSize',
required: true,
})
@IsNumberString()
@IsNotEmpty()
public pageSize: number;
} }
export class GetDeviceByGroupIdDto { export class GetDeviceByGroupIdDto {
@ApiProperty({ @ApiProperty({
description: 'groupId', description: 'groupUuid',
required: true, required: true,
}) })
@IsNumberString() @IsString()
@IsNotEmpty() @IsNotEmpty()
public groupId: string; public groupUuid: string;
@ApiProperty({
description: 'pageSize',
required: true,
})
@IsNumberString()
@IsNotEmpty()
public pageSize: number;
@ApiProperty({
description: 'pageNo',
required: true,
})
@IsNumberString()
@IsNotEmpty()
public pageNo: number;
} }

View File

@ -1,23 +1,28 @@
export interface GetDeviceDetailsInterface { export interface GetDeviceDetailsInterface {
result: { activeTime: number;
productId: string; assetId: string;
}; category: string;
success: boolean; categoryName: string;
msg: string; createTime: number;
} gatewayId: string;
export interface GetDevicesByRoomIdInterface { icon: string;
success: boolean; id: string;
msg: string; ip: string;
result: any; lat: string;
} localKey: string;
lon: string;
export interface GetDevicesByGroupIdInterface { model: string;
success: boolean; name: string;
msg: string; nodeId: string;
result: { online: boolean;
count: number; productId?: string;
data_list: []; productName?: string;
}; sub: boolean;
timeZone: string;
updateTime: number;
uuid: string;
productType: string;
productUuid: string;
} }
export interface addDeviceInRoomInterface { export interface addDeviceInRoomInterface {
@ -44,21 +49,13 @@ export interface GetDeviceDetailsFunctionsStatusInterface {
success: boolean; success: boolean;
msg: string; msg: string;
} }
export interface GetProductInterface {
productType: string;
productId: string;
}
export interface DeviceInstructionResponse { export interface DeviceInstructionResponse {
success: boolean; productUuid: string;
result: { productType: string;
productId: string; functions: {
productType: string; code: string;
functions: { values: any[];
code: string; dataType: string;
values: any[]; }[];
dataType: string;
}[];
};
msg: string;
} }

View File

@ -15,15 +15,11 @@ import {
GetDeviceDetailsFunctionsInterface, GetDeviceDetailsFunctionsInterface,
GetDeviceDetailsFunctionsStatusInterface, GetDeviceDetailsFunctionsStatusInterface,
GetDeviceDetailsInterface, GetDeviceDetailsInterface,
GetDevicesByGroupIdInterface,
GetDevicesByRoomIdInterface,
GetProductInterface,
addDeviceInRoomInterface,
controlDeviceInterface, controlDeviceInterface,
} from '../interfaces/get.device.interface'; } from '../interfaces/get.device.interface';
import { import {
GetDeviceByGroupIdDto, GetDeviceByGroupIdDto,
GetDeviceByRoomIdDto, GetDeviceByRoomUuidDto,
} from '../dtos/get.device.dto'; } from '../dtos/get.device.dto';
import { ControlDeviceDto } from '../dtos/control.device.dto'; import { ControlDeviceDto } from '../dtos/control.device.dto';
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
@ -51,25 +47,31 @@ export class DeviceService {
}); });
} }
async getDevicesByRoomId(getDeviceByRoomIdDto: GetDeviceByRoomIdDto) { async getDevicesByRoomId(
getDeviceByRoomUuidDto: GetDeviceByRoomUuidDto,
): Promise<GetDeviceDetailsInterface[]> {
try { try {
const findRoom = await this.spaceRepository.findOne({ const devices = await this.deviceRepository.find({
where: { where: {
uuid: getDeviceByRoomIdDto.roomId, spaceDevice: { uuid: getDeviceByRoomUuidDto.roomUuid },
}, },
relations: ['spaceDevice', 'productDevice'],
}); });
if (!findRoom) { const devicesData = await Promise.all(
throw new NotFoundException('Room Details Not Found'); devices.map(async (device) => {
} return {
...(await this.getDeviceDetailsByDeviceIdTuya(
const response = await this.getDevicesByRoomIdTuya(getDeviceByRoomIdDto); device.deviceTuyaUuid,
)),
return { uuid: device.uuid,
success: response.success, productUuid: device.productDevice.uuid,
devices: response.result, productType: device.productDevice.prodType,
msg: response.msg, } as GetDeviceDetailsInterface;
}; }),
);
return devicesData;
} catch (error) { } catch (error) {
// Handle the error here
throw new HttpException( throw new HttpException(
'Error fetching devices by room', 'Error fetching devices by room',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
@ -77,68 +79,26 @@ export class DeviceService {
} }
} }
async getDevicesByRoomIdTuya(
getDeviceByRoomIdDto: GetDeviceByRoomIdDto,
): Promise<GetDevicesByRoomIdInterface> {
try {
const path = `/v2.0/cloud/thing/space/device`;
const getDeviceByRoomId = await this.deviceRepository.find({
where: {
deviceTuyaUuid: getDeviceByRoomIdDto.roomId,
},
});
const response: any = await this.tuya.request({
method: 'GET',
path,
query: {
space_ids: getDeviceByRoomIdDto.roomId,
page_size: getDeviceByRoomIdDto.pageSize,
},
});
if (!getDeviceByRoomId.length) {
throw new NotFoundException('Devices Not Found');
}
const matchingRecords = [];
getDeviceByRoomId.forEach((item1) => {
const matchingItem = response.find(
(item2) => item1.deviceTuyaUuid === item2.uuid,
);
if (matchingItem) {
matchingRecords.push({ ...matchingItem });
}
});
return {
success: true,
msg: 'Device Tuya Details Fetched successfully',
result: matchingRecords,
};
} catch (error) {
throw new HttpException(
'Error fetching devices by room from Tuya',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async getDevicesByGroupId(getDeviceByGroupIdDto: GetDeviceByGroupIdDto) { async getDevicesByGroupId(getDeviceByGroupIdDto: GetDeviceByGroupIdDto) {
try { try {
const devicesIds: GetDevicesByGroupIdInterface = const groupDevices = await this.groupDeviceRepository.find({
await this.getDevicesByGroupIdTuya(getDeviceByGroupIdDto); where: { group: { uuid: getDeviceByGroupIdDto.groupUuid } },
const devicesDetails = await Promise.all( relations: ['device'],
devicesIds.result.data_list.map(async (device: any) => { });
const deviceData = await this.getDeviceDetailsByDeviceId( const devicesData = await Promise.all(
device.dev_id, groupDevices.map(async (device) => {
); return {
return deviceData.result; ...(await this.getDeviceDetailsByDeviceIdTuya(
device.device.deviceTuyaUuid,
)),
uuid: device.uuid,
productUuid: device.device.productDevice.uuid,
productType: device.device.productDevice.prodType,
} as GetDeviceDetailsInterface;
}), }),
); );
return {
success: devicesIds.success, return devicesData;
devices: devicesDetails,
msg: devicesIds.msg,
};
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException(
'Error fetching devices by group', 'Error fetching devices by group',
@ -147,149 +107,81 @@ export class DeviceService {
} }
} }
async getDevicesByGroupIdTuya(
getDeviceByGroupIdDto: GetDeviceByGroupIdDto,
): Promise<GetDevicesByGroupIdInterface> {
try {
const path = `/v2.0/cloud/thing/group/${getDeviceByGroupIdDto.groupId}/devices`;
const response = await this.tuya.request({
method: 'GET',
path,
query: {
page_size: getDeviceByGroupIdDto.pageSize,
page_no: getDeviceByGroupIdDto.pageNo,
},
});
return response as GetDevicesByGroupIdInterface;
} catch (error) {
throw new HttpException(
'Error fetching devices by group from Tuya',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async addDeviceInRoom(addDeviceInRoomDto: AddDeviceInRoomDto) { async addDeviceInRoom(addDeviceInRoomDto: AddDeviceInRoomDto) {
const [deviceDetails, roomDetails] = await Promise.all([
this.deviceRepository.findOne({
where: {
uuid: addDeviceInRoomDto.deviceId,
},
}),
this.spaceRepository.findOne({
where: {
uuid: addDeviceInRoomDto.roomId,
},
}),
]);
if (!roomDetails) {
throw new NotFoundException('Room Details Not Found');
}
if (!deviceDetails) {
throw new NotFoundException('Device Details Not Found');
}
const response = await this.addDeviceInRooms(addDeviceInRoomDto);
if (response.success) {
return {
success: response.success,
result: response.result,
msg: response.msg,
};
} else {
throw new HttpException(
response.msg || 'Unknown error',
HttpStatus.BAD_REQUEST,
);
}
}
async addDeviceInRooms(
addDeviceInRoomDto: AddDeviceInRoomDto,
): Promise<addDeviceInRoomInterface> {
try { try {
const path = `/v2.0/cloud/thing/${addDeviceInRoomDto.deviceId}/transfer`; const device = await this.getDeviceDetailsByDeviceIdTuya(
const response = await this.tuya.request({ addDeviceInRoomDto.deviceTuyaUuid,
method: 'POST',
path,
body: {
space_id: addDeviceInRoomDto.roomId,
},
});
return response as addDeviceInRoomInterface;
} catch (error) {
throw new HttpException(
'Error adding device in room from Tuya',
HttpStatus.INTERNAL_SERVER_ERROR,
); );
if (!device.productUuid) {
throw new Error('Product UUID is missing for the device.');
}
await this.deviceRepository.save({
deviceTuyaUuid: addDeviceInRoomDto.deviceTuyaUuid,
spaceDevice: { uuid: addDeviceInRoomDto.roomUuid },
productDevice: { uuid: device.productUuid },
});
return { message: 'device added in room successfully' };
} catch (error) {
if (error.code === '23505') {
throw new Error('Device already exists in the room.');
} else {
throw new Error('Failed to add device in room');
}
} }
} }
async addDeviceInGroup(addDeviceInGroupDto: AddDeviceInGroupDto) { async addDeviceInGroup(addDeviceInGroupDto: AddDeviceInGroupDto) {
const deviceDetails = this.deviceRepository.findOne({
where: {
uuid: addDeviceInGroupDto.deviceId,
},
});
if (!deviceDetails) {
throw new NotFoundException('Device Details Not Found');
}
const response = await this.addDeviceInGroups(addDeviceInGroupDto);
if (response.success) {
return {
success: response.success,
result: response.result,
msg: response.msg,
};
} else {
throw new HttpException(
response.msg || 'Unknown error',
HttpStatus.BAD_REQUEST,
);
}
}
async addDeviceInGroups(
addDeviceInGroupDto: AddDeviceInGroupDto,
): Promise<addDeviceInRoomInterface> {
try { try {
await this.groupDeviceRepository.create({ await this.groupDeviceRepository.save({
deviceUuid: addDeviceInGroupDto.deviceId, device: { uuid: addDeviceInGroupDto.deviceUuid },
groupUuid: addDeviceInGroupDto.groupId, group: { uuid: addDeviceInGroupDto.groupUuid },
}); });
return { message: 'device added in group successfully' };
return {
success: true,
msg: 'Group is Added to Specific Device',
result: true,
} as addDeviceInRoomInterface;
} catch (error) { } catch (error) {
throw new HttpException( if (error.code === '23505') {
'Error adding device in group from Tuya', throw new Error('Device already exists in the group.');
HttpStatus.INTERNAL_SERVER_ERROR, } else {
); throw new Error('Failed to add device in group');
}
} }
} }
async controlDevice(controlDeviceDto: ControlDeviceDto) { async controlDevice(controlDeviceDto: ControlDeviceDto) {
const response = await this.controlDeviceTuya(controlDeviceDto); try {
const deviceDetails = await this.deviceRepository.findOne({
where: {
uuid: controlDeviceDto.deviceUuid,
},
});
if (response.success) { if (!deviceDetails || !deviceDetails.deviceTuyaUuid) {
return response; throw new NotFoundException('Device Not Found');
} else { }
throw new HttpException( const response = await this.controlDeviceTuya(
response.msg || 'Unknown error', deviceDetails.deviceTuyaUuid,
HttpStatus.BAD_REQUEST, controlDeviceDto,
); );
if (response.success) {
return response;
} else {
throw new HttpException(
response.msg || 'Unknown error',
HttpStatus.BAD_REQUEST,
);
}
} catch (error) {
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
} }
} }
async controlDeviceTuya( async controlDeviceTuya(
deviceUuid: string,
controlDeviceDto: ControlDeviceDto, controlDeviceDto: ControlDeviceDto,
): Promise<controlDeviceInterface> { ): Promise<controlDeviceInterface> {
try { try {
const path = `/v1.0/iot-03/devices/${controlDeviceDto.deviceId}/commands`; const path = `/v1.0/iot-03/devices/${deviceUuid}/commands`;
const response = await this.tuya.request({ const response = await this.tuya.request({
method: 'POST', method: 'POST',
path, path,
@ -309,29 +201,30 @@ export class DeviceService {
} }
} }
async getDeviceDetailsByDeviceId(deviceId: string) { async getDeviceDetailsByDeviceId(deviceUuid: string) {
try { try {
const deviceDetails = await this.deviceRepository.findOne({ const deviceDetails = await this.deviceRepository.findOne({
where: { where: {
uuid: deviceId, uuid: deviceUuid,
}, },
}); });
if (!deviceDetails) { if (!deviceDetails) {
throw new NotFoundException('Device Details Not Found'); throw new NotFoundException('Device Not Found');
} }
const response = await this.getDeviceDetailsByDeviceIdTuya(deviceId); const response = await this.getDeviceDetailsByDeviceIdTuya(
deviceDetails.deviceTuyaUuid,
);
return { return {
success: response.success, ...response,
result: response.result, uuid: deviceDetails.uuid,
msg: response.msg, productUuid: deviceDetails.productDevice.uuid,
productType: deviceDetails.productDevice.prodType,
}; };
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
'Error fetching device details',
HttpStatus.INTERNAL_SERVER_ERROR,
);
} }
} }
async getDeviceDetailsByDeviceIdTuya( async getDeviceDetailsByDeviceIdTuya(
@ -343,19 +236,14 @@ export class DeviceService {
method: 'GET', method: 'GET',
path, path,
}); });
// Convert keys to camel case // Convert keys to camel case
const camelCaseResponse = convertKeysToCamelCase(response); const camelCaseResponse = convertKeysToCamelCase(response);
const productType: string = await this.getProductTypeByProductId(
camelCaseResponse.result.productId,
);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { productName, productId, ...rest } = camelCaseResponse.result;
return { return {
result: { ...rest,
...camelCaseResponse.result,
productType: productType,
},
success: camelCaseResponse.success,
msg: camelCaseResponse.msg,
} as GetDeviceDetailsInterface; } as GetDeviceDetailsInterface;
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException(
@ -364,93 +252,39 @@ export class DeviceService {
); );
} }
} }
async getProductIdByDeviceId(deviceId: string) {
try {
const deviceDetails: GetDeviceDetailsInterface =
await this.getDeviceDetailsByDeviceId(deviceId);
return deviceDetails.result.productId;
} catch (error) {
throw new HttpException(
'Error fetching product id by device id',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async getProductByProductId(productId: string): Promise<GetProductInterface> {
try {
const product = await this.productRepository
.createQueryBuilder('product')
.where('product.prodId = :productId', { productId })
.select(['product.prodId', 'product.prodType'])
.getOne();
if (product) {
return {
productType: product.prodType,
productId: product.prodId,
};
} else {
throw new HttpException('Product not found', HttpStatus.NOT_FOUND);
}
} catch (error) {
throw new HttpException(
'Error fetching product by product id from db',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async getProductTypeByProductId(productId: string) {
try {
const product = await this.getProductByProductId(productId);
return product.productType;
} catch (error) {
throw new HttpException(
'Error getting product type by product id',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async getDeviceInstructionByDeviceId( async getDeviceInstructionByDeviceId(
deviceId: string, deviceUuid: string,
): Promise<DeviceInstructionResponse> { ): Promise<DeviceInstructionResponse> {
try { try {
const deviceDetails = await this.deviceRepository.findOne({ const deviceDetails = await this.deviceRepository.findOne({
where: { where: {
uuid: deviceId, uuid: deviceUuid,
}, },
relations: ['productDevice'],
}); });
if (!deviceDetails) { if (!deviceDetails) {
throw new NotFoundException('Device Details Not Found'); throw new NotFoundException('Device Not Found');
} }
const response = await this.getDeviceInstructionByDeviceIdTuya(deviceId); const response = await this.getDeviceInstructionByDeviceIdTuya(
deviceDetails.deviceTuyaUuid,
const productId: string = await this.getProductIdByDeviceId(deviceId); );
const productType: string =
await this.getProductTypeByProductId(productId);
return { return {
success: response.success, productUuid: deviceDetails.productDevice.uuid,
result: { productType: deviceDetails.productDevice.prodType,
productId: productId, functions: response.result.functions.map((fun: any) => {
productType: productType, return {
functions: response.result.functions.map((fun: any) => { code: fun.code,
return { values: fun.values,
code: fun.code, dataType: fun.type,
values: fun.values, };
dataType: fun.type, }),
};
}),
},
msg: response.msg,
}; };
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
'Error fetching device functions by device id',
HttpStatus.INTERNAL_SERVER_ERROR,
);
} }
} }
@ -471,28 +305,26 @@ export class DeviceService {
); );
} }
} }
async getDevicesInstructionStatus(deviceId: string) { async getDevicesInstructionStatus(deviceUuid: string) {
try { try {
const deviceDetails = await this.deviceRepository.findOne({ const deviceDetails = await this.deviceRepository.findOne({
where: { where: {
uuid: deviceId, uuid: deviceUuid,
}, },
relations: ['productDevice'],
}); });
if (!deviceDetails) { if (!deviceDetails) {
throw new NotFoundException('Device Details Not Found'); throw new NotFoundException('Device Not Found');
} }
const deviceStatus = await this.getDevicesInstructionStatusTuya(deviceId); const deviceStatus = await this.getDevicesInstructionStatusTuya(
const productId: string = await this.getProductIdByDeviceId(deviceId); deviceDetails.deviceTuyaUuid,
const productType: string = );
await this.getProductTypeByProductId(productId);
return { return {
result: { productUuid: deviceDetails.productDevice.uuid,
productId: productId, productType: deviceDetails.productDevice.prodType,
productType: productType, status: deviceStatus.result[0].status,
status: deviceStatus.result[0].status,
},
success: deviceStatus.success,
msg: deviceStatus.msg,
}; };
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException(
@ -503,7 +335,7 @@ export class DeviceService {
} }
async getDevicesInstructionStatusTuya( async getDevicesInstructionStatusTuya(
deviceId: string, deviceUuid: string,
): Promise<GetDeviceDetailsFunctionsStatusInterface> { ): Promise<GetDeviceDetailsFunctionsStatusInterface> {
try { try {
const path = `/v1.0/iot-03/devices/status`; const path = `/v1.0/iot-03/devices/status`;
@ -511,10 +343,10 @@ export class DeviceService {
method: 'GET', method: 'GET',
path, path,
query: { query: {
device_ids: deviceId, device_ids: deviceUuid,
}, },
}); });
return response as unknown as GetDeviceDetailsFunctionsStatusInterface; return response as GetDeviceDetailsFunctionsStatusInterface;
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException(
'Error fetching device functions status from Tuya', 'Error fetching device functions status from Tuya',

View File

@ -43,7 +43,7 @@ export class GroupService {
const groupDevices = await this.groupDeviceRepository.find({ const groupDevices = await this.groupDeviceRepository.find({
relations: ['group', 'device'], relations: ['group', 'device'],
where: { where: {
device: { spaceUuid }, device: { spaceDevice: { uuid: spaceUuid } },
isActive: true, isActive: true,
}, },
}); });
@ -129,22 +129,26 @@ export class GroupService {
} }
} }
async controlDevice(controlDeviceDto: ControlDeviceDto) { async controlDevice(controlDeviceDto: ControlDeviceDto) {
const response = await this.controlDeviceTuya(controlDeviceDto); try {
const response = await this.controlDeviceTuya(controlDeviceDto);
if (response.success) { if (response.success) {
return response; return response;
} else { } else {
throw new HttpException( throw new HttpException(
response.msg || 'Unknown error', response.msg || 'Unknown error',
HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST,
); );
}
} catch (error) {
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
} }
} }
async controlDeviceTuya( async controlDeviceTuya(
controlDeviceDto: ControlDeviceDto, controlDeviceDto: ControlDeviceDto,
): Promise<controlDeviceInterface> { ): Promise<controlDeviceInterface> {
try { try {
const path = `/v1.0/iot-03/devices/${controlDeviceDto.deviceId}/commands`; const path = `/v1.0/iot-03/devices/${controlDeviceDto.deviceUuid}/commands`;
const response = await this.tuya.request({ const response = await this.tuya.request({
method: 'POST', method: 'POST',
path, path,
@ -170,7 +174,7 @@ export class GroupService {
await Promise.all( await Promise.all(
devices.map(async (device) => { devices.map(async (device) => {
return this.controlDevice({ return this.controlDevice({
deviceId: device.device.deviceTuyaUuid, deviceUuid: device.device.deviceTuyaUuid,
code: controlGroupDto.code, code: controlGroupDto.code,
value: controlGroupDto.value, value: controlGroupDto.value,
}); });

View File

@ -36,7 +36,7 @@ export class CheckProductUuidForAllDevicesGuard implements CanActivate {
throw new BadRequestException('First device not found'); throw new BadRequestException('First device not found');
} }
const firstProductUuid = firstDevice.productUuid; const firstProductUuid = firstDevice.productDevice.uuid;
for (let i = 1; i < deviceUuids.length; i++) { for (let i = 1; i < deviceUuids.length; i++) {
const device = await this.deviceRepository.findOne({ const device = await this.deviceRepository.findOne({
@ -47,7 +47,7 @@ export class CheckProductUuidForAllDevicesGuard implements CanActivate {
throw new BadRequestException(`Device ${deviceUuids[i]} not found`); throw new BadRequestException(`Device ${deviceUuids[i]} not found`);
} }
if (device.productUuid !== firstProductUuid) { if (device.productDevice.uuid !== firstProductUuid) {
throw new BadRequestException(`Devices have different product UUIDs`); throw new BadRequestException(`Devices have different product UUIDs`);
} }
} }

81
src/guards/group.guard.ts Normal file
View File

@ -0,0 +1,81 @@
import {
CanActivate,
ExecutionContext,
Injectable,
HttpStatus,
} from '@nestjs/common';
import { BadRequestException, NotFoundException } from '@nestjs/common';
import { DeviceRepository } from '@app/common/modules/device/repositories';
import { GroupRepository } from '@app/common/modules/group/repositories';
@Injectable()
export class CheckGroupGuard implements CanActivate {
constructor(
private readonly groupRepository: GroupRepository,
private readonly deviceRepository: DeviceRepository,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
try {
if (req.query && req.query.groupUuid) {
const { groupUuid } = req.query;
await this.checkGroupIsFound(groupUuid);
} else if (req.body && req.body.groupUuid && req.body.deviceUuid) {
const { groupUuid, deviceUuid } = req.body;
await this.checkGroupIsFound(groupUuid);
await this.checkDeviceIsFound(deviceUuid);
} else {
throw new BadRequestException('Invalid request parameters');
}
return true;
} catch (error) {
this.handleGuardError(error, context);
return false;
}
}
private async checkGroupIsFound(groupUuid: string) {
const group = await this.groupRepository.findOne({
where: {
uuid: groupUuid,
},
});
if (!group) {
throw new NotFoundException('Group not found');
}
}
async checkDeviceIsFound(deviceUuid: string) {
const device = await this.deviceRepository.findOne({
where: {
uuid: deviceUuid,
},
});
if (!device) {
throw new NotFoundException('Device not found');
}
}
private handleGuardError(error: Error, context: ExecutionContext) {
const response = context.switchToHttp().getResponse();
if (error instanceof NotFoundException) {
response
.status(HttpStatus.NOT_FOUND)
.json({ statusCode: HttpStatus.NOT_FOUND, message: error.message });
} else if (error instanceof BadRequestException) {
response
.status(HttpStatus.BAD_REQUEST)
.json({ statusCode: HttpStatus.BAD_REQUEST, message: error.message });
} else {
response.status(HttpStatus.BAD_REQUEST).json({
statusCode: HttpStatus.BAD_REQUEST,
message: 'Invalid UUID',
});
}
}
}

100
src/guards/room.guard.ts Normal file
View File

@ -0,0 +1,100 @@
import {
CanActivate,
ExecutionContext,
Injectable,
HttpStatus,
} from '@nestjs/common';
import { TuyaContext } from '@tuya/tuya-connector-nodejs';
import { SpaceRepository } from '@app/common/modules/space/repositories';
import { BadRequestException, NotFoundException } from '@nestjs/common';
import { DeviceRepository } from '@app/common/modules/device/repositories';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class CheckRoomGuard implements CanActivate {
private tuya: TuyaContext;
constructor(
private readonly configService: ConfigService,
private readonly spaceRepository: SpaceRepository,
private readonly deviceRepository: DeviceRepository,
) {
const accessKey = this.configService.get<string>('auth-config.ACCESS_KEY');
const secretKey = this.configService.get<string>('auth-config.SECRET_KEY');
this.tuya = new TuyaContext({
baseUrl: 'https://openapi.tuyaeu.com',
accessKey,
secretKey,
});
}
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
try {
console.log(req.body);
if (req.query && req.query.roomUuid) {
const { roomUuid } = req.query;
await this.checkRoomIsFound(roomUuid);
} else if (req.body && req.body.roomUuid && req.body.deviceTuyaUuid) {
const { roomUuid, deviceTuyaUuid } = req.body;
await this.checkRoomIsFound(roomUuid);
await this.checkDeviceIsFoundFromTuya(deviceTuyaUuid);
} else {
throw new BadRequestException('Invalid request parameters');
}
return true;
} catch (error) {
this.handleGuardError(error, context);
return false;
}
}
private async checkRoomIsFound(roomUuid: string) {
const room = await this.spaceRepository.findOne({
where: {
uuid: roomUuid,
spaceType: {
type: 'room',
},
},
});
if (!room) {
throw new NotFoundException('Room not found');
}
}
async checkDeviceIsFoundFromTuya(deviceTuyaUuid: string) {
console.log('deviceTuyaUuid: ', deviceTuyaUuid);
const path = `/v1.1/iot-03/devices/${deviceTuyaUuid}`;
const response = await this.tuya.request({
method: 'GET',
path,
});
console.log('checkDeviceIsFoundFromTuya: ', response);
if (!response.success) {
throw new NotFoundException('Device not found');
}
}
private handleGuardError(error: Error, context: ExecutionContext) {
const response = context.switchToHttp().getResponse();
if (error instanceof NotFoundException) {
response
.status(HttpStatus.NOT_FOUND)
.json({ statusCode: HttpStatus.NOT_FOUND, message: error.message });
} else if (error instanceof BadRequestException) {
response
.status(HttpStatus.BAD_REQUEST)
.json({ statusCode: HttpStatus.BAD_REQUEST, message: error.message });
} else {
response.status(HttpStatus.BAD_REQUEST).json({
statusCode: HttpStatus.BAD_REQUEST,
message: 'Invalid UUID',
});
}
}
}