mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-11-26 20:04:54 +00:00
266 lines
8.1 KiB
TypeScript
266 lines
8.1 KiB
TypeScript
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
|
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
|
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
|
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
|
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 { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
|
|
import { In, QueryRunner } from 'typeorm';
|
|
import { DeviceEntity } from '@app/common/modules/device/entities';
|
|
import { TagRepository } from '@app/common/modules/space';
|
|
|
|
@Injectable()
|
|
export class SubspaceDeviceService {
|
|
constructor(
|
|
private readonly subspaceRepository: SubspaceRepository,
|
|
private readonly deviceRepository: DeviceRepository,
|
|
private readonly tuyaService: TuyaService,
|
|
private readonly productRepository: ProductRepository,
|
|
private readonly validationService: ValidationService,
|
|
private readonly tagRepository: TagRepository,
|
|
) {}
|
|
|
|
async listDevicesInSubspace(
|
|
params: GetSubSpaceParam,
|
|
): Promise<BaseResponseDto> {
|
|
const { subSpaceUuid, spaceUuid, communityUuid, projectUuid } = params;
|
|
|
|
await this.validationService.checkCommunityAndProjectSpaceExistence(
|
|
communityUuid,
|
|
projectUuid,
|
|
spaceUuid,
|
|
);
|
|
|
|
const subspace = await this.findSubspaceWithDevices(subSpaceUuid);
|
|
|
|
const safeFetch = async (device: any) => {
|
|
try {
|
|
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,
|
|
};
|
|
} catch (error) {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const detailedDevices = await Promise.all(subspace.devices.map(safeFetch));
|
|
|
|
return new SuccessResponseDto({
|
|
data: detailedDevices.filter(Boolean), // Remove nulls
|
|
message: 'Successfully retrieved list of devices',
|
|
});
|
|
}
|
|
|
|
async associateDeviceToSubspace(
|
|
params: DeviceSubSpaceParam,
|
|
): Promise<BaseResponseDto> {
|
|
const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid, projectUuid } =
|
|
params;
|
|
try {
|
|
await this.validationService.checkCommunityAndProjectSpaceExistence(
|
|
communityUuid,
|
|
projectUuid,
|
|
spaceUuid,
|
|
);
|
|
|
|
const subspace = await this.findSubspace(subSpaceUuid);
|
|
const device = await this.findDevice(deviceUuid);
|
|
|
|
device.subspace = subspace;
|
|
|
|
const newDevice = await this.deviceRepository.save(device);
|
|
|
|
return new SuccessResponseDto({
|
|
data: newDevice,
|
|
message: `Successfully associated device to subspace`,
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof HttpException) {
|
|
throw error;
|
|
} else {
|
|
throw new HttpException(
|
|
`Failed to associate device to subspace with error = ${error}`,
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
async disassociateDeviceFromSubspace(
|
|
params: DeviceSubSpaceParam,
|
|
): Promise<BaseResponseDto> {
|
|
const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid, projectUuid } =
|
|
params;
|
|
try {
|
|
await this.validationService.checkCommunityAndProjectSpaceExistence(
|
|
communityUuid,
|
|
projectUuid,
|
|
spaceUuid,
|
|
);
|
|
const subspace = await this.findSubspace(subSpaceUuid);
|
|
const device = await this.findDeviceWithSubspaceAndTag(deviceUuid);
|
|
|
|
if (!device.subspace || device.subspace.uuid !== subspace.uuid) {
|
|
throw new HttpException(
|
|
`Device ${deviceUuid} is not associated with the specified subspace ${subSpaceUuid} `,
|
|
HttpStatus.BAD_REQUEST,
|
|
);
|
|
}
|
|
|
|
device.subspace = null;
|
|
const updatedDevice = await this.deviceRepository.save(device);
|
|
|
|
return new SuccessResponseDto({
|
|
data: updatedDevice,
|
|
message: 'Successfully dissociated device from subspace',
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof HttpException) {
|
|
throw error;
|
|
} else {
|
|
throw new HttpException(
|
|
`Failed to dissociate device from subspace error = ${error}`,
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper method to find subspace with devices relation
|
|
private async findSubspaceWithDevices(subSpaceUuid: string) {
|
|
const subspace = await this.subspaceRepository.findOne({
|
|
where: { uuid: subSpaceUuid },
|
|
relations: ['devices', 'devices.productDevice'],
|
|
});
|
|
if (!subspace) {
|
|
this.throwNotFound('Subspace', subSpaceUuid);
|
|
}
|
|
return subspace;
|
|
}
|
|
|
|
private async findSubspace(subSpaceUuid: string) {
|
|
const subspace = await this.subspaceRepository.findOne({
|
|
where: { uuid: subSpaceUuid },
|
|
});
|
|
if (!subspace) {
|
|
this.throwNotFound('Subspace', subSpaceUuid);
|
|
}
|
|
return subspace;
|
|
}
|
|
|
|
private async findDevice(deviceUuid: string) {
|
|
const device = await this.deviceRepository.findOne({
|
|
where: { uuid: deviceUuid },
|
|
relations: [
|
|
'subspace',
|
|
'tag',
|
|
'tag.space',
|
|
'tag.subspace',
|
|
'spaceDevice',
|
|
],
|
|
});
|
|
if (!device) {
|
|
this.throwNotFound('Device', deviceUuid);
|
|
}
|
|
return device;
|
|
}
|
|
|
|
async deleteSubspaceDevices(
|
|
devices: DeviceEntity[],
|
|
queryRunner: QueryRunner,
|
|
): Promise<void> {
|
|
const deviceUuids = devices.map((device) => device.uuid);
|
|
|
|
try {
|
|
if (deviceUuids.length === 0) {
|
|
return;
|
|
}
|
|
|
|
await queryRunner.manager.update(
|
|
this.deviceRepository.target,
|
|
{ uuid: In(deviceUuids) },
|
|
{ subspace: null },
|
|
);
|
|
} catch (error) {
|
|
throw new HttpException(
|
|
`Failed to delete devices with IDs ${deviceUuids.join(', ')}: ${error.message}`,
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
|
|
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 {
|
|
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> {
|
|
const tags = await this.tagRepository.find({ select: ['tag'] });
|
|
|
|
const tagNumbers = tags
|
|
.map((t) => t.tag.match(/^Tag (\d+)$/))
|
|
.filter((match) => match)
|
|
.map((match) => parseInt(match[1]))
|
|
.sort((a, b) => a - b);
|
|
|
|
const nextTagNumber = tagNumbers.length
|
|
? tagNumbers[tagNumbers.length - 1] + 1
|
|
: 1;
|
|
return nextTagNumber;
|
|
}
|
|
|
|
private async findDeviceWithSubspaceAndTag(deviceUuid: string) {
|
|
return await this.deviceRepository.findOne({
|
|
where: { uuid: deviceUuid },
|
|
relations: ['subspace', 'tag', 'spaceDevice'],
|
|
select: ['uuid', 'subspace', 'spaceDevice', 'productDevice', 'tag'],
|
|
});
|
|
}
|
|
}
|