mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-11-26 08:54:54 +00:00
Merge pull request #53 from SyncrowIOT/door-lock-temp-password
Door lock temp password
This commit is contained in:
9
libs/common/src/constants/working-days.ts
Normal file
9
libs/common/src/constants/working-days.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export enum WorkingDays {
|
||||
Sun = 'Sun',
|
||||
Mon = 'Mon',
|
||||
Tue = 'Tue',
|
||||
Wed = 'Wed',
|
||||
Thu = 'Thu',
|
||||
Fri = 'Fri',
|
||||
Sat = 'Sat',
|
||||
}
|
||||
@ -17,6 +17,7 @@ import { SeederModule } from '@app/common/seed/seeder.module';
|
||||
import { UserNotificationModule } from './user-notification/user-notification.module';
|
||||
import { DeviceMessagesSubscriptionModule } from './device-messages/device-messages.module';
|
||||
import { SceneModule } from './scene/scene.module';
|
||||
import { DoorLockModule } from './door-lock/door.lock.module';
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
@ -38,6 +39,7 @@ import { SceneModule } from './scene/scene.module';
|
||||
UserNotificationModule,
|
||||
SeederModule,
|
||||
SceneModule,
|
||||
DoorLockModule,
|
||||
],
|
||||
controllers: [AuthenticationController],
|
||||
})
|
||||
|
||||
182
src/door-lock/controllers/door.lock.controller.ts
Normal file
182
src/door-lock/controllers/door.lock.controller.ts
Normal file
@ -0,0 +1,182 @@
|
||||
import { DoorLockService } from '../services/door.lock.service';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Post,
|
||||
Param,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Get,
|
||||
Delete,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { AddDoorLockOnlineDto } from '../dtos/add.online-temp.dto';
|
||||
import { AddDoorLockOfflineTempDto } from '../dtos/add.offline-temp.dto';
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
|
||||
@ApiTags('Door Lock Module')
|
||||
@Controller({
|
||||
version: '1',
|
||||
path: 'door-lock',
|
||||
})
|
||||
export class DoorLockController {
|
||||
constructor(private readonly doorLockService: DoorLockService) {}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post('temporary-password/online/:doorLockUuid')
|
||||
async addOnlineTemporaryPassword(
|
||||
@Body() addDoorLockDto: AddDoorLockOnlineDto,
|
||||
@Param('doorLockUuid') doorLockUuid: string,
|
||||
) {
|
||||
try {
|
||||
const temporaryPassword =
|
||||
await this.doorLockService.addOnlineTemporaryPassword(
|
||||
addDoorLockDto,
|
||||
doorLockUuid,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'online temporary password added successfully',
|
||||
data: {
|
||||
id: temporaryPassword.id,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Internal server error',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post('temporary-password/offline/one-time/:doorLockUuid')
|
||||
async addOfflineOneTimeTemporaryPassword(
|
||||
@Body() addDoorLockOfflineTempDto: AddDoorLockOfflineTempDto,
|
||||
@Param('doorLockUuid') doorLockUuid: string,
|
||||
) {
|
||||
try {
|
||||
const temporaryPassword =
|
||||
await this.doorLockService.addOfflineOneTimeTemporaryPassword(
|
||||
addDoorLockOfflineTempDto,
|
||||
doorLockUuid,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'offline temporary password added successfully',
|
||||
data: temporaryPassword,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Internal server error',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post('temporary-password/offline/multiple-time/:doorLockUuid')
|
||||
async addOfflineMultipleTimeTemporaryPassword(
|
||||
@Body() addDoorLockOfflineTempDto: AddDoorLockOfflineTempDto,
|
||||
@Param('doorLockUuid') doorLockUuid: string,
|
||||
) {
|
||||
try {
|
||||
const temporaryPassword =
|
||||
await this.doorLockService.addOfflineMultipleTimeTemporaryPassword(
|
||||
addDoorLockOfflineTempDto,
|
||||
doorLockUuid,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'offline temporary password added successfully',
|
||||
data: temporaryPassword,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Internal server error',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('temporary-password/online/:doorLockUuid')
|
||||
async getOnlineTemporaryPasswords(
|
||||
@Param('doorLockUuid') doorLockUuid: string,
|
||||
) {
|
||||
try {
|
||||
return await this.doorLockService.getOnlineTemporaryPasswords(
|
||||
doorLockUuid,
|
||||
);
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Internal server error',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('temporary-password/offline/one-time/:doorLockUuid')
|
||||
async getOfflineOneTimeTemporaryPasswords(
|
||||
@Param('doorLockUuid') doorLockUuid: string,
|
||||
) {
|
||||
try {
|
||||
return await this.doorLockService.getOfflineOneTimeTemporaryPasswords(
|
||||
doorLockUuid,
|
||||
);
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Internal server error',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('temporary-password/offline/multiple-time/:doorLockUuid')
|
||||
async getOfflineMultipleTimeTemporaryPasswords(
|
||||
@Param('doorLockUuid') doorLockUuid: string,
|
||||
) {
|
||||
try {
|
||||
return await this.doorLockService.getOfflineMultipleTimeTemporaryPasswords(
|
||||
doorLockUuid,
|
||||
);
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Internal server error',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Delete('temporary-password/:doorLockUuid/:passwordId')
|
||||
async deleteDoorLockPassword(
|
||||
@Param('doorLockUuid') doorLockUuid: string,
|
||||
@Param('passwordId') passwordId: string,
|
||||
) {
|
||||
try {
|
||||
await this.doorLockService.deleteDoorLockPassword(
|
||||
doorLockUuid,
|
||||
passwordId,
|
||||
);
|
||||
return {
|
||||
statusCode: HttpStatus.OK,
|
||||
message: 'Temporary Password deleted Successfully',
|
||||
};
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Internal server error',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/door-lock/controllers/index.ts
Normal file
1
src/door-lock/controllers/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './door.lock.controller';
|
||||
14
src/door-lock/door.lock.module.ts
Normal file
14
src/door-lock/door.lock.module.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { DoorLockService } from './services/door.lock.service';
|
||||
import { DoorLockController } from './controllers/door.lock.controller';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { DeviceRepositoryModule } from '@app/common/modules/device';
|
||||
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
||||
import { PasswordEncryptionService } from './services/encryption.services';
|
||||
@Module({
|
||||
imports: [ConfigModule, DeviceRepositoryModule],
|
||||
controllers: [DoorLockController],
|
||||
providers: [DoorLockService, PasswordEncryptionService, DeviceRepository],
|
||||
exports: [DoorLockService],
|
||||
})
|
||||
export class DoorLockModule {}
|
||||
36
src/door-lock/dtos/add.offline-temp.dto.ts
Normal file
36
src/door-lock/dtos/add.offline-temp.dto.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString, Length } from 'class-validator';
|
||||
|
||||
export class AddDoorLockOfflineTempDto {
|
||||
@ApiProperty({
|
||||
description: 'name',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public name: string;
|
||||
@ApiProperty({
|
||||
description: 'password',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Length(7, 7)
|
||||
public password: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'effectiveTime',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public effectiveTime: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'invalidTime',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public invalidTime: string;
|
||||
}
|
||||
84
src/door-lock/dtos/add.online-temp.dto.ts
Normal file
84
src/door-lock/dtos/add.online-temp.dto.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import {
|
||||
IsNotEmpty,
|
||||
IsString,
|
||||
IsArray,
|
||||
ValidateNested,
|
||||
IsEnum,
|
||||
Length,
|
||||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { WorkingDays } from '@app/common/constants/working-days';
|
||||
|
||||
class ScheduleDto {
|
||||
@ApiProperty({
|
||||
description: 'effectiveTime',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public effectiveTime: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'invalidTime',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public invalidTime: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'workingDay',
|
||||
enum: WorkingDays,
|
||||
isArray: true,
|
||||
required: true,
|
||||
})
|
||||
@IsArray()
|
||||
@IsEnum(WorkingDays, { each: true })
|
||||
@IsNotEmpty()
|
||||
public workingDay: WorkingDays[];
|
||||
}
|
||||
|
||||
export class AddDoorLockOnlineDto {
|
||||
@ApiProperty({
|
||||
description: 'name',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public name: string;
|
||||
@ApiProperty({
|
||||
description: 'password',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Length(7, 7)
|
||||
public password: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'effectiveTime',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public effectiveTime: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'invalidTime',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public invalidTime: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'scheduleList',
|
||||
type: [ScheduleDto],
|
||||
required: false,
|
||||
})
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => ScheduleDto)
|
||||
public scheduleList: ScheduleDto[];
|
||||
}
|
||||
1
src/door-lock/dtos/index.ts
Normal file
1
src/door-lock/dtos/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './add.online-temp.dto';
|
||||
57
src/door-lock/interfaces/door.lock.interface.ts
Normal file
57
src/door-lock/interfaces/door.lock.interface.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { WorkingDays } from '@app/common/constants/working-days';
|
||||
|
||||
export interface createTickInterface {
|
||||
success: boolean;
|
||||
result: {
|
||||
expire_time: number;
|
||||
ticket_id: string;
|
||||
ticket_key: string;
|
||||
id?: number;
|
||||
};
|
||||
msg?: string;
|
||||
}
|
||||
export interface addDeviceObjectInterface {
|
||||
name: string;
|
||||
encryptedPassword: string;
|
||||
effectiveTime: string;
|
||||
invalidTime: string;
|
||||
ticketId: string;
|
||||
scheduleList?: any[];
|
||||
}
|
||||
|
||||
export interface ScheduleDto {
|
||||
effectiveTime: string;
|
||||
invalidTime: string;
|
||||
workingDay: WorkingDays[];
|
||||
}
|
||||
|
||||
export interface AddDoorLockOnlineInterface {
|
||||
name: string;
|
||||
password: string;
|
||||
effectiveTime: string;
|
||||
invalidTime: string;
|
||||
scheduleList?: ScheduleDto[];
|
||||
}
|
||||
export interface getPasswordInterface {
|
||||
success: boolean;
|
||||
result: [
|
||||
{
|
||||
effective_time: number;
|
||||
id: number;
|
||||
invalid_time: number;
|
||||
name: string;
|
||||
phase: number;
|
||||
phone: string;
|
||||
schedule_list?: [];
|
||||
sn: number;
|
||||
time_zone: string;
|
||||
type: number;
|
||||
},
|
||||
];
|
||||
msg?: string;
|
||||
}
|
||||
export interface deleteTemporaryPasswordInterface {
|
||||
success: boolean;
|
||||
result: boolean;
|
||||
msg?: string;
|
||||
}
|
||||
575
src/door-lock/services/door.lock.service.ts
Normal file
575
src/door-lock/services/door.lock.service.ts
Normal file
@ -0,0 +1,575 @@
|
||||
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
|
||||
import { TuyaContext } from '@tuya/tuya-connector-nodejs';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import {
|
||||
AddDoorLockOnlineInterface,
|
||||
addDeviceObjectInterface,
|
||||
createTickInterface,
|
||||
deleteTemporaryPasswordInterface,
|
||||
getPasswordInterface,
|
||||
} from '../interfaces/door.lock.interface';
|
||||
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
||||
import { ProductType } from '@app/common/constants/product-type.enum';
|
||||
|
||||
import { PasswordEncryptionService } from './encryption.services';
|
||||
import { AddDoorLockOfflineTempDto } from '../dtos/add.offline-temp.dto';
|
||||
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
|
||||
|
||||
@Injectable()
|
||||
export class DoorLockService {
|
||||
private tuya: TuyaContext;
|
||||
constructor(
|
||||
private readonly configService: ConfigService,
|
||||
private readonly deviceRepository: DeviceRepository,
|
||||
private readonly passwordEncryptionService: PasswordEncryptionService,
|
||||
) {
|
||||
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,
|
||||
});
|
||||
}
|
||||
async deleteDoorLockPassword(doorLockUuid: string, passwordId: string) {
|
||||
try {
|
||||
const deviceDetails = await this.getDeviceByDeviceUuid(doorLockUuid);
|
||||
|
||||
if (!deviceDetails || !deviceDetails.deviceTuyaUuid) {
|
||||
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
||||
} else if (deviceDetails.productDevice.prodType !== ProductType.DL) {
|
||||
throw new HttpException(
|
||||
'This is not a door lock device',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
const deletePass = await this.deleteDoorLockPasswordTuya(
|
||||
deviceDetails.deviceTuyaUuid,
|
||||
passwordId,
|
||||
);
|
||||
|
||||
if (!deletePass.success) {
|
||||
throw new HttpException('PasswordId not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
return deletePass;
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Error deleting temporary password',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
async deleteDoorLockPasswordTuya(
|
||||
doorLockUuid: string,
|
||||
passwordId: string,
|
||||
): Promise<deleteTemporaryPasswordInterface> {
|
||||
try {
|
||||
const path = `/v1.0/devices/${doorLockUuid}/door-lock/temp-passwords/${passwordId}`;
|
||||
|
||||
const response = await this.tuya.request({
|
||||
method: 'DELETE',
|
||||
path,
|
||||
});
|
||||
|
||||
return response as deleteTemporaryPasswordInterface;
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
'Error deleting temporary password from Tuya',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
async getOfflineMultipleTimeTemporaryPasswords(doorLockUuid: string) {
|
||||
try {
|
||||
const deviceDetails = await this.getDeviceByDeviceUuid(doorLockUuid);
|
||||
|
||||
if (!deviceDetails || !deviceDetails.deviceTuyaUuid) {
|
||||
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
||||
} else if (deviceDetails.productDevice.prodType !== ProductType.DL) {
|
||||
throw new HttpException(
|
||||
'This is not a door lock device',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
const passwords = await this.getTemporaryPasswordsTuya(
|
||||
deviceDetails.deviceTuyaUuid,
|
||||
);
|
||||
|
||||
if (passwords.result.length > 0) {
|
||||
const passwordFiltered = passwords.result.filter(
|
||||
(item) =>
|
||||
(!item.schedule_list || item.schedule_list.length === 0) &&
|
||||
item.type === 0,
|
||||
);
|
||||
|
||||
return convertKeysToCamelCase(passwordFiltered);
|
||||
}
|
||||
|
||||
return passwords;
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message ||
|
||||
'Error getting offline multiple time temporary passwords',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
async getOfflineOneTimeTemporaryPasswords(doorLockUuid: string) {
|
||||
try {
|
||||
const deviceDetails = await this.getDeviceByDeviceUuid(doorLockUuid);
|
||||
|
||||
if (!deviceDetails || !deviceDetails.deviceTuyaUuid) {
|
||||
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
||||
} else if (deviceDetails.productDevice.prodType !== ProductType.DL) {
|
||||
throw new HttpException(
|
||||
'This is not a door lock device',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
const passwords = await this.getTemporaryPasswordsTuya(
|
||||
deviceDetails.deviceTuyaUuid,
|
||||
);
|
||||
|
||||
if (passwords.result.length > 0) {
|
||||
const passwordFiltered = passwords.result.filter(
|
||||
(item) =>
|
||||
(!item.schedule_list || item.schedule_list.length === 0) &&
|
||||
item.type === 1,
|
||||
);
|
||||
|
||||
return convertKeysToCamelCase(passwordFiltered);
|
||||
}
|
||||
|
||||
return passwords;
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Error getting offline one time temporary passwords',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
async getOnlineTemporaryPasswords(doorLockUuid: string) {
|
||||
try {
|
||||
const deviceDetails = await this.getDeviceByDeviceUuid(doorLockUuid);
|
||||
|
||||
if (!deviceDetails || !deviceDetails.deviceTuyaUuid) {
|
||||
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
||||
} else if (deviceDetails.productDevice.prodType !== ProductType.DL) {
|
||||
throw new HttpException(
|
||||
'This is not a door lock device',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
const passwords = await this.getTemporaryPasswordsTuya(
|
||||
deviceDetails.deviceTuyaUuid,
|
||||
);
|
||||
|
||||
if (passwords.result.length > 0) {
|
||||
const passwordFiltered = passwords.result
|
||||
.filter((item) => item.schedule_list && item.schedule_list.length > 0)
|
||||
.map((password: any) => {
|
||||
password.schedule_list = password.schedule_list.map((schedule) => {
|
||||
schedule.working_day = this.getDaysFromWorkingDayValue(
|
||||
schedule.working_day,
|
||||
);
|
||||
schedule.effective_time = this.minutesToTime(
|
||||
schedule.effective_time,
|
||||
);
|
||||
schedule.invalid_time = this.minutesToTime(schedule.invalid_time);
|
||||
return schedule;
|
||||
});
|
||||
return password;
|
||||
});
|
||||
|
||||
return convertKeysToCamelCase(passwordFiltered);
|
||||
}
|
||||
|
||||
return passwords;
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Error getting online temporary passwords',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
async getTemporaryPasswordsTuya(
|
||||
doorLockUuid: string,
|
||||
): Promise<getPasswordInterface> {
|
||||
try {
|
||||
const path = `/v1.0/devices/${doorLockUuid}/door-lock/temp-passwords?valid=true`;
|
||||
|
||||
const response = await this.tuya.request({
|
||||
method: 'GET',
|
||||
path,
|
||||
});
|
||||
|
||||
return response as getPasswordInterface;
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
'Error getting temporary passwords from Tuya',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
async addOfflineMultipleTimeTemporaryPassword(
|
||||
addDoorLockOfflineTempDto: AddDoorLockOfflineTempDto,
|
||||
doorLockUuid: string,
|
||||
) {
|
||||
try {
|
||||
const createOnlinePass = await this.addOnlineTemporaryPassword(
|
||||
addDoorLockOfflineTempDto,
|
||||
doorLockUuid,
|
||||
'multiple',
|
||||
false,
|
||||
);
|
||||
if (!createOnlinePass) {
|
||||
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
const createOnceOfflinePass = await this.addOfflineTemporaryPasswordTuya(
|
||||
addDoorLockOfflineTempDto,
|
||||
createOnlinePass.id,
|
||||
createOnlinePass.deviceTuyaUuid,
|
||||
'multiple',
|
||||
);
|
||||
if (!createOnceOfflinePass.success) {
|
||||
throw new HttpException(
|
||||
createOnceOfflinePass.msg,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
return {
|
||||
result: createOnceOfflinePass.result,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Error adding offline temporary password from Tuya',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
async addOfflineOneTimeTemporaryPassword(
|
||||
addDoorLockOfflineTempDto: AddDoorLockOfflineTempDto,
|
||||
doorLockUuid: string,
|
||||
) {
|
||||
try {
|
||||
const createOnlinePass = await this.addOnlineTemporaryPassword(
|
||||
addDoorLockOfflineTempDto,
|
||||
doorLockUuid,
|
||||
'once',
|
||||
false,
|
||||
);
|
||||
if (!createOnlinePass) {
|
||||
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
const createOnceOfflinePass = await this.addOfflineTemporaryPasswordTuya(
|
||||
addDoorLockOfflineTempDto,
|
||||
createOnlinePass.id,
|
||||
createOnlinePass.deviceTuyaUuid,
|
||||
'once',
|
||||
);
|
||||
if (!createOnceOfflinePass.success) {
|
||||
throw new HttpException(
|
||||
createOnceOfflinePass.msg,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
return {
|
||||
result: createOnceOfflinePass.result,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Error adding offline temporary password from Tuya',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
async addOfflineTemporaryPasswordTuya(
|
||||
addDoorLockDto: AddDoorLockOnlineInterface,
|
||||
onlinePassId: number,
|
||||
doorLockUuid: string,
|
||||
type: string,
|
||||
): Promise<createTickInterface> {
|
||||
try {
|
||||
const path = `/v1.1/devices/${doorLockUuid}/door-lock/offline-temp-password`;
|
||||
|
||||
const response = await this.tuya.request({
|
||||
method: 'POST',
|
||||
path,
|
||||
body: {
|
||||
name: addDoorLockDto.name,
|
||||
...(type === 'multiple' && {
|
||||
effective_time: addDoorLockDto.effectiveTime,
|
||||
invalid_time: addDoorLockDto.invalidTime,
|
||||
}),
|
||||
|
||||
type,
|
||||
password_id: onlinePassId,
|
||||
},
|
||||
});
|
||||
|
||||
return response as createTickInterface;
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
'Error adding offline temporary password from Tuya',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
async addOnlineTemporaryPassword(
|
||||
addDoorLockDto: AddDoorLockOnlineInterface,
|
||||
doorLockUuid: string,
|
||||
type: string = 'once',
|
||||
isOnline: boolean = true,
|
||||
) {
|
||||
try {
|
||||
const passwordData = await this.getTicketAndEncryptedPassword(
|
||||
doorLockUuid,
|
||||
addDoorLockDto.password,
|
||||
);
|
||||
if (
|
||||
!passwordData.ticketKey ||
|
||||
!passwordData.encryptedPassword ||
|
||||
!passwordData.deviceTuyaUuid
|
||||
) {
|
||||
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
const addDeviceObj: addDeviceObjectInterface = {
|
||||
...addDoorLockDto,
|
||||
...passwordData,
|
||||
};
|
||||
const createPass = await this.addOnlineTemporaryPasswordTuya(
|
||||
addDeviceObj,
|
||||
passwordData.deviceTuyaUuid,
|
||||
type,
|
||||
isOnline,
|
||||
);
|
||||
|
||||
if (!createPass.success) {
|
||||
throw new HttpException(createPass.msg, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
return {
|
||||
id: createPass.result.id,
|
||||
deviceTuyaUuid: passwordData.deviceTuyaUuid,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Error adding online temporary password from Tuya',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
async getTicketAndEncryptedPassword(
|
||||
doorLockUuid: string,
|
||||
passwordPlan: string,
|
||||
) {
|
||||
try {
|
||||
const deviceDetails = await this.getDeviceByDeviceUuid(doorLockUuid);
|
||||
|
||||
if (!deviceDetails || !deviceDetails.deviceTuyaUuid) {
|
||||
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
||||
} else if (deviceDetails.productDevice.prodType !== ProductType.DL) {
|
||||
throw new HttpException(
|
||||
'This is not a door lock device',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
const ticketDetails = await this.createDoorLockTicketTuya(
|
||||
deviceDetails.deviceTuyaUuid,
|
||||
);
|
||||
|
||||
if (!ticketDetails.result.ticket_id || !ticketDetails.result.ticket_key) {
|
||||
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
const decrypted =
|
||||
this.passwordEncryptionService.generateEncryptedPassword(
|
||||
passwordPlan,
|
||||
ticketDetails.result.ticket_key,
|
||||
);
|
||||
return {
|
||||
ticketId: ticketDetails.result.ticket_id,
|
||||
ticketKey: ticketDetails.result.ticket_key,
|
||||
encryptedPassword: decrypted,
|
||||
deviceTuyaUuid: deviceDetails.deviceTuyaUuid,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Error processing the request',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async createDoorLockTicketTuya(
|
||||
deviceUuid: string,
|
||||
): Promise<createTickInterface> {
|
||||
try {
|
||||
const path = `/v1.0/smart-lock/devices/${deviceUuid}/password-ticket`;
|
||||
const response = await this.tuya.request({
|
||||
method: 'POST',
|
||||
path,
|
||||
});
|
||||
|
||||
return response as createTickInterface;
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
'Error creating door lock ticket from Tuya',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async addOnlineTemporaryPasswordTuya(
|
||||
addDeviceObj: addDeviceObjectInterface,
|
||||
doorLockUuid: string,
|
||||
type: string,
|
||||
isOnline: boolean = true,
|
||||
): Promise<createTickInterface> {
|
||||
try {
|
||||
const path = `/v1.0/devices/${doorLockUuid}/door-lock/temp-password`;
|
||||
let scheduleList;
|
||||
if (isOnline) {
|
||||
scheduleList = addDeviceObj.scheduleList.map((schedule) => ({
|
||||
effective_time: this.timeToMinutes(schedule.effectiveTime),
|
||||
invalid_time: this.timeToMinutes(schedule.invalidTime),
|
||||
working_day: this.getWorkingDayValue(schedule.workingDay),
|
||||
}));
|
||||
}
|
||||
|
||||
const response = await this.tuya.request({
|
||||
method: 'POST',
|
||||
path,
|
||||
body: {
|
||||
name: addDeviceObj.name,
|
||||
password: addDeviceObj.encryptedPassword,
|
||||
effective_time: addDeviceObj.effectiveTime,
|
||||
invalid_time: addDeviceObj.invalidTime,
|
||||
password_type: 'ticket',
|
||||
ticket_id: addDeviceObj.ticketId,
|
||||
...(isOnline && {
|
||||
schedule_list: scheduleList,
|
||||
}),
|
||||
type: type === 'multiple' ? '0' : '1',
|
||||
},
|
||||
});
|
||||
|
||||
return response as createTickInterface;
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.msg || 'Error adding online temporary password from Tuya',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
getWorkingDayValue(days) {
|
||||
// Array representing the days of the week
|
||||
const weekDays = ['Sat', 'Fri', 'Thu', 'Wed', 'Tue', 'Mon', 'Sun'];
|
||||
|
||||
// Initialize a binary string with 7 bits
|
||||
let binaryString = '0000000';
|
||||
|
||||
// Iterate through the input array and update the binary string
|
||||
days.forEach((day) => {
|
||||
const index = weekDays.indexOf(day);
|
||||
if (index !== -1) {
|
||||
// Set the corresponding bit to '1'
|
||||
binaryString =
|
||||
binaryString.substring(0, index) +
|
||||
'1' +
|
||||
binaryString.substring(index + 1);
|
||||
}
|
||||
});
|
||||
|
||||
// Convert the binary string to an integer
|
||||
const workingDayValue = parseInt(binaryString, 2);
|
||||
|
||||
return workingDayValue;
|
||||
}
|
||||
getDaysFromWorkingDayValue(workingDayValue) {
|
||||
// Array representing the days of the week
|
||||
const weekDays = ['Sat', 'Fri', 'Thu', 'Wed', 'Tue', 'Mon', 'Sun'];
|
||||
|
||||
// Convert the integer to a binary string and pad with leading zeros to ensure 7 bits
|
||||
const binaryString = workingDayValue.toString(2).padStart(7, '0');
|
||||
|
||||
// Initialize an array to hold the days of the week
|
||||
const days = [];
|
||||
|
||||
// Iterate through the binary string and weekDays array
|
||||
for (let i = 0; i < binaryString.length; i++) {
|
||||
if (binaryString[i] === '1') {
|
||||
days.push(weekDays[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return days;
|
||||
}
|
||||
timeToMinutes(timeStr) {
|
||||
try {
|
||||
// Special case for "24:00"
|
||||
if (timeStr === '24:00') {
|
||||
return 1440;
|
||||
}
|
||||
|
||||
// Regular expression to validate the 24-hour time format (HH:MM)
|
||||
const timePattern = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
||||
const match = timeStr.match(timePattern);
|
||||
|
||||
if (!match) {
|
||||
throw new Error('Invalid time format');
|
||||
}
|
||||
|
||||
// Extract hours and minutes from the matched groups
|
||||
const hours = parseInt(match[1], 10);
|
||||
const minutes = parseInt(match[2], 10);
|
||||
|
||||
// Calculate the total minutes
|
||||
const totalMinutes = hours * 60 + minutes;
|
||||
|
||||
return totalMinutes;
|
||||
} catch (error) {
|
||||
throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
minutesToTime(totalMinutes) {
|
||||
try {
|
||||
if (
|
||||
typeof totalMinutes !== 'number' ||
|
||||
totalMinutes < 0 ||
|
||||
totalMinutes > 1440
|
||||
) {
|
||||
throw new Error('Invalid minutes value');
|
||||
}
|
||||
|
||||
if (totalMinutes === 1440) {
|
||||
return '24:00';
|
||||
}
|
||||
|
||||
const hours = Math.floor(totalMinutes / 60);
|
||||
const minutes = totalMinutes % 60;
|
||||
|
||||
const formattedHours = String(hours).padStart(2, '0');
|
||||
const formattedMinutes = String(minutes).padStart(2, '0');
|
||||
|
||||
return `${formattedHours}:${formattedMinutes}`;
|
||||
} catch (error) {
|
||||
throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
async getDeviceByDeviceUuid(
|
||||
deviceUuid: string,
|
||||
withProductDevice: boolean = true,
|
||||
) {
|
||||
try {
|
||||
return await this.deviceRepository.findOne({
|
||||
where: {
|
||||
uuid: deviceUuid,
|
||||
},
|
||||
...(withProductDevice && { relations: ['productDevice'] }),
|
||||
});
|
||||
} catch (error) {
|
||||
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
57
src/door-lock/services/encryption.services.ts
Normal file
57
src/door-lock/services/encryption.services.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import * as CryptoJS from 'crypto-js';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
@Injectable()
|
||||
export class PasswordEncryptionService {
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
|
||||
encrypt(plainText: string, secretKey: string): string {
|
||||
const keyBytes = CryptoJS.enc.Utf8.parse(secretKey);
|
||||
|
||||
const encrypted = CryptoJS.AES.encrypt(plainText, keyBytes, {
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
});
|
||||
|
||||
return encrypted.ciphertext.toString(CryptoJS.enc.Hex);
|
||||
}
|
||||
|
||||
decrypt(encryptedText: string, secretKey: string): string {
|
||||
const keyBytes = CryptoJS.enc.Utf8.parse(secretKey);
|
||||
|
||||
const decrypted = CryptoJS.AES.decrypt(
|
||||
{
|
||||
ciphertext: CryptoJS.enc.Hex.parse(encryptedText),
|
||||
} as any,
|
||||
keyBytes,
|
||||
{
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
},
|
||||
);
|
||||
|
||||
return decrypted.toString(CryptoJS.enc.Utf8);
|
||||
}
|
||||
|
||||
generateEncryptedPassword(
|
||||
plainTextPassword: string,
|
||||
ticketKey: string,
|
||||
): string {
|
||||
try {
|
||||
const accessSecret = this.configService.get<string>(
|
||||
'auth-config.SECRET_KEY',
|
||||
);
|
||||
// The accessSecret must be 32 bytes, ensure it is properly padded or truncated
|
||||
const paddedAccessSecret = accessSecret.padEnd(32, '0').slice(0, 32);
|
||||
const plainTextTicketKey = this.decrypt(ticketKey, paddedAccessSecret);
|
||||
|
||||
return this.encrypt(plainTextPassword, plainTextTicketKey);
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
`Error encrypting password: ${error.message}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/door-lock/services/index.ts
Normal file
1
src/door-lock/services/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './door.lock.service';
|
||||
Reference in New Issue
Block a user