feat: add device name field to DeviceEntity and update related DTOs and services

This commit is contained in:
faris Aljohari
2025-04-22 01:53:03 +03:00
parent 1e6503c072
commit 2f12189eef
7 changed files with 69 additions and 126 deletions

View File

@ -32,7 +32,10 @@ export class DeviceEntity extends AbstractEntity<DeviceDto> {
type: 'boolean', type: 'boolean',
}) })
isActive: boolean; isActive: boolean;
@Column({
nullable: true,
})
name: string;
@OneToMany( @OneToMany(
() => DeviceUserPermissionEntity, () => DeviceUserPermissionEntity,
(permission) => permission.device, (permission) => permission.device,

View File

@ -17,6 +17,14 @@ export class AddDeviceDto {
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
public spaceUuid: string; public spaceUuid: string;
@ApiProperty({
description: 'deviceName',
required: true,
})
@IsString()
@IsNotEmpty()
public deviceName: string;
} }
export class AssignDeviceToSpaceDto { export class AssignDeviceToSpaceDto {
@ApiProperty({ @ApiProperty({

View File

@ -159,6 +159,7 @@ export class DeviceService {
deviceTuyaUuid: addDeviceDto.deviceTuyaUuid, deviceTuyaUuid: addDeviceDto.deviceTuyaUuid,
productDevice: { uuid: device.productUuid }, productDevice: { uuid: device.productUuid },
spaceDevice: { uuid: addDeviceDto.spaceUuid }, spaceDevice: { uuid: addDeviceDto.spaceUuid },
name: addDeviceDto.deviceName,
}); });
if (deviceSaved.uuid) { if (deviceSaved.uuid) {
const deviceStatus: BaseResponseDto = const deviceStatus: BaseResponseDto =
@ -299,22 +300,15 @@ export class DeviceService {
); );
} }
} }
async updateDeviceNameTuya( async updateDeviceName(deviceUuid: string, deviceName: string) {
deviceId: string,
deviceName: string,
): Promise<controlDeviceInterface> {
try { try {
const path = `/v2.0/cloud/thing/${deviceId}/attribute`; await this.deviceRepository.update(
const response = await this.tuya.request({ { uuid: deviceUuid },
method: 'POST', { name: deviceName },
path, );
body: { type: 1, data: deviceName },
});
return response as controlDeviceInterface;
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException(
'Error updating device name from Tuya', 'Error updating device name',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
); );
} }
@ -643,11 +637,8 @@ export class DeviceService {
true, true,
projectUuid, projectUuid,
); );
if (device.deviceTuyaUuid) { if (device.uuid) {
await this.updateDeviceNameTuya( await this.updateDeviceName(deviceUuid, updateDeviceDto.deviceName);
device.deviceTuyaUuid,
updateDeviceDto.deviceName,
);
} }
return new SuccessResponseDto({ return new SuccessResponseDto({
@ -682,15 +673,22 @@ export class DeviceService {
prodId: camelCaseResponse.result.productId, prodId: camelCaseResponse.result.productId,
}, },
}); });
const deviceDetails = await this.deviceRepository.findOne({
where: {
deviceTuyaUuid: deviceId,
},
});
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const { productId, id, ...rest } = camelCaseResponse.result; const { productId, id, name, ...rest } = camelCaseResponse.result;
return { return {
...rest, ...rest,
productUuid: product.uuid, productUuid: product.uuid,
name: deviceDetails.name,
} as GetDeviceDetailsInterface; } as GetDeviceDetailsInterface;
} catch (error) { } catch (error) {
console.log('error', error);
throw new HttpException( throw new HttpException(
'Error fetching device details from Tuya', 'Error fetching device details from Tuya',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,

View File

@ -6,6 +6,18 @@ import { ConfigModule } from '@nestjs/config';
import { DeviceRepositoryModule } from '@app/common/modules/device'; import { DeviceRepositoryModule } from '@app/common/modules/device';
import { SpaceRepository } from '@app/common/modules/space/repositories'; import { SpaceRepository } from '@app/common/modules/space/repositories';
import { ProductRepository } from '@app/common/modules/product/repositories'; import { ProductRepository } from '@app/common/modules/product/repositories';
import { DeviceService } from 'src/device/services';
import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories';
import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service';
import { SceneService } from 'src/scene/services';
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
import { ProjectRepository } from '@app/common/modules/project/repositiories';
import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories';
import {
SceneIconRepository,
SceneRepository,
} from '@app/common/modules/scene/repositories';
import { AutomationRepository } from '@app/common/modules/automation/repositories';
@Module({ @Module({
imports: [ConfigModule, DeviceRepositoryModule], imports: [ConfigModule, DeviceRepositoryModule],
controllers: [GroupController], controllers: [GroupController],
@ -15,6 +27,16 @@ import { ProductRepository } from '@app/common/modules/product/repositories';
SpaceRepository, SpaceRepository,
DeviceRepository, DeviceRepository,
ProductRepository, ProductRepository,
DeviceService,
SceneDeviceRepository,
DeviceStatusFirebaseService,
SceneService,
TuyaService,
ProjectRepository,
DeviceStatusLogRepository,
SceneIconRepository,
SceneRepository,
AutomationRepository,
], ],
exports: [GroupService], exports: [GroupService],
}) })

View File

@ -2,19 +2,17 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { TuyaContext } from '@tuya/tuya-connector-nodejs'; import { TuyaContext } from '@tuya/tuya-connector-nodejs';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { SpaceRepository } from '@app/common/modules/space/repositories'; import { SpaceRepository } from '@app/common/modules/space/repositories';
import { GetDeviceDetailsInterface } from 'src/device/interfaces/get.device.interface';
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
import { ProductRepository } from '@app/common/modules/product/repositories';
import { PermissionType } from '@app/common/constants/permission-type.enum'; import { PermissionType } from '@app/common/constants/permission-type.enum';
import { In } from 'typeorm'; import { In } from 'typeorm';
import { ProductType } from '@app/common/constants/product-type.enum'; import { ProductType } from '@app/common/constants/product-type.enum';
import { DeviceService } from 'src/device/services';
@Injectable() @Injectable()
export class GroupService { export class GroupService {
private tuya: TuyaContext; private tuya: TuyaContext;
constructor( constructor(
private readonly configService: ConfigService, private readonly configService: ConfigService,
private readonly productRepository: ProductRepository, private readonly deviceService: DeviceService,
private readonly spaceRepository: SpaceRepository, private readonly spaceRepository: SpaceRepository,
) { ) {
const accessKey = this.configService.get<string>('auth-config.ACCESS_KEY'); const accessKey = this.configService.get<string>('auth-config.ACCESS_KEY');
@ -101,7 +99,8 @@ export class GroupService {
spaces.flatMap(async (space) => { spaces.flatMap(async (space) => {
return await Promise.all( return await Promise.all(
space.devices.map(async (device) => { space.devices.map(async (device) => {
const deviceDetails = await this.getDeviceDetailsByDeviceIdTuya( const deviceDetails =
await this.deviceService.getDeviceDetailsByDeviceIdTuya(
device.deviceTuyaUuid, device.deviceTuyaUuid,
); );
return { return {
@ -126,37 +125,4 @@ export class GroupService {
); );
} }
} }
async getDeviceDetailsByDeviceIdTuya(
deviceId: string,
): Promise<GetDeviceDetailsInterface> {
try {
const path = `/v1.1/iot-03/devices/${deviceId}`;
const response = await this.tuya.request({
method: 'GET',
path,
});
// Convert keys to camel case
const camelCaseResponse = convertKeysToCamelCase(response);
const product = await this.productRepository.findOne({
where: {
prodId: camelCaseResponse.result.productId,
},
});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { productName, productId, ...rest } = camelCaseResponse.result;
return {
...rest,
productUuid: product.uuid,
} as GetDeviceDetailsInterface;
} catch (error) {
throw new HttpException(
'Error fetching device details from Tuya',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
} }

View File

@ -1,21 +1,21 @@
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { GetDeviceDetailsInterface } from 'src/device/interfaces/get.device.interface';
import { GetSpaceParam } from '../dtos'; import { GetSpaceParam } from '../dtos';
import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
import { ValidationService } from './space-validation.service'; import { ValidationService } from './space-validation.service';
import { ProductType } from '@app/common/constants/product-type.enum'; import { ProductType } from '@app/common/constants/product-type.enum';
import { BatteryStatus } from '@app/common/constants/battery-status.enum'; import { BatteryStatus } from '@app/common/constants/battery-status.enum';
import { DeviceService } from 'src/device/services';
@Injectable() @Injectable()
export class SpaceDeviceService { export class SpaceDeviceService {
constructor( constructor(
private readonly tuyaService: TuyaService, private readonly tuyaService: TuyaService,
private readonly validationService: ValidationService, private readonly validationService: ValidationService,
private readonly deviceService: DeviceService,
) {} ) {}
async listDevicesInSpace(params: GetSpaceParam): Promise<BaseResponseDto> { async listDevicesInSpace(params: GetSpaceParam): Promise<BaseResponseDto> {
@ -60,7 +60,9 @@ export class SpaceDeviceService {
try { try {
// Fetch Tuya details in parallel // Fetch Tuya details in parallel
const [tuyaDetails, tuyaDeviceStatusResponse] = await Promise.all([ const [tuyaDetails, tuyaDeviceStatusResponse] = await Promise.all([
this.getDeviceDetailsByDeviceIdTuya(device.deviceTuyaUuid), this.deviceService.getDeviceDetailsByDeviceIdTuya(
device.deviceTuyaUuid,
),
this.tuyaService.getDevicesInstructionStatusTuya(device.deviceTuyaUuid), this.tuyaService.getDevicesInstructionStatusTuya(device.deviceTuyaUuid),
]); ]);
@ -119,28 +121,4 @@ export class SpaceDeviceService {
); );
return batteryStatus ? batteryStatus.value : null; return batteryStatus ? batteryStatus.value : null;
} }
private async getDeviceDetailsByDeviceIdTuya(
deviceId: string,
): Promise<GetDeviceDetailsInterface> {
try {
const tuyaDeviceDetails =
await this.tuyaService.getDeviceDetails(deviceId);
// Convert keys to camel case
const camelCaseResponse = convertKeysToCamelCase(tuyaDeviceDetails);
// Exclude specific keys and add `productUuid`
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { uuid, ...rest } = camelCaseResponse;
return {
...rest,
} as GetDeviceDetailsInterface;
} catch (error) {
throw new HttpException(
`Error fetching device details from Tuya for device id ${deviceId}`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
} }

View File

@ -3,23 +3,19 @@ import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import { DeviceRepository } from '@app/common/modules/device/repositories'; import { DeviceRepository } from '@app/common/modules/device/repositories';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { DeviceSubSpaceParam, GetSubSpaceParam } from '../../dtos'; import { DeviceSubSpaceParam, GetSubSpaceParam } from '../../dtos';
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
import { ProductRepository } from '@app/common/modules/product/repositories';
import { GetDeviceDetailsInterface } from '../../../device/interfaces/get.device.interface';
import { ValidationService } from '../space-validation.service'; import { ValidationService } from '../space-validation.service';
import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
import { In, QueryRunner } from 'typeorm'; import { In, QueryRunner } from 'typeorm';
import { DeviceEntity } from '@app/common/modules/device/entities'; import { DeviceEntity } from '@app/common/modules/device/entities';
import { TagRepository } from '@app/common/modules/space'; import { TagRepository } from '@app/common/modules/space';
import { DeviceService } from 'src/device/services';
@Injectable() @Injectable()
export class SubspaceDeviceService { export class SubspaceDeviceService {
constructor( constructor(
private readonly subspaceRepository: SubspaceRepository, private readonly subspaceRepository: SubspaceRepository,
private readonly deviceRepository: DeviceRepository, private readonly deviceRepository: DeviceRepository,
private readonly tuyaService: TuyaService, private readonly deviceService: DeviceService,
private readonly productRepository: ProductRepository,
private readonly validationService: ValidationService, private readonly validationService: ValidationService,
private readonly tagRepository: TagRepository, private readonly tagRepository: TagRepository,
) {} ) {}
@ -39,7 +35,8 @@ export class SubspaceDeviceService {
const safeFetch = async (device: any) => { const safeFetch = async (device: any) => {
try { try {
const tuyaDetails = await this.getDeviceDetailsByDeviceIdTuya( const tuyaDetails =
await this.deviceService.getDeviceDetailsByDeviceIdTuya(
device.deviceTuyaUuid, device.deviceTuyaUuid,
); );
@ -205,35 +202,6 @@ export class SubspaceDeviceService {
); );
} }
private async getDeviceDetailsByDeviceIdTuya(
deviceId: string,
): Promise<GetDeviceDetailsInterface> {
try {
const tuyaDeviceDetails =
await this.tuyaService.getDeviceDetails(deviceId);
const camelCaseResponse = convertKeysToCamelCase(tuyaDeviceDetails);
const product = await this.productRepository.findOne({
where: {
prodId: camelCaseResponse.productId,
},
});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { uuid, ...rest } = camelCaseResponse;
return {
...rest,
productUuid: product?.uuid,
} as GetDeviceDetailsInterface;
} catch (error) {
throw new HttpException(
`Error fetching device details from Tuya for device uuid ${deviceId}.`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async findNextTag(): Promise<number> { async findNextTag(): Promise<number> {
const tags = await this.tagRepository.find({ select: ['tag'] }); const tags = await this.tagRepository.find({ select: ['tag'] });