diff --git a/libs/common/src/constants/device-type.enum.ts b/libs/common/src/constants/device-type.enum.ts new file mode 100644 index 0000000..c1509f5 --- /dev/null +++ b/libs/common/src/constants/device-type.enum.ts @@ -0,0 +1,3 @@ +export enum DeviceTypeEnum { + DOOR_LOCK = 'DOOR_LOCK', +} diff --git a/src/device/controllers/device-project.controller.ts b/src/device/controllers/device-project.controller.ts index efdb6ca..e5181dd 100644 --- a/src/device/controllers/device-project.controller.ts +++ b/src/device/controllers/device-project.controller.ts @@ -1,11 +1,11 @@ import { DeviceService } from '../services/device.service'; -import { Controller, Get, Param, UseGuards } from '@nestjs/common'; +import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common'; import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { ControllerRoute } from '@app/common/constants/controller-route'; import { PermissionsGuard } from 'src/guards/permissions.guard'; import { Permissions } from 'src/decorators/permissions.decorator'; -import { ProjectParam } from '../dtos'; +import { GetDoorLockDevices, ProjectParam } from '../dtos'; @ApiTags('Device Module') @Controller({ @@ -23,7 +23,10 @@ export class DeviceProjectController { summary: ControllerRoute.DEVICE.ACTIONS.GET_ALL_DEVICES_SUMMARY, description: ControllerRoute.DEVICE.ACTIONS.GET_ALL_DEVICES_DESCRIPTION, }) - async getAllDevices(@Param() param: ProjectParam) { - return await this.deviceService.getAllDevices(param); + async getAllDevices( + @Param() param: ProjectParam, + @Query() query: GetDoorLockDevices, + ) { + return await this.deviceService.getAllDevices(param, query); } } diff --git a/src/device/dtos/get.device.dto.ts b/src/device/dtos/get.device.dto.ts index f3ce5cc..9080062 100644 --- a/src/device/dtos/get.device.dto.ts +++ b/src/device/dtos/get.device.dto.ts @@ -1,5 +1,6 @@ +import { DeviceTypeEnum } from '@app/common/constants/device-type.enum'; import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { IsEnum, IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class GetDeviceBySpaceUuidDto { @ApiProperty({ @@ -33,3 +34,13 @@ export class GetDeviceLogsDto { @IsOptional() public endTime: string; } +export class GetDoorLockDevices { + @ApiProperty({ + description: 'Device Type', + enum: DeviceTypeEnum, + required: false, + }) + @IsEnum(DeviceTypeEnum) + @IsOptional() + public deviceType: DeviceTypeEnum; +} diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 18dc42d..5f71724 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -31,6 +31,7 @@ import { import { GetDeviceBySpaceUuidDto, GetDeviceLogsDto, + GetDoorLockDevices, } from '../dtos/get.device.dto'; import { BatchControlDevicesDto, @@ -63,6 +64,7 @@ import { SpaceEntity } from '@app/common/modules/space/entities/space.entity'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { ProjectParam } from '../dtos'; import { BatchDeviceTypeEnum } from '@app/common/constants/batch-device.enum'; +import { DeviceTypeEnum } from '@app/common/constants/device-type.enum'; @Injectable() export class DeviceService { @@ -980,131 +982,138 @@ export class DeviceService { } } - async getAllDevices(param: ProjectParam): Promise { + async getAllDevices( + param: ProjectParam, + query: GetDoorLockDevices, + ): Promise { try { await this.validateProject(param.projectUuid); - - const devices = await this.deviceRepository.find({ - where: { - isActive: true, - spaceDevice: { - community: { project: { uuid: param.projectUuid } }, - spaceName: Not(ORPHAN_SPACE_NAME), - }, - }, - relations: [ - 'spaceDevice.parent', - 'spaceDevice.community', - 'productDevice', - 'permission', - 'permission.permissionType', - 'subspace', - ], - }); - - const devicesData = await Promise.allSettled( - devices.map(async (device) => { - let battery = null; - - // Check if the device is a door lock (DL) - if (device.productDevice.prodType === ProductType.DL) { - const doorLockInstructionsStatus: BaseResponseDto = - await this.getDevicesInstructionStatus( - device.uuid, - param.projectUuid, - ); - - const batteryStatus: any = - doorLockInstructionsStatus.data.status.find( - (status: any) => - status.code === BatteryStatus.RESIDUAL_ELECTRICITY, - ); - - if (batteryStatus) { - battery = batteryStatus.value; - } - } - // Check if the device is a door sensor (DS) - if (device.productDevice.prodType === ProductType.DS) { - const doorSensorInstructionsStatus: BaseResponseDto = - await this.getDevicesInstructionStatus( - device.uuid, - param.projectUuid, - ); - - const batteryStatus: any = - doorSensorInstructionsStatus.data.status.find( - (status: any) => - status.code === BatteryStatus.BATTERY_PERCENTAGE, - ); - - if (batteryStatus) { - battery = batteryStatus.value; - } - } - // Check if the device is a water leak sensor (WL) - if (device.productDevice.prodType === ProductType.WL) { - const doorSensorInstructionsStatus: BaseResponseDto = - await this.getDevicesInstructionStatus( - device.uuid, - param.projectUuid, - ); - - const batteryStatus: any = - doorSensorInstructionsStatus.data.status.find( - (status: any) => - status.code === BatteryStatus.BATTERY_PERCENTAGE, - ); - - if (batteryStatus) { - battery = batteryStatus.value; - } - } - - const spaceHierarchy = await this.getParentHierarchy( - device?.spaceDevice, - ); - const orderedHierarchy = [ - device?.spaceDevice, - ...spaceHierarchy.reverse(), - ]; - - return { - spaces: orderedHierarchy.map((space) => ({ - uuid: space.uuid, - spaceName: space.spaceName, - })), - - productUuid: device.productDevice.uuid, - productType: device.productDevice.prodType, - community: { - uuid: device.spaceDevice.community.uuid, - name: device.spaceDevice.community.name, + if (query.deviceType === DeviceTypeEnum.DOOR_LOCK) { + return await this.getDoorLockDevices(param.projectUuid); + } else if (!query.deviceType) { + const devices = await this.deviceRepository.find({ + where: { + isActive: true, + spaceDevice: { + community: { project: { uuid: param.projectUuid } }, + spaceName: Not(ORPHAN_SPACE_NAME), }, - subspace: device.subspace, - // permissionType: device.permission[0].permissionType.type, - ...(await this.getDeviceDetailsByDeviceIdTuya( - device.deviceTuyaUuid, - )), - uuid: device.uuid, - ...(battery && { battery }), - } as GetDeviceDetailsInterface; - }), - ); + }, + relations: [ + 'spaceDevice.parent', + 'spaceDevice.community', + 'productDevice', + 'permission', + 'permission.permissionType', + 'subspace', + ], + }); - // Filter out rejected promises and extract the fulfilled values - const fulfilledDevices = devicesData - .filter((result) => result.status === DeviceStatuses.FULLFILLED) - .map( - (result) => - (result as PromiseFulfilledResult).value, + const devicesData = await Promise.allSettled( + devices.map(async (device) => { + let battery = null; + + // Check if the device is a door lock (DL) + if (device.productDevice.prodType === ProductType.DL) { + const doorLockInstructionsStatus: BaseResponseDto = + await this.getDevicesInstructionStatus( + device.uuid, + param.projectUuid, + ); + + const batteryStatus: any = + doorLockInstructionsStatus.data.status.find( + (status: any) => + status.code === BatteryStatus.RESIDUAL_ELECTRICITY, + ); + + if (batteryStatus) { + battery = batteryStatus.value; + } + } + // Check if the device is a door sensor (DS) + if (device.productDevice.prodType === ProductType.DS) { + const doorSensorInstructionsStatus: BaseResponseDto = + await this.getDevicesInstructionStatus( + device.uuid, + param.projectUuid, + ); + + const batteryStatus: any = + doorSensorInstructionsStatus.data.status.find( + (status: any) => + status.code === BatteryStatus.BATTERY_PERCENTAGE, + ); + + if (batteryStatus) { + battery = batteryStatus.value; + } + } + // Check if the device is a water leak sensor (WL) + if (device.productDevice.prodType === ProductType.WL) { + const doorSensorInstructionsStatus: BaseResponseDto = + await this.getDevicesInstructionStatus( + device.uuid, + param.projectUuid, + ); + + const batteryStatus: any = + doorSensorInstructionsStatus.data.status.find( + (status: any) => + status.code === BatteryStatus.BATTERY_PERCENTAGE, + ); + + if (batteryStatus) { + battery = batteryStatus.value; + } + } + + const spaceHierarchy = await this.getParentHierarchy( + device?.spaceDevice, + ); + const orderedHierarchy = [ + device?.spaceDevice, + ...spaceHierarchy.reverse(), + ]; + + return { + spaces: orderedHierarchy.map((space) => ({ + uuid: space.uuid, + spaceName: space.spaceName, + })), + + productUuid: device.productDevice.uuid, + productType: device.productDevice.prodType, + community: { + uuid: device.spaceDevice.community.uuid, + name: device.spaceDevice.community.name, + }, + subspace: device.subspace, + // permissionType: device.permission[0].permissionType.type, + ...(await this.getDeviceDetailsByDeviceIdTuya( + device.deviceTuyaUuid, + )), + uuid: device.uuid, + ...(battery && { battery }), + } as GetDeviceDetailsInterface; + }), ); - return new SuccessResponseDto({ - message: `Devices fetched successfully`, - data: fulfilledDevices, - statusCode: HttpStatus.OK, - }); + // Filter out rejected promises and extract the fulfilled values + const fulfilledDevices = devicesData + .filter((result) => result.status === DeviceStatuses.FULLFILLED) + .map( + (result) => + (result as PromiseFulfilledResult) + .value, + ); + + return new SuccessResponseDto({ + message: `Devices fetched successfully`, + data: fulfilledDevices, + statusCode: HttpStatus.OK, + }); + } } catch (error) { throw new HttpException( error.message || 'Internal server error', @@ -1650,4 +1659,60 @@ export class DeviceService { { spaceDevice: targetSpace }, ); } + async getDoorLockDevices(projectUuid: string) { + await this.validateProject(projectUuid); + + const devices = await this.deviceRepository.find({ + where: { + productDevice: { + prodType: ProductType.DL, + }, + spaceDevice: { + spaceName: Not(ORPHAN_SPACE_NAME), + community: { + project: { + uuid: projectUuid, + }, + }, + }, + isActive: true, + }, + relations: ['productDevice', 'spaceDevice'], + }); + + const devicesData = await Promise.all( + devices?.map(async (device) => { + try { + const deviceDetails = await this.getDeviceDetailsByDeviceIdTuya( + device.deviceTuyaUuid, + ); + return { + productUuid: device.productDevice.uuid, + productType: device.productDevice.prodType, + ...deviceDetails, + uuid: device.uuid, + spaceName: device.spaceDevice.spaceName, + } as GetDeviceDetailsInterface; + } catch (error) { + console.error( + `Error fetching details for device ${device.deviceTuyaUuid}:`, + error, + ); + // Return null to indicate the error + return null; + } + }), + ); + + // Filter out null values to only include successful device data + const filteredDevicesData = devicesData.filter( + (deviceData) => deviceData !== null, + ); + + return new SuccessResponseDto({ + message: 'Successfully retrieved all pass devices', + data: filteredDevicesData, + statusCode: HttpStatus.OK, + }); + } } diff --git a/src/vistor-password/controllers/visitor-password.controller.ts b/src/vistor-password/controllers/visitor-password.controller.ts index 5730b7b..96ecdad 100644 --- a/src/vistor-password/controllers/visitor-password.controller.ts +++ b/src/vistor-password/controllers/visitor-password.controller.ts @@ -1,13 +1,5 @@ import { VisitorPasswordService } from '../services/visitor-password.service'; -import { - Body, - Controller, - Post, - HttpStatus, - UseGuards, - Req, - Get, -} from '@nestjs/common'; +import { Body, Controller, Post, UseGuards, Req, Get } from '@nestjs/common'; import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; import { AddDoorLockTemporaryPasswordDto } from '../dtos/temp-pass.dto'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; @@ -40,16 +32,12 @@ export class VisitorPasswordController { @Req() req: any, ) { const userUuid = req.user.uuid; - const temporaryPasswords = - await this.visitorPasswordService.handleTemporaryPassword( - addDoorLockTemporaryPasswordDto, - userUuid, - ); - - return { - statusCode: HttpStatus.CREATED, - data: temporaryPasswords, - }; + const projectUuid = req.user.project.uuid; + return await this.visitorPasswordService.handleTemporaryPassword( + addDoorLockTemporaryPasswordDto, + userUuid, + projectUuid, + ); } @ApiBearerAuth() @UseGuards(PermissionsGuard) @@ -65,19 +53,4 @@ export class VisitorPasswordController { const projectUuid = req.user.project.uuid; return await this.visitorPasswordService.getPasswords(projectUuid); } - - @ApiBearerAuth() - @UseGuards(PermissionsGuard) - @Permissions('VISITOR_PASSWORD_VIEW') - @Get('/devices') - @ApiOperation({ - summary: - ControllerRoute.VISITOR_PASSWORD.ACTIONS.GET_VISITOR_DEVICES_SUMMARY, - description: - ControllerRoute.VISITOR_PASSWORD.ACTIONS.GET_VISITOR_DEVICES_DESCRIPTION, - }) - async GetVisitorDevices(@Req() req: any) { - const projectUuid = req.user.project.uuid; - return await this.visitorPasswordService.getAllPassDevices(projectUuid); - } } diff --git a/src/vistor-password/services/visitor-password.service.ts b/src/vistor-password/services/visitor-password.service.ts index 62a8716..0bc1b02 100644 --- a/src/vistor-password/services/visitor-password.service.ts +++ b/src/vistor-password/services/visitor-password.service.ts @@ -18,7 +18,6 @@ import { AddDoorLockTemporaryPasswordDto } from '../dtos'; import { EmailService } from '@app/common/util/email.service'; import { PasswordEncryptionService } from 'src/door-lock/services/encryption.services'; import { DoorLockService } from 'src/door-lock/services'; -import { GetDeviceDetailsInterface } from 'src/device/interfaces/get.device.interface'; import { DeviceService } from 'src/device/services'; import { DeviceStatuses } from '@app/common/constants/device-status.enum'; import { @@ -32,6 +31,9 @@ import { } from '@app/common/constants/hours-minutes.enum'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { VisitorPasswordEnum } from '@app/common/constants/visitor-password.enum'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { Not } from 'typeorm'; +import { ORPHAN_SPACE_NAME } from '@app/common/constants/orphan-constant'; @Injectable() export class VisitorPasswordService { @@ -58,43 +60,65 @@ export class VisitorPasswordService { async handleTemporaryPassword( addDoorLockTemporaryPasswordDto: AddDoorLockTemporaryPasswordDto, userUuid: string, + projectUuid: string, ) { const { operationType } = addDoorLockTemporaryPasswordDto; + let result; + switch (operationType) { case VisitorPasswordEnum.ONLINE_ONE_TIME: - return this.addOnlineTemporaryPasswordOneTime( + result = await this.addOnlineTemporaryPasswordOneTime( addDoorLockTemporaryPasswordDto, userUuid, + projectUuid, ); + break; case VisitorPasswordEnum.ONLINE_MULTIPLE_TIME: - return this.addOnlineTemporaryPasswordMultipleTime( + result = await this.addOnlineTemporaryPasswordMultipleTime( addDoorLockTemporaryPasswordDto, userUuid, + projectUuid, ); + break; case VisitorPasswordEnum.OFFLINE_ONE_TIME: - return this.addOfflineOneTimeTemporaryPassword( + result = await this.addOfflineOneTimeTemporaryPassword( addDoorLockTemporaryPasswordDto, userUuid, + projectUuid, ); + break; case VisitorPasswordEnum.OFFLINE_MULTIPLE_TIME: - return this.addOfflineMultipleTimeTemporaryPassword( + result = await this.addOfflineMultipleTimeTemporaryPassword( addDoorLockTemporaryPasswordDto, userUuid, + projectUuid, ); + break; default: throw new BadRequestException('Invalid operation type'); } + + return new SuccessResponseDto({ + message: `Successfully created new ${operationType.toLowerCase()} visitor password`, + data: result, + statusCode: HttpStatus.CREATED, + }); } async addOfflineMultipleTimeTemporaryPassword( addDoorLockOfflineMultipleDto: AddDoorLockTemporaryPasswordDto, userUuid: string, + projectUuid: string, ) { try { const deviceResults = await Promise.allSettled( addDoorLockOfflineMultipleDto.devicesUuid.map(async (deviceUuid) => { try { - const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); + const deviceDetails = await this.getDeviceByDeviceUuid( + deviceUuid, + true, + projectUuid, + ); if (!deviceDetails || !deviceDetails.deviceTuyaUuid) { throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); @@ -210,12 +234,17 @@ export class VisitorPasswordService { async addOfflineOneTimeTemporaryPassword( addDoorLockOfflineOneTimeDto: AddDoorLockTemporaryPasswordDto, userUuid: string, + projectUuid: string, ) { try { const deviceResults = await Promise.allSettled( addDoorLockOfflineOneTimeDto.devicesUuid.map(async (deviceUuid) => { try { - const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); + const deviceDetails = await this.getDeviceByDeviceUuid( + deviceUuid, + true, + projectUuid, + ); if (!deviceDetails || !deviceDetails.deviceTuyaUuid) { throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); @@ -361,6 +390,7 @@ export class VisitorPasswordService { async addOnlineTemporaryPasswordMultipleTime( addDoorLockOnlineMultipleDto: AddDoorLockTemporaryPasswordDto, userUuid: string, + projectUuid: string, ) { try { const deviceResults = await Promise.allSettled( @@ -369,6 +399,7 @@ export class VisitorPasswordService { const passwordData = await this.getTicketAndEncryptedPassword( deviceUuid, addDoorLockOnlineMultipleDto.password, + projectUuid, ); if ( @@ -486,6 +517,7 @@ export class VisitorPasswordService { prodType: ProductType.DL, }, spaceDevice: { + spaceName: Not(ORPHAN_SPACE_NAME), community: { project: { uuid: projectUuid, @@ -495,6 +527,7 @@ export class VisitorPasswordService { isActive: true, }, }); + const data = []; deviceIds.forEach((deviceId) => { data.push( @@ -524,62 +557,21 @@ export class VisitorPasswordService { .catch(() => {}), ); }); - return (await Promise.all(data)).flat().filter((datum) => { + const result = (await Promise.all(data)).flat().filter((datum) => { return datum != null; }); - } - async getAllPassDevices(projectUuid: string) { - await this.validateProject(projectUuid); - - const devices = await this.deviceRepository.find({ - where: { - productDevice: { - prodType: ProductType.DL, - }, - spaceDevice: { - community: { - project: { - uuid: projectUuid, - }, - }, - }, - isActive: true, - }, - - relations: ['productDevice', 'spaceDevice'], + return new SuccessResponseDto({ + message: 'Successfully retrieved temporary passwords', + data: result, + statusCode: HttpStatus.OK, }); - const devicesData = await Promise.all( - devices?.map(async (device) => { - try { - const deviceDetails = - await this.deviceService.getDeviceDetailsByDeviceIdTuya( - device.deviceTuyaUuid, - ); - return { - productUuid: device.productDevice.uuid, - productType: device.productDevice.prodType, - ...deviceDetails, - uuid: device.uuid, - spaceName: device.spaceDevice.spaceName, - } as GetDeviceDetailsInterface; - } catch (error) { - console.error( - `Error fetching details for device ${device.deviceTuyaUuid}:`, - error, - ); - // Return null or a specific value to indicate the error - return null; - } - }), - ); - // Filter out null values to only include successful device data - return devicesData.filter((deviceData) => deviceData !== null); } async addOnlineTemporaryPasswordOneTime( addDoorLockOnlineOneTimeDto: AddDoorLockTemporaryPasswordDto, userUuid: string, + projectUuid: string, ) { try { const deviceResults = await Promise.allSettled( @@ -588,6 +580,7 @@ export class VisitorPasswordService { const passwordData = await this.getTicketAndEncryptedPassword( deviceUuid, addDoorLockOnlineOneTimeDto.password, + projectUuid, ); if ( @@ -698,9 +691,14 @@ export class VisitorPasswordService { async getTicketAndEncryptedPassword( doorLockUuid: string, passwordPlan: string, + projectUuid: string, ) { try { - const deviceDetails = await this.getDeviceByDeviceUuid(doorLockUuid); + const deviceDetails = await this.getDeviceByDeviceUuid( + doorLockUuid, + true, + projectUuid, + ); if (!deviceDetails || !deviceDetails.deviceTuyaUuid) { throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); @@ -919,10 +917,19 @@ export class VisitorPasswordService { async getDeviceByDeviceUuid( deviceUuid: string, withProductDevice: boolean = true, + projectUuid: string, ) { try { return await this.deviceRepository.findOne({ where: { + spaceDevice: { + spaceName: Not(ORPHAN_SPACE_NAME), + community: { + project: { + uuid: projectUuid, + }, + }, + }, uuid: deviceUuid, isActive: true, },