mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-11-26 19:04:54 +00:00
Merge pull request #135 from SyncrowIOT/feature/space-management
Feature/space-management
This commit is contained in:
@ -3,14 +3,10 @@ import { ConfigModule } from '@nestjs/config';
|
||||
import config from './config';
|
||||
import { AuthenticationModule } from './auth/auth.module';
|
||||
import { UserModule } from './users/user.module';
|
||||
import { RoomModule } from './room/room.module';
|
||||
import { GroupModule } from './group/group.module';
|
||||
import { DeviceModule } from './device/device.module';
|
||||
import { UserDevicePermissionModule } from './user-device-permission/user-device-permission.module';
|
||||
import { CommunityModule } from './community/community.module';
|
||||
import { BuildingModule } from './building/building.module';
|
||||
import { FloorModule } from './floor/floor.module';
|
||||
import { UnitModule } from './unit/unit.module';
|
||||
import { RoleModule } from './role/role.module';
|
||||
import { SeederModule } from '@app/common/seed/seeder.module';
|
||||
import { UserNotificationModule } from './user-notification/user-notification.module';
|
||||
@ -24,6 +20,7 @@ import { RegionModule } from './region/region.module';
|
||||
import { TimeZoneModule } from './timezone/timezone.module';
|
||||
import { VisitorPasswordModule } from './vistor-password/visitor-password.module';
|
||||
import { ScheduleModule } from './schedule/schedule.module';
|
||||
import { SpaceModule } from './space/space.module';
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
@ -33,11 +30,9 @@ import { ScheduleModule } from './schedule/schedule.module';
|
||||
UserModule,
|
||||
RoleModule,
|
||||
CommunityModule,
|
||||
BuildingModule,
|
||||
FloorModule,
|
||||
UnitModule,
|
||||
RoomModule,
|
||||
RoomModule,
|
||||
|
||||
SpaceModule,
|
||||
|
||||
GroupModule,
|
||||
DeviceModule,
|
||||
DeviceMessagesSubscriptionModule,
|
||||
|
||||
@ -8,12 +8,14 @@ import { DeviceService } from 'src/device/services';
|
||||
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
||||
import { ProductRepository } from '@app/common/modules/product/repositories';
|
||||
import { DeviceStatusFirebaseModule } from '@app/common/firebase/devices-status/devices-status.module';
|
||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusFirebaseModule],
|
||||
controllers: [AutomationController],
|
||||
providers: [
|
||||
AutomationService,
|
||||
TuyaService,
|
||||
SpaceRepository,
|
||||
DeviceService,
|
||||
DeviceRepository,
|
||||
|
||||
@ -18,6 +18,11 @@ import {
|
||||
} from '../dtos/automation.dto';
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
|
||||
import {
|
||||
AutomationParamDto,
|
||||
DeleteAutomationParamDto,
|
||||
SpaceParamDto,
|
||||
} from '../dtos';
|
||||
|
||||
@ApiTags('Automation Module')
|
||||
@Controller({
|
||||
@ -42,28 +47,29 @@ export class AutomationController {
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get(':unitUuid')
|
||||
async getAutomationByUnit(@Param('unitUuid') unitUuid: string) {
|
||||
const automation =
|
||||
await this.automationService.getAutomationByUnit(unitUuid);
|
||||
@Get(':spaceUuid')
|
||||
async getAutomationBySpace(@Param() param: SpaceParamDto) {
|
||||
const automation = await this.automationService.getAutomationBySpace(
|
||||
param.spaceUuid,
|
||||
);
|
||||
return automation;
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('details/:automationId')
|
||||
async getAutomationDetails(@Param('automationId') automationId: string) {
|
||||
const automation =
|
||||
await this.automationService.getAutomationDetails(automationId);
|
||||
@Get('details/:automationUuid')
|
||||
async getAutomationDetails(@Param() param: AutomationParamDto) {
|
||||
const automation = await this.automationService.getAutomationDetails(
|
||||
param.automationUuid,
|
||||
);
|
||||
return automation;
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Delete(':unitUuid/:automationId')
|
||||
async deleteAutomation(
|
||||
@Param('unitUuid') unitUuid: string,
|
||||
@Param('automationId') automationId: string,
|
||||
) {
|
||||
await this.automationService.deleteAutomation(unitUuid, automationId);
|
||||
async deleteAutomation(@Param() param: DeleteAutomationParamDto) {
|
||||
await this.automationService.deleteAutomation(param);
|
||||
return {
|
||||
statusCode: HttpStatus.OK,
|
||||
message: 'Automation Deleted Successfully',
|
||||
@ -74,11 +80,11 @@ export class AutomationController {
|
||||
@Put(':automationId')
|
||||
async updateAutomation(
|
||||
@Body() updateAutomationDto: UpdateAutomationDto,
|
||||
@Param('automationId') automationId: string,
|
||||
@Param() param: AutomationParamDto,
|
||||
) {
|
||||
const automation = await this.automationService.updateAutomation(
|
||||
updateAutomationDto,
|
||||
automationId,
|
||||
param.automationUuid,
|
||||
);
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
@ -87,16 +93,17 @@ export class AutomationController {
|
||||
data: automation,
|
||||
};
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Put('status/:automationId')
|
||||
async updateAutomationStatus(
|
||||
@Body() updateAutomationStatusDto: UpdateAutomationStatusDto,
|
||||
@Param('automationId') automationId: string,
|
||||
@Param() param: AutomationParamDto,
|
||||
) {
|
||||
await this.automationService.updateAutomationStatus(
|
||||
updateAutomationStatusDto,
|
||||
automationId,
|
||||
param.automationUuid,
|
||||
);
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
|
||||
@ -114,10 +114,10 @@ class Action {
|
||||
}
|
||||
|
||||
export class AddAutomationDto {
|
||||
@ApiProperty({ description: 'Unit ID', required: true })
|
||||
@ApiProperty({ description: 'Space ID', required: true })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public unitUuid: string;
|
||||
public spaceUuid: string;
|
||||
|
||||
@ApiProperty({ description: 'Automation name', required: true })
|
||||
@IsString()
|
||||
@ -197,10 +197,10 @@ export class UpdateAutomationDto {
|
||||
}
|
||||
}
|
||||
export class UpdateAutomationStatusDto {
|
||||
@ApiProperty({ description: 'Unit uuid', required: true })
|
||||
@ApiProperty({ description: 'Space uuid', required: true })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public unitUuid: string;
|
||||
public spaceUuid: string;
|
||||
|
||||
@ApiProperty({ description: 'Is enable', required: true })
|
||||
@IsBoolean()
|
||||
|
||||
11
src/automation/dtos/automation.param.dto.ts
Normal file
11
src/automation/dtos/automation.param.dto.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsUUID } from 'class-validator';
|
||||
|
||||
export class AutomationParamDto {
|
||||
@ApiProperty({
|
||||
description: 'UUID of the automation',
|
||||
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
|
||||
})
|
||||
@IsUUID()
|
||||
automationUuid: string;
|
||||
}
|
||||
18
src/automation/dtos/delete.automation.param.dto.ts
Normal file
18
src/automation/dtos/delete.automation.param.dto.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsUUID } from 'class-validator';
|
||||
|
||||
export class DeleteAutomationParamDto {
|
||||
@ApiProperty({
|
||||
description: 'UUID of the Space',
|
||||
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
|
||||
})
|
||||
@IsUUID()
|
||||
spaceUuid: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'UUID of the Automation',
|
||||
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
|
||||
})
|
||||
@IsUUID()
|
||||
automationUuid: string;
|
||||
}
|
||||
@ -1 +1,4 @@
|
||||
export * from './automation.dto';
|
||||
export * from './space.param.dto';
|
||||
export * from './automation.param.dto';
|
||||
export * from './delete.automation.param.dto';
|
||||
|
||||
11
src/automation/dtos/space.param.dto.ts
Normal file
11
src/automation/dtos/space.param.dto.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsUUID } from 'class-validator';
|
||||
|
||||
export class SpaceParamDto {
|
||||
@ApiProperty({
|
||||
description: 'UUID of the space',
|
||||
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
|
||||
})
|
||||
@IsUUID()
|
||||
spaceUuid: string;
|
||||
}
|
||||
@ -5,7 +5,7 @@ export interface AddAutomationInterface {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
export interface GetAutomationByUnitInterface {
|
||||
export interface GetAutomationBySpaceInterface {
|
||||
success: boolean;
|
||||
msg?: string;
|
||||
result: {
|
||||
|
||||
@ -7,27 +7,29 @@ import {
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import {
|
||||
AddAutomationDto,
|
||||
DeleteAutomationParamDto,
|
||||
UpdateAutomationDto,
|
||||
UpdateAutomationStatusDto,
|
||||
} from '../dtos';
|
||||
import { GetUnitByUuidInterface } from 'src/unit/interface/unit.interface';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { TuyaContext } from '@tuya/tuya-connector-nodejs';
|
||||
import { convertKeysToSnakeCase } from '@app/common/helper/snakeCaseConverter';
|
||||
import { DeviceService } from 'src/device/services';
|
||||
import {
|
||||
Action,
|
||||
AddAutomationInterface,
|
||||
AutomationDetailsResult,
|
||||
AutomationResponseData,
|
||||
Condition,
|
||||
DeleteAutomationInterface,
|
||||
GetAutomationByUnitInterface,
|
||||
GetAutomationBySpaceInterface,
|
||||
} from '../interface/automation.interface';
|
||||
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
import {
|
||||
ActionExecutorEnum,
|
||||
EntityTypeEnum,
|
||||
} from '@app/common/constants/automation.enum';
|
||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||
|
||||
@Injectable()
|
||||
export class AutomationService {
|
||||
@ -36,6 +38,7 @@ export class AutomationService {
|
||||
private readonly configService: ConfigService,
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
private readonly deviceService: DeviceService,
|
||||
private readonly tuyaService: TuyaService,
|
||||
) {
|
||||
const accessKey = this.configService.get<string>('auth-config.ACCESS_KEY');
|
||||
const secretKey = this.configService.get<string>('auth-config.SECRET_KEY');
|
||||
@ -49,75 +52,39 @@ export class AutomationService {
|
||||
|
||||
async addAutomation(addAutomationDto: AddAutomationDto, spaceTuyaId = null) {
|
||||
try {
|
||||
let unitSpaceTuyaId;
|
||||
if (!spaceTuyaId) {
|
||||
const unitDetails = await this.getUnitByUuid(addAutomationDto.unitUuid);
|
||||
const { automationName, effectiveTime, decisionExpr } = addAutomationDto;
|
||||
const space = await this.getSpaceByUuid(addAutomationDto.spaceUuid);
|
||||
|
||||
unitSpaceTuyaId = unitDetails.spaceTuyaUuid;
|
||||
if (!unitDetails) {
|
||||
throw new BadRequestException('Invalid unit UUID');
|
||||
}
|
||||
} else {
|
||||
unitSpaceTuyaId = spaceTuyaId;
|
||||
}
|
||||
|
||||
const actions = addAutomationDto.actions.map((action) =>
|
||||
convertKeysToSnakeCase(action),
|
||||
);
|
||||
const conditions = addAutomationDto.conditions.map((condition) =>
|
||||
convertKeysToSnakeCase(condition),
|
||||
const actions = await this.processEntities<Action>(
|
||||
addAutomationDto.actions,
|
||||
'actionExecutor',
|
||||
{ [ActionExecutorEnum.DEVICE_ISSUE]: true },
|
||||
this.deviceService,
|
||||
);
|
||||
|
||||
for (const action of actions) {
|
||||
if (action.action_executor === ActionExecutorEnum.DEVICE_ISSUE) {
|
||||
const device = await this.deviceService.getDeviceByDeviceUuid(
|
||||
action.entity_id,
|
||||
false,
|
||||
);
|
||||
if (device) {
|
||||
action.entity_id = device.deviceTuyaUuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
const conditions = await this.processEntities<Condition>(
|
||||
addAutomationDto.conditions,
|
||||
'entityType',
|
||||
{ [EntityTypeEnum.DEVICE_REPORT]: true },
|
||||
this.deviceService,
|
||||
);
|
||||
|
||||
for (const condition of conditions) {
|
||||
if (condition.entity_type === EntityTypeEnum.DEVICE_REPORT) {
|
||||
const device = await this.deviceService.getDeviceByDeviceUuid(
|
||||
condition.entity_id,
|
||||
false,
|
||||
);
|
||||
if (device) {
|
||||
condition.entity_id = device.deviceTuyaUuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
const response = (await this.tuyaService.createAutomation(
|
||||
space.spaceTuyaUuid,
|
||||
automationName,
|
||||
effectiveTime,
|
||||
decisionExpr,
|
||||
conditions,
|
||||
actions,
|
||||
)) as AddAutomationInterface;
|
||||
|
||||
const path = `/v2.0/cloud/scene/rule`;
|
||||
const response: AddAutomationInterface = await this.tuya.request({
|
||||
method: 'POST',
|
||||
path,
|
||||
body: {
|
||||
space_id: unitSpaceTuyaId,
|
||||
name: addAutomationDto.automationName,
|
||||
effective_time: {
|
||||
...addAutomationDto.effectiveTime,
|
||||
timezone_id: 'Asia/Dubai',
|
||||
},
|
||||
type: 'automation',
|
||||
decision_expr: addAutomationDto.decisionExpr,
|
||||
conditions: conditions,
|
||||
actions: actions,
|
||||
},
|
||||
});
|
||||
if (!response.success) {
|
||||
throw new HttpException(response.msg, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
return {
|
||||
id: response.result.id,
|
||||
id: response?.result.id,
|
||||
};
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
throw err;
|
||||
} else {
|
||||
throw new HttpException(
|
||||
err.message || 'Automation not found',
|
||||
@ -126,45 +93,42 @@ export class AutomationService {
|
||||
}
|
||||
}
|
||||
}
|
||||
async getUnitByUuid(unitUuid: string): Promise<GetUnitByUuidInterface> {
|
||||
|
||||
async getSpaceByUuid(spaceUuid: string) {
|
||||
try {
|
||||
const unit = await this.spaceRepository.findOne({
|
||||
const space = await this.spaceRepository.findOne({
|
||||
where: {
|
||||
uuid: unitUuid,
|
||||
spaceType: {
|
||||
type: SpaceType.UNIT,
|
||||
},
|
||||
uuid: spaceUuid,
|
||||
},
|
||||
relations: ['spaceType'],
|
||||
relations: ['community'],
|
||||
});
|
||||
if (!unit || !unit.spaceType || unit.spaceType.type !== SpaceType.UNIT) {
|
||||
throw new BadRequestException('Invalid unit UUID');
|
||||
if (!space) {
|
||||
throw new BadRequestException(`Invalid space UUID ${spaceUuid}`);
|
||||
}
|
||||
return {
|
||||
uuid: unit.uuid,
|
||||
createdAt: unit.createdAt,
|
||||
updatedAt: unit.updatedAt,
|
||||
name: unit.spaceName,
|
||||
type: unit.spaceType.type,
|
||||
spaceTuyaUuid: unit.spaceTuyaUuid,
|
||||
uuid: space.uuid,
|
||||
createdAt: space.createdAt,
|
||||
updatedAt: space.updatedAt,
|
||||
name: space.spaceName,
|
||||
spaceTuyaUuid: space.community.externalId,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Unit not found', HttpStatus.NOT_FOUND);
|
||||
throw new HttpException('Space not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
async getAutomationByUnit(unitUuid: string) {
|
||||
async getAutomationBySpace(spaceUuid: string) {
|
||||
try {
|
||||
const unit = await this.getUnitByUuid(unitUuid);
|
||||
if (!unit.spaceTuyaUuid) {
|
||||
throw new BadRequestException('Invalid unit UUID');
|
||||
const space = await this.getSpaceByUuid(spaceUuid);
|
||||
if (!space.spaceTuyaUuid) {
|
||||
throw new BadRequestException(`Invalid space UUID ${spaceUuid}`);
|
||||
}
|
||||
|
||||
const path = `/v2.0/cloud/scene/rule?space_id=${unit.spaceTuyaUuid}&type=automation`;
|
||||
const response: GetAutomationByUnitInterface = await this.tuya.request({
|
||||
const path = `/v2.0/cloud/scene/rule?space_id=${space.spaceTuyaUuid}&type=automation`;
|
||||
const response: GetAutomationBySpaceInterface = await this.tuya.request({
|
||||
method: 'GET',
|
||||
path,
|
||||
});
|
||||
@ -192,11 +156,12 @@ export class AutomationService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getTapToRunSceneDetailsTuya(
|
||||
sceneId: string,
|
||||
sceneUuid: string,
|
||||
): Promise<AutomationDetailsResult> {
|
||||
try {
|
||||
const path = `/v2.0/cloud/scene/rule/${sceneId}`;
|
||||
const path = `/v2.0/cloud/scene/rule/${sceneUuid}`;
|
||||
const response = await this.tuya.request({
|
||||
method: 'GET',
|
||||
path,
|
||||
@ -224,9 +189,9 @@ export class AutomationService {
|
||||
}
|
||||
}
|
||||
}
|
||||
async getAutomationDetails(automationId: string, withSpaceId = false) {
|
||||
async getAutomationDetails(automationUuid: string, withSpaceId = false) {
|
||||
try {
|
||||
const path = `/v2.0/cloud/scene/rule/${automationId}`;
|
||||
const path = `/v2.0/cloud/scene/rule/${automationUuid}`;
|
||||
const response = await this.tuya.request({
|
||||
method: 'GET',
|
||||
path,
|
||||
@ -312,24 +277,21 @@ export class AutomationService {
|
||||
}
|
||||
}
|
||||
|
||||
async deleteAutomation(
|
||||
unitUuid: string,
|
||||
automationId: string,
|
||||
spaceTuyaId = null,
|
||||
) {
|
||||
async deleteAutomation(param: DeleteAutomationParamDto, spaceTuyaId = null) {
|
||||
try {
|
||||
let unitSpaceTuyaId;
|
||||
const { automationUuid, spaceUuid } = param;
|
||||
let tuyaSpaceId;
|
||||
if (!spaceTuyaId) {
|
||||
const unitDetails = await this.getUnitByUuid(unitUuid);
|
||||
unitSpaceTuyaId = unitDetails.spaceTuyaUuid;
|
||||
if (!unitSpaceTuyaId) {
|
||||
throw new BadRequestException('Invalid unit UUID');
|
||||
const space = await this.getSpaceByUuid(spaceUuid);
|
||||
tuyaSpaceId = space.spaceTuyaUuid;
|
||||
if (!tuyaSpaceId) {
|
||||
throw new BadRequestException(`Invalid space UUID ${spaceUuid}`);
|
||||
}
|
||||
} else {
|
||||
unitSpaceTuyaId = spaceTuyaId;
|
||||
tuyaSpaceId = spaceTuyaId;
|
||||
}
|
||||
|
||||
const path = `/v2.0/cloud/scene/rule?ids=${automationId}&space_id=${unitSpaceTuyaId}`;
|
||||
const path = `/v2.0/cloud/scene/rule?ids=${automationUuid}&space_id=${tuyaSpaceId}`;
|
||||
const response: DeleteAutomationInterface = await this.tuya.request({
|
||||
method: 'DELETE',
|
||||
path,
|
||||
@ -354,10 +316,10 @@ export class AutomationService {
|
||||
|
||||
async updateAutomation(
|
||||
updateAutomationDto: UpdateAutomationDto,
|
||||
automationId: string,
|
||||
automationUuid: string,
|
||||
) {
|
||||
try {
|
||||
const spaceTuyaId = await this.getAutomationDetails(automationId, true);
|
||||
const spaceTuyaId = await this.getAutomationDetails(automationUuid, true);
|
||||
if (!spaceTuyaId.spaceId) {
|
||||
throw new HttpException(
|
||||
"Automation doesn't exist",
|
||||
@ -366,14 +328,18 @@ export class AutomationService {
|
||||
}
|
||||
const addAutomation = {
|
||||
...updateAutomationDto,
|
||||
unitUuid: null,
|
||||
spaceUuid: null,
|
||||
};
|
||||
const newAutomation = await this.addAutomation(
|
||||
addAutomation,
|
||||
spaceTuyaId.spaceId,
|
||||
);
|
||||
const params: DeleteAutomationParamDto = {
|
||||
spaceUuid: spaceTuyaId.spaceId,
|
||||
automationUuid: automationUuid,
|
||||
};
|
||||
if (newAutomation.id) {
|
||||
await this.deleteAutomation(null, automationId, spaceTuyaId.spaceId);
|
||||
await this.deleteAutomation(null, params);
|
||||
return newAutomation;
|
||||
}
|
||||
} catch (err) {
|
||||
@ -389,22 +355,24 @@ export class AutomationService {
|
||||
}
|
||||
async updateAutomationStatus(
|
||||
updateAutomationStatusDto: UpdateAutomationStatusDto,
|
||||
automationId: string,
|
||||
automationUuid: string,
|
||||
) {
|
||||
try {
|
||||
const unitDetails = await this.getUnitByUuid(
|
||||
updateAutomationStatusDto.unitUuid,
|
||||
const space = await this.getSpaceByUuid(
|
||||
updateAutomationStatusDto.spaceUuid,
|
||||
);
|
||||
if (!unitDetails.spaceTuyaUuid) {
|
||||
throw new BadRequestException('Invalid unit UUID');
|
||||
if (!space.spaceTuyaUuid) {
|
||||
throw new BadRequestException(
|
||||
`Invalid space UUID ${updateAutomationStatusDto.spaceUuid}`,
|
||||
);
|
||||
}
|
||||
|
||||
const path = `/v2.0/cloud/scene/rule/state?space_id=${unitDetails.spaceTuyaUuid}`;
|
||||
const path = `/v2.0/cloud/scene/rule/state?space_id=${space.spaceTuyaUuid}`;
|
||||
const response: DeleteAutomationInterface = await this.tuya.request({
|
||||
method: 'PUT',
|
||||
path,
|
||||
body: {
|
||||
ids: automationId,
|
||||
ids: automationUuid,
|
||||
is_enable: updateAutomationStatusDto.isEnable,
|
||||
},
|
||||
});
|
||||
@ -425,4 +393,42 @@ export class AutomationService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async processEntities<T extends Action | Condition>(
|
||||
entities: T[], // Accepts either Action[] or Condition[]
|
||||
lookupKey: keyof T, // The key to look up, specific to T
|
||||
entityTypeOrExecutorMap: {
|
||||
[key in ActionExecutorEnum | EntityTypeEnum]?: boolean;
|
||||
},
|
||||
deviceService: {
|
||||
getDeviceByDeviceUuid: (
|
||||
id: string,
|
||||
flag: boolean,
|
||||
) => Promise<{ deviceTuyaUuid: string } | null>;
|
||||
},
|
||||
): Promise<T[]> {
|
||||
// Returns the same type as provided in the input
|
||||
return Promise.all(
|
||||
entities.map(async (entity) => {
|
||||
// Convert keys to snake case (assuming a utility function exists)
|
||||
const processedEntity = convertKeysToSnakeCase(entity) as T;
|
||||
|
||||
// Check if entity needs device UUID lookup
|
||||
const key = processedEntity[lookupKey];
|
||||
if (
|
||||
entityTypeOrExecutorMap[key as ActionExecutorEnum | EntityTypeEnum]
|
||||
) {
|
||||
const device = await deviceService.getDeviceByDeviceUuid(
|
||||
processedEntity.entityId,
|
||||
false,
|
||||
);
|
||||
if (device) {
|
||||
processedEntity.entityId = device.deviceTuyaUuid;
|
||||
}
|
||||
}
|
||||
|
||||
return processedEntity;
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { BuildingService } from './services/building.service';
|
||||
import { BuildingController } from './controllers/building.controller';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { SpaceTypeRepository } from '@app/common/modules/space/repositories';
|
||||
import { UserSpaceRepository } from '@app/common/modules/user/repositories';
|
||||
import { UserRepository } from '@app/common/modules/user/repositories';
|
||||
import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule],
|
||||
controllers: [BuildingController],
|
||||
providers: [
|
||||
BuildingService,
|
||||
SpaceRepository,
|
||||
SpaceTypeRepository,
|
||||
UserSpaceRepository,
|
||||
UserRepository,
|
||||
],
|
||||
exports: [BuildingService],
|
||||
})
|
||||
export class BuildingModule {}
|
||||
@ -1,106 +0,0 @@
|
||||
import { BuildingService } from '../services/building.service';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
HttpStatus,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { AddBuildingDto, AddUserBuildingDto } from '../dtos/add.building.dto';
|
||||
import { GetBuildingChildDto } from '../dtos/get.building.dto';
|
||||
import { UpdateBuildingNameDto } from '../dtos/update.building.dto';
|
||||
import { CheckCommunityTypeGuard } from 'src/guards/community.type.guard';
|
||||
import { CheckUserBuildingGuard } from 'src/guards/user.building.guard';
|
||||
import { AdminRoleGuard } from 'src/guards/admin.role.guard';
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
import { BuildingPermissionGuard } from 'src/guards/building.permission.guard';
|
||||
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
|
||||
@ApiTags('Building Module')
|
||||
@Controller({
|
||||
version: EnableDisableStatusEnum.ENABLED,
|
||||
path: SpaceType.BUILDING,
|
||||
})
|
||||
export class BuildingController {
|
||||
constructor(private readonly buildingService: BuildingService) {}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, CheckCommunityTypeGuard)
|
||||
@Post()
|
||||
async addBuilding(@Body() addBuildingDto: AddBuildingDto) {
|
||||
const building = await this.buildingService.addBuilding(addBuildingDto);
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'Building added successfully',
|
||||
data: building,
|
||||
};
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, BuildingPermissionGuard)
|
||||
@Get(':buildingUuid')
|
||||
async getBuildingByUuid(@Param('buildingUuid') buildingUuid: string) {
|
||||
const building = await this.buildingService.getBuildingByUuid(buildingUuid);
|
||||
return building;
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, BuildingPermissionGuard)
|
||||
@Get('child/:buildingUuid')
|
||||
async getBuildingChildByUuid(
|
||||
@Param('buildingUuid') buildingUuid: string,
|
||||
@Query() query: GetBuildingChildDto,
|
||||
) {
|
||||
const building = await this.buildingService.getBuildingChildByUuid(
|
||||
buildingUuid,
|
||||
query,
|
||||
);
|
||||
return building;
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, BuildingPermissionGuard)
|
||||
@Get('parent/:buildingUuid')
|
||||
async getBuildingParentByUuid(@Param('buildingUuid') buildingUuid: string) {
|
||||
const building =
|
||||
await this.buildingService.getBuildingParentByUuid(buildingUuid);
|
||||
return building;
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AdminRoleGuard, CheckUserBuildingGuard)
|
||||
@Post('user')
|
||||
async addUserBuilding(@Body() addUserBuildingDto: AddUserBuildingDto) {
|
||||
await this.buildingService.addUserBuilding(addUserBuildingDto);
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'user building added successfully',
|
||||
};
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('user/:userUuid')
|
||||
async getBuildingsByUserId(@Param('userUuid') userUuid: string) {
|
||||
return await this.buildingService.getBuildingsByUserId(userUuid);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, BuildingPermissionGuard)
|
||||
@Put(':buildingUuid')
|
||||
async renameBuildingByUuid(
|
||||
@Param('buildingUuid') buildingUuid: string,
|
||||
@Body() updateBuildingDto: UpdateBuildingNameDto,
|
||||
) {
|
||||
const building = await this.buildingService.renameBuildingByUuid(
|
||||
buildingUuid,
|
||||
updateBuildingDto,
|
||||
);
|
||||
return building;
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from './building.controller';
|
||||
@ -1,42 +0,0 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class AddBuildingDto {
|
||||
@ApiProperty({
|
||||
description: 'buildingName',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public buildingName: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'communityUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public communityUuid: string;
|
||||
constructor(dto: Partial<AddBuildingDto>) {
|
||||
Object.assign(this, dto);
|
||||
}
|
||||
}
|
||||
export class AddUserBuildingDto {
|
||||
@ApiProperty({
|
||||
description: 'buildingUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public buildingUuid: string;
|
||||
@ApiProperty({
|
||||
description: 'userUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public userUuid: string;
|
||||
constructor(dto: Partial<AddUserBuildingDto>) {
|
||||
Object.assign(this, dto);
|
||||
}
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
import { BooleanValues } from '@app/common/constants/boolean-values.enum';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Transform } from 'class-transformer';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
|
||||
export class GetBuildingDto {
|
||||
@ApiProperty({
|
||||
description: 'buildingUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public buildingUuid: string;
|
||||
}
|
||||
|
||||
export class GetBuildingChildDto {
|
||||
@ApiProperty({ example: 1, description: 'Page number', required: true })
|
||||
@IsInt({ message: 'Page must be a number' })
|
||||
@Min(1, { message: 'Page must not be less than 1' })
|
||||
@IsNotEmpty()
|
||||
public page: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: 10,
|
||||
description: 'Number of items per page',
|
||||
required: true,
|
||||
})
|
||||
@IsInt({ message: 'Page size must be a number' })
|
||||
@Min(1, { message: 'Page size must not be less than 1' })
|
||||
@IsNotEmpty()
|
||||
public pageSize: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: true,
|
||||
description: 'Flag to determine whether to fetch full hierarchy',
|
||||
required: false,
|
||||
default: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@Transform((value) => {
|
||||
return value.obj.includeSubSpaces === BooleanValues.TRUE;
|
||||
})
|
||||
public includeSubSpaces: boolean = false;
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from './add.building.dto';
|
||||
@ -1,16 +0,0 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class UpdateBuildingNameDto {
|
||||
@ApiProperty({
|
||||
description: 'buildingName',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public buildingName: string;
|
||||
|
||||
constructor(dto: Partial<UpdateBuildingNameDto>) {
|
||||
Object.assign(this, dto);
|
||||
}
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
export interface GetBuildingByUuidInterface {
|
||||
uuid: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface BuildingChildInterface {
|
||||
uuid: string;
|
||||
name: string;
|
||||
type: string;
|
||||
totalCount?: number;
|
||||
children?: BuildingChildInterface[];
|
||||
}
|
||||
export interface BuildingParentInterface {
|
||||
uuid: string;
|
||||
name: string;
|
||||
type: string;
|
||||
parent?: BuildingParentInterface;
|
||||
}
|
||||
export interface RenameBuildingByUuidInterface {
|
||||
uuid: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
export interface GetBuildingByUserUuidInterface {
|
||||
uuid: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
@ -1,317 +0,0 @@
|
||||
import { GetBuildingChildDto } from '../dtos/get.building.dto';
|
||||
import { SpaceTypeRepository } from '../../../libs/common/src/modules/space/repositories/space.repository';
|
||||
import {
|
||||
Injectable,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { AddBuildingDto, AddUserBuildingDto } from '../dtos';
|
||||
import {
|
||||
BuildingChildInterface,
|
||||
BuildingParentInterface,
|
||||
GetBuildingByUserUuidInterface,
|
||||
GetBuildingByUuidInterface,
|
||||
RenameBuildingByUuidInterface,
|
||||
} from '../interface/building.interface';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities';
|
||||
import { UpdateBuildingNameDto } from '../dtos/update.building.dto';
|
||||
import { UserSpaceRepository } from '@app/common/modules/user/repositories';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
import { CommonErrorCodes } from '@app/common/constants/error-codes.enum';
|
||||
|
||||
@Injectable()
|
||||
export class BuildingService {
|
||||
constructor(
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
private readonly spaceTypeRepository: SpaceTypeRepository,
|
||||
private readonly userSpaceRepository: UserSpaceRepository,
|
||||
) {}
|
||||
|
||||
async addBuilding(addBuildingDto: AddBuildingDto) {
|
||||
try {
|
||||
const spaceType = await this.spaceTypeRepository.findOne({
|
||||
where: {
|
||||
type: SpaceType.BUILDING,
|
||||
},
|
||||
});
|
||||
|
||||
if (!spaceType) {
|
||||
throw new BadRequestException('Invalid building UUID');
|
||||
}
|
||||
const building = await this.spaceRepository.save({
|
||||
spaceName: addBuildingDto.buildingName,
|
||||
parent: { uuid: addBuildingDto.communityUuid },
|
||||
spaceType: { uuid: spaceType.uuid },
|
||||
});
|
||||
return building;
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Building not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getBuildingByUuid(
|
||||
buildingUuid: string,
|
||||
): Promise<GetBuildingByUuidInterface> {
|
||||
try {
|
||||
const building = await this.spaceRepository.findOne({
|
||||
where: {
|
||||
uuid: buildingUuid,
|
||||
spaceType: {
|
||||
type: SpaceType.BUILDING,
|
||||
},
|
||||
},
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
if (
|
||||
!building ||
|
||||
!building.spaceType ||
|
||||
building.spaceType.type !== SpaceType.BUILDING
|
||||
) {
|
||||
throw new BadRequestException('Invalid building UUID');
|
||||
}
|
||||
return {
|
||||
uuid: building.uuid,
|
||||
createdAt: building.createdAt,
|
||||
updatedAt: building.updatedAt,
|
||||
name: building.spaceName,
|
||||
type: building.spaceType.type,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Building not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
async getBuildingChildByUuid(
|
||||
buildingUuid: string,
|
||||
getBuildingChildDto: GetBuildingChildDto,
|
||||
): Promise<BuildingChildInterface> {
|
||||
try {
|
||||
const { includeSubSpaces, page, pageSize } = getBuildingChildDto;
|
||||
|
||||
const space = await this.spaceRepository.findOneOrFail({
|
||||
where: { uuid: buildingUuid },
|
||||
relations: ['children', 'spaceType'],
|
||||
});
|
||||
if (
|
||||
!space ||
|
||||
!space.spaceType ||
|
||||
space.spaceType.type !== SpaceType.BUILDING
|
||||
) {
|
||||
throw new BadRequestException('Invalid building UUID');
|
||||
}
|
||||
|
||||
const totalCount = await this.spaceRepository.count({
|
||||
where: { parent: { uuid: space.uuid } },
|
||||
});
|
||||
|
||||
const children = await this.buildHierarchy(
|
||||
space,
|
||||
includeSubSpaces,
|
||||
page,
|
||||
pageSize,
|
||||
);
|
||||
|
||||
return {
|
||||
uuid: space.uuid,
|
||||
name: space.spaceName,
|
||||
type: space.spaceType.type,
|
||||
totalCount,
|
||||
children,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Building not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async buildHierarchy(
|
||||
space: SpaceEntity,
|
||||
includeSubSpaces: boolean,
|
||||
page: number,
|
||||
pageSize: number,
|
||||
): Promise<BuildingChildInterface[]> {
|
||||
const children = await this.spaceRepository.find({
|
||||
where: { parent: { uuid: space.uuid } },
|
||||
relations: ['spaceType'],
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
});
|
||||
|
||||
if (!children || children.length === 0 || !includeSubSpaces) {
|
||||
return children
|
||||
.filter(
|
||||
(child) =>
|
||||
child.spaceType.type !== SpaceType.BUILDING &&
|
||||
child.spaceType.type !== SpaceType.COMMUNITY,
|
||||
) // Filter remaining building and community types
|
||||
.map((child) => ({
|
||||
uuid: child.uuid,
|
||||
name: child.spaceName,
|
||||
type: child.spaceType.type,
|
||||
}));
|
||||
}
|
||||
|
||||
const childHierarchies = await Promise.all(
|
||||
children
|
||||
.filter(
|
||||
(child) =>
|
||||
child.spaceType.type !== SpaceType.BUILDING &&
|
||||
child.spaceType.type !== SpaceType.COMMUNITY,
|
||||
) // Filter remaining building and community types
|
||||
.map(async (child) => ({
|
||||
uuid: child.uuid,
|
||||
name: child.spaceName,
|
||||
type: child.spaceType.type,
|
||||
children: await this.buildHierarchy(child, true, 1, pageSize),
|
||||
})),
|
||||
);
|
||||
|
||||
return childHierarchies;
|
||||
}
|
||||
|
||||
async getBuildingParentByUuid(
|
||||
buildingUuid: string,
|
||||
): Promise<BuildingParentInterface> {
|
||||
try {
|
||||
const building = await this.spaceRepository.findOne({
|
||||
where: {
|
||||
uuid: buildingUuid,
|
||||
spaceType: {
|
||||
type: SpaceType.BUILDING,
|
||||
},
|
||||
},
|
||||
relations: ['spaceType', 'parent', 'parent.spaceType'],
|
||||
});
|
||||
if (
|
||||
!building ||
|
||||
!building.spaceType ||
|
||||
building.spaceType.type !== SpaceType.BUILDING
|
||||
) {
|
||||
throw new BadRequestException('Invalid building UUID');
|
||||
}
|
||||
return {
|
||||
uuid: building.uuid,
|
||||
name: building.spaceName,
|
||||
type: building.spaceType.type,
|
||||
parent: {
|
||||
uuid: building.parent.uuid,
|
||||
name: building.parent.spaceName,
|
||||
type: building.parent.spaceType.type,
|
||||
},
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Building not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getBuildingsByUserId(
|
||||
userUuid: string,
|
||||
): Promise<GetBuildingByUserUuidInterface[]> {
|
||||
try {
|
||||
const buildings = await this.userSpaceRepository.find({
|
||||
relations: ['space', 'space.spaceType'],
|
||||
where: {
|
||||
user: { uuid: userUuid },
|
||||
space: { spaceType: { type: SpaceType.BUILDING } },
|
||||
},
|
||||
});
|
||||
|
||||
if (buildings.length === 0) {
|
||||
throw new HttpException(
|
||||
'this user has no buildings',
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
const spaces = buildings.map((building) => ({
|
||||
uuid: building.space.uuid,
|
||||
name: building.space.spaceName,
|
||||
type: building.space.spaceType.type,
|
||||
}));
|
||||
|
||||
return spaces;
|
||||
} catch (err) {
|
||||
if (err instanceof HttpException) {
|
||||
throw err;
|
||||
} else {
|
||||
throw new HttpException('user not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
async addUserBuilding(addUserBuildingDto: AddUserBuildingDto) {
|
||||
try {
|
||||
await this.userSpaceRepository.save({
|
||||
user: { uuid: addUserBuildingDto.userUuid },
|
||||
space: { uuid: addUserBuildingDto.buildingUuid },
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.code === CommonErrorCodes.DUPLICATE_ENTITY) {
|
||||
throw new HttpException(
|
||||
'User already belongs to this building',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
throw new HttpException(
|
||||
err.message || 'Internal Server Error',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
async renameBuildingByUuid(
|
||||
buildingUuid: string,
|
||||
updateBuildingNameDto: UpdateBuildingNameDto,
|
||||
): Promise<RenameBuildingByUuidInterface> {
|
||||
try {
|
||||
const building = await this.spaceRepository.findOneOrFail({
|
||||
where: { uuid: buildingUuid },
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
|
||||
if (
|
||||
!building ||
|
||||
!building.spaceType ||
|
||||
building.spaceType.type !== SpaceType.BUILDING
|
||||
) {
|
||||
throw new BadRequestException('Invalid building UUID');
|
||||
}
|
||||
|
||||
await this.spaceRepository.update(
|
||||
{ uuid: buildingUuid },
|
||||
{ spaceName: updateBuildingNameDto.buildingName },
|
||||
);
|
||||
|
||||
// Fetch the updated building
|
||||
const updatedBuilding = await this.spaceRepository.findOneOrFail({
|
||||
where: { uuid: buildingUuid },
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
|
||||
return {
|
||||
uuid: updatedBuilding.uuid,
|
||||
name: updatedBuilding.spaceName,
|
||||
type: updatedBuilding.spaceType.type,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Building not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from './building.service';
|
||||
@ -4,11 +4,11 @@ import { CommunityController } from './controllers/community.controller';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { SpaceTypeRepository } from '@app/common/modules/space/repositories';
|
||||
import { UserSpaceRepository } from '@app/common/modules/user/repositories';
|
||||
import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module';
|
||||
import { UserRepository } from '@app/common/modules/user/repositories';
|
||||
import { SpacePermissionService } from '@app/common/helper/services';
|
||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule],
|
||||
@ -16,9 +16,9 @@ import { SpacePermissionService } from '@app/common/helper/services';
|
||||
providers: [
|
||||
CommunityService,
|
||||
SpaceRepository,
|
||||
SpaceTypeRepository,
|
||||
UserSpaceRepository,
|
||||
UserRepository,
|
||||
TuyaService,
|
||||
CommunityRepository,
|
||||
SpacePermissionService,
|
||||
],
|
||||
exports: [CommunityService, SpacePermissionService],
|
||||
|
||||
@ -2,31 +2,28 @@ import { CommunityService } from '../services/community.service';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpStatus,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import {
|
||||
AddCommunityDto,
|
||||
AddUserCommunityDto,
|
||||
} from '../dtos/add.community.dto';
|
||||
import { GetCommunityChildDto } from '../dtos/get.community.dto';
|
||||
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
||||
import { AddCommunityDto } from '../dtos/add.community.dto';
|
||||
import { GetCommunityParams } from '../dtos/get.community.dto';
|
||||
import { UpdateCommunityNameDto } from '../dtos/update.community.dto';
|
||||
import { AdminRoleGuard } from 'src/guards/admin.role.guard';
|
||||
// import { CheckUserCommunityGuard } from 'src/guards/user.community.guard';
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
// import { CommunityPermissionGuard } from 'src/guards/community.permission.guard';
|
||||
import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { PaginationRequestGetListDto } from '@app/common/dto/pagination.request.dto';
|
||||
|
||||
@ApiTags('Community Module')
|
||||
@Controller({
|
||||
version: EnableDisableStatusEnum.ENABLED,
|
||||
path: SpaceType.COMMUNITY,
|
||||
version: '1',
|
||||
path: ControllerRoute.COMMUNITY.ROUTE,
|
||||
})
|
||||
export class CommunityController {
|
||||
constructor(private readonly communityService: CommunityService) {}
|
||||
@ -34,73 +31,70 @@ export class CommunityController {
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post()
|
||||
async addCommunity(@Body() addCommunityDto: AddCommunityDto) {
|
||||
const community = await this.communityService.addCommunity(addCommunityDto);
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'Community added successfully',
|
||||
data: community,
|
||||
};
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.COMMUNITY.ACTIONS.CREATE_COMMUNITY_SUMMARY,
|
||||
description: ControllerRoute.COMMUNITY.ACTIONS.CREATE_COMMUNITY_DESCRIPTION,
|
||||
})
|
||||
async createCommunity(
|
||||
@Body() addCommunityDto: AddCommunityDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
return await this.communityService.createCommunity(addCommunityDto);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get(':communityUuid')
|
||||
async getCommunityByUuid(@Param('communityUuid') communityUuid: string) {
|
||||
const community =
|
||||
await this.communityService.getCommunityByUuid(communityUuid);
|
||||
return community;
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.COMMUNITY.ACTIONS.GET_COMMUNITY_BY_ID_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.COMMUNITY.ACTIONS.GET_COMMUNITY_BY_ID_DESCRIPTION,
|
||||
})
|
||||
@Get('/:communityUuid')
|
||||
async getCommunityByUuid(
|
||||
@Param() params: GetCommunityParams,
|
||||
): Promise<BaseResponseDto> {
|
||||
return await this.communityService.getCommunityById(params.communityUuid);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.COMMUNITY.ACTIONS.LIST_COMMUNITY_SUMMARY,
|
||||
description: ControllerRoute.COMMUNITY.ACTIONS.LIST_COMMUNITY_DESCRIPTION,
|
||||
})
|
||||
@Get()
|
||||
async getCommunities() {
|
||||
const communities = await this.communityService.getCommunities();
|
||||
return communities;
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('child/:communityUuid')
|
||||
async getCommunityChildByUuid(
|
||||
@Param('communityUuid') communityUuid: string,
|
||||
@Query() query: GetCommunityChildDto,
|
||||
) {
|
||||
const community = await this.communityService.getCommunityChildByUuid(
|
||||
communityUuid,
|
||||
query,
|
||||
);
|
||||
return community;
|
||||
async getCommunities(
|
||||
@Query() query: PaginationRequestGetListDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
return this.communityService.getCommunities(query);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('user/:userUuid')
|
||||
async getCommunitiesByUserId(@Param('userUuid') userUuid: string) {
|
||||
return await this.communityService.getCommunitiesByUserId(userUuid);
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AdminRoleGuard)
|
||||
@Post('user')
|
||||
async addUserCommunity(@Body() addUserCommunityDto: AddUserCommunityDto) {
|
||||
await this.communityService.addUserCommunity(addUserCommunityDto);
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'user community added successfully',
|
||||
};
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Put(':communityUuid')
|
||||
async renameCommunityByUuid(
|
||||
@Param('communityUuid') communityUuid: string,
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.COMMUNITY.ACTIONS.UPDATE_COMMUNITY_SUMMARY,
|
||||
description: ControllerRoute.COMMUNITY.ACTIONS.UPDATE_COMMUNITY_DESCRIPTION,
|
||||
})
|
||||
@Put('/:communityUuid')
|
||||
async updateCommunity(
|
||||
@Param() param: GetCommunityParams,
|
||||
@Body() updateCommunityDto: UpdateCommunityNameDto,
|
||||
) {
|
||||
const community = await this.communityService.renameCommunityByUuid(
|
||||
communityUuid,
|
||||
return this.communityService.updateCommunity(
|
||||
param.communityUuid,
|
||||
updateCommunityDto,
|
||||
);
|
||||
return community;
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Delete('/:communityUuid')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.COMMUNITY.ACTIONS.DELETE_COMMUNITY_SUMMARY,
|
||||
description: ControllerRoute.COMMUNITY.ACTIONS.DELETE_COMMUNITY_DESCRIPTION,
|
||||
})
|
||||
async deleteCommunity(
|
||||
@Param() param: GetCommunityParams,
|
||||
): Promise<BaseResponseDto> {
|
||||
return this.communityService.deleteCommunity(param.communityUuid);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,19 +1,30 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
|
||||
|
||||
export class AddCommunityDto {
|
||||
@ApiProperty({
|
||||
description: 'communityName',
|
||||
description: 'The name of the community',
|
||||
example: 'Community A',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public communityName: string;
|
||||
public name: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'A description of the community',
|
||||
example: 'This is a community for developers.',
|
||||
required: false,
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
public description?: string;
|
||||
|
||||
constructor(dto: Partial<AddCommunityDto>) {
|
||||
Object.assign(this, dto);
|
||||
}
|
||||
}
|
||||
|
||||
export class AddUserCommunityDto {
|
||||
@ApiProperty({
|
||||
description: 'communityUuid',
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
|
||||
@ -20,6 +21,18 @@ export class GetCommunityDto {
|
||||
public communityUuid: string;
|
||||
}
|
||||
|
||||
export class GetCommunityParams {
|
||||
@ApiProperty({
|
||||
description: 'Community id of the specific community',
|
||||
required: true,
|
||||
name: 'communityUuid',
|
||||
})
|
||||
@IsUUID()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public communityUuid: string;
|
||||
}
|
||||
|
||||
export class GetCommunityChildDto {
|
||||
@ApiProperty({ example: 1, description: 'Page number', required: true })
|
||||
@IsInt({ message: 'Page must be a number' })
|
||||
|
||||
@ -3,12 +3,12 @@ import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class UpdateCommunityNameDto {
|
||||
@ApiProperty({
|
||||
description: 'communityName',
|
||||
description: 'community name',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public communityName: string;
|
||||
public name: string;
|
||||
|
||||
constructor(dto: Partial<UpdateCommunityNameDto>) {
|
||||
Object.assign(this, dto);
|
||||
|
||||
@ -1,278 +1,172 @@
|
||||
import { GetCommunityChildDto } from './../dtos/get.community.dto';
|
||||
import { SpaceTypeRepository } from './../../../libs/common/src/modules/space/repositories/space.repository';
|
||||
import {
|
||||
Injectable,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { AddCommunityDto, AddUserCommunityDto } from '../dtos';
|
||||
import {
|
||||
CommunityChildInterface,
|
||||
GetCommunitiesInterface,
|
||||
GetCommunityByUserUuidInterface,
|
||||
GetCommunityByUuidInterface,
|
||||
RenameCommunityByUuidInterface,
|
||||
} from '../interface/community.interface';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities';
|
||||
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
|
||||
import { AddCommunityDto } from '../dtos';
|
||||
import { UpdateCommunityNameDto } from '../dtos/update.community.dto';
|
||||
import { UserSpaceRepository } from '@app/common/modules/user/repositories';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
import { CommonErrorCodes } from '@app/common/constants/error-codes.enum';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import {
|
||||
TypeORMCustomModel,
|
||||
TypeORMCustomModelFindAllQuery,
|
||||
} from '@app/common/models/typeOrmCustom.model';
|
||||
import { PageResponse } from '@app/common/dto/pagination.response.dto';
|
||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||
import { CommunityDto } from '@app/common/modules/community/dtos';
|
||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||
|
||||
@Injectable()
|
||||
export class CommunityService {
|
||||
constructor(
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
private readonly spaceTypeRepository: SpaceTypeRepository,
|
||||
private readonly userSpaceRepository: UserSpaceRepository,
|
||||
private readonly communityRepository: CommunityRepository,
|
||||
private readonly tuyaService: TuyaService,
|
||||
) {}
|
||||
|
||||
async addCommunity(addCommunityDto: AddCommunityDto) {
|
||||
try {
|
||||
const spaceType = await this.spaceTypeRepository.findOne({
|
||||
where: {
|
||||
type: SpaceType.COMMUNITY,
|
||||
},
|
||||
});
|
||||
async createCommunity(dto: AddCommunityDto): Promise<BaseResponseDto> {
|
||||
const { name, description } = dto;
|
||||
|
||||
const community = await this.spaceRepository.save({
|
||||
spaceName: addCommunityDto.communityName,
|
||||
spaceType: { uuid: spaceType.uuid },
|
||||
});
|
||||
return community;
|
||||
} catch (err) {
|
||||
throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getCommunityByUuid(
|
||||
communityUuid: string,
|
||||
): Promise<GetCommunityByUuidInterface> {
|
||||
try {
|
||||
const community = await this.spaceRepository.findOne({
|
||||
where: {
|
||||
uuid: communityUuid,
|
||||
spaceType: {
|
||||
type: SpaceType.COMMUNITY,
|
||||
},
|
||||
},
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
if (
|
||||
!community ||
|
||||
!community.spaceType ||
|
||||
community.spaceType.type !== SpaceType.COMMUNITY
|
||||
) {
|
||||
throw new BadRequestException('Invalid community UUID');
|
||||
}
|
||||
return {
|
||||
uuid: community.uuid,
|
||||
createdAt: community.createdAt,
|
||||
updatedAt: community.updatedAt,
|
||||
name: community.spaceName,
|
||||
type: community.spaceType.type,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Community not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
async getCommunities(): Promise<GetCommunitiesInterface> {
|
||||
try {
|
||||
const community = await this.spaceRepository.find({
|
||||
where: { spaceType: { type: SpaceType.COMMUNITY } },
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
return community.map((community) => ({
|
||||
uuid: community.uuid,
|
||||
createdAt: community.createdAt,
|
||||
updatedAt: community.updatedAt,
|
||||
name: community.spaceName,
|
||||
type: community.spaceType.type,
|
||||
}));
|
||||
} catch (err) {
|
||||
throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
async getCommunityChildByUuid(
|
||||
communityUuid: string,
|
||||
getCommunityChildDto: GetCommunityChildDto,
|
||||
): Promise<CommunityChildInterface> {
|
||||
try {
|
||||
const { includeSubSpaces, page, pageSize } = getCommunityChildDto;
|
||||
|
||||
const space = await this.spaceRepository.findOneOrFail({
|
||||
where: { uuid: communityUuid },
|
||||
relations: ['children', 'spaceType'],
|
||||
});
|
||||
|
||||
if (
|
||||
!space ||
|
||||
!space.spaceType ||
|
||||
space.spaceType.type !== SpaceType.COMMUNITY
|
||||
) {
|
||||
throw new BadRequestException('Invalid community UUID');
|
||||
}
|
||||
const totalCount = await this.spaceRepository.count({
|
||||
where: { parent: { uuid: space.uuid } },
|
||||
});
|
||||
const children = await this.buildHierarchy(
|
||||
space,
|
||||
includeSubSpaces,
|
||||
page,
|
||||
pageSize,
|
||||
const existingCommunity = await this.communityRepository.findOneBy({
|
||||
name,
|
||||
});
|
||||
if (existingCommunity) {
|
||||
throw new HttpException(
|
||||
`A community with the name '${name}' already exists.`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
return {
|
||||
uuid: space.uuid,
|
||||
name: space.spaceName,
|
||||
type: space.spaceType.type,
|
||||
totalCount,
|
||||
children,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Community not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async buildHierarchy(
|
||||
space: SpaceEntity,
|
||||
includeSubSpaces: boolean,
|
||||
page: number,
|
||||
pageSize: number,
|
||||
): Promise<CommunityChildInterface[]> {
|
||||
const children = await this.spaceRepository.find({
|
||||
where: { parent: { uuid: space.uuid } },
|
||||
relations: ['spaceType'],
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
// Create the new community entity
|
||||
const community = this.communityRepository.create({
|
||||
name: name,
|
||||
description: description,
|
||||
});
|
||||
|
||||
if (!children || children.length === 0 || !includeSubSpaces) {
|
||||
return children
|
||||
.filter((child) => child.spaceType.type !== SpaceType.COMMUNITY) // Filter remaining community type
|
||||
.map((child) => ({
|
||||
uuid: child.uuid,
|
||||
name: child.spaceName,
|
||||
type: child.spaceType.type,
|
||||
}));
|
||||
// Save the community to the database
|
||||
try {
|
||||
const externalId = await this.createTuyaSpace(name);
|
||||
community.externalId = externalId;
|
||||
await this.communityRepository.save(community);
|
||||
return new SuccessResponseDto({
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
data: community,
|
||||
message: 'Community created successfully',
|
||||
});
|
||||
} catch (error) {
|
||||
throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
const childHierarchies = await Promise.all(
|
||||
children
|
||||
.filter((child) => child.spaceType.type !== SpaceType.COMMUNITY) // Filter remaining community type
|
||||
.map(async (child) => ({
|
||||
uuid: child.uuid,
|
||||
name: child.spaceName,
|
||||
type: child.spaceType.type,
|
||||
children: await this.buildHierarchy(child, true, 1, pageSize),
|
||||
})),
|
||||
);
|
||||
|
||||
return childHierarchies;
|
||||
}
|
||||
|
||||
async getCommunitiesByUserId(
|
||||
userUuid: string,
|
||||
): Promise<GetCommunityByUserUuidInterface[]> {
|
||||
async getCommunityById(communityUuid: string): Promise<BaseResponseDto> {
|
||||
const community = await this.communityRepository.findOneBy({
|
||||
uuid: communityUuid,
|
||||
});
|
||||
|
||||
// If the community is not found, throw a 404 NotFoundException
|
||||
if (!community) {
|
||||
throw new HttpException(
|
||||
`Community with ID ${communityUuid} not found.`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
// Return a success response
|
||||
return new SuccessResponseDto({
|
||||
data: community,
|
||||
message: 'Community fetched successfully',
|
||||
});
|
||||
}
|
||||
|
||||
async getCommunities(
|
||||
pageable: Partial<TypeORMCustomModelFindAllQuery>,
|
||||
): Promise<BaseResponseDto> {
|
||||
pageable.modelName = 'community';
|
||||
|
||||
const customModel = TypeORMCustomModel(this.communityRepository);
|
||||
|
||||
const { baseResponseDto, paginationResponseDto } =
|
||||
await customModel.findAll(pageable);
|
||||
|
||||
return new PageResponse<CommunityDto>(
|
||||
baseResponseDto,
|
||||
paginationResponseDto,
|
||||
);
|
||||
}
|
||||
|
||||
async updateCommunity(
|
||||
communityUuid: string,
|
||||
updateCommunityDto: UpdateCommunityNameDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
const community = await this.communityRepository.findOne({
|
||||
where: { uuid: communityUuid },
|
||||
});
|
||||
|
||||
// If the community doesn't exist, throw a 404 error
|
||||
if (!community) {
|
||||
throw new HttpException(
|
||||
`Community with ID ${communityUuid} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const communities = await this.userSpaceRepository.find({
|
||||
relations: ['space', 'space.spaceType'],
|
||||
where: {
|
||||
user: { uuid: userUuid },
|
||||
space: { spaceType: { type: SpaceType.COMMUNITY } },
|
||||
},
|
||||
const { name } = updateCommunityDto;
|
||||
|
||||
community.name = name;
|
||||
|
||||
const updatedCommunity = await this.communityRepository.save(community);
|
||||
|
||||
return new SuccessResponseDto<CommunityDto>({
|
||||
message: 'Success update Community',
|
||||
data: updatedCommunity,
|
||||
});
|
||||
|
||||
if (communities.length === 0) {
|
||||
throw new HttpException(
|
||||
'this user has no communities',
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
} catch (err) {
|
||||
// Catch and handle any errors
|
||||
if (err instanceof HttpException) {
|
||||
throw err; // If it's an HttpException, rethrow it
|
||||
} else {
|
||||
// Throw a generic 404 error if anything else goes wrong
|
||||
throw new HttpException('Community not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
const spaces = communities.map((community) => ({
|
||||
uuid: community.space.uuid,
|
||||
name: community.space.spaceName,
|
||||
type: community.space.spaceType.type,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return spaces;
|
||||
async deleteCommunity(communityUuid: string): Promise<BaseResponseDto> {
|
||||
const community = await this.communityRepository.findOne({
|
||||
where: { uuid: communityUuid },
|
||||
});
|
||||
|
||||
// If the community is not found, throw an error
|
||||
if (!community) {
|
||||
throw new HttpException(
|
||||
`Community with ID ${communityUuid} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
try {
|
||||
await this.communityRepository.remove(community);
|
||||
|
||||
return new SuccessResponseDto({
|
||||
message: `Community with ID ${communityUuid} has been successfully deleted`,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof HttpException) {
|
||||
throw err;
|
||||
} else {
|
||||
throw new HttpException('user not found', HttpStatus.NOT_FOUND);
|
||||
throw new HttpException(
|
||||
'An error occurred while deleting the community',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async addUserCommunity(addUserCommunityDto: AddUserCommunityDto) {
|
||||
private async createTuyaSpace(name: string): Promise<string> {
|
||||
try {
|
||||
await this.userSpaceRepository.save({
|
||||
user: { uuid: addUserCommunityDto.userUuid },
|
||||
space: { uuid: addUserCommunityDto.communityUuid },
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.code === CommonErrorCodes.DUPLICATE_ENTITY) {
|
||||
throw new HttpException(
|
||||
'User already belongs to this community',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
const response = await this.tuyaService.createSpace({ name });
|
||||
return response;
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
err.message || 'Internal Server Error',
|
||||
'Failed to create a Tuya space',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
async renameCommunityByUuid(
|
||||
communityUuid: string,
|
||||
updateCommunityDto: UpdateCommunityNameDto,
|
||||
): Promise<RenameCommunityByUuidInterface> {
|
||||
try {
|
||||
const community = await this.spaceRepository.findOneOrFail({
|
||||
where: { uuid: communityUuid },
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
|
||||
if (
|
||||
!community ||
|
||||
!community.spaceType ||
|
||||
community.spaceType.type !== SpaceType.COMMUNITY
|
||||
) {
|
||||
throw new BadRequestException('Invalid community UUID');
|
||||
}
|
||||
|
||||
await this.spaceRepository.update(
|
||||
{ uuid: communityUuid },
|
||||
{ spaceName: updateCommunityDto.communityName },
|
||||
);
|
||||
|
||||
// Fetch the updated community
|
||||
const updatedCommunity = await this.spaceRepository.findOneOrFail({
|
||||
where: { uuid: communityUuid },
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
|
||||
return {
|
||||
uuid: updatedCommunity.uuid,
|
||||
name: updatedCommunity.spaceName,
|
||||
type: updatedCommunity.spaceType.type,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Community not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,11 +12,8 @@ import {
|
||||
Put,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { AddDeviceDto, UpdateDeviceInRoomDto } from '../dtos/add.device.dto';
|
||||
import {
|
||||
GetDeviceByRoomUuidDto,
|
||||
GetDeviceLogsDto,
|
||||
} from '../dtos/get.device.dto';
|
||||
import { AddDeviceDto, UpdateDeviceInSpaceDto } from '../dtos/add.device.dto';
|
||||
import { GetDeviceLogsDto } from '../dtos/get.device.dto';
|
||||
import {
|
||||
ControlDeviceDto,
|
||||
BatchControlDevicesDto,
|
||||
@ -24,13 +21,10 @@ import {
|
||||
BatchFactoryResetDevicesDto,
|
||||
} from '../dtos/control.device.dto';
|
||||
import { CheckRoomGuard } from 'src/guards/room.guard';
|
||||
import { CheckUserHavePermission } from 'src/guards/user.device.permission.guard';
|
||||
import { CheckUserHaveControllablePermission } from 'src/guards/user.device.controllable.permission.guard';
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
import { CheckDeviceGuard } from 'src/guards/device.guard';
|
||||
import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard';
|
||||
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
|
||||
@ApiTags('Device Module')
|
||||
@Controller({
|
||||
@ -58,33 +52,21 @@ export class DeviceController {
|
||||
async getDevicesByUser(@Param('userUuid') userUuid: string) {
|
||||
return await this.deviceService.getDevicesByUser(userUuid);
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, CheckRoomGuard)
|
||||
@Get(SpaceType.ROOM)
|
||||
async getDevicesByRoomId(
|
||||
@Query() getDeviceByRoomUuidDto: GetDeviceByRoomUuidDto,
|
||||
@Req() req: any,
|
||||
) {
|
||||
const userUuid = req.user.uuid;
|
||||
return await this.deviceService.getDevicesByRoomId(
|
||||
getDeviceByRoomUuidDto,
|
||||
userUuid,
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('unit/:unitUuid')
|
||||
async getDevicesByUnitId(@Param('unitUuid') unitUuid: string) {
|
||||
return await this.deviceService.getDevicesByUnitId(unitUuid);
|
||||
@Get('space/:spaceUuid')
|
||||
async getDevicesByUnitId(@Param('spaceUuid') spaceUuid: string) {
|
||||
return await this.deviceService.getDevicesBySpaceUuid(spaceUuid);
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, CheckRoomGuard)
|
||||
@Put('room')
|
||||
@Put('space')
|
||||
async updateDeviceInRoom(
|
||||
@Body() updateDeviceInRoomDto: UpdateDeviceInRoomDto,
|
||||
@Body() updateDeviceInSpaceDto: UpdateDeviceInSpaceDto,
|
||||
) {
|
||||
const device = await this.deviceService.updateDeviceInRoom(
|
||||
updateDeviceInRoomDto,
|
||||
const device = await this.deviceService.updateDeviceInSpace(
|
||||
updateDeviceInSpaceDto,
|
||||
);
|
||||
|
||||
return {
|
||||
@ -96,7 +78,7 @@ export class DeviceController {
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, CheckUserHavePermission)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get(':deviceUuid')
|
||||
async getDeviceDetailsByDeviceId(
|
||||
@Param('deviceUuid') deviceUuid: string,
|
||||
@ -109,7 +91,7 @@ export class DeviceController {
|
||||
);
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, CheckUserHavePermission)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get(':deviceUuid/functions')
|
||||
async getDeviceInstructionByDeviceId(
|
||||
@Param('deviceUuid') deviceUuid: string,
|
||||
@ -117,14 +99,14 @@ export class DeviceController {
|
||||
return await this.deviceService.getDeviceInstructionByDeviceId(deviceUuid);
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, CheckUserHavePermission)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get(':deviceUuid/functions/status')
|
||||
async getDevicesInstructionStatus(@Param('deviceUuid') deviceUuid: string) {
|
||||
return await this.deviceService.getDevicesInstructionStatus(deviceUuid);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, CheckUserHaveControllablePermission)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post(':deviceUuid/control')
|
||||
async controlDevice(
|
||||
@Body() controlDeviceDto: ControlDeviceDto,
|
||||
@ -156,6 +138,7 @@ export class DeviceController {
|
||||
async getAllDevices() {
|
||||
return await this.deviceService.getAllDevices();
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('report-logs/:deviceUuid')
|
||||
|
||||
@ -18,7 +18,7 @@ export class AddDeviceDto {
|
||||
@IsNotEmpty()
|
||||
public userUuid: string;
|
||||
}
|
||||
export class UpdateDeviceInRoomDto {
|
||||
export class UpdateDeviceInSpaceDto {
|
||||
@ApiProperty({
|
||||
description: 'deviceUuid',
|
||||
required: true,
|
||||
@ -28,10 +28,10 @@ export class UpdateDeviceInRoomDto {
|
||||
public deviceUuid: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'roomUuid',
|
||||
description: 'spaceUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public roomUuid: string;
|
||||
public spaceUuid: string;
|
||||
}
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class GetDeviceByRoomUuidDto {
|
||||
export class GetDeviceBySpaceUuidDto {
|
||||
@ApiProperty({
|
||||
description: 'roomUuid',
|
||||
description: 'spaceUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public roomUuid: string;
|
||||
public spaceUuid: string;
|
||||
}
|
||||
export class GetDeviceLogsDto {
|
||||
@ApiProperty({
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
export interface GetDeviceDetailsInterface {
|
||||
activeTime: number;
|
||||
assetId: string;
|
||||
assetId?: string;
|
||||
category: string;
|
||||
categoryName: string;
|
||||
createTime: number;
|
||||
@ -13,6 +13,7 @@ export interface GetDeviceDetailsInterface {
|
||||
lon: string;
|
||||
model: string;
|
||||
name: string;
|
||||
battery?: number;
|
||||
nodeId: string;
|
||||
online: boolean;
|
||||
productId?: string;
|
||||
@ -23,6 +24,18 @@ export interface GetDeviceDetailsInterface {
|
||||
uuid: string;
|
||||
productType: string;
|
||||
productUuid: string;
|
||||
spaces?: SpaceInterface[];
|
||||
community?: CommunityInterface;
|
||||
}
|
||||
|
||||
export interface SpaceInterface {
|
||||
uuid: string;
|
||||
spaceName: string;
|
||||
}
|
||||
|
||||
export interface CommunityInterface {
|
||||
uuid: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface addDeviceInRoomInterface {
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { TuyaContext } from '@tuya/tuya-connector-nodejs';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { AddDeviceDto, UpdateDeviceInRoomDto } from '../dtos/add.device.dto';
|
||||
import { AddDeviceDto, UpdateDeviceInSpaceDto } from '../dtos/add.device.dto';
|
||||
import {
|
||||
DeviceInstructionResponse,
|
||||
GetDeviceDetailsFunctionsInterface,
|
||||
@ -20,7 +20,7 @@ import {
|
||||
updateDeviceFirmwareInterface,
|
||||
} from '../interfaces/get.device.interface';
|
||||
import {
|
||||
GetDeviceByRoomUuidDto,
|
||||
GetDeviceBySpaceUuidDto,
|
||||
GetDeviceLogsDto,
|
||||
} from '../dtos/get.device.dto';
|
||||
import {
|
||||
@ -39,6 +39,7 @@ import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status
|
||||
import { DeviceStatuses } from '@app/common/constants/device-status.enum';
|
||||
import { CommonErrorCodes } from '@app/common/constants/error-codes.enum';
|
||||
import { BatteryStatus } from '@app/common/constants/battery-status.enum';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities';
|
||||
|
||||
@Injectable()
|
||||
export class DeviceService {
|
||||
@ -70,6 +71,7 @@ export class DeviceService {
|
||||
...(withProductDevice && { relations: ['productDevice'] }),
|
||||
});
|
||||
}
|
||||
|
||||
async getDeviceByDeviceTuyaUuid(deviceTuyaUuid: string) {
|
||||
return await this.deviceRepository.findOne({
|
||||
where: {
|
||||
@ -78,6 +80,7 @@ export class DeviceService {
|
||||
relations: ['productDevice'],
|
||||
});
|
||||
}
|
||||
|
||||
async addDeviceUser(addDeviceDto: AddDeviceDto) {
|
||||
try {
|
||||
const device = await this.getDeviceDetailsByDeviceIdTuya(
|
||||
@ -117,12 +120,13 @@ export class DeviceService {
|
||||
);
|
||||
} else {
|
||||
throw new HttpException(
|
||||
error.message || 'Failed to add device in room',
|
||||
error.message || 'Failed to add device in space',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getDevicesByUser(
|
||||
userUuid: string,
|
||||
): Promise<GetDeviceDetailsInterface[]> {
|
||||
@ -169,14 +173,15 @@ export class DeviceService {
|
||||
);
|
||||
}
|
||||
}
|
||||
async getDevicesByRoomId(
|
||||
getDeviceByRoomUuidDto: GetDeviceByRoomUuidDto,
|
||||
|
||||
async getDevicesBySpaceId(
|
||||
getDeviceBySpaceUuidDto: GetDeviceBySpaceUuidDto,
|
||||
userUuid: string,
|
||||
): Promise<GetDeviceDetailsInterface[]> {
|
||||
try {
|
||||
const devices = await this.deviceRepository.find({
|
||||
where: {
|
||||
spaceDevice: { uuid: getDeviceByRoomUuidDto.roomUuid },
|
||||
spaceDevice: { uuid: getDeviceBySpaceUuidDto.spaceUuid },
|
||||
isActive: true,
|
||||
permission: {
|
||||
userUuid,
|
||||
@ -211,22 +216,22 @@ export class DeviceService {
|
||||
} catch (error) {
|
||||
// Handle the error here
|
||||
throw new HttpException(
|
||||
'Error fetching devices by room',
|
||||
'Error fetching devices by space',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
async updateDeviceInRoom(updateDeviceInRoomDto: UpdateDeviceInRoomDto) {
|
||||
async updateDeviceInSpace(updateDeviceInSpaceDto: UpdateDeviceInSpaceDto) {
|
||||
try {
|
||||
await this.deviceRepository.update(
|
||||
{ uuid: updateDeviceInRoomDto.deviceUuid },
|
||||
{ uuid: updateDeviceInSpaceDto.deviceUuid },
|
||||
{
|
||||
spaceDevice: { uuid: updateDeviceInRoomDto.roomUuid },
|
||||
spaceDevice: { uuid: updateDeviceInSpaceDto.spaceUuid },
|
||||
},
|
||||
);
|
||||
const device = await this.deviceRepository.findOne({
|
||||
where: {
|
||||
uuid: updateDeviceInRoomDto.deviceUuid,
|
||||
uuid: updateDeviceInSpaceDto.deviceUuid,
|
||||
},
|
||||
relations: ['spaceDevice', 'spaceDevice.parent'],
|
||||
});
|
||||
@ -239,15 +244,16 @@ export class DeviceService {
|
||||
|
||||
return {
|
||||
uuid: device.uuid,
|
||||
roomUuid: device.spaceDevice.uuid,
|
||||
spaceUuid: device.spaceDevice.uuid,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
'Failed to add device in room',
|
||||
'Failed to add device in space',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async transferDeviceInSpacesTuya(
|
||||
deviceId: string,
|
||||
spaceId: string,
|
||||
@ -295,6 +301,7 @@ export class DeviceService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async factoryResetDeviceTuya(
|
||||
deviceUuid: string,
|
||||
): Promise<controlDeviceInterface> {
|
||||
@ -337,6 +344,7 @@ export class DeviceService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async batchControlDevices(batchControlDevicesDto: BatchControlDevicesDto) {
|
||||
const { devicesUuid } = batchControlDevicesDto;
|
||||
|
||||
@ -562,13 +570,12 @@ export class DeviceService {
|
||||
async getDeviceInstructionByDeviceId(
|
||||
deviceUuid: string,
|
||||
): Promise<DeviceInstructionResponse> {
|
||||
const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid);
|
||||
|
||||
if (!deviceDetails) {
|
||||
throw new NotFoundException('Device Not Found');
|
||||
}
|
||||
try {
|
||||
const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid);
|
||||
|
||||
if (!deviceDetails) {
|
||||
throw new NotFoundException('Device Not Found');
|
||||
}
|
||||
|
||||
const response = await this.getDeviceInstructionByDeviceIdTuya(
|
||||
deviceDetails.deviceTuyaUuid,
|
||||
);
|
||||
@ -623,6 +630,7 @@ export class DeviceService {
|
||||
status: deviceStatus.result[0].status,
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw new HttpException(
|
||||
'Error fetching device functions status',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
@ -778,22 +786,22 @@ export class DeviceService {
|
||||
);
|
||||
}
|
||||
}
|
||||
async getDevicesByUnitId(unitUuid: string) {
|
||||
async getDevicesBySpaceUuid(SpaceUuid: string) {
|
||||
try {
|
||||
const spaces = await this.spaceRepository.find({
|
||||
where: {
|
||||
parent: {
|
||||
uuid: unitUuid,
|
||||
uuid: SpaceUuid,
|
||||
},
|
||||
devicesSpaceEntity: {
|
||||
devices: {
|
||||
isActive: true,
|
||||
},
|
||||
},
|
||||
relations: ['devicesSpaceEntity', 'devicesSpaceEntity.productDevice'],
|
||||
relations: ['devices', 'devices.productDevice'],
|
||||
});
|
||||
|
||||
const devices = spaces.flatMap((space) => {
|
||||
return space.devicesSpaceEntity.map((device) => device);
|
||||
return space.devices.map((device) => device);
|
||||
});
|
||||
|
||||
const devicesData = await Promise.all(
|
||||
@ -814,7 +822,7 @@ export class DeviceService {
|
||||
return devicesData;
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
'This unit does not have any devices',
|
||||
'This space does not have any devices',
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
@ -825,11 +833,13 @@ export class DeviceService {
|
||||
where: { isActive: true },
|
||||
relations: [
|
||||
'spaceDevice.parent',
|
||||
'spaceDevice.community',
|
||||
'productDevice',
|
||||
'permission',
|
||||
'permission.permissionType',
|
||||
],
|
||||
});
|
||||
|
||||
const devicesData = await Promise.allSettled(
|
||||
devices.map(async (device) => {
|
||||
let battery = null;
|
||||
@ -874,20 +884,24 @@ export class DeviceService {
|
||||
battery = batteryStatus.value;
|
||||
}
|
||||
}
|
||||
const spaceDevice = device?.spaceDevice;
|
||||
const parentDevice = spaceDevice?.parent;
|
||||
|
||||
const spaceHierarchy = await this.getFullSpaceHierarchy(
|
||||
device?.spaceDevice,
|
||||
);
|
||||
const orderedHierarchy = spaceHierarchy.reverse();
|
||||
|
||||
return {
|
||||
room: {
|
||||
uuid: spaceDevice?.uuid,
|
||||
name: spaceDevice?.spaceName,
|
||||
},
|
||||
unit: {
|
||||
uuid: parentDevice?.uuid,
|
||||
name: parentDevice?.spaceName,
|
||||
},
|
||||
spaces: orderedHierarchy.map((space) => ({
|
||||
uuid: space.uuid,
|
||||
spaceName: space.spaceName,
|
||||
})),
|
||||
productUuid: device.productDevice.uuid,
|
||||
productType: device.productDevice.prodType,
|
||||
permissionType: device.permission[0].permissionType.type,
|
||||
community: {
|
||||
uuid: device.spaceDevice.community.uuid,
|
||||
name: device.spaceDevice.community.name,
|
||||
},
|
||||
// permissionType: device.permission[0].permissionType.type,
|
||||
...(await this.getDeviceDetailsByDeviceIdTuya(
|
||||
device.deviceTuyaUuid,
|
||||
)),
|
||||
@ -913,6 +927,7 @@ export class DeviceService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getDeviceLogs(deviceUuid: string, query: GetDeviceLogsDto) {
|
||||
try {
|
||||
const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid);
|
||||
@ -966,6 +981,40 @@ export class DeviceService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getFullSpaceHierarchy(
|
||||
space: SpaceEntity,
|
||||
): Promise<{ uuid: string; spaceName: string }[]> {
|
||||
try {
|
||||
// Fetch only the relevant spaces, starting with the target space
|
||||
const targetSpace = await this.spaceRepository.findOne({
|
||||
where: { uuid: space.uuid },
|
||||
relations: ['parent', 'children'],
|
||||
});
|
||||
|
||||
// Fetch only the ancestors of the target space
|
||||
const ancestors = await this.fetchAncestors(targetSpace);
|
||||
|
||||
// Optionally, fetch descendants if required
|
||||
const descendants = await this.fetchDescendants(targetSpace);
|
||||
|
||||
const fullHierarchy = [...ancestors, targetSpace, ...descendants].map(
|
||||
(space) => ({
|
||||
uuid: space.uuid,
|
||||
spaceName: space.spaceName,
|
||||
}),
|
||||
);
|
||||
|
||||
return fullHierarchy;
|
||||
} catch (error) {
|
||||
console.error('Error fetching space hierarchy:', error.message);
|
||||
throw new HttpException(
|
||||
'Error fetching space hierarchy',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getPowerClampInstructionStatus(powerClampUuid: string) {
|
||||
try {
|
||||
const deviceDetails = await this.getDeviceByDeviceUuid(powerClampUuid);
|
||||
@ -1043,4 +1092,48 @@ export class DeviceService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchAncestors(space: SpaceEntity): Promise<SpaceEntity[]> {
|
||||
const ancestors: SpaceEntity[] = [];
|
||||
|
||||
let currentSpace = space;
|
||||
while (currentSpace && currentSpace.parent) {
|
||||
// Fetch the parent space
|
||||
const parent = await this.spaceRepository.findOne({
|
||||
where: { uuid: currentSpace.parent.uuid },
|
||||
relations: ['parent'], // To continue fetching upwards
|
||||
});
|
||||
|
||||
if (parent) {
|
||||
ancestors.push(parent);
|
||||
currentSpace = parent;
|
||||
} else {
|
||||
currentSpace = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the ancestors in reverse order to have the root at the start
|
||||
return ancestors.reverse();
|
||||
}
|
||||
|
||||
private async fetchDescendants(space: SpaceEntity): Promise<SpaceEntity[]> {
|
||||
const descendants: SpaceEntity[] = [];
|
||||
|
||||
// Fetch the immediate children of the current space
|
||||
const children = await this.spaceRepository.find({
|
||||
where: { parent: { uuid: space.uuid } },
|
||||
relations: ['children'], // To continue fetching downwards
|
||||
});
|
||||
|
||||
for (const child of children) {
|
||||
// Add the child to the descendants list
|
||||
descendants.push(child);
|
||||
|
||||
// Recursively fetch the child's descendants
|
||||
const childDescendants = await this.fetchDescendants(child);
|
||||
descendants.push(...childDescendants);
|
||||
}
|
||||
|
||||
return descendants;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,104 +0,0 @@
|
||||
import { FloorService } from '../services/floor.service';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
HttpStatus,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { AddFloorDto, AddUserFloorDto } from '../dtos/add.floor.dto';
|
||||
import { GetFloorChildDto } from '../dtos/get.floor.dto';
|
||||
import { UpdateFloorNameDto } from '../dtos/update.floor.dto';
|
||||
import { CheckBuildingTypeGuard } from 'src/guards/building.type.guard';
|
||||
import { CheckUserFloorGuard } from 'src/guards/user.floor.guard';
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
import { AdminRoleGuard } from 'src/guards/admin.role.guard';
|
||||
import { FloorPermissionGuard } from 'src/guards/floor.permission.guard';
|
||||
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
|
||||
@ApiTags('Floor Module')
|
||||
@Controller({
|
||||
version: EnableDisableStatusEnum.ENABLED,
|
||||
path: SpaceType.FLOOR,
|
||||
})
|
||||
export class FloorController {
|
||||
constructor(private readonly floorService: FloorService) {}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, CheckBuildingTypeGuard)
|
||||
@Post()
|
||||
async addFloor(@Body() addFloorDto: AddFloorDto) {
|
||||
const floor = await this.floorService.addFloor(addFloorDto);
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'Floor added successfully',
|
||||
data: floor,
|
||||
};
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, FloorPermissionGuard)
|
||||
@Get(':floorUuid')
|
||||
async getFloorByUuid(@Param('floorUuid') floorUuid: string) {
|
||||
const floor = await this.floorService.getFloorByUuid(floorUuid);
|
||||
return floor;
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, FloorPermissionGuard)
|
||||
@Get('child/:floorUuid')
|
||||
async getFloorChildByUuid(
|
||||
@Param('floorUuid') floorUuid: string,
|
||||
@Query() query: GetFloorChildDto,
|
||||
) {
|
||||
const floor = await this.floorService.getFloorChildByUuid(floorUuid, query);
|
||||
return floor;
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, FloorPermissionGuard)
|
||||
@Get('parent/:floorUuid')
|
||||
async getFloorParentByUuid(@Param('floorUuid') floorUuid: string) {
|
||||
const floor = await this.floorService.getFloorParentByUuid(floorUuid);
|
||||
return floor;
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AdminRoleGuard, CheckUserFloorGuard)
|
||||
@Post('user')
|
||||
async addUserFloor(@Body() addUserFloorDto: AddUserFloorDto) {
|
||||
await this.floorService.addUserFloor(addUserFloorDto);
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'user floor added successfully',
|
||||
};
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('user/:userUuid')
|
||||
async getFloorsByUserId(@Param('userUuid') userUuid: string) {
|
||||
return await this.floorService.getFloorsByUserId(userUuid);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, FloorPermissionGuard)
|
||||
@Put(':floorUuid')
|
||||
async renameFloorByUuid(
|
||||
@Param('floorUuid') floorUuid: string,
|
||||
@Body() updateFloorNameDto: UpdateFloorNameDto,
|
||||
) {
|
||||
const floor = await this.floorService.renameFloorByUuid(
|
||||
floorUuid,
|
||||
updateFloorNameDto,
|
||||
);
|
||||
return floor;
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from './floor.controller';
|
||||
@ -1,42 +0,0 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class AddFloorDto {
|
||||
@ApiProperty({
|
||||
description: 'floorName',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public floorName: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'buildingUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public buildingUuid: string;
|
||||
constructor(dto: Partial<AddFloorDto>) {
|
||||
Object.assign(this, dto);
|
||||
}
|
||||
}
|
||||
export class AddUserFloorDto {
|
||||
@ApiProperty({
|
||||
description: 'floorUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public floorUuid: string;
|
||||
@ApiProperty({
|
||||
description: 'userUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public userUuid: string;
|
||||
constructor(dto: Partial<AddUserFloorDto>) {
|
||||
Object.assign(this, dto);
|
||||
}
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
import { BooleanValues } from '@app/common/constants/boolean-values.enum';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Transform } from 'class-transformer';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
|
||||
export class GetFloorDto {
|
||||
@ApiProperty({
|
||||
description: 'floorUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public floorUuid: string;
|
||||
}
|
||||
|
||||
export class GetFloorChildDto {
|
||||
@ApiProperty({ example: 1, description: 'Page number', required: true })
|
||||
@IsInt({ message: 'Page must be a number' })
|
||||
@Min(1, { message: 'Page must not be less than 1' })
|
||||
@IsNotEmpty()
|
||||
public page: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: 10,
|
||||
description: 'Number of items per page',
|
||||
required: true,
|
||||
})
|
||||
@IsInt({ message: 'Page size must be a number' })
|
||||
@Min(1, { message: 'Page size must not be less than 1' })
|
||||
@IsNotEmpty()
|
||||
public pageSize: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: true,
|
||||
description: 'Flag to determine whether to fetch full hierarchy',
|
||||
required: false,
|
||||
default: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@Transform((value) => {
|
||||
return value.obj.includeSubSpaces === BooleanValues.TRUE;
|
||||
})
|
||||
public includeSubSpaces: boolean = false;
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from './add.floor.dto';
|
||||
@ -1,16 +0,0 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class UpdateFloorNameDto {
|
||||
@ApiProperty({
|
||||
description: 'floorName',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public floorName: string;
|
||||
|
||||
constructor(dto: Partial<UpdateFloorNameDto>) {
|
||||
Object.assign(this, dto);
|
||||
}
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { FloorService } from './services/floor.service';
|
||||
import { FloorController } from './controllers/floor.controller';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { SpaceTypeRepository } from '@app/common/modules/space/repositories';
|
||||
import { UserSpaceRepository } from '@app/common/modules/user/repositories';
|
||||
import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module';
|
||||
import { UserRepository } from '@app/common/modules/user/repositories';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule],
|
||||
controllers: [FloorController],
|
||||
providers: [
|
||||
FloorService,
|
||||
SpaceRepository,
|
||||
SpaceTypeRepository,
|
||||
UserSpaceRepository,
|
||||
UserRepository,
|
||||
],
|
||||
exports: [FloorService],
|
||||
})
|
||||
export class FloorModule {}
|
||||
@ -1,32 +0,0 @@
|
||||
export interface GetFloorByUuidInterface {
|
||||
uuid: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface FloorChildInterface {
|
||||
uuid: string;
|
||||
name: string;
|
||||
type: string;
|
||||
totalCount?: number;
|
||||
children?: FloorChildInterface[];
|
||||
}
|
||||
export interface FloorParentInterface {
|
||||
uuid: string;
|
||||
name: string;
|
||||
type: string;
|
||||
parent?: FloorParentInterface;
|
||||
}
|
||||
export interface RenameFloorByUuidInterface {
|
||||
uuid: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface GetFloorByUserUuidInterface {
|
||||
uuid: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
@ -1,310 +0,0 @@
|
||||
import { GetFloorChildDto } from '../dtos/get.floor.dto';
|
||||
import { SpaceTypeRepository } from '../../../libs/common/src/modules/space/repositories/space.repository';
|
||||
import {
|
||||
Injectable,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { AddFloorDto, AddUserFloorDto } from '../dtos';
|
||||
import {
|
||||
FloorChildInterface,
|
||||
FloorParentInterface,
|
||||
GetFloorByUserUuidInterface,
|
||||
GetFloorByUuidInterface,
|
||||
RenameFloorByUuidInterface,
|
||||
} from '../interface/floor.interface';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities';
|
||||
import { UpdateFloorNameDto } from '../dtos/update.floor.dto';
|
||||
import { UserSpaceRepository } from '@app/common/modules/user/repositories';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
import { CommonErrorCodes } from '@app/common/constants/error-codes.enum';
|
||||
|
||||
@Injectable()
|
||||
export class FloorService {
|
||||
constructor(
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
private readonly spaceTypeRepository: SpaceTypeRepository,
|
||||
private readonly userSpaceRepository: UserSpaceRepository,
|
||||
) {}
|
||||
|
||||
async addFloor(addFloorDto: AddFloorDto) {
|
||||
try {
|
||||
const spaceType = await this.spaceTypeRepository.findOne({
|
||||
where: {
|
||||
type: SpaceType.FLOOR,
|
||||
},
|
||||
});
|
||||
|
||||
const floor = await this.spaceRepository.save({
|
||||
spaceName: addFloorDto.floorName,
|
||||
parent: { uuid: addFloorDto.buildingUuid },
|
||||
spaceType: { uuid: spaceType.uuid },
|
||||
});
|
||||
return floor;
|
||||
} catch (err) {
|
||||
throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getFloorByUuid(floorUuid: string): Promise<GetFloorByUuidInterface> {
|
||||
try {
|
||||
const floor = await this.spaceRepository.findOne({
|
||||
where: {
|
||||
uuid: floorUuid,
|
||||
spaceType: {
|
||||
type: SpaceType.FLOOR,
|
||||
},
|
||||
},
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
if (
|
||||
!floor ||
|
||||
!floor.spaceType ||
|
||||
floor.spaceType.type !== SpaceType.FLOOR
|
||||
) {
|
||||
throw new BadRequestException('Invalid floor UUID');
|
||||
}
|
||||
|
||||
return {
|
||||
uuid: floor.uuid,
|
||||
createdAt: floor.createdAt,
|
||||
updatedAt: floor.updatedAt,
|
||||
name: floor.spaceName,
|
||||
type: floor.spaceType.type,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Floor not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
async getFloorChildByUuid(
|
||||
floorUuid: string,
|
||||
getFloorChildDto: GetFloorChildDto,
|
||||
): Promise<FloorChildInterface> {
|
||||
try {
|
||||
const { includeSubSpaces, page, pageSize } = getFloorChildDto;
|
||||
|
||||
const space = await this.spaceRepository.findOneOrFail({
|
||||
where: { uuid: floorUuid },
|
||||
relations: ['children', 'spaceType'],
|
||||
});
|
||||
|
||||
if (
|
||||
!space ||
|
||||
!space.spaceType ||
|
||||
space.spaceType.type !== SpaceType.FLOOR
|
||||
) {
|
||||
throw new BadRequestException('Invalid floor UUID');
|
||||
}
|
||||
const totalCount = await this.spaceRepository.count({
|
||||
where: { parent: { uuid: space.uuid } },
|
||||
});
|
||||
|
||||
const children = await this.buildHierarchy(
|
||||
space,
|
||||
includeSubSpaces,
|
||||
page,
|
||||
pageSize,
|
||||
);
|
||||
|
||||
return {
|
||||
uuid: space.uuid,
|
||||
name: space.spaceName,
|
||||
type: space.spaceType.type,
|
||||
totalCount,
|
||||
children,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Floor not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async buildHierarchy(
|
||||
space: SpaceEntity,
|
||||
includeSubSpaces: boolean,
|
||||
page: number,
|
||||
pageSize: number,
|
||||
): Promise<FloorChildInterface[]> {
|
||||
const children = await this.spaceRepository.find({
|
||||
where: { parent: { uuid: space.uuid } },
|
||||
relations: ['spaceType'],
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
});
|
||||
|
||||
if (!children || children.length === 0 || !includeSubSpaces) {
|
||||
return children
|
||||
.filter(
|
||||
(child) =>
|
||||
child.spaceType.type !== SpaceType.FLOOR &&
|
||||
child.spaceType.type !== SpaceType.BUILDING &&
|
||||
child.spaceType.type !== SpaceType.COMMUNITY,
|
||||
) // Filter remaining floor and building and community types
|
||||
.map((child) => ({
|
||||
uuid: child.uuid,
|
||||
name: child.spaceName,
|
||||
type: child.spaceType.type,
|
||||
}));
|
||||
}
|
||||
|
||||
const childHierarchies = await Promise.all(
|
||||
children
|
||||
.filter(
|
||||
(child) =>
|
||||
child.spaceType.type !== SpaceType.FLOOR &&
|
||||
child.spaceType.type !== SpaceType.BUILDING &&
|
||||
child.spaceType.type !== SpaceType.COMMUNITY,
|
||||
) // Filter remaining floor and building and community types
|
||||
.map(async (child) => ({
|
||||
uuid: child.uuid,
|
||||
name: child.spaceName,
|
||||
type: child.spaceType.type,
|
||||
children: await this.buildHierarchy(child, true, 1, pageSize),
|
||||
})),
|
||||
);
|
||||
|
||||
return childHierarchies;
|
||||
}
|
||||
|
||||
async getFloorParentByUuid(floorUuid: string): Promise<FloorParentInterface> {
|
||||
try {
|
||||
const floor = await this.spaceRepository.findOne({
|
||||
where: {
|
||||
uuid: floorUuid,
|
||||
spaceType: {
|
||||
type: SpaceType.FLOOR,
|
||||
},
|
||||
},
|
||||
relations: ['spaceType', 'parent', 'parent.spaceType'],
|
||||
});
|
||||
if (
|
||||
!floor ||
|
||||
!floor.spaceType ||
|
||||
floor.spaceType.type !== SpaceType.FLOOR
|
||||
) {
|
||||
throw new BadRequestException('Invalid floor UUID');
|
||||
}
|
||||
|
||||
return {
|
||||
uuid: floor.uuid,
|
||||
name: floor.spaceName,
|
||||
type: floor.spaceType.type,
|
||||
parent: {
|
||||
uuid: floor.parent.uuid,
|
||||
name: floor.parent.spaceName,
|
||||
type: floor.parent.spaceType.type,
|
||||
},
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Floor not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getFloorsByUserId(
|
||||
userUuid: string,
|
||||
): Promise<GetFloorByUserUuidInterface[]> {
|
||||
try {
|
||||
const floors = await this.userSpaceRepository.find({
|
||||
relations: ['space', 'space.spaceType'],
|
||||
where: {
|
||||
user: { uuid: userUuid },
|
||||
space: { spaceType: { type: SpaceType.FLOOR } },
|
||||
},
|
||||
});
|
||||
|
||||
if (floors.length === 0) {
|
||||
throw new HttpException(
|
||||
'this user has no floors',
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
const spaces = floors.map((floor) => ({
|
||||
uuid: floor.space.uuid,
|
||||
name: floor.space.spaceName,
|
||||
type: floor.space.spaceType.type,
|
||||
}));
|
||||
|
||||
return spaces;
|
||||
} catch (err) {
|
||||
if (err instanceof HttpException) {
|
||||
throw err;
|
||||
} else {
|
||||
throw new HttpException('user not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
async addUserFloor(addUserFloorDto: AddUserFloorDto) {
|
||||
try {
|
||||
await this.userSpaceRepository.save({
|
||||
user: { uuid: addUserFloorDto.userUuid },
|
||||
space: { uuid: addUserFloorDto.floorUuid },
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.code === CommonErrorCodes.DUPLICATE_ENTITY) {
|
||||
throw new HttpException(
|
||||
'User already belongs to this floor',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
throw new HttpException(
|
||||
err.message || 'Internal Server Error',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
async renameFloorByUuid(
|
||||
floorUuid: string,
|
||||
updateFloorDto: UpdateFloorNameDto,
|
||||
): Promise<RenameFloorByUuidInterface> {
|
||||
try {
|
||||
const floor = await this.spaceRepository.findOneOrFail({
|
||||
where: { uuid: floorUuid },
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
|
||||
if (
|
||||
!floor ||
|
||||
!floor.spaceType ||
|
||||
floor.spaceType.type !== SpaceType.FLOOR
|
||||
) {
|
||||
throw new BadRequestException('Invalid floor UUID');
|
||||
}
|
||||
|
||||
await this.spaceRepository.update(
|
||||
{ uuid: floorUuid },
|
||||
{ spaceName: updateFloorDto.floorName },
|
||||
);
|
||||
|
||||
// Fetch the updated floor
|
||||
const updatedFloor = await this.spaceRepository.findOneOrFail({
|
||||
where: { uuid: floorUuid },
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
|
||||
return {
|
||||
uuid: updatedFloor.uuid,
|
||||
name: updatedFloor.spaceName,
|
||||
type: updatedFloor.spaceType.type,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Floor not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from './floor.service';
|
||||
@ -14,13 +14,14 @@ export class GroupController {
|
||||
constructor(private readonly groupService: GroupService) {}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, UnitPermissionGuard)
|
||||
@Get(':unitUuid')
|
||||
async getGroupsBySpaceUuid(@Param('unitUuid') unitUuid: string) {
|
||||
return await this.groupService.getGroupsByUnitUuid(unitUuid);
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get(':spaceUuid')
|
||||
async getGroupsBySpaceUuid(@Param('spaceUuid') spaceUuid: string) {
|
||||
return await this.groupService.getGroupsByUnitUuid(spaceUuid);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, UnitPermissionGuard)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get(':unitUuid/devices/:groupName')
|
||||
async getUnitDevicesByGroupName(
|
||||
@Param('unitUuid') unitUuid: string,
|
||||
|
||||
@ -31,17 +31,13 @@ export class GroupService {
|
||||
try {
|
||||
const spaces = await this.spaceRepository.find({
|
||||
where: {
|
||||
parent: {
|
||||
uuid: unitUuid,
|
||||
},
|
||||
uuid: unitUuid,
|
||||
},
|
||||
relations: ['devicesSpaceEntity', 'devicesSpaceEntity.productDevice'],
|
||||
relations: ['devices', 'devices.productDevice'],
|
||||
});
|
||||
|
||||
const groupNames = spaces.flatMap((space) => {
|
||||
return space.devicesSpaceEntity.map(
|
||||
(device) => device.productDevice.prodType,
|
||||
);
|
||||
return space.devices.map((device) => device.productDevice.prodType);
|
||||
});
|
||||
|
||||
const uniqueGroupNames = [...new Set(groupNames)];
|
||||
@ -82,7 +78,7 @@ export class GroupService {
|
||||
parent: {
|
||||
uuid: unitUuid,
|
||||
},
|
||||
devicesSpaceEntity: {
|
||||
devices: {
|
||||
productDevice: {
|
||||
prodType: groupName,
|
||||
},
|
||||
@ -95,18 +91,18 @@ export class GroupService {
|
||||
},
|
||||
},
|
||||
relations: [
|
||||
'devicesSpaceEntity',
|
||||
'devicesSpaceEntity.productDevice',
|
||||
'devicesSpaceEntity.spaceDevice',
|
||||
'devicesSpaceEntity.permission',
|
||||
'devicesSpaceEntity.permission.permissionType',
|
||||
'devices',
|
||||
'devices.productDevice',
|
||||
'devices.spaceDevice',
|
||||
'devices.permission',
|
||||
'devices.permission.permissionType',
|
||||
],
|
||||
});
|
||||
|
||||
const devices = await Promise.all(
|
||||
spaces.flatMap(async (space) => {
|
||||
return await Promise.all(
|
||||
space.devicesSpaceEntity.map(async (device) => {
|
||||
space.devices.map(async (device) => {
|
||||
const deviceDetails = await this.getDeviceDetailsByDeviceIdTuya(
|
||||
device.deviceTuyaUuid,
|
||||
);
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
import { SpacePermissionService } from '@app/common/helper/services/space.permission.service';
|
||||
import {
|
||||
BadRequestException,
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class BuildingPermissionGuard implements CanActivate {
|
||||
constructor(private readonly permissionService: SpacePermissionService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const req = context.switchToHttp().getRequest();
|
||||
|
||||
try {
|
||||
const { buildingUuid } = req.params;
|
||||
const { user } = req;
|
||||
|
||||
if (!buildingUuid) {
|
||||
throw new BadRequestException('buildingUuid is required');
|
||||
}
|
||||
|
||||
await this.permissionService.checkUserPermission(
|
||||
buildingUuid,
|
||||
user.uuid,
|
||||
SpaceType.BUILDING,
|
||||
);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import {
|
||||
Injectable,
|
||||
CanActivate,
|
||||
HttpStatus,
|
||||
BadRequestException,
|
||||
ExecutionContext,
|
||||
} from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class CheckBuildingTypeGuard implements CanActivate {
|
||||
constructor(private readonly spaceRepository: SpaceRepository) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const req = context.switchToHttp().getRequest();
|
||||
|
||||
try {
|
||||
const { floorName, buildingUuid } = req.body;
|
||||
|
||||
if (!floorName) {
|
||||
throw new BadRequestException('floorName is required');
|
||||
}
|
||||
|
||||
if (!buildingUuid) {
|
||||
throw new BadRequestException('buildingUuid is required');
|
||||
}
|
||||
|
||||
await this.checkBuildingIsBuildingType(buildingUuid);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.handleGuardError(error, context);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async checkBuildingIsBuildingType(buildingUuid: string) {
|
||||
const buildingData = await this.spaceRepository.findOne({
|
||||
where: { uuid: buildingUuid },
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
if (
|
||||
!buildingData ||
|
||||
!buildingData.spaceType ||
|
||||
buildingData.spaceType.type !== SpaceType.BUILDING
|
||||
) {
|
||||
throw new BadRequestException('Invalid building UUID');
|
||||
}
|
||||
}
|
||||
|
||||
private handleGuardError(error: Error, context: ExecutionContext) {
|
||||
const response = context.switchToHttp().getResponse();
|
||||
console.error(error);
|
||||
|
||||
if (error instanceof BadRequestException) {
|
||||
response
|
||||
.status(HttpStatus.BAD_REQUEST)
|
||||
.json({ statusCode: HttpStatus.BAD_REQUEST, message: error.message });
|
||||
} else {
|
||||
response.status(HttpStatus.NOT_FOUND).json({
|
||||
statusCode: HttpStatus.NOT_FOUND,
|
||||
message: 'Building not found',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
import { SpacePermissionService } from '@app/common/helper/services/space.permission.service';
|
||||
import { RoleType } from '@app/common/constants/role.type.enum';
|
||||
import { CommunityPermissionService } from '@app/common/helper/services/community.permission.service';
|
||||
import {
|
||||
BadRequestException,
|
||||
CanActivate,
|
||||
@ -9,7 +9,7 @@ import {
|
||||
|
||||
@Injectable()
|
||||
export class CommunityPermissionGuard implements CanActivate {
|
||||
constructor(private readonly permissionService: SpacePermissionService) {}
|
||||
constructor(private readonly permissionService: CommunityPermissionService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const req = context.switchToHttp().getRequest();
|
||||
@ -18,15 +18,22 @@ export class CommunityPermissionGuard implements CanActivate {
|
||||
const { communityUuid } = req.params;
|
||||
const { user } = req;
|
||||
|
||||
if (
|
||||
user &&
|
||||
user.roles &&
|
||||
user.roles.some(
|
||||
(role) =>
|
||||
role.type === RoleType.ADMIN || role.type === RoleType.SUPER_ADMIN,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!communityUuid) {
|
||||
throw new BadRequestException('communityUuid is required');
|
||||
}
|
||||
|
||||
await this.permissionService.checkUserPermission(
|
||||
communityUuid,
|
||||
user.uuid,
|
||||
SpaceType.COMMUNITY,
|
||||
);
|
||||
await this.permissionService.checkUserPermission(communityUuid);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
|
||||
@ -1,68 +0,0 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
|
||||
@Injectable()
|
||||
export class CheckCommunityTypeGuard implements CanActivate {
|
||||
constructor(private readonly spaceRepository: SpaceRepository) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const req = context.switchToHttp().getRequest();
|
||||
|
||||
try {
|
||||
const { buildingName, communityUuid } = req.body;
|
||||
|
||||
if (!buildingName) {
|
||||
throw new BadRequestException('buildingName is required');
|
||||
}
|
||||
|
||||
if (!communityUuid) {
|
||||
throw new BadRequestException('communityUuid is required');
|
||||
}
|
||||
|
||||
await this.checkCommunityIsCommunityType(communityUuid);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.handleGuardError(error, context);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async checkCommunityIsCommunityType(communityUuid: string) {
|
||||
const communityData = await this.spaceRepository.findOne({
|
||||
where: { uuid: communityUuid },
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
|
||||
if (
|
||||
!communityData ||
|
||||
!communityData.spaceType ||
|
||||
communityData.spaceType.type !== SpaceType.COMMUNITY
|
||||
) {
|
||||
throw new BadRequestException('Invalid community UUID');
|
||||
}
|
||||
}
|
||||
|
||||
private handleGuardError(error: Error, context: ExecutionContext) {
|
||||
const response = context.switchToHttp().getResponse();
|
||||
console.error(error);
|
||||
|
||||
if (error instanceof BadRequestException) {
|
||||
response
|
||||
.status(HttpStatus.BAD_REQUEST)
|
||||
.json({ statusCode: HttpStatus.BAD_REQUEST, message: error.message });
|
||||
} else {
|
||||
response.status(HttpStatus.NOT_FOUND).json({
|
||||
statusCode: HttpStatus.NOT_FOUND,
|
||||
message: 'Community not found',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
import { SpacePermissionService } from '@app/common/helper/services/space.permission.service';
|
||||
import {
|
||||
BadRequestException,
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class FloorPermissionGuard implements CanActivate {
|
||||
constructor(private readonly permissionService: SpacePermissionService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const req = context.switchToHttp().getRequest();
|
||||
|
||||
try {
|
||||
const { floorUuid } = req.params;
|
||||
const { user } = req;
|
||||
|
||||
if (!floorUuid) {
|
||||
throw new BadRequestException('floorUuid is required');
|
||||
}
|
||||
|
||||
await this.permissionService.checkUserPermission(
|
||||
floorUuid,
|
||||
user.uuid,
|
||||
SpaceType.FLOOR,
|
||||
);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import {
|
||||
Injectable,
|
||||
@ -37,14 +36,9 @@ export class CheckFloorTypeGuard implements CanActivate {
|
||||
async checkFloorIsFloorType(floorUuid: string) {
|
||||
const floorData = await this.spaceRepository.findOne({
|
||||
where: { uuid: floorUuid },
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
|
||||
if (
|
||||
!floorData ||
|
||||
!floorData.spaceType ||
|
||||
floorData.spaceType.type !== SpaceType.FLOOR
|
||||
) {
|
||||
if (!floorData) {
|
||||
throw new BadRequestException('Invalid floor UUID');
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,6 @@ import {
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
||||
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
|
||||
@Injectable()
|
||||
export class CheckRoomGuard implements CanActivate {
|
||||
@ -43,9 +42,6 @@ export class CheckRoomGuard implements CanActivate {
|
||||
const room = await this.spaceRepository.findOne({
|
||||
where: {
|
||||
uuid: roomUuid,
|
||||
spaceType: {
|
||||
type: SpaceType.ROOM,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!room) {
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
import { SpacePermissionService } from '@app/common/helper/services/space.permission.service';
|
||||
import {
|
||||
BadRequestException,
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class RoomPermissionGuard implements CanActivate {
|
||||
constructor(private readonly permissionService: SpacePermissionService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const req = context.switchToHttp().getRequest();
|
||||
|
||||
try {
|
||||
const { roomUuid } = req.params;
|
||||
const { user } = req;
|
||||
|
||||
if (!roomUuid) {
|
||||
throw new BadRequestException('roomUuid is required');
|
||||
}
|
||||
|
||||
await this.permissionService.checkUserPermission(
|
||||
roomUuid,
|
||||
user.uuid,
|
||||
SpaceType.ROOM,
|
||||
);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
import { SpacePermissionService } from '@app/common/helper/services/space.permission.service';
|
||||
import {
|
||||
BadRequestException,
|
||||
@ -22,11 +21,7 @@ export class UnitPermissionGuard implements CanActivate {
|
||||
throw new BadRequestException('unitUuid is required');
|
||||
}
|
||||
|
||||
await this.permissionService.checkUserPermission(
|
||||
unitUuid,
|
||||
user.uuid,
|
||||
SpaceType.UNIT,
|
||||
);
|
||||
await this.permissionService.checkUserPermission(unitUuid, user.uuid);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import {
|
||||
Injectable,
|
||||
@ -34,18 +33,13 @@ export class CheckUnitTypeGuard implements CanActivate {
|
||||
}
|
||||
}
|
||||
|
||||
async checkFloorIsFloorType(unitUuid: string) {
|
||||
const unitData = await this.spaceRepository.findOne({
|
||||
where: { uuid: unitUuid },
|
||||
relations: ['spaceType'],
|
||||
async checkFloorIsFloorType(spaceUuid: string) {
|
||||
const space = await this.spaceRepository.findOne({
|
||||
where: { uuid: spaceUuid },
|
||||
});
|
||||
|
||||
if (
|
||||
!unitData ||
|
||||
!unitData.spaceType ||
|
||||
unitData.spaceType.type !== SpaceType.UNIT
|
||||
) {
|
||||
throw new BadRequestException('Invalid unit UUID');
|
||||
if (!space) {
|
||||
throw new BadRequestException(`Invalid space UUID ${spaceUuid}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,71 +0,0 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
||||
import { UserRepository } from '@app/common/modules/user/repositories';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
|
||||
@Injectable()
|
||||
export class CheckUserBuildingGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
private readonly userRepository: UserRepository,
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const req = context.switchToHttp().getRequest();
|
||||
|
||||
try {
|
||||
const { userUuid, buildingUuid } = req.body;
|
||||
|
||||
await this.checkUserIsFound(userUuid);
|
||||
|
||||
await this.checkBuildingIsFound(buildingUuid);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.handleGuardError(error, context);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async checkUserIsFound(userUuid: string) {
|
||||
const userData = await this.userRepository.findOne({
|
||||
where: { uuid: userUuid },
|
||||
});
|
||||
if (!userData) {
|
||||
throw new NotFoundException('User not found');
|
||||
}
|
||||
}
|
||||
|
||||
private async checkBuildingIsFound(spaceUuid: string) {
|
||||
const spaceData = await this.spaceRepository.findOne({
|
||||
where: { uuid: spaceUuid, spaceType: { type: SpaceType.BUILDING } },
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
if (!spaceData) {
|
||||
throw new NotFoundException('Building not found');
|
||||
}
|
||||
}
|
||||
|
||||
private handleGuardError(error: Error, context: ExecutionContext) {
|
||||
const response = context.switchToHttp().getResponse();
|
||||
if (
|
||||
error instanceof BadRequestException ||
|
||||
error instanceof NotFoundException
|
||||
) {
|
||||
response
|
||||
.status(HttpStatus.NOT_FOUND)
|
||||
.json({ statusCode: HttpStatus.NOT_FOUND, message: error.message });
|
||||
} else {
|
||||
response.status(HttpStatus.BAD_REQUEST).json({
|
||||
statusCode: HttpStatus.BAD_REQUEST,
|
||||
message: 'invalid userUuid or buildingUuid',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,6 @@ import {
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
||||
import { UserRepository } from '@app/common/modules/user/repositories';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
|
||||
@Injectable()
|
||||
export class CheckUserCommunityGuard implements CanActivate {
|
||||
@ -44,8 +43,7 @@ export class CheckUserCommunityGuard implements CanActivate {
|
||||
|
||||
private async checkCommunityIsFound(spaceUuid: string) {
|
||||
const spaceData = await this.spaceRepository.findOne({
|
||||
where: { uuid: spaceUuid, spaceType: { type: SpaceType.COMMUNITY } },
|
||||
relations: ['spaceType'],
|
||||
where: { uuid: spaceUuid },
|
||||
});
|
||||
if (!spaceData) {
|
||||
throw new NotFoundException('Community not found');
|
||||
|
||||
@ -1,71 +0,0 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
||||
import { UserRepository } from '@app/common/modules/user/repositories';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
|
||||
@Injectable()
|
||||
export class CheckUserFloorGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
private readonly userRepository: UserRepository,
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const req = context.switchToHttp().getRequest();
|
||||
|
||||
try {
|
||||
const { userUuid, floorUuid } = req.body;
|
||||
|
||||
await this.checkUserIsFound(userUuid);
|
||||
|
||||
await this.checkFloorIsFound(floorUuid);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.handleGuardError(error, context);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async checkUserIsFound(userUuid: string) {
|
||||
const userData = await this.userRepository.findOne({
|
||||
where: { uuid: userUuid },
|
||||
});
|
||||
if (!userData) {
|
||||
throw new NotFoundException('User not found');
|
||||
}
|
||||
}
|
||||
|
||||
private async checkFloorIsFound(spaceUuid: string) {
|
||||
const spaceData = await this.spaceRepository.findOne({
|
||||
where: { uuid: spaceUuid, spaceType: { type: SpaceType.FLOOR } },
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
if (!spaceData) {
|
||||
throw new NotFoundException('Floor not found');
|
||||
}
|
||||
}
|
||||
|
||||
private handleGuardError(error: Error, context: ExecutionContext) {
|
||||
const response = context.switchToHttp().getResponse();
|
||||
if (
|
||||
error instanceof BadRequestException ||
|
||||
error instanceof NotFoundException
|
||||
) {
|
||||
response
|
||||
.status(HttpStatus.NOT_FOUND)
|
||||
.json({ statusCode: HttpStatus.NOT_FOUND, message: error.message });
|
||||
} else {
|
||||
response.status(HttpStatus.BAD_REQUEST).json({
|
||||
statusCode: HttpStatus.BAD_REQUEST,
|
||||
message: 'invalid userUuid or floorUuid',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,71 +0,0 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
||||
import { UserRepository } from '@app/common/modules/user/repositories';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
|
||||
@Injectable()
|
||||
export class CheckUserRoomGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
private readonly userRepository: UserRepository,
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const req = context.switchToHttp().getRequest();
|
||||
|
||||
try {
|
||||
const { userUuid, roomUuid } = req.body;
|
||||
|
||||
await this.checkUserIsFound(userUuid);
|
||||
|
||||
await this.checkRoomIsFound(roomUuid);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.handleGuardError(error, context);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async checkUserIsFound(userUuid: string) {
|
||||
const userData = await this.userRepository.findOne({
|
||||
where: { uuid: userUuid },
|
||||
});
|
||||
if (!userData) {
|
||||
throw new NotFoundException('User not found');
|
||||
}
|
||||
}
|
||||
|
||||
private async checkRoomIsFound(spaceUuid: string) {
|
||||
const spaceData = await this.spaceRepository.findOne({
|
||||
where: { uuid: spaceUuid, spaceType: { type: SpaceType.ROOM } },
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
if (!spaceData) {
|
||||
throw new NotFoundException('Room not found');
|
||||
}
|
||||
}
|
||||
|
||||
private handleGuardError(error: Error, context: ExecutionContext) {
|
||||
const response = context.switchToHttp().getResponse();
|
||||
if (
|
||||
error instanceof BadRequestException ||
|
||||
error instanceof NotFoundException
|
||||
) {
|
||||
response
|
||||
.status(HttpStatus.NOT_FOUND)
|
||||
.json({ statusCode: HttpStatus.NOT_FOUND, message: error.message });
|
||||
} else {
|
||||
response.status(HttpStatus.BAD_REQUEST).json({
|
||||
statusCode: HttpStatus.BAD_REQUEST,
|
||||
message: 'invalid userUuid or roomUuid',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,71 +0,0 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
||||
import { UserRepository } from '@app/common/modules/user/repositories';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
|
||||
@Injectable()
|
||||
export class CheckUserUnitGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
private readonly userRepository: UserRepository,
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const req = context.switchToHttp().getRequest();
|
||||
|
||||
try {
|
||||
const { userUuid, unitUuid } = req.body;
|
||||
|
||||
await this.checkUserIsFound(userUuid);
|
||||
|
||||
await this.checkUnitIsFound(unitUuid);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.handleGuardError(error, context);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async checkUserIsFound(userUuid: string) {
|
||||
const userData = await this.userRepository.findOne({
|
||||
where: { uuid: userUuid },
|
||||
});
|
||||
if (!userData) {
|
||||
throw new NotFoundException('User not found');
|
||||
}
|
||||
}
|
||||
|
||||
private async checkUnitIsFound(spaceUuid: string) {
|
||||
const spaceData = await this.spaceRepository.findOne({
|
||||
where: { uuid: spaceUuid, spaceType: { type: SpaceType.UNIT } },
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
if (!spaceData) {
|
||||
throw new NotFoundException('Unit not found');
|
||||
}
|
||||
}
|
||||
|
||||
private handleGuardError(error: Error, context: ExecutionContext) {
|
||||
const response = context.switchToHttp().getResponse();
|
||||
if (
|
||||
error instanceof BadRequestException ||
|
||||
error instanceof NotFoundException
|
||||
) {
|
||||
response
|
||||
.status(HttpStatus.NOT_FOUND)
|
||||
.json({ statusCode: HttpStatus.NOT_FOUND, message: error.message });
|
||||
} else {
|
||||
response.status(HttpStatus.BAD_REQUEST).json({
|
||||
statusCode: HttpStatus.BAD_REQUEST,
|
||||
message: 'invalid userUuid or unitUuid',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from './room.controller';
|
||||
@ -1,90 +0,0 @@
|
||||
import { RoomService } from '../services/room.service';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
HttpStatus,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { AddRoomDto, AddUserRoomDto } from '../dtos/add.room.dto';
|
||||
import { UpdateRoomNameDto } from '../dtos/update.room.dto';
|
||||
import { CheckUnitTypeGuard } from 'src/guards/unit.type.guard';
|
||||
import { CheckUserRoomGuard } from 'src/guards/user.room.guard';
|
||||
import { AdminRoleGuard } from 'src/guards/admin.role.guard';
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
import { RoomPermissionGuard } from 'src/guards/room.permission.guard';
|
||||
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
|
||||
@ApiTags('Room Module')
|
||||
@Controller({
|
||||
version: EnableDisableStatusEnum.ENABLED,
|
||||
path: SpaceType.ROOM,
|
||||
})
|
||||
export class RoomController {
|
||||
constructor(private readonly roomService: RoomService) {}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, CheckUnitTypeGuard)
|
||||
@Post()
|
||||
async addRoom(@Body() addRoomDto: AddRoomDto) {
|
||||
const room = await this.roomService.addRoom(addRoomDto);
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'Room added successfully',
|
||||
data: room,
|
||||
};
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, RoomPermissionGuard)
|
||||
@Get(':roomUuid')
|
||||
async getRoomByUuid(@Param('roomUuid') roomUuid: string) {
|
||||
const room = await this.roomService.getRoomByUuid(roomUuid);
|
||||
return room;
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, RoomPermissionGuard)
|
||||
@Get('parent/:roomUuid')
|
||||
async getRoomParentByUuid(@Param('roomUuid') roomUuid: string) {
|
||||
const room = await this.roomService.getRoomParentByUuid(roomUuid);
|
||||
return room;
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AdminRoleGuard, CheckUserRoomGuard)
|
||||
@Post('user')
|
||||
async addUserRoom(@Body() addUserRoomDto: AddUserRoomDto) {
|
||||
await this.roomService.addUserRoom(addUserRoomDto);
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'user room added successfully',
|
||||
};
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('user/:userUuid')
|
||||
async getRoomsByUserId(@Param('userUuid') userUuid: string) {
|
||||
return await this.roomService.getRoomsByUserId(userUuid);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, RoomPermissionGuard)
|
||||
@Put(':roomUuid')
|
||||
async renameRoomByUuid(
|
||||
@Param('roomUuid') roomUuid: string,
|
||||
@Body() updateRoomNameDto: UpdateRoomNameDto,
|
||||
) {
|
||||
const room = await this.roomService.renameRoomByUuid(
|
||||
roomUuid,
|
||||
updateRoomNameDto,
|
||||
);
|
||||
return room;
|
||||
}
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class AddRoomDto {
|
||||
@ApiProperty({
|
||||
description: 'roomName',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public roomName: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'unitUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public unitUuid: string;
|
||||
constructor(dto: Partial<AddRoomDto>) {
|
||||
Object.assign(this, dto);
|
||||
}
|
||||
}
|
||||
export class AddUserRoomDto {
|
||||
@ApiProperty({
|
||||
description: 'roomUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public roomUuid: string;
|
||||
@ApiProperty({
|
||||
description: 'userUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public userUuid: string;
|
||||
constructor(dto: Partial<AddUserRoomDto>) {
|
||||
Object.assign(this, dto);
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from './add.room.dto';
|
||||
@ -1,16 +0,0 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class UpdateRoomNameDto {
|
||||
@ApiProperty({
|
||||
description: 'roomName',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public roomName: string;
|
||||
|
||||
constructor(dto: Partial<UpdateRoomNameDto>) {
|
||||
Object.assign(this, dto);
|
||||
}
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
export interface GetRoomByUuidInterface {
|
||||
uuid: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface RoomParentInterface {
|
||||
uuid: string;
|
||||
name: string;
|
||||
type: string;
|
||||
parent?: RoomParentInterface;
|
||||
}
|
||||
export interface RenameRoomByUuidInterface {
|
||||
uuid: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
export interface GetRoomByUserUuidInterface {
|
||||
uuid: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { RoomService } from './services/room.service';
|
||||
import { RoomController } from './controllers/room.controller';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { SpaceTypeRepository } from '@app/common/modules/space/repositories';
|
||||
import { UserSpaceRepository } from '@app/common/modules/user/repositories';
|
||||
import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module';
|
||||
import { UserRepository } from '@app/common/modules/user/repositories';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule],
|
||||
controllers: [RoomController],
|
||||
providers: [
|
||||
RoomService,
|
||||
SpaceRepository,
|
||||
SpaceTypeRepository,
|
||||
UserSpaceRepository,
|
||||
UserRepository,
|
||||
],
|
||||
exports: [RoomService],
|
||||
})
|
||||
export class RoomModule {}
|
||||
@ -1 +0,0 @@
|
||||
export * from './room.service';
|
||||
@ -1,200 +0,0 @@
|
||||
import { SpaceTypeRepository } from '../../../libs/common/src/modules/space/repositories/space.repository';
|
||||
import {
|
||||
Injectable,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { AddRoomDto, AddUserRoomDto } from '../dtos';
|
||||
import {
|
||||
RoomParentInterface,
|
||||
GetRoomByUuidInterface,
|
||||
RenameRoomByUuidInterface,
|
||||
GetRoomByUserUuidInterface,
|
||||
} from '../interface/room.interface';
|
||||
import { UpdateRoomNameDto } from '../dtos/update.room.dto';
|
||||
import { UserSpaceRepository } from '@app/common/modules/user/repositories';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
import { CommonErrorCodes } from '@app/common/constants/error-codes.enum';
|
||||
|
||||
@Injectable()
|
||||
export class RoomService {
|
||||
constructor(
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
private readonly spaceTypeRepository: SpaceTypeRepository,
|
||||
private readonly userSpaceRepository: UserSpaceRepository,
|
||||
) {}
|
||||
|
||||
async addRoom(addRoomDto: AddRoomDto) {
|
||||
try {
|
||||
const spaceType = await this.spaceTypeRepository.findOne({
|
||||
where: {
|
||||
type: SpaceType.ROOM,
|
||||
},
|
||||
});
|
||||
|
||||
const room = await this.spaceRepository.save({
|
||||
spaceName: addRoomDto.roomName,
|
||||
parent: { uuid: addRoomDto.unitUuid },
|
||||
spaceType: { uuid: spaceType.uuid },
|
||||
});
|
||||
return room;
|
||||
} catch (err) {
|
||||
throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getRoomByUuid(roomUuid: string): Promise<GetRoomByUuidInterface> {
|
||||
try {
|
||||
const room = await this.spaceRepository.findOne({
|
||||
where: {
|
||||
uuid: roomUuid,
|
||||
spaceType: {
|
||||
type: SpaceType.ROOM,
|
||||
},
|
||||
},
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
if (!room || !room.spaceType || room.spaceType.type !== SpaceType.ROOM) {
|
||||
throw new BadRequestException('Invalid room UUID');
|
||||
}
|
||||
|
||||
return {
|
||||
uuid: room.uuid,
|
||||
createdAt: room.createdAt,
|
||||
updatedAt: room.updatedAt,
|
||||
name: room.spaceName,
|
||||
type: room.spaceType.type,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Room not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getRoomParentByUuid(roomUuid: string): Promise<RoomParentInterface> {
|
||||
try {
|
||||
const room = await this.spaceRepository.findOne({
|
||||
where: {
|
||||
uuid: roomUuid,
|
||||
spaceType: {
|
||||
type: SpaceType.ROOM,
|
||||
},
|
||||
},
|
||||
relations: ['spaceType', 'parent', 'parent.spaceType'],
|
||||
});
|
||||
if (!room || !room.spaceType || room.spaceType.type !== SpaceType.ROOM) {
|
||||
throw new BadRequestException('Invalid room UUID');
|
||||
}
|
||||
|
||||
return {
|
||||
uuid: room.uuid,
|
||||
name: room.spaceName,
|
||||
type: room.spaceType.type,
|
||||
parent: {
|
||||
uuid: room.parent.uuid,
|
||||
name: room.parent.spaceName,
|
||||
type: room.parent.spaceType.type,
|
||||
},
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Room not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getRoomsByUserId(
|
||||
userUuid: string,
|
||||
): Promise<GetRoomByUserUuidInterface[]> {
|
||||
try {
|
||||
const rooms = await this.userSpaceRepository.find({
|
||||
relations: ['space', 'space.spaceType'],
|
||||
where: {
|
||||
user: { uuid: userUuid },
|
||||
space: { spaceType: { type: SpaceType.ROOM } },
|
||||
},
|
||||
});
|
||||
|
||||
if (rooms.length === 0) {
|
||||
throw new HttpException('this user has no rooms', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
const spaces = rooms.map((room) => ({
|
||||
uuid: room.space.uuid,
|
||||
name: room.space.spaceName,
|
||||
type: room.space.spaceType.type,
|
||||
}));
|
||||
|
||||
return spaces;
|
||||
} catch (err) {
|
||||
if (err instanceof HttpException) {
|
||||
throw err;
|
||||
} else {
|
||||
throw new HttpException('user not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
async addUserRoom(addUserRoomDto: AddUserRoomDto) {
|
||||
try {
|
||||
await this.userSpaceRepository.save({
|
||||
user: { uuid: addUserRoomDto.userUuid },
|
||||
space: { uuid: addUserRoomDto.roomUuid },
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.code === CommonErrorCodes.DUPLICATE_ENTITY) {
|
||||
throw new HttpException(
|
||||
'User already belongs to this room',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
throw new HttpException(
|
||||
err.message || 'Internal Server Error',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
async renameRoomByUuid(
|
||||
roomUuid: string,
|
||||
updateRoomNameDto: UpdateRoomNameDto,
|
||||
): Promise<RenameRoomByUuidInterface> {
|
||||
try {
|
||||
const room = await this.spaceRepository.findOneOrFail({
|
||||
where: { uuid: roomUuid },
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
|
||||
if (!room || !room.spaceType || room.spaceType.type !== SpaceType.ROOM) {
|
||||
throw new BadRequestException('Invalid room UUID');
|
||||
}
|
||||
|
||||
await this.spaceRepository.update(
|
||||
{ uuid: roomUuid },
|
||||
{ spaceName: updateRoomNameDto.roomName },
|
||||
);
|
||||
|
||||
// Fetch the updated room
|
||||
const updateRoom = await this.spaceRepository.findOneOrFail({
|
||||
where: { uuid: roomUuid },
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
|
||||
return {
|
||||
uuid: updateRoom.uuid,
|
||||
name: updateRoom.spaceName,
|
||||
type: updateRoom.spaceType.type,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Room not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8,23 +8,24 @@ import {
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
||||
import {
|
||||
AddSceneIconDto,
|
||||
AddSceneTapToRunDto,
|
||||
GetSceneDto,
|
||||
UpdateSceneTapToRunDto,
|
||||
} from '../dtos/scene.dto';
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
|
||||
import { SceneParamDto } from '../dtos';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||
|
||||
@ApiTags('Scene Module')
|
||||
@Controller({
|
||||
version: EnableDisableStatusEnum.ENABLED,
|
||||
path: 'scene',
|
||||
path: ControllerRoute.SCENE.ROUTE,
|
||||
})
|
||||
export class SceneController {
|
||||
constructor(private readonly sceneService: SceneService) {}
|
||||
@ -32,80 +33,74 @@ export class SceneController {
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post('tap-to-run')
|
||||
async addTapToRunScene(@Body() addSceneTapToRunDto: AddSceneTapToRunDto) {
|
||||
const tapToRunScene =
|
||||
await this.sceneService.addTapToRunScene(addSceneTapToRunDto);
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'Scene added successfully',
|
||||
data: tapToRunScene,
|
||||
};
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('tap-to-run/:unitUuid')
|
||||
async getTapToRunSceneByUnit(
|
||||
@Param('unitUuid') unitUuid: string,
|
||||
@Query() inHomePage: GetSceneDto,
|
||||
) {
|
||||
const tapToRunScenes = await this.sceneService.getTapToRunSceneByUnit(
|
||||
unitUuid,
|
||||
inHomePage,
|
||||
);
|
||||
return tapToRunScenes;
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Delete('tap-to-run/:unitUuid/:sceneId')
|
||||
async deleteTapToRunScene(
|
||||
@Param('unitUuid') unitUuid: string,
|
||||
@Param('sceneId') sceneId: string,
|
||||
) {
|
||||
await this.sceneService.deleteTapToRunScene(unitUuid, sceneId);
|
||||
return {
|
||||
statusCode: HttpStatus.OK,
|
||||
message: 'Scene Deleted Successfully',
|
||||
};
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post('tap-to-run/trigger/:sceneId')
|
||||
async triggerTapToRunScene(@Param('sceneId') sceneId: string) {
|
||||
await this.sceneService.triggerTapToRunScene(sceneId);
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'Scene trigger successfully',
|
||||
};
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.SCENE.ACTIONS.CREATE_TAP_TO_RUN_SCENE_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.SCENE.ACTIONS.CREATE_TAP_TO_RUN_SCENE_DESCRIPTION,
|
||||
})
|
||||
async addTapToRunScene(
|
||||
@Body() addSceneTapToRunDto: AddSceneTapToRunDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
return await this.sceneService.createScene(addSceneTapToRunDto);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('tap-to-run/details/:sceneId')
|
||||
async getTapToRunSceneDetails(@Param('sceneId') sceneId: string) {
|
||||
const tapToRunScenes =
|
||||
await this.sceneService.getTapToRunSceneDetails(sceneId);
|
||||
return tapToRunScenes;
|
||||
@Delete('tap-to-run/:sceneUuid')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.SCENE.ACTIONS.DELETE_TAP_TO_RUN_SCENE_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.SCENE.ACTIONS.DELETE_TAP_TO_RUN_SCENE_DESCRIPTION,
|
||||
})
|
||||
async deleteTapToRunScene(
|
||||
@Param() param: SceneParamDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
return await this.sceneService.deleteScene(param);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Put('tap-to-run/:sceneId')
|
||||
@Post('tap-to-run/:sceneUuid/trigger')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.SCENE.ACTIONS.TRIGGER_TAP_TO_RUN_SCENE_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.SCENE.ACTIONS.TRIGGER_TAP_TO_RUN_SCENE_DESCRIPTION,
|
||||
})
|
||||
async triggerTapToRunScene(@Param() param: SceneParamDto) {
|
||||
return await this.sceneService.triggerTapToRunScene(param.sceneUuid);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('tap-to-run/:sceneUuid')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.SCENE.ACTIONS.GET_TAP_TO_RUN_SCENE_SUMMARY,
|
||||
description: ControllerRoute.SCENE.ACTIONS.GET_TAP_TO_RUN_SCENE_DESCRIPTION,
|
||||
})
|
||||
async getTapToRunSceneDetails(
|
||||
@Param() param: SceneParamDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
return await this.sceneService.getSceneByUuid(param.sceneUuid);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Put('tap-to-run/:sceneUuid')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.SCENE.ACTIONS.UPDATE_TAP_TO_RUN_SCENE_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.SCENE.ACTIONS.UPDATE_TAP_TO_RUN_SCENE_DESCRIPTION,
|
||||
})
|
||||
async updateTapToRunScene(
|
||||
@Body() updateSceneTapToRunDto: UpdateSceneTapToRunDto,
|
||||
@Param('sceneId') sceneId: string,
|
||||
@Param() param: SceneParamDto,
|
||||
) {
|
||||
const tapToRunScene = await this.sceneService.updateTapToRunScene(
|
||||
return await this.sceneService.updateTapToRunScene(
|
||||
updateSceneTapToRunDto,
|
||||
sceneId,
|
||||
param.sceneUuid,
|
||||
);
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'Scene updated successfully',
|
||||
data: tapToRunScene,
|
||||
};
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post('icon')
|
||||
|
||||
18
src/scene/dtos/delete.scene.param.dto.ts
Normal file
18
src/scene/dtos/delete.scene.param.dto.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsUUID } from 'class-validator';
|
||||
|
||||
export class DeleteSceneParamDto {
|
||||
@ApiProperty({
|
||||
description: 'UUID of the Space',
|
||||
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
|
||||
})
|
||||
@IsUUID()
|
||||
spaceUuid: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'UUID of the Scene',
|
||||
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
|
||||
})
|
||||
@IsUUID()
|
||||
sceneUuid: string;
|
||||
}
|
||||
@ -1 +1,4 @@
|
||||
export * from './scene.dto';
|
||||
export * from './space.param.dto';
|
||||
export * from './scene.param.dto';
|
||||
export * from './delete.scene.param.dto';
|
||||
|
||||
@ -36,7 +36,7 @@ class ExecutorProperty {
|
||||
public delaySeconds?: number;
|
||||
}
|
||||
|
||||
class Action {
|
||||
export class Action {
|
||||
@ApiProperty({
|
||||
description: 'Entity ID',
|
||||
required: true,
|
||||
@ -66,12 +66,13 @@ class Action {
|
||||
|
||||
export class AddSceneTapToRunDto {
|
||||
@ApiProperty({
|
||||
description: 'Unit UUID',
|
||||
description: 'Space UUID',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public unitUuid: string;
|
||||
public spaceUuid: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Icon UUID',
|
||||
required: false,
|
||||
|
||||
11
src/scene/dtos/scene.param.dto.ts
Normal file
11
src/scene/dtos/scene.param.dto.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsUUID } from 'class-validator';
|
||||
|
||||
export class SceneParamDto {
|
||||
@ApiProperty({
|
||||
description: 'UUID of the Scene',
|
||||
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
|
||||
})
|
||||
@IsUUID()
|
||||
sceneUuid: string;
|
||||
}
|
||||
11
src/scene/dtos/space.param.dto.ts
Normal file
11
src/scene/dtos/space.param.dto.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsUUID } from 'class-validator';
|
||||
|
||||
export class SpaceParamDto {
|
||||
@ApiProperty({
|
||||
description: 'UUID of the space',
|
||||
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
|
||||
})
|
||||
@IsUUID()
|
||||
spaceUuid: string;
|
||||
}
|
||||
@ -1,3 +1,5 @@
|
||||
import { Action } from '../dtos';
|
||||
|
||||
export interface AddTapToRunSceneInterface {
|
||||
success: boolean;
|
||||
msg?: string;
|
||||
@ -25,4 +27,19 @@ export interface SceneDetailsResult {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
actions?: any;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export interface SceneDetails {
|
||||
uuid: string;
|
||||
sceneTuyaId: string;
|
||||
name: string;
|
||||
status: string;
|
||||
icon?: string;
|
||||
iconUuid?: string;
|
||||
showInHome: boolean;
|
||||
type: string;
|
||||
actions: Action[];
|
||||
spaceId: string;
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
SceneIconRepository,
|
||||
SceneRepository,
|
||||
} from '@app/common/modules/scene/repositories';
|
||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusFirebaseModule],
|
||||
@ -20,6 +21,7 @@ import {
|
||||
SceneService,
|
||||
SpaceRepository,
|
||||
DeviceService,
|
||||
TuyaService,
|
||||
DeviceRepository,
|
||||
ProductRepository,
|
||||
SceneIconRepository,
|
||||
|
||||
@ -6,190 +6,220 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import {
|
||||
Action,
|
||||
AddSceneIconDto,
|
||||
AddSceneTapToRunDto,
|
||||
GetSceneDto,
|
||||
SceneParamDto,
|
||||
UpdateSceneTapToRunDto,
|
||||
} from '../dtos';
|
||||
import { GetUnitByUuidInterface } from 'src/unit/interface/unit.interface';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { TuyaContext } from '@tuya/tuya-connector-nodejs';
|
||||
import { convertKeysToSnakeCase } from '@app/common/helper/snakeCaseConverter';
|
||||
import { DeviceService } from 'src/device/services';
|
||||
import {
|
||||
AddTapToRunSceneInterface,
|
||||
DeleteTapToRunSceneInterface,
|
||||
SceneDetails,
|
||||
SceneDetailsResult,
|
||||
} from '../interface/scene.interface';
|
||||
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
|
||||
import { SpaceType } from '@app/common/constants/space-type.enum';
|
||||
import { ActionExecutorEnum } from '@app/common/constants/automation.enum';
|
||||
import {
|
||||
SceneIconRepository,
|
||||
SceneRepository,
|
||||
} from '@app/common/modules/scene/repositories';
|
||||
import { SceneIconType } from '@app/common/constants/secne-icon-type.enum';
|
||||
import {
|
||||
SceneEntity,
|
||||
SceneIconEntity,
|
||||
} from '@app/common/modules/scene/entities';
|
||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||
import { HttpStatusCode } from 'axios';
|
||||
import { ConvertedAction } from '@app/common/integrations/tuya/interfaces';
|
||||
|
||||
@Injectable()
|
||||
export class SceneService {
|
||||
private tuya: TuyaContext;
|
||||
constructor(
|
||||
private readonly configService: ConfigService,
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
private readonly sceneIconRepository: SceneIconRepository,
|
||||
private readonly sceneRepository: SceneRepository,
|
||||
private readonly deviceService: DeviceService,
|
||||
) {
|
||||
const accessKey = this.configService.get<string>('auth-config.ACCESS_KEY');
|
||||
const secretKey = this.configService.get<string>('auth-config.SECRET_KEY');
|
||||
const tuyaEuUrl = this.configService.get<string>('tuya-config.TUYA_EU_URL');
|
||||
this.tuya = new TuyaContext({
|
||||
baseUrl: tuyaEuUrl,
|
||||
accessKey,
|
||||
secretKey,
|
||||
});
|
||||
private readonly tuyaService: TuyaService,
|
||||
) {}
|
||||
|
||||
async createScene(
|
||||
addSceneTapToRunDto: AddSceneTapToRunDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
try {
|
||||
const { spaceUuid } = addSceneTapToRunDto;
|
||||
|
||||
const space = await this.getSpaceByUuid(spaceUuid);
|
||||
|
||||
const scene = await this.create(space.spaceTuyaUuid, addSceneTapToRunDto);
|
||||
|
||||
return new SuccessResponseDto({
|
||||
message: `Successfully created new scene with uuid ${scene.uuid}`,
|
||||
data: scene,
|
||||
statusCode: HttpStatus.CREATED,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`Error in createScene for space UUID ${addSceneTapToRunDto.spaceUuid}:`,
|
||||
err.message,
|
||||
);
|
||||
throw err instanceof HttpException
|
||||
? err
|
||||
: new HttpException(
|
||||
'Failed to create scene',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async addTapToRunScene(
|
||||
async create(
|
||||
spaceTuyaUuid: string,
|
||||
addSceneTapToRunDto: AddSceneTapToRunDto,
|
||||
spaceTuyaId = null,
|
||||
unitUuid?: string,
|
||||
) {
|
||||
): Promise<SceneEntity> {
|
||||
const { iconUuid, showInHomePage, spaceUuid } = addSceneTapToRunDto;
|
||||
|
||||
try {
|
||||
let unitSpaceTuyaId;
|
||||
if (!spaceTuyaId) {
|
||||
const unitDetails = await this.getUnitByUuid(
|
||||
addSceneTapToRunDto.unitUuid,
|
||||
const [defaultSceneIcon] = await Promise.all([
|
||||
this.getDefaultSceneIcon(),
|
||||
]);
|
||||
if (!defaultSceneIcon) {
|
||||
throw new HttpException(
|
||||
'Default scene icon not found',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
unitSpaceTuyaId = unitDetails.spaceTuyaUuid;
|
||||
if (!unitDetails) {
|
||||
throw new BadRequestException('Invalid unit UUID');
|
||||
}
|
||||
} else {
|
||||
unitSpaceTuyaId = spaceTuyaId;
|
||||
}
|
||||
|
||||
const actions = addSceneTapToRunDto.actions.map((action) => {
|
||||
return {
|
||||
...action,
|
||||
};
|
||||
const response = await this.createSceneExternalService(
|
||||
spaceTuyaUuid,
|
||||
addSceneTapToRunDto,
|
||||
);
|
||||
|
||||
const scene = await this.sceneRepository.save({
|
||||
sceneTuyaUuid: response.result.id,
|
||||
sceneIcon: { uuid: iconUuid || defaultSceneIcon.uuid },
|
||||
showInHomePage,
|
||||
spaceUuid,
|
||||
});
|
||||
|
||||
const convertedData = convertKeysToSnakeCase(actions);
|
||||
for (const action of convertedData) {
|
||||
if (action.action_executor === ActionExecutorEnum.DEVICE_ISSUE) {
|
||||
const device = await this.deviceService.getDeviceByDeviceUuid(
|
||||
action.entity_id,
|
||||
false,
|
||||
);
|
||||
if (device) {
|
||||
action.entity_id = device.deviceTuyaUuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
const path = `/v2.0/cloud/scene/rule`;
|
||||
const response: AddTapToRunSceneInterface = await this.tuya.request({
|
||||
method: 'POST',
|
||||
path,
|
||||
body: {
|
||||
space_id: unitSpaceTuyaId,
|
||||
name: addSceneTapToRunDto.sceneName,
|
||||
type: 'scene',
|
||||
decision_expr: addSceneTapToRunDto.decisionExpr,
|
||||
actions: convertedData,
|
||||
},
|
||||
});
|
||||
if (!response.success) {
|
||||
throw new HttpException(response.msg, HttpStatus.BAD_REQUEST);
|
||||
} else {
|
||||
const defaultSceneIcon = await this.sceneIconRepository.findOne({
|
||||
where: { iconType: SceneIconType.Default },
|
||||
});
|
||||
|
||||
await this.sceneRepository.save({
|
||||
sceneTuyaUuid: response.result.id,
|
||||
sceneIcon: {
|
||||
uuid: addSceneTapToRunDto.iconUuid
|
||||
? addSceneTapToRunDto.iconUuid
|
||||
: defaultSceneIcon.uuid,
|
||||
},
|
||||
showInHomePage: addSceneTapToRunDto.showInHomePage,
|
||||
unitUuid: unitUuid ? unitUuid : addSceneTapToRunDto.unitUuid,
|
||||
});
|
||||
}
|
||||
return {
|
||||
id: response.result.id,
|
||||
};
|
||||
return scene;
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
if (err instanceof HttpException) {
|
||||
throw err;
|
||||
} else if (err.message?.includes('tuya')) {
|
||||
throw new HttpException(
|
||||
'API error: Failed to create scene',
|
||||
HttpStatus.BAD_GATEWAY,
|
||||
);
|
||||
} else {
|
||||
throw new HttpException(
|
||||
err.message || 'Scene not found',
|
||||
err.status || HttpStatus.NOT_FOUND,
|
||||
'Database error: Failed to save scene',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
async getUnitByUuid(unitUuid: string): Promise<GetUnitByUuidInterface> {
|
||||
|
||||
async createSceneExternalService(
|
||||
spaceTuyaUuid: string,
|
||||
addSceneTapToRunDto: AddSceneTapToRunDto,
|
||||
) {
|
||||
const { sceneName, decisionExpr, actions } = addSceneTapToRunDto;
|
||||
try {
|
||||
const unit = await this.spaceRepository.findOne({
|
||||
where: {
|
||||
uuid: unitUuid,
|
||||
spaceType: {
|
||||
type: SpaceType.UNIT,
|
||||
},
|
||||
},
|
||||
relations: ['spaceType'],
|
||||
});
|
||||
if (!unit || !unit.spaceType || unit.spaceType.type !== SpaceType.UNIT) {
|
||||
throw new BadRequestException('Invalid unit UUID');
|
||||
const formattedActions = await this.prepareActions(actions);
|
||||
|
||||
const response = (await this.tuyaService.addTapToRunScene(
|
||||
spaceTuyaUuid,
|
||||
sceneName,
|
||||
formattedActions,
|
||||
decisionExpr,
|
||||
)) as AddTapToRunSceneInterface;
|
||||
|
||||
if (!response.result?.id) {
|
||||
throw new HttpException(
|
||||
'Failed to create scene in Tuya',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
return {
|
||||
uuid: unit.uuid,
|
||||
createdAt: unit.createdAt,
|
||||
updatedAt: unit.updatedAt,
|
||||
name: unit.spaceName,
|
||||
type: unit.spaceType.type,
|
||||
spaceTuyaUuid: unit.spaceTuyaUuid,
|
||||
};
|
||||
|
||||
return response;
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
if (err instanceof HttpException) {
|
||||
throw err;
|
||||
} else if (err.message?.includes('tuya')) {
|
||||
throw new HttpException(
|
||||
'API error: Failed to create scene',
|
||||
HttpStatus.BAD_GATEWAY,
|
||||
);
|
||||
} else {
|
||||
throw new HttpException('Unit not found', HttpStatus.NOT_FOUND);
|
||||
throw new HttpException(
|
||||
'An Internal error has been occured',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
async getTapToRunSceneByUnit(unitUuid: string, inHomePage: GetSceneDto) {
|
||||
|
||||
async findScenesBySpace(spaceUuid: string, filter: GetSceneDto) {
|
||||
try {
|
||||
const showInHomePage = inHomePage?.showInHomePage;
|
||||
await this.getSpaceByUuid(spaceUuid);
|
||||
const showInHomePage = filter?.showInHomePage;
|
||||
|
||||
const scenesData = await this.sceneRepository.find({
|
||||
where: {
|
||||
unitUuid,
|
||||
...(showInHomePage ? { showInHomePage } : {}),
|
||||
spaceUuid,
|
||||
...(showInHomePage !== undefined ? { showInHomePage } : {}),
|
||||
},
|
||||
});
|
||||
|
||||
if (!scenesData.length) {
|
||||
throw new HttpException(
|
||||
`No scenes found for space UUID ${spaceUuid}`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const scenes = await Promise.all(
|
||||
scenesData.map(async (scene) => {
|
||||
const sceneData = await this.getTapToRunSceneDetails(
|
||||
scene.sceneTuyaUuid,
|
||||
false,
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { actions, ...rest } = sceneData;
|
||||
return {
|
||||
...rest,
|
||||
};
|
||||
const { actions, ...sceneDetails } = await this.getScene(scene);
|
||||
|
||||
return sceneDetails;
|
||||
}),
|
||||
);
|
||||
|
||||
return scenes;
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`Error fetching Tap-to-Run scenes for space UUID ${spaceUuid}:`,
|
||||
err.message,
|
||||
);
|
||||
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
throw err;
|
||||
} else {
|
||||
throw new HttpException(
|
||||
'An error occurred while retrieving scenes',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async triggerTapToRunScene(sceneUuid: string) {
|
||||
try {
|
||||
const scene = await this.findScene(sceneUuid);
|
||||
await this.tuyaService.triggerScene(scene.sceneTuyaUuid);
|
||||
return new SuccessResponseDto({
|
||||
message: `Scene with ID ${sceneUuid} triggered successfully`,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof HttpException) {
|
||||
throw err;
|
||||
} else {
|
||||
throw new HttpException(
|
||||
err.message || 'Scene not found',
|
||||
@ -198,215 +228,103 @@ export class SceneService {
|
||||
}
|
||||
}
|
||||
}
|
||||
async deleteTapToRunScene(
|
||||
unitUuid: string,
|
||||
sceneId: string,
|
||||
spaceTuyaId = null,
|
||||
) {
|
||||
try {
|
||||
let unitSpaceTuyaId;
|
||||
if (!spaceTuyaId) {
|
||||
const unitDetails = await this.getUnitByUuid(unitUuid);
|
||||
unitSpaceTuyaId = unitDetails.spaceTuyaUuid;
|
||||
if (!unitSpaceTuyaId) {
|
||||
throw new BadRequestException('Invalid unit UUID');
|
||||
}
|
||||
} else {
|
||||
unitSpaceTuyaId = spaceTuyaId;
|
||||
}
|
||||
|
||||
const path = `/v2.0/cloud/scene/rule?ids=${sceneId}&space_id=${unitSpaceTuyaId}`;
|
||||
const response: DeleteTapToRunSceneInterface = await this.tuya.request({
|
||||
method: 'DELETE',
|
||||
path,
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
throw new HttpException('Scene not found', HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
await this.sceneRepository.delete({ sceneTuyaUuid: sceneId });
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException(
|
||||
err.message || 'Scene not found',
|
||||
err.status || HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
async triggerTapToRunScene(sceneId: string) {
|
||||
try {
|
||||
const path = `/v2.0/cloud/scene/rule/${sceneId}/actions/trigger`;
|
||||
const response: DeleteTapToRunSceneInterface = await this.tuya.request({
|
||||
method: 'POST',
|
||||
path,
|
||||
});
|
||||
if (!response.success) {
|
||||
throw new HttpException(response.msg, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
return response;
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException(
|
||||
err.message || 'Scene not found',
|
||||
err.status || HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
async getTapToRunSceneDetails(sceneId: string, withSpaceId = false) {
|
||||
try {
|
||||
const path = `/v2.0/cloud/scene/rule/${sceneId}`;
|
||||
const response = await this.tuya.request({
|
||||
method: 'GET',
|
||||
path,
|
||||
});
|
||||
if (!response.success) {
|
||||
throw new HttpException(response.msg, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
const responseData = convertKeysToCamelCase(response.result);
|
||||
const actions = responseData.actions.map((action) => {
|
||||
return {
|
||||
...action,
|
||||
};
|
||||
});
|
||||
|
||||
for (const action of actions) {
|
||||
if (action.actionExecutor === ActionExecutorEnum.DEVICE_ISSUE) {
|
||||
const device = await this.deviceService.getDeviceByDeviceTuyaUuid(
|
||||
action.entityId,
|
||||
);
|
||||
|
||||
if (device) {
|
||||
action.entityId = device.uuid;
|
||||
}
|
||||
} else if (
|
||||
action.actionExecutor !== ActionExecutorEnum.DEVICE_ISSUE &&
|
||||
action.actionExecutor !== ActionExecutorEnum.DELAY
|
||||
) {
|
||||
const sceneDetails = await this.getTapToRunSceneDetailsTuya(
|
||||
action.entityId,
|
||||
);
|
||||
|
||||
if (sceneDetails.id) {
|
||||
action.name = sceneDetails.name;
|
||||
action.type = sceneDetails.type;
|
||||
}
|
||||
}
|
||||
}
|
||||
const scene = await this.sceneRepository.findOne({
|
||||
where: { sceneTuyaUuid: sceneId },
|
||||
relations: ['sceneIcon'],
|
||||
});
|
||||
return {
|
||||
id: responseData.id,
|
||||
name: responseData.name,
|
||||
status: responseData.status,
|
||||
icon: scene.sceneIcon?.icon,
|
||||
iconUuid: scene.sceneIcon?.uuid,
|
||||
showInHome: scene.showInHomePage,
|
||||
type: 'tap_to_run',
|
||||
actions: actions,
|
||||
...(withSpaceId && { spaceId: responseData.spaceId }),
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('Scene not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
async getTapToRunSceneDetailsTuya(
|
||||
async fetchSceneDetailsFromTuya(
|
||||
sceneId: string,
|
||||
): Promise<SceneDetailsResult> {
|
||||
try {
|
||||
const path = `/v2.0/cloud/scene/rule/${sceneId}`;
|
||||
const response = await this.tuya.request({
|
||||
method: 'GET',
|
||||
path,
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
throw new HttpException(response.msg, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
const response = await this.tuyaService.getSceneRule(sceneId);
|
||||
const camelCaseResponse = convertKeysToCamelCase(response);
|
||||
const { id, name, type } = camelCaseResponse.result;
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
type,
|
||||
status,
|
||||
actions: tuyaActions = [],
|
||||
} = camelCaseResponse.result;
|
||||
|
||||
const actions = tuyaActions.map((action) => ({ ...action }));
|
||||
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
type,
|
||||
status,
|
||||
actions,
|
||||
} as SceneDetailsResult;
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
console.error(
|
||||
`Error fetching scene details for scene ID ${sceneId}:`,
|
||||
err,
|
||||
);
|
||||
if (err instanceof HttpException) {
|
||||
throw err;
|
||||
} else {
|
||||
throw new HttpException(
|
||||
'Scene not found for Tuya',
|
||||
HttpStatus.NOT_FOUND,
|
||||
'An error occurred while fetching scene details from Tuya',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async updateTapToRunScene(
|
||||
updateSceneTapToRunDto: UpdateSceneTapToRunDto,
|
||||
sceneId: string,
|
||||
sceneUuid: string,
|
||||
) {
|
||||
try {
|
||||
const spaceTuyaId = await this.getTapToRunSceneDetails(sceneId, true);
|
||||
if (!spaceTuyaId.spaceId) {
|
||||
throw new HttpException("Scene doesn't exist", HttpStatus.NOT_FOUND);
|
||||
}
|
||||
const scene = await this.findScene(sceneUuid);
|
||||
const space = await this.getSpaceByUuid(scene.spaceUuid);
|
||||
|
||||
const addSceneTapToRunDto: AddSceneTapToRunDto = {
|
||||
...updateSceneTapToRunDto,
|
||||
unitUuid: null,
|
||||
spaceUuid: scene.spaceUuid,
|
||||
iconUuid: updateSceneTapToRunDto.iconUuid,
|
||||
showInHomePage: updateSceneTapToRunDto.showInHomePage,
|
||||
};
|
||||
const scene = await this.sceneRepository.findOne({
|
||||
where: { sceneTuyaUuid: sceneId },
|
||||
});
|
||||
|
||||
const newTapToRunScene = await this.addTapToRunScene(
|
||||
const createdTuyaSceneResponse = await this.createSceneExternalService(
|
||||
space.spaceTuyaUuid,
|
||||
addSceneTapToRunDto,
|
||||
spaceTuyaId.spaceId,
|
||||
scene.unitUuid,
|
||||
);
|
||||
if (newTapToRunScene.id) {
|
||||
await this.deleteTapToRunScene(null, sceneId, spaceTuyaId.spaceId);
|
||||
const newSceneTuyaUuid = createdTuyaSceneResponse.result?.id;
|
||||
|
||||
await this.sceneRepository.update(
|
||||
{ sceneTuyaUuid: sceneId },
|
||||
{
|
||||
sceneTuyaUuid: newTapToRunScene.id,
|
||||
showInHomePage: addSceneTapToRunDto.showInHomePage,
|
||||
sceneIcon: {
|
||||
uuid: addSceneTapToRunDto.iconUuid,
|
||||
},
|
||||
unitUuid: scene.unitUuid,
|
||||
},
|
||||
if (!newSceneTuyaUuid) {
|
||||
throw new HttpException(
|
||||
`Failed to create a external new scene`,
|
||||
HttpStatus.BAD_GATEWAY,
|
||||
);
|
||||
return newTapToRunScene;
|
||||
}
|
||||
|
||||
await this.delete(scene.sceneTuyaUuid, space.spaceTuyaUuid);
|
||||
|
||||
const updatedScene = await this.sceneRepository.update(
|
||||
{ uuid: sceneUuid },
|
||||
{
|
||||
sceneTuyaUuid: newSceneTuyaUuid,
|
||||
showInHomePage: addSceneTapToRunDto.showInHomePage,
|
||||
sceneIcon: {
|
||||
uuid: addSceneTapToRunDto.iconUuid,
|
||||
},
|
||||
spaceUuid: scene.spaceUuid,
|
||||
},
|
||||
);
|
||||
return new SuccessResponseDto({
|
||||
data: updatedScene,
|
||||
message: `Scene with ID ${sceneUuid} updated successfully`,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
if (err instanceof HttpException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException(
|
||||
err.message || 'Scene not found',
|
||||
err.message || `Scene not found for id ${sceneUuid}`,
|
||||
err.status || HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async addSceneIcon(addSceneIconDto: AddSceneIconDto) {
|
||||
try {
|
||||
const icon = await this.sceneIconRepository.save({
|
||||
@ -424,6 +342,7 @@ export class SceneService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllIcons() {
|
||||
try {
|
||||
const icons = await this.sceneIconRepository.find({
|
||||
@ -449,4 +368,209 @@ export class SceneService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getSceneByUuid(sceneUuid: string): Promise<BaseResponseDto> {
|
||||
try {
|
||||
const scene = await this.findScene(sceneUuid);
|
||||
const sceneDetails = await this.getScene(scene);
|
||||
|
||||
return new SuccessResponseDto({
|
||||
data: sceneDetails,
|
||||
message: `Scene details for ${sceneUuid} retrieved successfully`,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error fetching scene details for sceneUuid ${sceneUuid}:`,
|
||||
error,
|
||||
);
|
||||
|
||||
if (error instanceof HttpException) {
|
||||
throw error;
|
||||
} else {
|
||||
throw new HttpException(
|
||||
'An error occurred while retrieving scene details',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getScene(scene: SceneEntity): Promise<SceneDetails> {
|
||||
try {
|
||||
const { actions, name, status } = await this.fetchSceneDetailsFromTuya(
|
||||
scene.sceneTuyaUuid,
|
||||
);
|
||||
|
||||
for (const action of actions) {
|
||||
if (action.actionExecutor === ActionExecutorEnum.DEVICE_ISSUE) {
|
||||
const device = await this.deviceService.getDeviceByDeviceTuyaUuid(
|
||||
action.entityId,
|
||||
);
|
||||
|
||||
if (device) {
|
||||
action.entityId = device.uuid;
|
||||
}
|
||||
} else if (
|
||||
action.actionExecutor !== ActionExecutorEnum.DEVICE_ISSUE &&
|
||||
action.actionExecutor !== ActionExecutorEnum.DELAY
|
||||
) {
|
||||
const sceneDetails = await this.fetchSceneDetailsFromTuya(
|
||||
action.entityId,
|
||||
);
|
||||
|
||||
if (sceneDetails.id) {
|
||||
action.name = sceneDetails.name;
|
||||
action.type = sceneDetails.type;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
uuid: scene.uuid,
|
||||
sceneTuyaId: scene.sceneTuyaUuid,
|
||||
name,
|
||||
status,
|
||||
icon: scene.sceneIcon?.icon,
|
||||
iconUuid: scene.sceneIcon?.uuid,
|
||||
showInHome: scene.showInHomePage,
|
||||
type: 'tap_to_run',
|
||||
actions,
|
||||
spaceId: scene.spaceUuid,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err;
|
||||
} else {
|
||||
console.log(err);
|
||||
throw new HttpException(
|
||||
`An error occurred while retrieving scene details for ${scene.uuid}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async deleteScene(params: SceneParamDto): Promise<BaseResponseDto> {
|
||||
try {
|
||||
const { sceneUuid } = params;
|
||||
const scene = await this.findScene(sceneUuid);
|
||||
const space = await this.getSpaceByUuid(scene.spaceUuid);
|
||||
|
||||
await this.delete(scene.sceneTuyaUuid, space.spaceTuyaUuid);
|
||||
return new SuccessResponseDto({
|
||||
message: `Scene with ID ${sceneUuid} deleted successfully`,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof HttpException) {
|
||||
throw err;
|
||||
} else {
|
||||
throw new HttpException(
|
||||
err.message || `Scene not found for id ${params.sceneUuid}`,
|
||||
err.status || HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async findScene(sceneUuid: string): Promise<SceneEntity> {
|
||||
const scene = await this.sceneRepository.findOne({
|
||||
where: { uuid: sceneUuid },
|
||||
relations: ['sceneIcon'],
|
||||
});
|
||||
|
||||
if (!scene) {
|
||||
throw new HttpException(
|
||||
`Invalid scene with id ${sceneUuid}`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
return scene;
|
||||
}
|
||||
|
||||
async delete(tuyaSceneId: string, tuyaSpaceId: string) {
|
||||
try {
|
||||
const response = (await this.tuyaService.deleteSceneRule(
|
||||
tuyaSceneId,
|
||||
tuyaSpaceId,
|
||||
)) as DeleteTapToRunSceneInterface;
|
||||
return response;
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) {
|
||||
throw error;
|
||||
} else {
|
||||
throw new HttpException(
|
||||
'Failed to delete scene rule in Tuya',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async prepareActions(actions: Action[]): Promise<ConvertedAction[]> {
|
||||
const convertedData = convertKeysToSnakeCase(actions) as ConvertedAction[];
|
||||
|
||||
await Promise.all(
|
||||
convertedData.map(async (action) => {
|
||||
if (action.action_executor === ActionExecutorEnum.DEVICE_ISSUE) {
|
||||
const device = await this.deviceService.getDeviceByDeviceUuid(
|
||||
action.entity_id,
|
||||
false,
|
||||
);
|
||||
if (device) {
|
||||
action.entity_id = device.deviceTuyaUuid;
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return convertedData;
|
||||
}
|
||||
|
||||
private async getDefaultSceneIcon(): Promise<SceneIconEntity> {
|
||||
const defaultIcon = await this.sceneIconRepository.findOne({
|
||||
where: { iconType: SceneIconType.Default },
|
||||
});
|
||||
return defaultIcon;
|
||||
}
|
||||
|
||||
async getSpaceByUuid(spaceUuid: string) {
|
||||
try {
|
||||
const space = await this.spaceRepository.findOne({
|
||||
where: {
|
||||
uuid: spaceUuid,
|
||||
},
|
||||
relations: ['community'],
|
||||
});
|
||||
|
||||
if (!space) {
|
||||
throw new HttpException(
|
||||
`Invalid space UUID ${spaceUuid}`,
|
||||
HttpStatusCode.BadRequest,
|
||||
);
|
||||
}
|
||||
|
||||
if (!space.community.externalId) {
|
||||
throw new HttpException(
|
||||
`Space doesn't have any association with tuya${spaceUuid}`,
|
||||
HttpStatusCode.BadRequest,
|
||||
);
|
||||
}
|
||||
return {
|
||||
uuid: space.uuid,
|
||||
createdAt: space.createdAt,
|
||||
updatedAt: space.updatedAt,
|
||||
name: space.spaceName,
|
||||
spaceTuyaUuid: space.community.externalId,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
console.log(err);
|
||||
throw err;
|
||||
} else {
|
||||
throw new HttpException(
|
||||
`Space with id ${spaceUuid} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
5
src/space/controllers/index.ts
Normal file
5
src/space/controllers/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from './space.controller';
|
||||
export * from './space-user.controller';
|
||||
export * from './space-device.controller';
|
||||
export * from './space-scene.controller';
|
||||
export * from './subspace';
|
||||
30
src/space/controllers/space-device.controller.ts
Normal file
30
src/space/controllers/space-device.controller.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||
import { Controller, Get, Param, UseGuards } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
import { GetSpaceParam } from '../dtos';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { SpaceDeviceService } from '../services';
|
||||
|
||||
@ApiTags('Space Module')
|
||||
@Controller({
|
||||
version: '1',
|
||||
path: ControllerRoute.SPACE_DEVICES.ROUTE,
|
||||
})
|
||||
export class SpaceDeviceController {
|
||||
constructor(private readonly spaceDeviceService: SpaceDeviceService) {}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.SPACE_DEVICES.ACTIONS.LIST_SPACE_DEVICE_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.SPACE_DEVICES.ACTIONS.LIST_SPACE_DEVICE_DESCRIPTION,
|
||||
})
|
||||
@Get()
|
||||
async listDevicesInSpace(
|
||||
@Param() params: GetSpaceParam,
|
||||
): Promise<BaseResponseDto> {
|
||||
return await this.spaceDeviceService.listDevicesInSpace(params);
|
||||
}
|
||||
}
|
||||
34
src/space/controllers/space-scene.controller.ts
Normal file
34
src/space/controllers/space-scene.controller.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { SpaceSceneService } from '../services';
|
||||
import { GetSceneDto } from '../../scene/dtos';
|
||||
import { GetSpaceParam } from '../dtos';
|
||||
|
||||
@ApiTags('Space Module')
|
||||
@Controller({
|
||||
version: '1',
|
||||
path: ControllerRoute.SPACE_SCENE.ROUTE,
|
||||
})
|
||||
export class SpaceSceneController {
|
||||
constructor(private readonly sceneService: SpaceSceneService) {}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
summary:
|
||||
ControllerRoute.SPACE_SCENE.ACTIONS.GET_TAP_TO_RUN_SCENE_BY_SPACE_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.SPACE_SCENE.ACTIONS
|
||||
.GET_TAP_TO_RUN_SCENE_BY_SPACE_DESCRIPTION,
|
||||
})
|
||||
@Get()
|
||||
async getTapToRunSceneByUnit(
|
||||
@Param() params: GetSpaceParam,
|
||||
@Query() inHomePage: GetSceneDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
return await this.sceneService.getScenes(params, inHomePage);
|
||||
}
|
||||
}
|
||||
51
src/space/controllers/space-user.controller.ts
Normal file
51
src/space/controllers/space-user.controller.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||
import { Controller, Delete, Param, Post, UseGuards } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { SpaceUserService } from '../services';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
import { UserSpaceParam } from '../dtos';
|
||||
|
||||
@ApiTags('Space Module')
|
||||
@Controller({
|
||||
version: '1',
|
||||
path: ControllerRoute.SPACE_USER.ROUTE,
|
||||
})
|
||||
export class SpaceUserController {
|
||||
constructor(private readonly spaceUserService: SpaceUserService) {}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@Post('/:userUuid')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
summary:
|
||||
ControllerRoute.SPACE_USER.ACTIONS.ASSOCIATE_SPACE_USER_DESCRIPTION,
|
||||
description:
|
||||
ControllerRoute.SPACE_USER.ACTIONS.ASSOCIATE_SPACE_USER_DESCRIPTION,
|
||||
})
|
||||
async associateUserToSpace(
|
||||
@Param() params: UserSpaceParam,
|
||||
): Promise<BaseResponseDto> {
|
||||
return this.spaceUserService.associateUserToSpace(
|
||||
params.userUuid,
|
||||
params.spaceUuid,
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@Delete('/:userUuid')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.SPACE_USER.ACTIONS.DISSOCIATE_SPACE_USER_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.SPACE_USER.ACTIONS.DISSOCIATE_SPACE_USER_DESCRIPTION,
|
||||
})
|
||||
async disassociateUserFromSpace(
|
||||
@Param() params: UserSpaceParam,
|
||||
): Promise<BaseResponseDto> {
|
||||
return this.spaceUserService.disassociateUserFromSpace(
|
||||
params.userUuid,
|
||||
params.spaceUuid,
|
||||
);
|
||||
}
|
||||
}
|
||||
127
src/space/controllers/space.controller.ts
Normal file
127
src/space/controllers/space.controller.ts
Normal file
@ -0,0 +1,127 @@
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { SpaceService } from '../services';
|
||||
import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
import { AddSpaceDto, CommunitySpaceParam } from '../dtos';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { GetSpaceParam } from '../dtos/get.space.param';
|
||||
|
||||
@ApiTags('Space Module')
|
||||
@Controller({
|
||||
version: '1',
|
||||
path: ControllerRoute.SPACE.ROUTE,
|
||||
})
|
||||
export class SpaceController {
|
||||
constructor(private readonly spaceService: SpaceService) {}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.SPACE.ACTIONS.CREATE_SPACE_SUMMARY,
|
||||
description: ControllerRoute.SPACE.ACTIONS.CREATE_SPACE_DESCRIPTION,
|
||||
})
|
||||
@Post()
|
||||
async createSpace(
|
||||
@Body() addSpaceDto: AddSpaceDto,
|
||||
@Param() communitySpaceParam: CommunitySpaceParam,
|
||||
): Promise<BaseResponseDto> {
|
||||
return await this.spaceService.createSpace(
|
||||
addSpaceDto,
|
||||
communitySpaceParam.communityUuid,
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
summary:
|
||||
ControllerRoute.SPACE.ACTIONS.GET_COMMUNITY_SPACES_HIERARCHY_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.SPACE.ACTIONS.GET_COMMUNITY_SPACES_HIERARCHY_DESCRIPTION,
|
||||
})
|
||||
@Get()
|
||||
async getHierarchy(
|
||||
@Param() param: CommunitySpaceParam,
|
||||
): Promise<BaseResponseDto> {
|
||||
return this.spaceService.getSpacesHierarchyForCommunity(
|
||||
param.communityUuid,
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.SPACE.ACTIONS.DELETE_SPACE_SUMMARY,
|
||||
description: ControllerRoute.SPACE.ACTIONS.DELETE_SPACE_DESCRIPTION,
|
||||
})
|
||||
@Delete('/:spaceUuid')
|
||||
async deleteSpace(@Param() params: GetSpaceParam): Promise<BaseResponseDto> {
|
||||
return this.spaceService.delete(params.spaceUuid, params.communityUuid);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Put('/:spaceUuid')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.SPACE.ACTIONS.UPDATE_SPACE_SUMMARY,
|
||||
description: ControllerRoute.SPACE.ACTIONS.UPDATE_SPACE_SUMMARY,
|
||||
})
|
||||
async updateSpace(
|
||||
@Param() params: GetSpaceParam,
|
||||
@Body() updateSpaceDto: AddSpaceDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
return this.spaceService.updateSpace(
|
||||
params.spaceUuid,
|
||||
params.communityUuid,
|
||||
updateSpaceDto,
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.SPACE.ACTIONS.GET_SPACE_SUMMARY,
|
||||
description: ControllerRoute.SPACE.ACTIONS.GET_SPACE_DESCRIPTION,
|
||||
})
|
||||
@Get('/:spaceUuid')
|
||||
async get(@Param() params: GetSpaceParam): Promise<BaseResponseDto> {
|
||||
return this.spaceService.findOne(params.spaceUuid);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.SPACE.ACTIONS.GET_HEIRARCHY_SUMMARY,
|
||||
description: ControllerRoute.SPACE.ACTIONS.GET_HEIRARCHY_DESCRIPTION,
|
||||
})
|
||||
@Get('/:spaceUuid/hierarchy')
|
||||
async getHierarchyUnderSpace(
|
||||
@Param() params: GetSpaceParam,
|
||||
): Promise<BaseResponseDto> {
|
||||
return this.spaceService.getSpacesHierarchyForSpace(params.spaceUuid);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.SPACE.ACTIONS.CREATE_INVITATION_CODE_SPACE_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.SPACE.ACTIONS.CREATE_INVITATION_CODE_SPACE_DESCRIPTION,
|
||||
})
|
||||
@Post(':spaceUuid/invitation-code')
|
||||
async generateSpaceInvitationCode(
|
||||
@Param() params: GetSpaceParam,
|
||||
): Promise<BaseResponseDto> {
|
||||
return this.spaceService.getSpaceInvitationCode(params.spaceUuid);
|
||||
}
|
||||
}
|
||||
2
src/space/controllers/subspace/index.ts
Normal file
2
src/space/controllers/subspace/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './subspace.controller';
|
||||
export * from './subspace-device.controller';
|
||||
64
src/space/controllers/subspace/subspace-device.controller.ts
Normal file
64
src/space/controllers/subspace/subspace-device.controller.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
import { Controller, Get, Param, Post, UseGuards } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { DeviceSubSpaceParam, GetSubSpaceParam } from '../../dtos';
|
||||
import { SubspaceDeviceService } from 'src/space/services';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
|
||||
@ApiTags('Space Module')
|
||||
@Controller({
|
||||
version: '1',
|
||||
path: ControllerRoute.SUBSPACE_DEVICE.ROUTE,
|
||||
})
|
||||
export class SubSpaceDeviceController {
|
||||
constructor(private readonly subspaceDeviceService: SubspaceDeviceService) {}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
summary:
|
||||
ControllerRoute.SUBSPACE_DEVICE.ACTIONS.LIST_SUBSPACE_DEVICE_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.SUBSPACE_DEVICE.ACTIONS.LIST_SUBSPACE_DEVICE_DESCRIPTION,
|
||||
})
|
||||
@Get()
|
||||
async listDevicesInSubspace(
|
||||
@Param() params: GetSubSpaceParam,
|
||||
): Promise<BaseResponseDto> {
|
||||
return await this.subspaceDeviceService.listDevicesInSubspace(params);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
summary:
|
||||
ControllerRoute.SUBSPACE_DEVICE.ACTIONS.ASSOCIATE_SUBSPACE_DEVICE_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.SUBSPACE_DEVICE.ACTIONS
|
||||
.ASSOCIATE_SUBSPACE_DEVICE_DESCRIPTION,
|
||||
})
|
||||
@Post('/:deviceUuid')
|
||||
async associateDeviceToSubspace(
|
||||
@Param() params: DeviceSubSpaceParam,
|
||||
): Promise<BaseResponseDto> {
|
||||
return await this.subspaceDeviceService.associateDeviceToSubspace(params);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
summary:
|
||||
ControllerRoute.SUBSPACE_DEVICE.ACTIONS
|
||||
.DISASSOCIATE_SUBSPACE_DEVICE_SUMMARY,
|
||||
description:
|
||||
ControllerRoute.SUBSPACE_DEVICE.ACTIONS
|
||||
.DISASSOCIATE_SUBSPACE_DEVICE_DESCRIPTION,
|
||||
})
|
||||
@Post('/:deviceUuid')
|
||||
async disassociateDeviceFromSubspace(
|
||||
@Param() params: DeviceSubSpaceParam,
|
||||
): Promise<BaseResponseDto> {
|
||||
return await this.subspaceDeviceService.associateDeviceToSubspace(params);
|
||||
}
|
||||
}
|
||||
91
src/space/controllers/subspace/subspace.controller.ts
Normal file
91
src/space/controllers/subspace/subspace.controller.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { SubSpaceService } from '../../services';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { AddSubspaceDto, GetSpaceParam, GetSubSpaceParam } from '../../dtos';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
import { PaginationRequestGetListDto } from '@app/common/dto/pagination.request.dto';
|
||||
|
||||
@ApiTags('Space Module')
|
||||
@Controller({
|
||||
version: '1',
|
||||
path: ControllerRoute.SUBSPACE.ROUTE,
|
||||
})
|
||||
export class SubSpaceController {
|
||||
constructor(private readonly subSpaceService: SubSpaceService) {}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post()
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.SUBSPACE.ACTIONS.CREATE_SUBSPACE_SUMMARY,
|
||||
description: ControllerRoute.SUBSPACE.ACTIONS.CREATE_SUBSPACE_DESCRIPTION,
|
||||
})
|
||||
async createSubspace(
|
||||
@Param() params: GetSpaceParam,
|
||||
@Body() addSubspaceDto: AddSubspaceDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
return this.subSpaceService.createSubspace(addSubspaceDto, params);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.SUBSPACE.ACTIONS.LIST_SUBSPACES_SUMMARY,
|
||||
description: ControllerRoute.SUBSPACE.ACTIONS.LIST_SUBSPACES_DESCRIPTION,
|
||||
})
|
||||
@Get()
|
||||
async list(
|
||||
@Param() params: GetSpaceParam,
|
||||
@Query() query: PaginationRequestGetListDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
return this.subSpaceService.list(params, query);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.SUBSPACE.ACTIONS.GET_SUBSPACE_SUMMARY,
|
||||
description: ControllerRoute.SUBSPACE.ACTIONS.GET_SUBSPACE_DESCRIPTION,
|
||||
})
|
||||
@Get(':subSpaceUuid')
|
||||
async findOne(@Param() params: GetSubSpaceParam): Promise<BaseResponseDto> {
|
||||
return this.subSpaceService.findOne(params);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.SUBSPACE.ACTIONS.UPDATE_SUBSPACE_SUMMARY,
|
||||
description: ControllerRoute.SUBSPACE.ACTIONS.UPDATE_SUBSPACE_DESCRIPTION,
|
||||
})
|
||||
@Put(':subSpaceUuid')
|
||||
async updateSubspace(
|
||||
@Param() params: GetSubSpaceParam,
|
||||
@Body() updateSubSpaceDto: AddSubspaceDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
return this.subSpaceService.updateSubSpace(params, updateSubSpaceDto);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.SUBSPACE.ACTIONS.DELETE_SUBSPACE_SUMMARY,
|
||||
description: ControllerRoute.SUBSPACE.ACTIONS.DELETE_SUBSPACE_DESCRIPTION,
|
||||
})
|
||||
@Delete(':subSpaceUuid')
|
||||
async delete(@Param() params: GetSubSpaceParam): Promise<BaseResponseDto> {
|
||||
return this.subSpaceService.delete(params);
|
||||
}
|
||||
}
|
||||
35
src/space/dtos/add.space.dto.ts
Normal file
35
src/space/dtos/add.space.dto.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
|
||||
export class AddSpaceDto {
|
||||
@ApiProperty({
|
||||
description: 'Name of the space (e.g., Floor 1, Unit 101)',
|
||||
example: 'Unit 101',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
spaceName: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'UUID of the parent space (if any, for hierarchical spaces)',
|
||||
example: 'f5d7e9c3-44bc-4b12-88f1-1b3cda84752e',
|
||||
required: false,
|
||||
})
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
parentUuid?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Indicates whether the space is private or public',
|
||||
example: false,
|
||||
default: false,
|
||||
})
|
||||
@IsBoolean()
|
||||
isPrivate: boolean;
|
||||
}
|
||||
11
src/space/dtos/community-space.param.ts
Normal file
11
src/space/dtos/community-space.param.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsUUID } from 'class-validator';
|
||||
|
||||
export class CommunitySpaceParam {
|
||||
@ApiProperty({
|
||||
description: 'UUID of the community this space belongs to',
|
||||
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
|
||||
})
|
||||
@IsUUID()
|
||||
communityUuid: string;
|
||||
}
|
||||
12
src/space/dtos/get.space.param.ts
Normal file
12
src/space/dtos/get.space.param.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsUUID } from 'class-validator';
|
||||
import { CommunitySpaceParam } from './community-space.param';
|
||||
|
||||
export class GetSpaceParam extends CommunitySpaceParam {
|
||||
@ApiProperty({
|
||||
description: 'UUID of the Space',
|
||||
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
|
||||
})
|
||||
@IsUUID()
|
||||
spaceUuid: string;
|
||||
}
|
||||
5
src/space/dtos/index.ts
Normal file
5
src/space/dtos/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from './add.space.dto';
|
||||
export * from './community-space.param';
|
||||
export * from './get.space.param';
|
||||
export * from './user-space.param';
|
||||
export * from './subspace';
|
||||
12
src/space/dtos/subspace/add.subspace-device.param.ts
Normal file
12
src/space/dtos/subspace/add.subspace-device.param.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsUUID } from 'class-validator';
|
||||
import { GetSubSpaceParam } from './get.subspace.param';
|
||||
|
||||
export class DeviceSubSpaceParam extends GetSubSpaceParam {
|
||||
@ApiProperty({
|
||||
description: 'UUID of the device',
|
||||
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
|
||||
})
|
||||
@IsUUID()
|
||||
deviceUuid: string;
|
||||
}
|
||||
12
src/space/dtos/subspace/add.subspace.dto.ts
Normal file
12
src/space/dtos/subspace/add.subspace.dto.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class AddSubspaceDto {
|
||||
@ApiProperty({
|
||||
example: 'Meeting Room 1',
|
||||
description: 'Name of the subspace',
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
subspaceName: string;
|
||||
}
|
||||
12
src/space/dtos/subspace/get.subspace.param.ts
Normal file
12
src/space/dtos/subspace/get.subspace.param.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsUUID } from 'class-validator';
|
||||
import { GetSpaceParam } from '../get.space.param';
|
||||
|
||||
export class GetSubSpaceParam extends GetSpaceParam {
|
||||
@ApiProperty({
|
||||
description: 'UUID of the sub space',
|
||||
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
|
||||
})
|
||||
@IsUUID()
|
||||
subSpaceUuid: string;
|
||||
}
|
||||
3
src/space/dtos/subspace/index.ts
Normal file
3
src/space/dtos/subspace/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './add.subspace.dto';
|
||||
export * from './get.subspace.param';
|
||||
export * from './add.subspace-device.param';
|
||||
12
src/space/dtos/user-space.param.ts
Normal file
12
src/space/dtos/user-space.param.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsUUID } from 'class-validator';
|
||||
import { GetSpaceParam } from './get.space.param';
|
||||
|
||||
export class UserSpaceParam extends GetSpaceParam {
|
||||
@ApiProperty({
|
||||
description: 'Uuid of the user to be associated/ dissociated',
|
||||
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
|
||||
})
|
||||
@IsUUID()
|
||||
userUuid: string;
|
||||
}
|
||||
5
src/space/services/index.ts
Normal file
5
src/space/services/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from './space.service';
|
||||
export * from './space-user.service';
|
||||
export * from './space-device.service';
|
||||
export * from './subspace';
|
||||
export * from './space-scene.service';
|
||||
115
src/space/services/space-device.service.ts
Normal file
115
src/space/services/space-device.service.ts
Normal file
@ -0,0 +1,115 @@
|
||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { GetDeviceDetailsInterface } from 'src/device/interfaces/get.device.interface';
|
||||
import { GetSpaceParam } from '../dtos';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
|
||||
import { ProductRepository } from '@app/common/modules/product/repositories';
|
||||
|
||||
@Injectable()
|
||||
export class SpaceDeviceService {
|
||||
constructor(
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
private readonly tuyaService: TuyaService,
|
||||
private readonly productRepository: ProductRepository,
|
||||
private readonly communityRepository: CommunityRepository,
|
||||
) {}
|
||||
|
||||
async listDevicesInSpace(params: GetSpaceParam): Promise<BaseResponseDto> {
|
||||
const { spaceUuid, communityUuid } = params;
|
||||
try {
|
||||
const space = await this.validateCommunityAndSpace(
|
||||
communityUuid,
|
||||
spaceUuid,
|
||||
);
|
||||
|
||||
const detailedDevices = await Promise.all(
|
||||
space.devices.map(async (device) => {
|
||||
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,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
return new SuccessResponseDto({
|
||||
data: detailedDevices,
|
||||
message: 'Successfully retrieved list of devices',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error listing devices in space:', error);
|
||||
throw new HttpException(
|
||||
error.message || 'Failed to retrieve devices in space',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async validateCommunityAndSpace(communityUuid: string, spaceUuid: string) {
|
||||
const community = await this.communityRepository.findOne({
|
||||
where: { uuid: communityUuid },
|
||||
});
|
||||
if (!community) {
|
||||
this.throwNotFound('Community', communityUuid);
|
||||
}
|
||||
|
||||
const space = await this.spaceRepository.findOne({
|
||||
where: { uuid: spaceUuid, community: { uuid: communityUuid } },
|
||||
relations: ['devices', 'devices.productDevice'],
|
||||
});
|
||||
if (!space) {
|
||||
this.throwNotFound('Space', spaceUuid);
|
||||
}
|
||||
return space;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Convert keys to camel case
|
||||
const camelCaseResponse = convertKeysToCamelCase(tuyaDeviceDetails);
|
||||
|
||||
const product = await this.productRepository.findOne({
|
||||
where: {
|
||||
prodId: camelCaseResponse.productId,
|
||||
},
|
||||
});
|
||||
|
||||
// Exclude specific keys and add `productUuid`
|
||||
const { ...rest } = camelCaseResponse;
|
||||
return {
|
||||
...rest,
|
||||
productUuid: product?.uuid,
|
||||
} as GetDeviceDetailsInterface;
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
`Error fetching device details from Tuya for device id ${deviceId}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
55
src/space/services/space-scene.service.ts
Normal file
55
src/space/services/space-scene.service.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
import { GetSpaceParam } from '../dtos';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { SpaceService } from './space.service';
|
||||
import { SceneService } from '../../scene/services';
|
||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||
import { GetSceneDto } from '../../scene/dtos';
|
||||
|
||||
@Injectable()
|
||||
export class SpaceSceneService {
|
||||
constructor(
|
||||
private readonly spaceSevice: SpaceService,
|
||||
private readonly sceneSevice: SceneService,
|
||||
) {}
|
||||
|
||||
async getScenes(
|
||||
params: GetSpaceParam,
|
||||
getSceneDto: GetSceneDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
try {
|
||||
const { spaceUuid, communityUuid } = params;
|
||||
|
||||
await this.spaceSevice.validateCommunityAndSpace(
|
||||
communityUuid,
|
||||
spaceUuid,
|
||||
);
|
||||
|
||||
const scenes = await this.sceneSevice.findScenesBySpace(
|
||||
spaceUuid,
|
||||
getSceneDto,
|
||||
);
|
||||
|
||||
return new SuccessResponseDto({
|
||||
message: `Scenes retrieved successfully for space ${spaceUuid}`,
|
||||
data: scenes,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error retrieving scenes:', error);
|
||||
|
||||
if (error instanceof BadRequestException) {
|
||||
throw new HttpException(error.message, HttpStatus.BAD_REQUEST);
|
||||
} else {
|
||||
throw new HttpException(
|
||||
'An error occurred while retrieving scenes',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
108
src/space/services/space-user.service.ts
Normal file
108
src/space/services/space-user.service.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import {
|
||||
UserRepository,
|
||||
UserSpaceRepository,
|
||||
} from '@app/common/modules/user/repositories';
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class SpaceUserService {
|
||||
constructor(
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
private readonly userRepository: UserRepository,
|
||||
private readonly userSpaceRepository: UserSpaceRepository,
|
||||
) {}
|
||||
async associateUserToSpace(
|
||||
userUuid: string,
|
||||
spaceUuid: string,
|
||||
): Promise<BaseResponseDto> {
|
||||
// Find the user by ID
|
||||
const user = await this.userRepository.findOne({
|
||||
where: { uuid: userUuid },
|
||||
});
|
||||
if (!user) {
|
||||
throw new HttpException(
|
||||
`User with ID ${userUuid} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
// Find the space by ID
|
||||
const space = await this.spaceRepository.findOne({
|
||||
where: { uuid: spaceUuid },
|
||||
});
|
||||
if (!space) {
|
||||
throw new HttpException(
|
||||
`Space with ID ${spaceUuid} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
// Check if the association already exists
|
||||
const existingAssociation = await this.userSpaceRepository.findOne({
|
||||
where: { user: { uuid: userUuid }, space: { uuid: spaceUuid } },
|
||||
});
|
||||
if (existingAssociation) {
|
||||
throw new HttpException(
|
||||
`User is already associated with the space`,
|
||||
HttpStatus.CONFLICT,
|
||||
);
|
||||
}
|
||||
|
||||
const userSpace = this.userSpaceRepository.create({ user, space });
|
||||
|
||||
await this.userSpaceRepository.save(userSpace);
|
||||
return new SuccessResponseDto({
|
||||
data: userSpace,
|
||||
message: `Space ${spaceUuid} has been successfully associated t user ${userUuid}`,
|
||||
});
|
||||
}
|
||||
|
||||
async disassociateUserFromSpace(
|
||||
userUuid: string,
|
||||
spaceUuid: string,
|
||||
): Promise<BaseResponseDto> {
|
||||
// Find the user by ID
|
||||
const user = await this.userRepository.findOne({
|
||||
where: { uuid: userUuid },
|
||||
});
|
||||
if (!user) {
|
||||
throw new HttpException(
|
||||
`User with ID ${userUuid} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
// Find the space by ID
|
||||
const space = await this.spaceRepository.findOne({
|
||||
where: { uuid: spaceUuid },
|
||||
});
|
||||
if (!space) {
|
||||
throw new HttpException(
|
||||
`Space with ID ${spaceUuid} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
// Find the existing association
|
||||
const existingAssociation = await this.userSpaceRepository.findOne({
|
||||
where: { user: { uuid: userUuid }, space: { uuid: spaceUuid } },
|
||||
});
|
||||
|
||||
if (!existingAssociation) {
|
||||
throw new HttpException(
|
||||
`No association found between user ${userUuid} and space ${spaceUuid}`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
// Remove the association
|
||||
await this.userSpaceRepository.remove(existingAssociation);
|
||||
|
||||
return new SuccessResponseDto({
|
||||
message: `User ${userUuid} has been successfully disassociated from space ${spaceUuid}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
385
src/space/services/space.service.ts
Normal file
385
src/space/services/space.service.ts
Normal file
@ -0,0 +1,385 @@
|
||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||
import {
|
||||
BadRequestException,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
import { AddSpaceDto } from '../dtos';
|
||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||
import { SpaceEntity } from '@app/common/modules/space/entities';
|
||||
import { generateRandomString } from '@app/common/helper/randomString';
|
||||
|
||||
@Injectable()
|
||||
export class SpaceService {
|
||||
constructor(
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
private readonly communityRepository: CommunityRepository,
|
||||
) {}
|
||||
|
||||
async createSpace(
|
||||
addSpaceDto: AddSpaceDto,
|
||||
communityId: string,
|
||||
): Promise<BaseResponseDto> {
|
||||
let parent: SpaceEntity | null = null;
|
||||
|
||||
const { parentUuid } = addSpaceDto;
|
||||
const community = await this.communityRepository.findOne({
|
||||
where: { uuid: communityId },
|
||||
});
|
||||
|
||||
// If the community doesn't exist, throw a 404 error
|
||||
if (!community) {
|
||||
throw new HttpException(
|
||||
`Community with ID ${communityId} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
if (parentUuid) {
|
||||
parent = await this.spaceRepository.findOne({
|
||||
where: { uuid: parentUuid },
|
||||
});
|
||||
|
||||
// If the community doesn't exist, throw a 404 error
|
||||
if (!parent) {
|
||||
throw new HttpException(
|
||||
`Parent with ID ${parentUuid} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
try {
|
||||
const newSpace = this.spaceRepository.create({
|
||||
...addSpaceDto,
|
||||
community,
|
||||
parent: parentUuid ? parent : null,
|
||||
});
|
||||
|
||||
await this.spaceRepository.save(newSpace);
|
||||
|
||||
return new SuccessResponseDto({
|
||||
statusCode: HttpStatus.CREATED,
|
||||
data: newSpace,
|
||||
message: 'Space created successfully',
|
||||
});
|
||||
} catch (error) {
|
||||
throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async getSpacesHierarchyForCommunity(
|
||||
communityUuid: string,
|
||||
): Promise<BaseResponseDto> {
|
||||
const community = await this.communityRepository.findOne({
|
||||
where: { uuid: communityUuid },
|
||||
});
|
||||
|
||||
// If the community doesn't exist, throw a 404 error
|
||||
if (!community) {
|
||||
throw new HttpException(
|
||||
`Community with ID ${communityUuid} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
try {
|
||||
// Get all spaces related to the community, including the parent-child relations
|
||||
const spaces = await this.spaceRepository.find({
|
||||
where: { community: { uuid: communityUuid } },
|
||||
relations: ['parent', 'children'], // Include parent and children relations
|
||||
});
|
||||
|
||||
// Organize spaces into a hierarchical structure
|
||||
const spaceHierarchy = this.buildSpaceHierarchy(spaces);
|
||||
|
||||
return new SuccessResponseDto({
|
||||
message: `Spaces in community ${communityUuid} successfully fetched in hierarchy`,
|
||||
data: spaceHierarchy,
|
||||
statusCode: HttpStatus.OK,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
'An error occurred while fetching the spaces',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async findOne(spaceUuid: string): Promise<BaseResponseDto> {
|
||||
try {
|
||||
const space = await this.spaceRepository.findOne({
|
||||
where: {
|
||||
uuid: spaceUuid,
|
||||
},
|
||||
});
|
||||
|
||||
// If space is not found, throw a NotFoundException
|
||||
if (!space) {
|
||||
throw new HttpException(
|
||||
`Space with UUID ${spaceUuid} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
return new SuccessResponseDto({
|
||||
message: `Space with ID ${spaceUuid} successfully fetched`,
|
||||
data: space,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) {
|
||||
throw error; // If it's an HttpException, rethrow it
|
||||
} else {
|
||||
throw new HttpException(
|
||||
'An error occurred while deleting the community',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async delete(
|
||||
spaceUuid: string,
|
||||
communityUuid: string,
|
||||
): Promise<BaseResponseDto> {
|
||||
try {
|
||||
// First, check if the community exists
|
||||
const community = await this.communityRepository.findOne({
|
||||
where: { uuid: communityUuid },
|
||||
});
|
||||
|
||||
if (!community) {
|
||||
throw new HttpException(
|
||||
`Community with ID ${communityUuid} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
// Check if the space exists
|
||||
const space = await this.spaceRepository.findOne({
|
||||
where: { uuid: spaceUuid, community: { uuid: communityUuid } },
|
||||
});
|
||||
|
||||
if (!space) {
|
||||
throw new HttpException(
|
||||
`Space with ID ${spaceUuid} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
// Delete the space
|
||||
await this.spaceRepository.remove(space);
|
||||
|
||||
return new SuccessResponseDto({
|
||||
message: `Space with ID ${spaceUuid} successfully deleted`,
|
||||
statusCode: HttpStatus.OK,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) {
|
||||
throw error;
|
||||
}
|
||||
throw new HttpException(
|
||||
'An error occurred while deleting the space',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async updateSpace(
|
||||
spaceUuid: string,
|
||||
communityId: string,
|
||||
updateSpaceDto: AddSpaceDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
try {
|
||||
// First, check if the community exists
|
||||
const community = await this.communityRepository.findOne({
|
||||
where: { uuid: communityId },
|
||||
});
|
||||
|
||||
if (!community) {
|
||||
throw new HttpException(
|
||||
`Community with ID ${communityId} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
// Check if the space exists
|
||||
const space = await this.spaceRepository.findOne({
|
||||
where: { uuid: spaceUuid, community: { uuid: communityId } },
|
||||
});
|
||||
|
||||
if (!space) {
|
||||
throw new HttpException(
|
||||
`Space with ID ${spaceUuid} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
// If a parentId is provided, check if the parent exists
|
||||
const { parentUuid } = updateSpaceDto;
|
||||
let parent: SpaceEntity | null = null;
|
||||
if (parentUuid) {
|
||||
parent = await this.spaceRepository.findOne({
|
||||
where: { uuid: parentUuid, community: { uuid: communityId } },
|
||||
});
|
||||
|
||||
// If the parent doesn't exist, throw a 404 error
|
||||
if (!parent) {
|
||||
throw new HttpException(
|
||||
`Parent space with ID ${parentUuid} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
// Set the parent of the current space
|
||||
space.parent = parent;
|
||||
} else {
|
||||
space.parent = null; // If no parent is provided, clear the parent
|
||||
}
|
||||
|
||||
// Update other space properties from updateSpaceDto
|
||||
Object.assign(space, updateSpaceDto);
|
||||
|
||||
// Save the updated space
|
||||
await this.spaceRepository.save(space);
|
||||
|
||||
return new SuccessResponseDto({
|
||||
message: `Space with ID ${spaceUuid} successfully updated`,
|
||||
data: space,
|
||||
statusCode: HttpStatus.OK,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) {
|
||||
throw error;
|
||||
}
|
||||
throw new HttpException(
|
||||
'An error occurred while updating the space',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getSpacesHierarchyForSpace(
|
||||
spaceUuid: string,
|
||||
): Promise<BaseResponseDto> {
|
||||
const space = await this.spaceRepository.findOne({
|
||||
where: { uuid: spaceUuid },
|
||||
});
|
||||
|
||||
// If the space doesn't exist, throw a 404 error
|
||||
if (!space) {
|
||||
throw new HttpException(
|
||||
`Space with ID ${spaceUuid} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// Get all spaces that are children of the provided space, including the parent-child relations
|
||||
const spaces = await this.spaceRepository.find({
|
||||
where: { parent: { uuid: spaceUuid } },
|
||||
relations: ['parent', 'children'], // Include parent and children relations
|
||||
});
|
||||
|
||||
// Organize spaces into a hierarchical structure
|
||||
const spaceHierarchy = this.buildSpaceHierarchy(spaces);
|
||||
|
||||
return new SuccessResponseDto({
|
||||
message: `Spaces under space ${spaceUuid} successfully fetched in hierarchy`,
|
||||
data: spaceHierarchy,
|
||||
statusCode: HttpStatus.OK,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
'An error occurred while fetching the spaces under the space',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getSpaceInvitationCode(spaceUuid: string): Promise<any> {
|
||||
try {
|
||||
const invitationCode = generateRandomString(6);
|
||||
|
||||
const space = await this.spaceRepository.findOne({
|
||||
where: {
|
||||
uuid: spaceUuid,
|
||||
},
|
||||
});
|
||||
|
||||
if (!space) {
|
||||
throw new HttpException(
|
||||
`Space with ID ${spaceUuid} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
space.invitationCode = invitationCode;
|
||||
await this.spaceRepository.save(space);
|
||||
|
||||
return new SuccessResponseDto({
|
||||
message: `Invitation code has been successfuly added to the space`,
|
||||
data: space,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err;
|
||||
} else {
|
||||
throw new HttpException('Space not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private buildSpaceHierarchy(spaces: SpaceEntity[]): SpaceEntity[] {
|
||||
const map = new Map<string, SpaceEntity>();
|
||||
|
||||
// Step 1: Create a map of spaces by UUID
|
||||
spaces.forEach((space) => {
|
||||
map.set(
|
||||
space.uuid,
|
||||
this.spaceRepository.create({
|
||||
...space,
|
||||
children: [], // Add children if needed
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
// Step 2: Organize the hierarchy
|
||||
const rootSpaces: SpaceEntity[] = [];
|
||||
spaces.forEach((space) => {
|
||||
if (space.parent && space.parent.uuid) {
|
||||
const parent = map.get(space.parent.uuid);
|
||||
parent?.children?.push(map.get(space.uuid));
|
||||
} else {
|
||||
rootSpaces.push(map.get(space.uuid));
|
||||
}
|
||||
});
|
||||
|
||||
return rootSpaces; // Return the root spaces with children nested within them
|
||||
}
|
||||
|
||||
async validateCommunityAndSpace(communityUuid: string, spaceUuid: string) {
|
||||
const community = await this.communityRepository.findOne({
|
||||
where: { uuid: communityUuid },
|
||||
});
|
||||
if (!community) {
|
||||
this.throwNotFound('Community', communityUuid);
|
||||
}
|
||||
|
||||
const space = await this.spaceRepository.findOne({
|
||||
where: { uuid: spaceUuid, community: { uuid: communityUuid } },
|
||||
relations: ['devices', 'devices.productDevice'],
|
||||
});
|
||||
if (!space) {
|
||||
this.throwNotFound('Space', spaceUuid);
|
||||
}
|
||||
return space;
|
||||
}
|
||||
|
||||
private throwNotFound(entity: string, uuid: string) {
|
||||
throw new HttpException(
|
||||
`${entity} with ID ${uuid} not found`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
2
src/space/services/subspace/index.ts
Normal file
2
src/space/services/subspace/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './subspace.service';
|
||||
export * from './subspace-device.service';
|
||||
181
src/space/services/subspace/subspace-device.service.ts
Normal file
181
src/space/services/subspace/subspace-device.service.ts
Normal file
@ -0,0 +1,181 @@
|
||||
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 {
|
||||
SpaceRepository,
|
||||
SubspaceRepository,
|
||||
} from '@app/common/modules/space/repositories';
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { DeviceSubSpaceParam, GetSubSpaceParam } from '../../dtos';
|
||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class SubspaceDeviceService {
|
||||
constructor(
|
||||
private readonly spaceRepository: SpaceRepository,
|
||||
private readonly communityRepository: CommunityRepository,
|
||||
private readonly subspaceRepository: SubspaceRepository,
|
||||
private readonly deviceRepository: DeviceRepository,
|
||||
private readonly tuyaService: TuyaService,
|
||||
private readonly productRepository: ProductRepository,
|
||||
) {}
|
||||
|
||||
async listDevicesInSubspace(
|
||||
params: GetSubSpaceParam,
|
||||
): Promise<BaseResponseDto> {
|
||||
const { subSpaceUuid, spaceUuid, communityUuid } = params;
|
||||
|
||||
await this.validateCommunityAndSpace(communityUuid, spaceUuid);
|
||||
|
||||
const subspace = await this.findSubspaceWithDevices(subSpaceUuid);
|
||||
|
||||
const detailedDevices = await Promise.all(
|
||||
subspace.devices.map(async (device) => {
|
||||
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,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
return new SuccessResponseDto({
|
||||
data: detailedDevices,
|
||||
message: 'Successfully retrieved list of devices',
|
||||
});
|
||||
}
|
||||
async associateDeviceToSubspace(params: DeviceSubSpaceParam) {
|
||||
const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid } = params;
|
||||
|
||||
await this.validateCommunityAndSpace(communityUuid, spaceUuid);
|
||||
|
||||
const subspace = await this.findSubspace(subSpaceUuid);
|
||||
const device = await this.findDevice(deviceUuid);
|
||||
|
||||
device.subspace = subspace;
|
||||
await this.deviceRepository.save(device);
|
||||
|
||||
return new SuccessResponseDto({
|
||||
data: device,
|
||||
message: 'Successfully associated device to subspace',
|
||||
});
|
||||
}
|
||||
|
||||
async disassociateDeviceFromSubspace(params: DeviceSubSpaceParam) {
|
||||
const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid } = params;
|
||||
|
||||
await this.validateCommunityAndSpace(communityUuid, spaceUuid);
|
||||
|
||||
await this.findSubspace(subSpaceUuid);
|
||||
const device = await this.findDevice(deviceUuid);
|
||||
|
||||
device.subspace = null;
|
||||
await this.deviceRepository.save(device);
|
||||
|
||||
return new SuccessResponseDto({
|
||||
data: device,
|
||||
message: 'Successfully dissociated device from subspace',
|
||||
});
|
||||
}
|
||||
|
||||
// Helper method to validate community and space
|
||||
private async validateCommunityAndSpace(
|
||||
communityUuid: string,
|
||||
spaceUuid: string,
|
||||
) {
|
||||
const community = await this.communityRepository.findOne({
|
||||
where: { uuid: communityUuid },
|
||||
});
|
||||
if (!community) {
|
||||
this.throwNotFound('Community', communityUuid);
|
||||
}
|
||||
|
||||
const space = await this.spaceRepository.findOne({
|
||||
where: { uuid: spaceUuid, community: { uuid: communityUuid } },
|
||||
});
|
||||
if (!space) {
|
||||
this.throwNotFound('Space', spaceUuid);
|
||||
}
|
||||
return space;
|
||||
}
|
||||
|
||||
// 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 },
|
||||
});
|
||||
if (!device) {
|
||||
this.throwNotFound('Device', deviceUuid);
|
||||
}
|
||||
return device;
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
const { uuid, ...rest } = camelCaseResponse;
|
||||
return {
|
||||
...rest,
|
||||
productUuid: product?.uuid,
|
||||
} as GetDeviceDetailsInterface;
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
'Error fetching device details from Tuya',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user