mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-07-10 15:17:41 +00:00
feat: add DEVICE_SPACE_COMMUNITY route and controller for device retrieval by space or community
This commit is contained in:
@ -624,6 +624,15 @@ export class ControllerRoute {
|
|||||||
'This endpoint retrieves all devices in the system.';
|
'This endpoint retrieves all devices in the system.';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
static DEVICE_SPACE_COMMUNITY = class {
|
||||||
|
public static readonly ROUTE = 'devices/recursive-child';
|
||||||
|
static ACTIONS = class {
|
||||||
|
public static readonly GET_ALL_DEVICES_BY_SPACE_OR_COMMUNITY_WITH_RECURSIVE_CHILD_SUMMARY =
|
||||||
|
'Get all devices by space or community with recursive child';
|
||||||
|
public static readonly GET_ALL_DEVICES_BY_SPACE_OR_COMMUNITY_WITH_RECURSIVE_CHILD_DESCRIPTION =
|
||||||
|
'This endpoint retrieves all devices in the system by space or community with recursive child.';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
static DEVICE_PERMISSION = class {
|
static DEVICE_PERMISSION = class {
|
||||||
public static readonly ROUTE = 'device-permission';
|
public static readonly ROUTE = 'device-permission';
|
||||||
|
@ -18,6 +18,7 @@ import { SceneDeviceRepository } from '@app/common/modules/scene-device/reposito
|
|||||||
import { AutomationRepository } from '@app/common/modules/automation/repositories';
|
import { AutomationRepository } from '@app/common/modules/automation/repositories';
|
||||||
import { ProjectRepository } from '@app/common/modules/project/repositiories/project.repository';
|
import { ProjectRepository } from '@app/common/modules/project/repositiories/project.repository';
|
||||||
import { AutomationSpaceController } from './controllers/automation-space.controller';
|
import { AutomationSpaceController } from './controllers/automation-space.controller';
|
||||||
|
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusFirebaseModule],
|
imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusFirebaseModule],
|
||||||
@ -35,6 +36,7 @@ import { AutomationSpaceController } from './controllers/automation-space.contro
|
|||||||
SceneDeviceRepository,
|
SceneDeviceRepository,
|
||||||
AutomationRepository,
|
AutomationRepository,
|
||||||
ProjectRepository,
|
ProjectRepository,
|
||||||
|
CommunityRepository,
|
||||||
],
|
],
|
||||||
exports: [AutomationService],
|
exports: [AutomationService],
|
||||||
})
|
})
|
||||||
|
52
src/device/controllers/device-space-community.controller.ts
Normal file
52
src/device/controllers/device-space-community.controller.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { DeviceService } from '../services/device.service';
|
||||||
|
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
ApiTags,
|
||||||
|
ApiBearerAuth,
|
||||||
|
ApiOperation,
|
||||||
|
ApiQuery,
|
||||||
|
} 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 { GetDevicesBySpaceOrCommunityDto } from '../dtos';
|
||||||
|
|
||||||
|
@ApiTags('Device Module')
|
||||||
|
@Controller({
|
||||||
|
version: EnableDisableStatusEnum.ENABLED,
|
||||||
|
path: ControllerRoute.DEVICE_SPACE_COMMUNITY.ROUTE,
|
||||||
|
})
|
||||||
|
export class DeviceSpaceOrCommunityController {
|
||||||
|
constructor(private readonly deviceService: DeviceService) {}
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(PermissionsGuard)
|
||||||
|
@Permissions('DEVICE_VIEW')
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({
|
||||||
|
summary:
|
||||||
|
ControllerRoute.DEVICE_SPACE_COMMUNITY.ACTIONS
|
||||||
|
.GET_ALL_DEVICES_BY_SPACE_OR_COMMUNITY_WITH_RECURSIVE_CHILD_SUMMARY,
|
||||||
|
description:
|
||||||
|
ControllerRoute.DEVICE_SPACE_COMMUNITY.ACTIONS
|
||||||
|
.GET_ALL_DEVICES_BY_SPACE_OR_COMMUNITY_WITH_RECURSIVE_CHILD_DESCRIPTION,
|
||||||
|
})
|
||||||
|
@ApiQuery({
|
||||||
|
name: 'spaceUuid',
|
||||||
|
description: 'UUID of the Space',
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
@ApiQuery({
|
||||||
|
name: 'communityUuid',
|
||||||
|
description: 'UUID of the Community',
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
async getAllDevicesBySpaceOrCommunityWithChild(
|
||||||
|
@Query() query: GetDevicesBySpaceOrCommunityDto,
|
||||||
|
) {
|
||||||
|
return await this.deviceService.getAllDevicesBySpaceOrCommunityWithChild(
|
||||||
|
query,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,8 @@ import { SceneDeviceRepository } from '@app/common/modules/scene-device/reposito
|
|||||||
import { AutomationRepository } from '@app/common/modules/automation/repositories';
|
import { AutomationRepository } from '@app/common/modules/automation/repositories';
|
||||||
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
||||||
import { DeviceProjectController } from './controllers/device-project.controller';
|
import { DeviceProjectController } from './controllers/device-project.controller';
|
||||||
|
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||||
|
import { DeviceSpaceOrCommunityController } from './controllers/device-space-community.controller';
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule,
|
ConfigModule,
|
||||||
@ -30,7 +32,11 @@ import { DeviceProjectController } from './controllers/device-project.controller
|
|||||||
DeviceRepositoryModule,
|
DeviceRepositoryModule,
|
||||||
DeviceStatusFirebaseModule,
|
DeviceStatusFirebaseModule,
|
||||||
],
|
],
|
||||||
controllers: [DeviceController, DeviceProjectController],
|
controllers: [
|
||||||
|
DeviceController,
|
||||||
|
DeviceProjectController,
|
||||||
|
DeviceSpaceOrCommunityController,
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
DeviceService,
|
DeviceService,
|
||||||
ProductRepository,
|
ProductRepository,
|
||||||
@ -46,6 +52,7 @@ import { DeviceProjectController } from './controllers/device-project.controller
|
|||||||
SceneRepository,
|
SceneRepository,
|
||||||
SceneDeviceRepository,
|
SceneDeviceRepository,
|
||||||
AutomationRepository,
|
AutomationRepository,
|
||||||
|
CommunityRepository,
|
||||||
],
|
],
|
||||||
exports: [DeviceService],
|
exports: [DeviceService],
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
import { DeviceTypeEnum } from '@app/common/constants/device-type.enum';
|
import { DeviceTypeEnum } from '@app/common/constants/device-type.enum';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { IsEnum, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
import {
|
||||||
|
IsEnum,
|
||||||
|
IsNotEmpty,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
IsUUID,
|
||||||
|
} from 'class-validator';
|
||||||
|
|
||||||
export class GetDeviceBySpaceUuidDto {
|
export class GetDeviceBySpaceUuidDto {
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
@ -44,3 +50,20 @@ export class GetDoorLockDevices {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
public deviceType: DeviceTypeEnum;
|
public deviceType: DeviceTypeEnum;
|
||||||
}
|
}
|
||||||
|
export class GetDevicesBySpaceOrCommunityDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Device Product Type',
|
||||||
|
example: 'PC',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public deviceType: string;
|
||||||
|
@IsUUID('4', { message: 'Invalid space UUID format' })
|
||||||
|
@IsOptional()
|
||||||
|
spaceUuid?: string;
|
||||||
|
|
||||||
|
@IsUUID('4', { message: 'Invalid community UUID format' })
|
||||||
|
@IsOptional()
|
||||||
|
communityUuid?: string;
|
||||||
|
}
|
||||||
|
@ -31,6 +31,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
GetDeviceBySpaceUuidDto,
|
GetDeviceBySpaceUuidDto,
|
||||||
GetDeviceLogsDto,
|
GetDeviceLogsDto,
|
||||||
|
GetDevicesBySpaceOrCommunityDto,
|
||||||
GetDoorLockDevices,
|
GetDoorLockDevices,
|
||||||
} from '../dtos/get.device.dto';
|
} from '../dtos/get.device.dto';
|
||||||
import {
|
import {
|
||||||
@ -65,6 +66,7 @@ import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
|||||||
import { ProjectParam } from '../dtos';
|
import { ProjectParam } from '../dtos';
|
||||||
import { BatchDeviceTypeEnum } from '@app/common/constants/batch-device.enum';
|
import { BatchDeviceTypeEnum } from '@app/common/constants/batch-device.enum';
|
||||||
import { DeviceTypeEnum } from '@app/common/constants/device-type.enum';
|
import { DeviceTypeEnum } from '@app/common/constants/device-type.enum';
|
||||||
|
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeviceService {
|
export class DeviceService {
|
||||||
@ -80,6 +82,7 @@ export class DeviceService {
|
|||||||
private readonly sceneService: SceneService,
|
private readonly sceneService: SceneService,
|
||||||
private readonly tuyaService: TuyaService,
|
private readonly tuyaService: TuyaService,
|
||||||
private readonly projectRepository: ProjectRepository,
|
private readonly projectRepository: ProjectRepository,
|
||||||
|
private readonly communityRepository: CommunityRepository,
|
||||||
) {
|
) {
|
||||||
const accessKey = this.configService.get<string>('auth-config.ACCESS_KEY');
|
const accessKey = this.configService.get<string>('auth-config.ACCESS_KEY');
|
||||||
const secretKey = this.configService.get<string>('auth-config.SECRET_KEY');
|
const secretKey = this.configService.get<string>('auth-config.SECRET_KEY');
|
||||||
@ -1722,4 +1725,142 @@ export class DeviceService {
|
|||||||
statusCode: HttpStatus.OK,
|
statusCode: HttpStatus.OK,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
async getAllDevicesBySpaceOrCommunityWithChild(
|
||||||
|
query: GetDevicesBySpaceOrCommunityDto,
|
||||||
|
): Promise<BaseResponseDto> {
|
||||||
|
try {
|
||||||
|
const { spaceUuid, communityUuid, deviceType } = query;
|
||||||
|
if (!spaceUuid && !communityUuid) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'Either spaceUuid or communityUuid must be provided',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get devices based on space or community
|
||||||
|
const devices = spaceUuid
|
||||||
|
? await this.getAllDevicesBySpace(spaceUuid)
|
||||||
|
: await this.getAllDevicesByCommunity(communityUuid);
|
||||||
|
|
||||||
|
if (!devices?.length) {
|
||||||
|
return new SuccessResponseDto({
|
||||||
|
message: `No devices found for ${spaceUuid ? 'space' : 'community'}`,
|
||||||
|
data: [],
|
||||||
|
statusCode: HttpStatus.CREATED,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const devicesFilterd = devices.filter(
|
||||||
|
(device) => device.productDevice?.prodType === deviceType,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (devicesFilterd.length === 0) {
|
||||||
|
return new SuccessResponseDto({
|
||||||
|
message: `No ${deviceType} devices found for ${spaceUuid ? 'space' : 'community'}`,
|
||||||
|
data: [],
|
||||||
|
statusCode: HttpStatus.CREATED,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SuccessResponseDto({
|
||||||
|
message: `Devices fetched successfully`,
|
||||||
|
data: devicesFilterd,
|
||||||
|
statusCode: HttpStatus.OK,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof HttpException) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'Internal server error',
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async getAllDevicesBySpace(spaceUuid: string): Promise<DeviceEntity[]> {
|
||||||
|
const space = await this.spaceRepository.findOne({
|
||||||
|
where: { uuid: spaceUuid },
|
||||||
|
relations: ['children', 'devices', 'devices.productDevice'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!space) {
|
||||||
|
throw new NotFoundException('Space not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const allDevices: DeviceEntity[] = [...space.devices];
|
||||||
|
|
||||||
|
// Recursive fetch function
|
||||||
|
const fetchChildren = async (parentSpace: SpaceEntity) => {
|
||||||
|
const children = await this.spaceRepository.find({
|
||||||
|
where: { parent: { uuid: parentSpace.uuid } },
|
||||||
|
relations: ['children', 'devices', 'devices.productDevice'],
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const child of children) {
|
||||||
|
allDevices.push(...child.devices);
|
||||||
|
|
||||||
|
if (child.children.length > 0) {
|
||||||
|
await fetchChildren(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start recursive fetch
|
||||||
|
await fetchChildren(space);
|
||||||
|
|
||||||
|
return allDevices;
|
||||||
|
}
|
||||||
|
async getAllDevicesByCommunity(
|
||||||
|
communityUuid: string,
|
||||||
|
): Promise<DeviceEntity[]> {
|
||||||
|
// Fetch the community and its top-level spaces
|
||||||
|
const community = await this.communityRepository.findOne({
|
||||||
|
where: { uuid: communityUuid },
|
||||||
|
relations: [
|
||||||
|
'spaces',
|
||||||
|
'spaces.children',
|
||||||
|
'spaces.devices',
|
||||||
|
'spaces.devices.productDevice',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!community) {
|
||||||
|
throw new NotFoundException('Community not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const allDevices: DeviceEntity[] = [];
|
||||||
|
|
||||||
|
// Recursive fetch function for spaces
|
||||||
|
const fetchSpaceDevices = async (space: SpaceEntity) => {
|
||||||
|
if (space.devices && space.devices.length > 0) {
|
||||||
|
allDevices.push(...space.devices);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (space.children && space.children.length > 0) {
|
||||||
|
for (const childSpace of space.children) {
|
||||||
|
const fullChildSpace = await this.spaceRepository.findOne({
|
||||||
|
where: { uuid: childSpace.uuid },
|
||||||
|
relations: ['children', 'devices', 'devices.productDevice'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fullChildSpace) {
|
||||||
|
await fetchSpaceDevices(fullChildSpace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start recursive fetch for all top-level spaces
|
||||||
|
for (const space of community.spaces) {
|
||||||
|
const fullSpace = await this.spaceRepository.findOne({
|
||||||
|
where: { uuid: space.uuid },
|
||||||
|
relations: ['children', 'devices', 'devices.productDevice'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fullSpace) {
|
||||||
|
await fetchSpaceDevices(fullSpace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allDevices;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ import {
|
|||||||
} from '@app/common/modules/power-clamp/repositories';
|
} from '@app/common/modules/power-clamp/repositories';
|
||||||
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
||||||
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
||||||
|
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule, DeviceRepositoryModule],
|
imports: [ConfigModule, DeviceRepositoryModule],
|
||||||
controllers: [DoorLockController],
|
controllers: [DoorLockController],
|
||||||
@ -54,6 +55,7 @@ import { OccupancyService } from '@app/common/helper/services/occupancy.service'
|
|||||||
PowerClampMonthlyRepository,
|
PowerClampMonthlyRepository,
|
||||||
SqlLoaderService,
|
SqlLoaderService,
|
||||||
OccupancyService,
|
OccupancyService,
|
||||||
|
CommunityRepository,
|
||||||
],
|
],
|
||||||
exports: [DoorLockService],
|
exports: [DoorLockService],
|
||||||
})
|
})
|
||||||
|
@ -26,6 +26,7 @@ import {
|
|||||||
} from '@app/common/modules/power-clamp/repositories';
|
} from '@app/common/modules/power-clamp/repositories';
|
||||||
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
||||||
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
||||||
|
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule, DeviceRepositoryModule],
|
imports: [ConfigModule, DeviceRepositoryModule],
|
||||||
controllers: [GroupController],
|
controllers: [GroupController],
|
||||||
@ -51,6 +52,7 @@ import { OccupancyService } from '@app/common/helper/services/occupancy.service'
|
|||||||
PowerClampMonthlyRepository,
|
PowerClampMonthlyRepository,
|
||||||
SqlLoaderService,
|
SqlLoaderService,
|
||||||
OccupancyService,
|
OccupancyService,
|
||||||
|
CommunityRepository,
|
||||||
],
|
],
|
||||||
exports: [GroupService],
|
exports: [GroupService],
|
||||||
})
|
})
|
||||||
|
@ -16,6 +16,7 @@ import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service
|
|||||||
import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories';
|
import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories';
|
||||||
import { AutomationRepository } from '@app/common/modules/automation/repositories';
|
import { AutomationRepository } from '@app/common/modules/automation/repositories';
|
||||||
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
||||||
|
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusFirebaseModule],
|
imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusFirebaseModule],
|
||||||
@ -32,6 +33,7 @@ import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
|||||||
ProjectRepository,
|
ProjectRepository,
|
||||||
SceneDeviceRepository,
|
SceneDeviceRepository,
|
||||||
AutomationRepository,
|
AutomationRepository,
|
||||||
|
CommunityRepository,
|
||||||
],
|
],
|
||||||
exports: [SceneService],
|
exports: [SceneService],
|
||||||
})
|
})
|
||||||
|
@ -30,6 +30,7 @@ import {
|
|||||||
} from '@app/common/modules/power-clamp/repositories';
|
} from '@app/common/modules/power-clamp/repositories';
|
||||||
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
||||||
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
||||||
|
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule, DeviceRepositoryModule, DoorLockModule],
|
imports: [ConfigModule, DeviceRepositoryModule, DoorLockModule],
|
||||||
controllers: [VisitorPasswordController],
|
controllers: [VisitorPasswordController],
|
||||||
@ -57,6 +58,7 @@ import { OccupancyService } from '@app/common/helper/services/occupancy.service'
|
|||||||
PowerClampMonthlyRepository,
|
PowerClampMonthlyRepository,
|
||||||
SqlLoaderService,
|
SqlLoaderService,
|
||||||
OccupancyService,
|
OccupancyService,
|
||||||
|
CommunityRepository,
|
||||||
],
|
],
|
||||||
exports: [VisitorPasswordService],
|
exports: [VisitorPasswordService],
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user