Merge pull request #53 from SyncrowIOT/door-lock-temp-password

Door lock temp password
This commit is contained in:
faris Aljohari
2024-06-28 21:14:37 +03:00
committed by GitHub
12 changed files with 1019 additions and 0 deletions

View File

@ -0,0 +1,9 @@
export enum WorkingDays {
Sun = 'Sun',
Mon = 'Mon',
Tue = 'Tue',
Wed = 'Wed',
Thu = 'Thu',
Fri = 'Fri',
Sat = 'Sat',
}

View File

@ -17,6 +17,7 @@ import { SeederModule } from '@app/common/seed/seeder.module';
import { UserNotificationModule } from './user-notification/user-notification.module'; import { UserNotificationModule } from './user-notification/user-notification.module';
import { DeviceMessagesSubscriptionModule } from './device-messages/device-messages.module'; import { DeviceMessagesSubscriptionModule } from './device-messages/device-messages.module';
import { SceneModule } from './scene/scene.module'; import { SceneModule } from './scene/scene.module';
import { DoorLockModule } from './door-lock/door.lock.module';
@Module({ @Module({
imports: [ imports: [
ConfigModule.forRoot({ ConfigModule.forRoot({
@ -38,6 +39,7 @@ import { SceneModule } from './scene/scene.module';
UserNotificationModule, UserNotificationModule,
SeederModule, SeederModule,
SceneModule, SceneModule,
DoorLockModule,
], ],
controllers: [AuthenticationController], controllers: [AuthenticationController],
}) })

View 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,
);
}
}
}

View File

@ -0,0 +1 @@
export * from './door.lock.controller';

View 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 {}

View 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;
}

View 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[];
}

View File

@ -0,0 +1 @@
export * from './add.online-temp.dto';

View 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;
}

View 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);
}
}
}

View 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,
);
}
}
}

View File

@ -0,0 +1 @@
export * from './door.lock.service';