mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-11-26 08:24:55 +00:00
Merge branch 'dev' into feat/project-tag
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,6 +3,9 @@
|
|||||||
/node_modules
|
/node_modules
|
||||||
/build
|
/build
|
||||||
|
|
||||||
|
#github
|
||||||
|
/.github
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
|
|||||||
@ -558,6 +558,15 @@ export class ControllerRoute {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static DEVICE_PROJECT = class {
|
||||||
|
public static readonly ROUTE = '/projects/:projectUuid/devices';
|
||||||
|
static ACTIONS = class {
|
||||||
|
public static readonly GET_ALL_DEVICES_SUMMARY = 'Get all devices';
|
||||||
|
public static readonly GET_ALL_DEVICES_DESCRIPTION =
|
||||||
|
'This endpoint retrieves all devices in the system.';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
static DEVICE_PERMISSION = class {
|
static DEVICE_PERMISSION = class {
|
||||||
public static readonly ROUTE = 'device-permission';
|
public static readonly ROUTE = 'device-permission';
|
||||||
|
|
||||||
@ -698,6 +707,8 @@ export class ControllerRoute {
|
|||||||
};
|
};
|
||||||
static VISITOR_PASSWORD = class {
|
static VISITOR_PASSWORD = class {
|
||||||
public static readonly ROUTE = 'visitor-password';
|
public static readonly ROUTE = 'visitor-password';
|
||||||
|
public static readonly PROJECT_ROUTE =
|
||||||
|
'/projects/:projectUuid/visitor-password';
|
||||||
|
|
||||||
static ACTIONS = class {
|
static ACTIONS = class {
|
||||||
public static readonly ADD_ONLINE_TEMP_PASSWORD_MULTIPLE_TIME_SUMMARY =
|
public static readonly ADD_ONLINE_TEMP_PASSWORD_MULTIPLE_TIME_SUMMARY =
|
||||||
|
|||||||
@ -10,7 +10,13 @@ import { GetDeviceDetailsFunctionsStatusInterface } from 'src/device/interfaces/
|
|||||||
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 { firebaseDataBase } from '../../firebase.config';
|
import { firebaseDataBase } from '../../firebase.config';
|
||||||
import { Database, DataSnapshot, get, ref, set } from 'firebase/database';
|
import {
|
||||||
|
Database,
|
||||||
|
DataSnapshot,
|
||||||
|
get,
|
||||||
|
ref,
|
||||||
|
runTransaction,
|
||||||
|
} from 'firebase/database';
|
||||||
import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories';
|
import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeviceStatusFirebaseService {
|
export class DeviceStatusFirebaseService {
|
||||||
@ -154,39 +160,48 @@ export class DeviceStatusFirebaseService {
|
|||||||
this.firebaseDb,
|
this.firebaseDb,
|
||||||
`device-status/${addDeviceStatusDto.deviceUuid}`,
|
`device-status/${addDeviceStatusDto.deviceUuid}`,
|
||||||
);
|
);
|
||||||
const snapshot: DataSnapshot = await get(dataRef);
|
|
||||||
const existingData = snapshot.val() || {};
|
|
||||||
|
|
||||||
// Assign default values if fields are not present
|
// Use a transaction to handle concurrent updates
|
||||||
if (!existingData.deviceTuyaUuid) {
|
await runTransaction(dataRef, (existingData) => {
|
||||||
existingData.deviceTuyaUuid = addDeviceStatusDto.deviceTuyaUuid;
|
if (!existingData) {
|
||||||
}
|
existingData = {};
|
||||||
if (!existingData.productUuid) {
|
}
|
||||||
existingData.productUuid = addDeviceStatusDto.productUuid;
|
|
||||||
}
|
|
||||||
if (!existingData.productType) {
|
|
||||||
existingData.productType = addDeviceStatusDto.productType;
|
|
||||||
}
|
|
||||||
if (!existingData.status) {
|
|
||||||
existingData.status = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a map to track existing status codes
|
// Assign default values if fields are not present
|
||||||
const statusMap = new Map(
|
if (!existingData.deviceTuyaUuid) {
|
||||||
existingData.status.map((item) => [item.code, item.value]),
|
existingData.deviceTuyaUuid = addDeviceStatusDto.deviceTuyaUuid;
|
||||||
);
|
}
|
||||||
|
if (!existingData.productUuid) {
|
||||||
|
existingData.productUuid = addDeviceStatusDto.productUuid;
|
||||||
|
}
|
||||||
|
if (!existingData.productType) {
|
||||||
|
existingData.productType = addDeviceStatusDto.productType;
|
||||||
|
}
|
||||||
|
if (!existingData.status) {
|
||||||
|
existingData.status = [];
|
||||||
|
}
|
||||||
|
|
||||||
// Update or add status codes
|
// Create a map to track existing status codes
|
||||||
|
const statusMap = new Map(
|
||||||
|
existingData.status.map((item) => [item.code, item.value]),
|
||||||
|
);
|
||||||
|
|
||||||
for (const statusItem of addDeviceStatusDto.status) {
|
// Update or add status codes
|
||||||
statusMap.set(statusItem.code, statusItem.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the map back to an array format
|
for (const statusItem of addDeviceStatusDto.status) {
|
||||||
existingData.status = Array.from(statusMap, ([code, value]) => ({
|
statusMap.set(statusItem.code, statusItem.value);
|
||||||
code,
|
}
|
||||||
value,
|
|
||||||
}));
|
// Convert the map back to an array format
|
||||||
|
existingData.status = Array.from(statusMap, ([code, value]) => ({
|
||||||
|
code,
|
||||||
|
value,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return existingData;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save logs to your repository
|
||||||
const newLogs = addDeviceStatusDto.log.properties.map((property) => {
|
const newLogs = addDeviceStatusDto.log.properties.map((property) => {
|
||||||
return this.deviceStatusLogRepository.create({
|
return this.deviceStatusLogRepository.create({
|
||||||
deviceId: addDeviceStatusDto.deviceUuid,
|
deviceId: addDeviceStatusDto.deviceUuid,
|
||||||
@ -200,10 +215,9 @@ export class DeviceStatusFirebaseService {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
await this.deviceStatusLogRepository.save(newLogs);
|
await this.deviceStatusLogRepository.save(newLogs);
|
||||||
// Save the updated data to Firebase
|
|
||||||
await set(dataRef, existingData);
|
|
||||||
|
|
||||||
// Return the updated data
|
// Return the updated data
|
||||||
return existingData;
|
const snapshot: DataSnapshot = await get(dataRef);
|
||||||
|
return snapshot.val();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import {
|
|||||||
} from '@app/common/modules/scene/repositories';
|
} from '@app/common/modules/scene/repositories';
|
||||||
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/project.repository';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusFirebaseModule],
|
imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusFirebaseModule],
|
||||||
@ -32,6 +33,7 @@ import { AutomationRepository } from '@app/common/modules/automation/repositorie
|
|||||||
SceneRepository,
|
SceneRepository,
|
||||||
SceneDeviceRepository,
|
SceneDeviceRepository,
|
||||||
AutomationRepository,
|
AutomationRepository,
|
||||||
|
ProjectRepository,
|
||||||
],
|
],
|
||||||
exports: [AutomationService],
|
exports: [AutomationService],
|
||||||
})
|
})
|
||||||
|
|||||||
29
src/device/controllers/device-project.controller.ts
Normal file
29
src/device/controllers/device-project.controller.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { DeviceService } from '../services/device.service';
|
||||||
|
import { Controller, Get, Param, 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';
|
||||||
|
|
||||||
|
@ApiTags('Device Module')
|
||||||
|
@Controller({
|
||||||
|
version: EnableDisableStatusEnum.ENABLED,
|
||||||
|
path: ControllerRoute.DEVICE_PROJECT.ROUTE,
|
||||||
|
})
|
||||||
|
export class DeviceProjectController {
|
||||||
|
constructor(private readonly deviceService: DeviceService) {}
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(PermissionsGuard)
|
||||||
|
@Permissions('DEVICE_VIEW')
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -224,17 +224,6 @@ export class DeviceController {
|
|||||||
async getDevicesInGateway(@Param('gatewayUuid') gatewayUuid: string) {
|
async getDevicesInGateway(@Param('gatewayUuid') gatewayUuid: string) {
|
||||||
return await this.deviceService.getDevicesInGateway(gatewayUuid);
|
return await this.deviceService.getDevicesInGateway(gatewayUuid);
|
||||||
}
|
}
|
||||||
@ApiBearerAuth()
|
|
||||||
@UseGuards(PermissionsGuard)
|
|
||||||
@Permissions('DEVICE_VIEW')
|
|
||||||
@Get()
|
|
||||||
@ApiOperation({
|
|
||||||
summary: ControllerRoute.DEVICE.ACTIONS.GET_ALL_DEVICES_SUMMARY,
|
|
||||||
description: ControllerRoute.DEVICE.ACTIONS.GET_ALL_DEVICES_DESCRIPTION,
|
|
||||||
})
|
|
||||||
async getAllDevices() {
|
|
||||||
return await this.deviceService.getAllDevices();
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@UseGuards(PermissionsGuard)
|
@UseGuards(PermissionsGuard)
|
||||||
|
|||||||
@ -20,6 +20,8 @@ import {
|
|||||||
} from '@app/common/modules/scene/repositories';
|
} from '@app/common/modules/scene/repositories';
|
||||||
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 { DeviceProjectController } from './controllers/device-project.controller';
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule,
|
ConfigModule,
|
||||||
@ -28,13 +30,14 @@ import { AutomationRepository } from '@app/common/modules/automation/repositorie
|
|||||||
DeviceRepositoryModule,
|
DeviceRepositoryModule,
|
||||||
DeviceStatusFirebaseModule,
|
DeviceStatusFirebaseModule,
|
||||||
],
|
],
|
||||||
controllers: [DeviceController],
|
controllers: [DeviceController, DeviceProjectController],
|
||||||
providers: [
|
providers: [
|
||||||
DeviceService,
|
DeviceService,
|
||||||
ProductRepository,
|
ProductRepository,
|
||||||
DeviceUserPermissionRepository,
|
DeviceUserPermissionRepository,
|
||||||
PermissionTypeRepository,
|
PermissionTypeRepository,
|
||||||
SpaceRepository,
|
SpaceRepository,
|
||||||
|
ProjectRepository,
|
||||||
DeviceRepository,
|
DeviceRepository,
|
||||||
UserRepository,
|
UserRepository,
|
||||||
TuyaService,
|
TuyaService,
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
export * from './add.device.dto';
|
export * from './add.device.dto';
|
||||||
export * from './control.device.dto';
|
export * from './control.device.dto';
|
||||||
export * from './get.device.dto';
|
export * from './get.device.dto';
|
||||||
|
export * from './project.param.dto';
|
||||||
|
|||||||
11
src/device/dtos/project.param.dto.ts
Normal file
11
src/device/dtos/project.param.dto.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsUUID } from 'class-validator';
|
||||||
|
|
||||||
|
export class ProjectParam {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'UUID of the project this community belongs to',
|
||||||
|
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
|
||||||
|
})
|
||||||
|
@IsUUID()
|
||||||
|
projectUuid: string;
|
||||||
|
}
|
||||||
@ -60,6 +60,8 @@ import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
|||||||
import { DeleteSceneFromSceneDeviceDto } from '../dtos/delete.device.dto';
|
import { DeleteSceneFromSceneDeviceDto } from '../dtos/delete.device.dto';
|
||||||
import { DeviceEntity } from '@app/common/modules/device/entities';
|
import { DeviceEntity } from '@app/common/modules/device/entities';
|
||||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||||
|
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
||||||
|
import { ProjectParam } from '../dtos';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeviceService {
|
export class DeviceService {
|
||||||
@ -74,6 +76,7 @@ export class DeviceService {
|
|||||||
@Inject(forwardRef(() => SceneService))
|
@Inject(forwardRef(() => SceneService))
|
||||||
private readonly sceneService: SceneService,
|
private readonly sceneService: SceneService,
|
||||||
private readonly tuyaService: TuyaService,
|
private readonly tuyaService: TuyaService,
|
||||||
|
private readonly projectRepository: ProjectRepository,
|
||||||
) {
|
) {
|
||||||
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');
|
||||||
@ -950,10 +953,19 @@ export class DeviceService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async getAllDevices(): Promise<GetDeviceDetailsInterface[]> {
|
async getAllDevices(
|
||||||
|
param: ProjectParam,
|
||||||
|
): Promise<GetDeviceDetailsInterface[]> {
|
||||||
try {
|
try {
|
||||||
|
await this.validateProject(param.projectUuid);
|
||||||
|
|
||||||
const devices = await this.deviceRepository.find({
|
const devices = await this.deviceRepository.find({
|
||||||
where: { isActive: true },
|
where: {
|
||||||
|
isActive: true,
|
||||||
|
spaceDevice: {
|
||||||
|
community: { project: { uuid: param.projectUuid } },
|
||||||
|
},
|
||||||
|
},
|
||||||
relations: [
|
relations: [
|
||||||
'spaceDevice.parent',
|
'spaceDevice.parent',
|
||||||
'spaceDevice.community',
|
'spaceDevice.community',
|
||||||
@ -1009,10 +1021,13 @@ export class DeviceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const spaceHierarchy = await this.getFullSpaceHierarchy(
|
const spaceHierarchy = await this.getParentHierarchy(
|
||||||
device?.spaceDevice,
|
device?.spaceDevice,
|
||||||
);
|
);
|
||||||
const orderedHierarchy = spaceHierarchy.reverse();
|
const orderedHierarchy = [
|
||||||
|
device?.spaceDevice,
|
||||||
|
...spaceHierarchy.reverse(),
|
||||||
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
spaces: orderedHierarchy.map((space) => ({
|
spaces: orderedHierarchy.map((space) => ({
|
||||||
@ -1490,4 +1505,45 @@ export class DeviceService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async validateProject(uuid: string) {
|
||||||
|
const project = await this.projectRepository.findOne({
|
||||||
|
where: { uuid },
|
||||||
|
});
|
||||||
|
if (!project) {
|
||||||
|
throw new HttpException(
|
||||||
|
`A project with the uuid '${uuid}' doesn't exists.`,
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return project;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getParentHierarchy(
|
||||||
|
space: SpaceEntity,
|
||||||
|
): Promise<{ uuid: string; spaceName: string }[]> {
|
||||||
|
try {
|
||||||
|
const targetSpace = await this.spaceRepository.findOne({
|
||||||
|
where: { uuid: space.uuid },
|
||||||
|
relations: ['parent'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!targetSpace) {
|
||||||
|
throw new HttpException('Space not found', HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ancestors = await this.fetchAncestors(targetSpace);
|
||||||
|
|
||||||
|
return ancestors.map((parentSpace) => ({
|
||||||
|
uuid: parentSpace.uuid,
|
||||||
|
spaceName: parentSpace.spaceName,
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching parent hierarchy:', error.message);
|
||||||
|
throw new HttpException(
|
||||||
|
'Error fetching parent hierarchy',
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {
|
|||||||
} from '@app/common/modules/scene/repositories';
|
} from '@app/common/modules/scene/repositories';
|
||||||
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';
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule, DeviceRepositoryModule],
|
imports: [ConfigModule, DeviceRepositoryModule],
|
||||||
controllers: [DoorLockController],
|
controllers: [DoorLockController],
|
||||||
@ -32,6 +33,7 @@ import { AutomationRepository } from '@app/common/modules/automation/repositorie
|
|||||||
DeviceStatusFirebaseService,
|
DeviceStatusFirebaseService,
|
||||||
SpaceRepository,
|
SpaceRepository,
|
||||||
DeviceStatusLogRepository,
|
DeviceStatusLogRepository,
|
||||||
|
ProjectRepository,
|
||||||
TuyaService,
|
TuyaService,
|
||||||
SceneService,
|
SceneService,
|
||||||
SceneIconRepository,
|
SceneIconRepository,
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import {
|
|||||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
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';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusFirebaseModule],
|
imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusFirebaseModule],
|
||||||
@ -28,6 +29,7 @@ import { AutomationRepository } from '@app/common/modules/automation/repositorie
|
|||||||
ProductRepository,
|
ProductRepository,
|
||||||
SceneIconRepository,
|
SceneIconRepository,
|
||||||
SceneRepository,
|
SceneRepository,
|
||||||
|
ProjectRepository,
|
||||||
SceneDeviceRepository,
|
SceneDeviceRepository,
|
||||||
AutomationRepository,
|
AutomationRepository,
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import { SpaceModelEntity } from '@app/common/modules/space-model';
|
import { SpaceModelEntity } from '@app/common/modules/space-model';
|
||||||
|
import { QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
export class PropogateDeleteSpaceModelCommand {
|
export class PropogateDeleteSpaceModelCommand {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly param: {
|
public readonly param: {
|
||||||
spaceModel: SpaceModelEntity;
|
spaceModel: SpaceModelEntity;
|
||||||
|
queryRunner: QueryRunner;
|
||||||
},
|
},
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,8 +19,7 @@ export class PropogateDeleteSpaceModelHandler
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(command: PropogateDeleteSpaceModelCommand): Promise<void> {
|
async execute(command: PropogateDeleteSpaceModelCommand): Promise<void> {
|
||||||
const { spaceModel } = command.param;
|
const { spaceModel, queryRunner } = command.param;
|
||||||
const queryRunner = this.dataSource.createQueryRunner();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await queryRunner.connect();
|
await queryRunner.connect();
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
|||||||
import { CommandBus } from '@nestjs/cqrs';
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
import { ProcessTagDto } from 'src/tags/dtos';
|
import { ProcessTagDto } from 'src/tags/dtos';
|
||||||
import { SpaceModelProductAllocationService } from './space-model-product-allocation.service';
|
import { SpaceModelProductAllocationService } from './space-model-product-allocation.service';
|
||||||
|
import { PropogateDeleteSpaceModelCommand } from '../commands';
|
||||||
import {
|
import {
|
||||||
SpaceProductAllocationRepository,
|
SpaceProductAllocationRepository,
|
||||||
SpaceRepository,
|
SpaceRepository,
|
||||||
@ -283,6 +284,13 @@ export class SpaceModelService {
|
|||||||
{ disabled: true },
|
{ disabled: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await this.commandBus.execute(
|
||||||
|
new PropogateDeleteSpaceModelCommand({
|
||||||
|
spaceModel: spaceModel,
|
||||||
|
queryRunner,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
await queryRunner.commitTransaction();
|
await queryRunner.commitTransaction();
|
||||||
|
|
||||||
return new SuccessResponseDto({
|
return new SuccessResponseDto({
|
||||||
|
|||||||
@ -32,10 +32,10 @@ export class SpaceDeviceService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch space hierarchy **once** and reverse it to get an ordered hierarchy
|
|
||||||
const spaceHierarchy =
|
const spaceHierarchy =
|
||||||
await this.validationService.getFullSpaceHierarchy(space);
|
await this.validationService.getParentHierarchy(space);
|
||||||
const orderedHierarchy = spaceHierarchy.reverse();
|
|
||||||
|
const orderedHierarchy = [space, ...spaceHierarchy.reverse()];
|
||||||
|
|
||||||
// Fetch Tuya details for each device in parallel using Promise.allSettled
|
// Fetch Tuya details for each device in parallel using Promise.allSettled
|
||||||
const deviceDetailsPromises = space.devices.map((device) =>
|
const deviceDetailsPromises = space.devices.map((device) =>
|
||||||
|
|||||||
@ -222,4 +222,32 @@ export class ValidationService {
|
|||||||
|
|
||||||
return descendants;
|
return descendants;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getParentHierarchy(
|
||||||
|
space: SpaceEntity,
|
||||||
|
): Promise<{ uuid: string; spaceName: string }[]> {
|
||||||
|
try {
|
||||||
|
const targetSpace = await this.spaceRepository.findOne({
|
||||||
|
where: { uuid: space.uuid },
|
||||||
|
relations: ['parent'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!targetSpace) {
|
||||||
|
throw new HttpException('Space not found', HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ancestors = await this.fetchAncestors(targetSpace);
|
||||||
|
|
||||||
|
return ancestors.map((parentSpace) => ({
|
||||||
|
uuid: parentSpace.uuid,
|
||||||
|
spaceName: parentSpace.spaceName,
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching parent hierarchy:', error.message);
|
||||||
|
throw new HttpException(
|
||||||
|
'Error fetching parent hierarchy',
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,7 +84,7 @@ export class SubspaceDeviceService {
|
|||||||
if (device.tag?.subspace?.uuid !== subspace.uuid) {
|
if (device.tag?.subspace?.uuid !== subspace.uuid) {
|
||||||
await this.tagRepository.update(
|
await this.tagRepository.update(
|
||||||
{ uuid: device.tag.uuid },
|
{ uuid: device.tag.uuid },
|
||||||
{ subspace },
|
{ subspace, space: null },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,7 @@ export class UserService {
|
|||||||
where: {
|
where: {
|
||||||
uuid: userUuid,
|
uuid: userUuid,
|
||||||
},
|
},
|
||||||
relations: ['region', 'timezone', 'roleType'],
|
relations: ['region', 'timezone', 'roleType', 'project'],
|
||||||
});
|
});
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new BadRequestException('Invalid room UUID');
|
throw new BadRequestException('Invalid room UUID');
|
||||||
@ -52,6 +52,7 @@ export class UserService {
|
|||||||
hasAcceptedAppAgreement: user?.hasAcceptedAppAgreement,
|
hasAcceptedAppAgreement: user?.hasAcceptedAppAgreement,
|
||||||
appAgreementAcceptedAt: user?.appAgreementAcceptedAt,
|
appAgreementAcceptedAt: user?.appAgreementAcceptedAt,
|
||||||
role: user?.roleType,
|
role: user?.roleType,
|
||||||
|
project: user?.project,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof BadRequestException) {
|
if (err instanceof BadRequestException) {
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
export * from './visitor-password.controller';
|
export * from './visitor-password.controller';
|
||||||
|
export * from './project-visitor-password.controller';
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
import { VisitorPasswordService } from '../services/visitor-password.service';
|
||||||
|
import { Controller, UseGuards, Get, Param } from '@nestjs/common';
|
||||||
|
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
||||||
|
import { Permissions } from 'src/decorators/permissions.decorator';
|
||||||
|
|
||||||
|
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
|
||||||
|
import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||||
|
import { ProjectParam } from 'src/community/dtos';
|
||||||
|
import { PermissionsGuard } from 'src/guards/permissions.guard';
|
||||||
|
|
||||||
|
@ApiTags('Visitor Password Module')
|
||||||
|
@Controller({
|
||||||
|
version: EnableDisableStatusEnum.ENABLED,
|
||||||
|
path: ControllerRoute.VISITOR_PASSWORD.PROJECT_ROUTE,
|
||||||
|
})
|
||||||
|
export class VisitorPasswordProjectController {
|
||||||
|
constructor(
|
||||||
|
private readonly visitorPasswordService: VisitorPasswordService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(PermissionsGuard)
|
||||||
|
@Permissions('VISITOR_PASSWORD_ADD')
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({
|
||||||
|
summary:
|
||||||
|
ControllerRoute.VISITOR_PASSWORD.ACTIONS.GET_VISITOR_PASSWORD_SUMMARY,
|
||||||
|
description:
|
||||||
|
ControllerRoute.VISITOR_PASSWORD.ACTIONS.GET_VISITOR_PASSWORD_DESCRIPTION,
|
||||||
|
})
|
||||||
|
async GetVisitorPassword(@Param() param: ProjectParam) {
|
||||||
|
return await this.visitorPasswordService.getPasswords(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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(@Param() param: ProjectParam) {
|
||||||
|
return await this.visitorPasswordService.getAllPassDevices(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,7 +5,6 @@ import {
|
|||||||
Post,
|
Post,
|
||||||
HttpStatus,
|
HttpStatus,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
Get,
|
|
||||||
Req,
|
Req,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
||||||
@ -146,32 +145,4 @@ export class VisitorPasswordController {
|
|||||||
data: temporaryPassword,
|
data: temporaryPassword,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiBearerAuth()
|
|
||||||
@UseGuards(PermissionsGuard)
|
|
||||||
@Permissions('VISITOR_PASSWORD_VIEW')
|
|
||||||
@Get()
|
|
||||||
@ApiOperation({
|
|
||||||
summary:
|
|
||||||
ControllerRoute.VISITOR_PASSWORD.ACTIONS.GET_VISITOR_PASSWORD_SUMMARY,
|
|
||||||
description:
|
|
||||||
ControllerRoute.VISITOR_PASSWORD.ACTIONS.GET_VISITOR_PASSWORD_DESCRIPTION,
|
|
||||||
})
|
|
||||||
async GetVisitorPassword() {
|
|
||||||
return await this.visitorPasswordService.getPasswords();
|
|
||||||
}
|
|
||||||
|
|
||||||
@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() {
|
|
||||||
return await this.visitorPasswordService.getAllPassDevices();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
export * from './temp-pass.dto';
|
export * from './temp-pass.dto';
|
||||||
|
export * from './project.param.dto';
|
||||||
11
src/vistor-password/dtos/project.param.dto.ts
Normal file
11
src/vistor-password/dtos/project.param.dto.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsUUID } from 'class-validator';
|
||||||
|
|
||||||
|
export class ProjectParam {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'UUID of the project this community belongs to',
|
||||||
|
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
|
||||||
|
})
|
||||||
|
@IsUUID()
|
||||||
|
projectUuid: string;
|
||||||
|
}
|
||||||
@ -14,6 +14,7 @@ import {
|
|||||||
AddDoorLockOfflineOneTimeDto,
|
AddDoorLockOfflineOneTimeDto,
|
||||||
AddDoorLockOnlineMultipleDto,
|
AddDoorLockOnlineMultipleDto,
|
||||||
AddDoorLockOnlineOneTimeDto,
|
AddDoorLockOnlineOneTimeDto,
|
||||||
|
ProjectParam,
|
||||||
} from '../dtos';
|
} from '../dtos';
|
||||||
import { EmailService } from '@app/common/util/email.service';
|
import { EmailService } from '@app/common/util/email.service';
|
||||||
import { PasswordEncryptionService } from 'src/door-lock/services/encryption.services';
|
import { PasswordEncryptionService } from 'src/door-lock/services/encryption.services';
|
||||||
@ -30,6 +31,7 @@ import {
|
|||||||
CommonHourMinutes,
|
CommonHourMinutes,
|
||||||
CommonHours,
|
CommonHours,
|
||||||
} from '@app/common/constants/hours-minutes.enum';
|
} from '@app/common/constants/hours-minutes.enum';
|
||||||
|
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class VisitorPasswordService {
|
export class VisitorPasswordService {
|
||||||
@ -42,6 +44,7 @@ export class VisitorPasswordService {
|
|||||||
private readonly doorLockService: DoorLockService,
|
private readonly doorLockService: DoorLockService,
|
||||||
private readonly deviceService: DeviceService,
|
private readonly deviceService: DeviceService,
|
||||||
private readonly passwordEncryptionService: PasswordEncryptionService,
|
private readonly passwordEncryptionService: PasswordEncryptionService,
|
||||||
|
private readonly projectRepository: ProjectRepository,
|
||||||
) {
|
) {
|
||||||
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');
|
||||||
@ -444,12 +447,21 @@ export class VisitorPasswordService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async getPasswords() {
|
async getPasswords(param: ProjectParam) {
|
||||||
|
await this.validateProject(param.projectUuid);
|
||||||
|
|
||||||
const deviceIds = await this.deviceRepository.find({
|
const deviceIds = await this.deviceRepository.find({
|
||||||
where: {
|
where: {
|
||||||
productDevice: {
|
productDevice: {
|
||||||
prodType: ProductType.DL,
|
prodType: ProductType.DL,
|
||||||
},
|
},
|
||||||
|
spaceDevice: {
|
||||||
|
community: {
|
||||||
|
project: {
|
||||||
|
uuid: param.projectUuid,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
isActive: true,
|
isActive: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -487,15 +499,25 @@ export class VisitorPasswordService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllPassDevices() {
|
async getAllPassDevices(param: ProjectParam) {
|
||||||
|
await this.validateProject(param.projectUuid);
|
||||||
|
|
||||||
const devices = await this.deviceRepository.find({
|
const devices = await this.deviceRepository.find({
|
||||||
where: {
|
where: {
|
||||||
productDevice: {
|
productDevice: {
|
||||||
prodType: ProductType.DL,
|
prodType: ProductType.DL,
|
||||||
},
|
},
|
||||||
|
spaceDevice: {
|
||||||
|
community: {
|
||||||
|
project: {
|
||||||
|
uuid: param.projectUuid,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
isActive: true,
|
isActive: true,
|
||||||
},
|
},
|
||||||
relations: ['productDevice'],
|
|
||||||
|
relations: ['productDevice', 'spaceDevice'],
|
||||||
});
|
});
|
||||||
const devicesData = await Promise.all(
|
const devicesData = await Promise.all(
|
||||||
devices?.map(async (device) => {
|
devices?.map(async (device) => {
|
||||||
@ -509,6 +531,7 @@ export class VisitorPasswordService {
|
|||||||
productType: device.productDevice.prodType,
|
productType: device.productDevice.prodType,
|
||||||
...deviceDetails,
|
...deviceDetails,
|
||||||
uuid: device.uuid,
|
uuid: device.uuid,
|
||||||
|
spaceName: device.spaceDevice.spaceName,
|
||||||
} as GetDeviceDetailsInterface;
|
} as GetDeviceDetailsInterface;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
@ -907,4 +930,17 @@ export class VisitorPasswordService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async validateProject(uuid: string) {
|
||||||
|
const project = await this.projectRepository.findOne({
|
||||||
|
where: { uuid },
|
||||||
|
});
|
||||||
|
if (!project) {
|
||||||
|
throw new HttpException(
|
||||||
|
`A project with the uuid '${uuid}' doesn't exists.`,
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return project;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,9 +21,11 @@ import {
|
|||||||
} from '@app/common/modules/scene/repositories';
|
} from '@app/common/modules/scene/repositories';
|
||||||
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 { VisitorPasswordProjectController } from './controllers';
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule, DeviceRepositoryModule, DoorLockModule],
|
imports: [ConfigModule, DeviceRepositoryModule, DoorLockModule],
|
||||||
controllers: [VisitorPasswordController],
|
controllers: [VisitorPasswordController, VisitorPasswordProjectController],
|
||||||
providers: [
|
providers: [
|
||||||
VisitorPasswordService,
|
VisitorPasswordService,
|
||||||
EmailService,
|
EmailService,
|
||||||
@ -41,6 +43,7 @@ import { AutomationRepository } from '@app/common/modules/automation/repositorie
|
|||||||
SceneRepository,
|
SceneRepository,
|
||||||
SceneDeviceRepository,
|
SceneDeviceRepository,
|
||||||
AutomationRepository,
|
AutomationRepository,
|
||||||
|
ProjectRepository,
|
||||||
],
|
],
|
||||||
exports: [VisitorPasswordService],
|
exports: [VisitorPasswordService],
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user