mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-11-26 09:24:54 +00:00
Merge pull request #79 from SyncrowIOT/create-vistor-password
Create vistor password
This commit is contained in:
17
.env.example
17
.env.example
@ -72,4 +72,19 @@ TRUN_ON_TUYA_SOCKET=
|
|||||||
|
|
||||||
TUYA_EU_URL=
|
TUYA_EU_URL=
|
||||||
|
|
||||||
MONGODB_URI=
|
FIREBASE_API_KEY=
|
||||||
|
|
||||||
|
FIREBASE_AUTH_DOMAIN=
|
||||||
|
|
||||||
|
FIREBASE_PROJECT_ID=
|
||||||
|
|
||||||
|
FIREBASE_STORAGE_BUCKET=
|
||||||
|
|
||||||
|
FIREBASE_MESSAGING_SENDER_ID=
|
||||||
|
|
||||||
|
FIREBASE_APP_ID=d
|
||||||
|
|
||||||
|
FIREBASE_MEASUREMENT_ID=
|
||||||
|
|
||||||
|
FIREBASE_DATABASE_URL=
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export class EmailService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendOTPEmail(
|
async sendEmail(
|
||||||
email: string,
|
email: string,
|
||||||
subject: string,
|
subject: string,
|
||||||
message: string,
|
message: string,
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import { LoggingInterceptor } from './interceptors/logging.interceptor';
|
|||||||
import { AutomationModule } from './automation/automation.module';
|
import { AutomationModule } from './automation/automation.module';
|
||||||
import { RegionModule } from './region/region.module';
|
import { RegionModule } from './region/region.module';
|
||||||
import { TimeZoneModule } from './timezone/timezone.module';
|
import { TimeZoneModule } from './timezone/timezone.module';
|
||||||
|
import { VisitorPasswordModule } from './vistor-password/visitor-password.module';
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({
|
ConfigModule.forRoot({
|
||||||
@ -48,6 +49,7 @@ import { TimeZoneModule } from './timezone/timezone.module';
|
|||||||
DoorLockModule,
|
DoorLockModule,
|
||||||
RegionModule,
|
RegionModule,
|
||||||
TimeZoneModule,
|
TimeZoneModule,
|
||||||
|
VisitorPasswordModule,
|
||||||
],
|
],
|
||||||
controllers: [AuthenticationController],
|
controllers: [AuthenticationController],
|
||||||
providers: [
|
providers: [
|
||||||
|
|||||||
@ -202,7 +202,7 @@ export class UserAuthService {
|
|||||||
});
|
});
|
||||||
const subject = 'OTP send successfully';
|
const subject = 'OTP send successfully';
|
||||||
const message = `Your OTP code is ${otpCode}`;
|
const message = `Your OTP code is ${otpCode}`;
|
||||||
this.emailService.sendOTPEmail(data.email, subject, message);
|
this.emailService.sendEmail(data.email, subject, message);
|
||||||
return otpCode;
|
return otpCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
|
import {
|
||||||
|
Injectable,
|
||||||
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
|
BadRequestException,
|
||||||
|
} from '@nestjs/common';
|
||||||
import { TuyaContext } from '@tuya/tuya-connector-nodejs';
|
import { TuyaContext } from '@tuya/tuya-connector-nodejs';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import {
|
import {
|
||||||
@ -84,7 +89,10 @@ export class DoorLockService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async getOfflineMultipleTimeTemporaryPasswords(doorLockUuid: string) {
|
async getOfflineMultipleTimeTemporaryPasswords(
|
||||||
|
doorLockUuid: string,
|
||||||
|
fromVisitor?: boolean,
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const deviceDetails = await this.getDeviceByDeviceUuid(doorLockUuid);
|
const deviceDetails = await this.getDeviceByDeviceUuid(doorLockUuid);
|
||||||
|
|
||||||
@ -100,11 +108,25 @@ export class DoorLockService {
|
|||||||
deviceDetails.deviceTuyaUuid,
|
deviceDetails.deviceTuyaUuid,
|
||||||
'multiple',
|
'multiple',
|
||||||
);
|
);
|
||||||
|
if (!passwords.result.records.length && fromVisitor) {
|
||||||
if (passwords.result.records.length > 0) {
|
throw new BadRequestException();
|
||||||
return convertKeysToCamelCase(passwords.result.records);
|
}
|
||||||
|
if (passwords.result.records.length > 0) {
|
||||||
|
return fromVisitor
|
||||||
|
? convertKeysToCamelCase(passwords.result.records).map((password) => {
|
||||||
|
return {
|
||||||
|
passwordId: `${password.pwdId}`,
|
||||||
|
invalidTime: `${password.gmtExpired}`,
|
||||||
|
effectiveTime: `${password.gmtStart}`,
|
||||||
|
passwordCreated: `${password.gmtCreate}`,
|
||||||
|
createdTime: password.pwdName,
|
||||||
|
passwordStatus: `${password.status}`,
|
||||||
|
passwordType: 'OFFLINE_MULTIPLE',
|
||||||
|
deviceUuid: doorLockUuid,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
: convertKeysToCamelCase(passwords.result.records);
|
||||||
}
|
}
|
||||||
|
|
||||||
return passwords;
|
return passwords;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
@ -114,10 +136,12 @@ export class DoorLockService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async getOfflineOneTimeTemporaryPasswords(doorLockUuid: string) {
|
async getOfflineOneTimeTemporaryPasswords(
|
||||||
|
doorLockUuid: string,
|
||||||
|
fromVisitor?: boolean,
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const deviceDetails = await this.getDeviceByDeviceUuid(doorLockUuid);
|
const deviceDetails = await this.getDeviceByDeviceUuid(doorLockUuid);
|
||||||
|
|
||||||
if (!deviceDetails || !deviceDetails.deviceTuyaUuid) {
|
if (!deviceDetails || !deviceDetails.deviceTuyaUuid) {
|
||||||
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
||||||
} else if (deviceDetails.productDevice.prodType !== ProductType.DL) {
|
} else if (deviceDetails.productDevice.prodType !== ProductType.DL) {
|
||||||
@ -130,9 +154,24 @@ export class DoorLockService {
|
|||||||
deviceDetails.deviceTuyaUuid,
|
deviceDetails.deviceTuyaUuid,
|
||||||
'once',
|
'once',
|
||||||
);
|
);
|
||||||
|
if (!passwords.result.records.length && fromVisitor) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
if (passwords.result.records.length > 0) {
|
if (passwords.result.records.length > 0) {
|
||||||
return convertKeysToCamelCase(passwords.result.records);
|
return fromVisitor
|
||||||
|
? convertKeysToCamelCase(passwords.result.records).map((password) => {
|
||||||
|
return {
|
||||||
|
passwordId: `${password.pwdId}`,
|
||||||
|
invalidTime: `${password.gmtExpired}`,
|
||||||
|
effectiveTime: `${password.gmtStart}`,
|
||||||
|
createdTime: `${password.gmtCreate}`,
|
||||||
|
passwordName: password.pwdName,
|
||||||
|
passwordStatus: `${password.status}`,
|
||||||
|
passwordType: 'OFFLINE_ONETIME',
|
||||||
|
deviceUuid: doorLockUuid,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
: convertKeysToCamelCase(passwords.result.records);
|
||||||
}
|
}
|
||||||
|
|
||||||
return passwords;
|
return passwords;
|
||||||
@ -143,7 +182,10 @@ export class DoorLockService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async getOnlineTemporaryPasswords(doorLockUuid: string) {
|
async getOnlineTemporaryPasswords(
|
||||||
|
doorLockUuid: string,
|
||||||
|
fromVisitor?: boolean,
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const deviceDetails = await this.getDeviceByDeviceUuid(doorLockUuid);
|
const deviceDetails = await this.getDeviceByDeviceUuid(doorLockUuid);
|
||||||
|
|
||||||
@ -158,8 +200,7 @@ export class DoorLockService {
|
|||||||
const passwords = await this.getOnlineTemporaryPasswordsTuya(
|
const passwords = await this.getOnlineTemporaryPasswordsTuya(
|
||||||
deviceDetails.deviceTuyaUuid,
|
deviceDetails.deviceTuyaUuid,
|
||||||
);
|
);
|
||||||
|
if (passwords.result?.length > 0) {
|
||||||
if (passwords.result.length > 0) {
|
|
||||||
const passwordFiltered = passwords.result
|
const passwordFiltered = passwords.result
|
||||||
.filter((item) => item.type === 0)
|
.filter((item) => item.type === 0)
|
||||||
.map((password: any) => {
|
.map((password: any) => {
|
||||||
@ -182,9 +223,25 @@ export class DoorLockService {
|
|||||||
return password;
|
return password;
|
||||||
});
|
});
|
||||||
|
|
||||||
return convertKeysToCamelCase(passwordFiltered);
|
return fromVisitor
|
||||||
|
? convertKeysToCamelCase(passwordFiltered).map((password) => {
|
||||||
|
return {
|
||||||
|
passwodId: `${password.id}`,
|
||||||
|
invalidTime: `${password.invalidTime}`,
|
||||||
|
effectiveTime: `${password.effectiveTime}`,
|
||||||
|
createdTime: '',
|
||||||
|
scheduleList: password?.scheduleList,
|
||||||
|
passwodName: password.name,
|
||||||
|
passwordStatus: '',
|
||||||
|
passwordType: 'ONLINE_MULTIPLE',
|
||||||
|
deviceUuid: doorLockUuid,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
: convertKeysToCamelCase(passwordFiltered);
|
||||||
|
}
|
||||||
|
if (fromVisitor) {
|
||||||
|
throw new BadRequestException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return passwords;
|
return passwords;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
@ -193,6 +250,74 @@ export class DoorLockService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async getOnlineTemporaryPasswordsOneTime(
|
||||||
|
doorLockUuid: string,
|
||||||
|
fromVisitor?: boolean,
|
||||||
|
) {
|
||||||
|
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.getOnlineTemporaryPasswordsTuya(
|
||||||
|
deviceDetails.deviceTuyaUuid,
|
||||||
|
);
|
||||||
|
if (passwords.result?.length > 0) {
|
||||||
|
const passwordFiltered = passwords.result
|
||||||
|
.filter((item) => item.type === 1)
|
||||||
|
.map((password: any) => {
|
||||||
|
if (password.schedule_list?.length > 0) {
|
||||||
|
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 fromVisitor
|
||||||
|
? convertKeysToCamelCase(passwordFiltered).map((password) => {
|
||||||
|
return {
|
||||||
|
passwodId: `${password.id}`,
|
||||||
|
invalidTime: `${password.invalidTime}`,
|
||||||
|
effectiveTime: `${password.effectiveTime}`,
|
||||||
|
createdTime: '',
|
||||||
|
passwodName: password.name,
|
||||||
|
passwordStatus: '',
|
||||||
|
passwordType: 'ONLINE_ONETIME',
|
||||||
|
deviceUuid: doorLockUuid,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
: convertKeysToCamelCase(passwordFiltered);
|
||||||
|
}
|
||||||
|
if (fromVisitor) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
return passwords;
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'Error getting online temporary passwords',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getOnlineTemporaryPasswordsTuya(
|
async getOnlineTemporaryPasswordsTuya(
|
||||||
doorLockUuid: string,
|
doorLockUuid: string,
|
||||||
): Promise<getPasswordInterface> {
|
): Promise<getPasswordInterface> {
|
||||||
|
|||||||
1
src/vistor-password/controllers/index.ts
Normal file
1
src/vistor-password/controllers/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './visitor-password.controller';
|
||||||
148
src/vistor-password/controllers/visitor-password.controller.ts
Normal file
148
src/vistor-password/controllers/visitor-password.controller.ts
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import { VisitorPasswordService } from '../services/visitor-password.service';
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Post,
|
||||||
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
|
UseGuards,
|
||||||
|
Get,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||||
|
import {
|
||||||
|
AddDoorLockOfflineMultipleDto,
|
||||||
|
AddDoorLockOfflineOneTimeDto,
|
||||||
|
AddDoorLockOnlineMultipleDto,
|
||||||
|
AddDoorLockOnlineOneTimeDto,
|
||||||
|
} from '../dtos/temp-pass.dto';
|
||||||
|
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||||
|
|
||||||
|
@ApiTags('Visitor Password Module')
|
||||||
|
@Controller({
|
||||||
|
version: '1',
|
||||||
|
path: 'visitor-password',
|
||||||
|
})
|
||||||
|
export class VisitorPasswordController {
|
||||||
|
constructor(
|
||||||
|
private readonly visitorPasswordService: VisitorPasswordService,
|
||||||
|
) {}
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Post('temporary-password/online/multiple-time')
|
||||||
|
async addOnlineTemporaryPasswordMultipleTime(
|
||||||
|
@Body() addDoorLockOnlineMultipleDto: AddDoorLockOnlineMultipleDto,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const temporaryPasswords =
|
||||||
|
await this.visitorPasswordService.addOnlineTemporaryPasswordMultipleTime(
|
||||||
|
addDoorLockOnlineMultipleDto,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: HttpStatus.CREATED,
|
||||||
|
data: temporaryPasswords,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'Internal server error',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Post('temporary-password/online/one-time')
|
||||||
|
async addOnlineTemporaryPassword(
|
||||||
|
@Body() addDoorLockOnlineOneTimeDto: AddDoorLockOnlineOneTimeDto,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const temporaryPasswords =
|
||||||
|
await this.visitorPasswordService.addOnlineTemporaryPasswordOneTime(
|
||||||
|
addDoorLockOnlineOneTimeDto,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: HttpStatus.CREATED,
|
||||||
|
data: temporaryPasswords,
|
||||||
|
};
|
||||||
|
} 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')
|
||||||
|
async addOfflineOneTimeTemporaryPassword(
|
||||||
|
@Body() addDoorLockOfflineOneTimeDto: AddDoorLockOfflineOneTimeDto,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const temporaryPassword =
|
||||||
|
await this.visitorPasswordService.addOfflineOneTimeTemporaryPassword(
|
||||||
|
addDoorLockOfflineOneTimeDto,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: HttpStatus.CREATED,
|
||||||
|
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')
|
||||||
|
async addOfflineMultipleTimeTemporaryPassword(
|
||||||
|
@Body()
|
||||||
|
addDoorLockOfflineMultipleDto: AddDoorLockOfflineMultipleDto,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const temporaryPassword =
|
||||||
|
await this.visitorPasswordService.addOfflineMultipleTimeTemporaryPassword(
|
||||||
|
addDoorLockOfflineMultipleDto,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: HttpStatus.CREATED,
|
||||||
|
data: temporaryPassword,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'Internal server error',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Get()
|
||||||
|
async GetVisitorPassword() {
|
||||||
|
try {
|
||||||
|
return await this.visitorPasswordService.getPasswords();
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'Internal server error',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Get('/devices')
|
||||||
|
async GetVisitorDevices() {
|
||||||
|
try {
|
||||||
|
return await this.visitorPasswordService.getAllPassDevices();
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'Internal server error',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/vistor-password/dtos/index.ts
Normal file
1
src/vistor-password/dtos/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './temp-pass.dto';
|
||||||
213
src/vistor-password/dtos/temp-pass.dto.ts
Normal file
213
src/vistor-password/dtos/temp-pass.dto.ts
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import {
|
||||||
|
IsNotEmpty,
|
||||||
|
IsString,
|
||||||
|
IsArray,
|
||||||
|
ValidateNested,
|
||||||
|
IsEnum,
|
||||||
|
Length,
|
||||||
|
IsOptional,
|
||||||
|
IsEmail,
|
||||||
|
} 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 AddDoorLockOnlineMultipleDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'email',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@IsEmail()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public email: string;
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'devicesUuid',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@IsArray()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public devicesUuid: [string];
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'password name',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public passwordName: 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 })
|
||||||
|
@IsOptional()
|
||||||
|
@Type(() => ScheduleDto)
|
||||||
|
public scheduleList: ScheduleDto[];
|
||||||
|
}
|
||||||
|
export class AddDoorLockOnlineOneTimeDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'email',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@IsEmail()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public email: string;
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'devicesUuid',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@IsArray()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public devicesUuid: [string];
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'password name',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public passwordName: 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;
|
||||||
|
}
|
||||||
|
export class AddDoorLockOfflineOneTimeDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'email',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@IsEmail()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public email: string;
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'password name',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public passwordName: string;
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'devicesUuid',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@IsArray()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public devicesUuid: [string];
|
||||||
|
}
|
||||||
|
export class AddDoorLockOfflineMultipleDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'email',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@IsEmail()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public email: string;
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'devicesUuid',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@IsArray()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public devicesUuid: [string];
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'password name',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public passwordName: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'effectiveTime',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public effectiveTime: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'invalidTime',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public invalidTime: string;
|
||||||
|
}
|
||||||
66
src/vistor-password/interfaces/visitor-password.interface.ts
Normal file
66
src/vistor-password/interfaces/visitor-password.interface.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
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;
|
||||||
|
offline_temp_password_id?: string;
|
||||||
|
offline_temp_password?: string;
|
||||||
|
};
|
||||||
|
msg?: string;
|
||||||
|
}
|
||||||
|
export interface addDeviceObjectInterface {
|
||||||
|
passwordName: string;
|
||||||
|
encryptedPassword: string;
|
||||||
|
effectiveTime: string;
|
||||||
|
invalidTime: string;
|
||||||
|
ticketId: string;
|
||||||
|
scheduleList?: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScheduleDto {
|
||||||
|
effectiveTime: string;
|
||||||
|
invalidTime: string;
|
||||||
|
workingDay: WorkingDays[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AddDoorLockOnlineInterface {
|
||||||
|
passwordName: string;
|
||||||
|
password: string;
|
||||||
|
effectiveTime: string;
|
||||||
|
invalidTime: string;
|
||||||
|
scheduleList?: ScheduleDto[];
|
||||||
|
}
|
||||||
|
export interface getPasswordInterface {
|
||||||
|
success: boolean;
|
||||||
|
result: [
|
||||||
|
{
|
||||||
|
effective_time: number;
|
||||||
|
id: number;
|
||||||
|
invalid_time: number;
|
||||||
|
passwordName: 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;
|
||||||
|
}
|
||||||
|
export interface getPasswordOfflineInterface {
|
||||||
|
success: boolean;
|
||||||
|
result: {
|
||||||
|
records: [];
|
||||||
|
};
|
||||||
|
msg?: string;
|
||||||
|
}
|
||||||
1
src/vistor-password/services/index.ts
Normal file
1
src/vistor-password/services/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './visitor-password.service';
|
||||||
815
src/vistor-password/services/visitor-password.service.ts
Normal file
815
src/vistor-password/services/visitor-password.service.ts
Normal file
@ -0,0 +1,815 @@
|
|||||||
|
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
|
||||||
|
import { TuyaContext } from '@tuya/tuya-connector-nodejs';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import {
|
||||||
|
addDeviceObjectInterface,
|
||||||
|
createTickInterface,
|
||||||
|
} from '../interfaces/visitor-password.interface';
|
||||||
|
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
||||||
|
import { ProductType } from '@app/common/constants/product-type.enum';
|
||||||
|
|
||||||
|
import {
|
||||||
|
AddDoorLockOfflineMultipleDto,
|
||||||
|
AddDoorLockOfflineOneTimeDto,
|
||||||
|
AddDoorLockOnlineMultipleDto,
|
||||||
|
AddDoorLockOnlineOneTimeDto,
|
||||||
|
} from '../dtos';
|
||||||
|
import { EmailService } from '@app/common/util/email.service';
|
||||||
|
import { PasswordEncryptionService } from 'src/door-lock/services/encryption.services';
|
||||||
|
import { DoorLockService } from 'src/door-lock/services';
|
||||||
|
import { GetDeviceDetailsInterface } from 'src/device/interfaces/get.device.interface';
|
||||||
|
import { DeviceService } from 'src/device/services';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class VisitorPasswordService {
|
||||||
|
private tuya: TuyaContext;
|
||||||
|
constructor(
|
||||||
|
private readonly configService: ConfigService,
|
||||||
|
private readonly deviceRepository: DeviceRepository,
|
||||||
|
private readonly emailService: EmailService,
|
||||||
|
private readonly doorLockService: DoorLockService,
|
||||||
|
private readonly deviceService: DeviceService,
|
||||||
|
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 addOfflineMultipleTimeTemporaryPassword(
|
||||||
|
addDoorLockOfflineMultipleDto: AddDoorLockOfflineMultipleDto,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const deviceResults = await Promise.allSettled(
|
||||||
|
addDoorLockOfflineMultipleDto.devicesUuid.map(async (deviceUuid) => {
|
||||||
|
try {
|
||||||
|
const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid);
|
||||||
|
|
||||||
|
if (!deviceDetails || !deviceDetails.deviceTuyaUuid) {
|
||||||
|
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deviceDetails.productDevice.prodType !== ProductType.DL) {
|
||||||
|
throw new HttpException(
|
||||||
|
'This is not a door lock device',
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const createMultipleOfflinePass =
|
||||||
|
await this.addOfflineTemporaryPasswordTuya(
|
||||||
|
deviceDetails.deviceTuyaUuid,
|
||||||
|
'multiple',
|
||||||
|
addDoorLockOfflineMultipleDto,
|
||||||
|
addDoorLockOfflineMultipleDto.passwordName,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!createMultipleOfflinePass.success) {
|
||||||
|
throw new HttpException(
|
||||||
|
createMultipleOfflinePass.msg,
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send email if the password creation is successful
|
||||||
|
if (createMultipleOfflinePass.result.offline_temp_password_id) {
|
||||||
|
const emailSubject = 'One-Time Password Creation Success';
|
||||||
|
const emailBody = `Your multiple time offline temporary password "${createMultipleOfflinePass.result.offline_temp_password}" has been successfully created for the device with UUID: ${deviceUuid}.\n\nThank you for using our service.\n\nBest Regards,\nSyncrow`;
|
||||||
|
|
||||||
|
await this.emailService.sendEmail(
|
||||||
|
addDoorLockOfflineMultipleDto.email,
|
||||||
|
emailSubject,
|
||||||
|
emailBody,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
result: createMultipleOfflinePass.result,
|
||||||
|
deviceUuid,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
deviceUuid,
|
||||||
|
error: error.message || 'Error processing device',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Process results: separate successful and failed operations
|
||||||
|
const successfulResults = deviceResults
|
||||||
|
.filter(
|
||||||
|
(result) =>
|
||||||
|
result.status === 'fulfilled' &&
|
||||||
|
(result as PromiseFulfilledResult<any>).value.success,
|
||||||
|
)
|
||||||
|
.map((result) => (result as PromiseFulfilledResult<any>).value);
|
||||||
|
|
||||||
|
const failedResults = deviceResults
|
||||||
|
.filter(
|
||||||
|
(result) =>
|
||||||
|
result.status === 'rejected' ||
|
||||||
|
!(result as PromiseFulfilledResult<any>).value.success,
|
||||||
|
)
|
||||||
|
.map((result) =>
|
||||||
|
result.status === 'rejected'
|
||||||
|
? {
|
||||||
|
success: false,
|
||||||
|
error:
|
||||||
|
(result as PromiseRejectedResult).reason.message ||
|
||||||
|
'Error processing device',
|
||||||
|
}
|
||||||
|
: (result as PromiseFulfilledResult<any>).value,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Return results if there are successful operations
|
||||||
|
if (successfulResults.length > 0) {
|
||||||
|
return {
|
||||||
|
successOperations: successfulResults,
|
||||||
|
failedOperations: failedResults,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all failed, throw an error with all failure messages
|
||||||
|
throw new HttpException(
|
||||||
|
failedResults.map((res) => res.error).join('; ') ||
|
||||||
|
'All device operations failed',
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
throw new HttpException(
|
||||||
|
error.message ||
|
||||||
|
'Error adding offline multiple-time temporary password from Tuya',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addOfflineOneTimeTemporaryPassword(
|
||||||
|
addDoorLockOfflineOneTimeDto: AddDoorLockOfflineOneTimeDto,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const deviceResults = await Promise.allSettled(
|
||||||
|
addDoorLockOfflineOneTimeDto.devicesUuid.map(async (deviceUuid) => {
|
||||||
|
try {
|
||||||
|
const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid);
|
||||||
|
|
||||||
|
if (!deviceDetails || !deviceDetails.deviceTuyaUuid) {
|
||||||
|
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deviceDetails.productDevice.prodType !== ProductType.DL) {
|
||||||
|
throw new HttpException(
|
||||||
|
'This is not a door lock device',
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const createOnceOfflinePass =
|
||||||
|
await this.addOfflineTemporaryPasswordTuya(
|
||||||
|
deviceDetails.deviceTuyaUuid,
|
||||||
|
'once',
|
||||||
|
null,
|
||||||
|
addDoorLockOfflineOneTimeDto.passwordName,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!createOnceOfflinePass.success) {
|
||||||
|
throw new HttpException(
|
||||||
|
createOnceOfflinePass.msg,
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send email if the password creation is successful
|
||||||
|
if (createOnceOfflinePass.result.offline_temp_password_id) {
|
||||||
|
const emailSubject = 'One-Time Password Creation Success';
|
||||||
|
const emailBody = `Your one-time offline temporary password "${createOnceOfflinePass.result.offline_temp_password}" has been successfully created for the device with UUID: ${deviceUuid}.\n\nThank you for using our service.\n\nBest Regards,\nSyncrow`;
|
||||||
|
|
||||||
|
await this.emailService.sendEmail(
|
||||||
|
addDoorLockOfflineOneTimeDto.email,
|
||||||
|
emailSubject,
|
||||||
|
emailBody,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
result: createOnceOfflinePass.result,
|
||||||
|
deviceUuid,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
deviceUuid,
|
||||||
|
error: error.message || 'Error processing device',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Process results: separate successful and failed operations
|
||||||
|
const successfulResults = deviceResults
|
||||||
|
.filter(
|
||||||
|
(result) =>
|
||||||
|
result.status === 'fulfilled' &&
|
||||||
|
(result as PromiseFulfilledResult<any>).value.success,
|
||||||
|
)
|
||||||
|
.map((result) => (result as PromiseFulfilledResult<any>).value);
|
||||||
|
|
||||||
|
const failedResults = deviceResults
|
||||||
|
.filter(
|
||||||
|
(result) =>
|
||||||
|
result.status === 'rejected' ||
|
||||||
|
!(result as PromiseFulfilledResult<any>).value.success,
|
||||||
|
)
|
||||||
|
.map((result) =>
|
||||||
|
result.status === 'rejected'
|
||||||
|
? {
|
||||||
|
success: false,
|
||||||
|
error:
|
||||||
|
(result as PromiseRejectedResult).reason.message ||
|
||||||
|
'Error processing device',
|
||||||
|
}
|
||||||
|
: (result as PromiseFulfilledResult<any>).value,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Return results if there are successful operations
|
||||||
|
if (successfulResults.length > 0) {
|
||||||
|
return {
|
||||||
|
successOperations: successfulResults,
|
||||||
|
failedOperations: failedResults,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all failed, throw an error with all failure messages
|
||||||
|
throw new HttpException(
|
||||||
|
failedResults.map((res) => res.error).join('; ') ||
|
||||||
|
'All device operations failed',
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
throw new HttpException(
|
||||||
|
error.message ||
|
||||||
|
'Error adding offline one-time temporary password from Tuya',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addOfflineTemporaryPasswordTuya(
|
||||||
|
doorLockUuid: string,
|
||||||
|
type: string,
|
||||||
|
addDoorLockOfflineMultipleDto: AddDoorLockOfflineMultipleDto,
|
||||||
|
passwordName: 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: {
|
||||||
|
...(type === 'multiple' && {
|
||||||
|
effective_time: addDoorLockOfflineMultipleDto.effectiveTime,
|
||||||
|
invalid_time: addDoorLockOfflineMultipleDto.invalidTime,
|
||||||
|
}),
|
||||||
|
name: passwordName,
|
||||||
|
type,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return response as createTickInterface;
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
'Error adding offline temporary password from Tuya',
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async addOnlineTemporaryPasswordMultipleTime(
|
||||||
|
addDoorLockOnlineMultipleDto: AddDoorLockOnlineMultipleDto,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const deviceResults = await Promise.allSettled(
|
||||||
|
addDoorLockOnlineMultipleDto.devicesUuid.map(async (deviceUuid) => {
|
||||||
|
try {
|
||||||
|
const passwordData = await this.getTicketAndEncryptedPassword(
|
||||||
|
deviceUuid,
|
||||||
|
addDoorLockOnlineMultipleDto.password,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!passwordData.ticketKey ||
|
||||||
|
!passwordData.encryptedPassword ||
|
||||||
|
!passwordData.deviceTuyaUuid
|
||||||
|
) {
|
||||||
|
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
const addDeviceObj: addDeviceObjectInterface = {
|
||||||
|
...addDoorLockOnlineMultipleDto,
|
||||||
|
...passwordData,
|
||||||
|
};
|
||||||
|
|
||||||
|
const createPass =
|
||||||
|
await this.addOnlineTemporaryPasswordMultipleTuya(
|
||||||
|
addDeviceObj,
|
||||||
|
passwordData.deviceTuyaUuid,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!createPass.success) {
|
||||||
|
throw new HttpException(createPass.msg, HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send email if the password creation is successful
|
||||||
|
if (createPass.result.id) {
|
||||||
|
const emailSubject = 'Password Creation Success';
|
||||||
|
const emailBody = `Your temporary password "${addDoorLockOnlineMultipleDto.password}" has been successfully created for the device with UUID: ${deviceUuid}.\n\nThank you for using our service.\n\nBest Regards,\nSyncrow`;
|
||||||
|
|
||||||
|
await this.emailService.sendEmail(
|
||||||
|
addDoorLockOnlineMultipleDto.email,
|
||||||
|
emailSubject,
|
||||||
|
emailBody,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
id: createPass.result.id,
|
||||||
|
deviceUuid,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
deviceUuid,
|
||||||
|
error: error.message || 'Error processing device',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Process results: separate successful and failed operations
|
||||||
|
const successfulResults = deviceResults
|
||||||
|
.filter(
|
||||||
|
(result) =>
|
||||||
|
result.status === 'fulfilled' &&
|
||||||
|
(result as PromiseFulfilledResult<any>).value.success,
|
||||||
|
)
|
||||||
|
.map((result) => (result as PromiseFulfilledResult<any>).value);
|
||||||
|
|
||||||
|
const failedResults = deviceResults
|
||||||
|
.filter(
|
||||||
|
(result) =>
|
||||||
|
result.status === 'rejected' ||
|
||||||
|
!(result as PromiseFulfilledResult<any>).value.success,
|
||||||
|
)
|
||||||
|
.map((result) =>
|
||||||
|
result.status === 'rejected'
|
||||||
|
? {
|
||||||
|
success: false,
|
||||||
|
error:
|
||||||
|
(result as PromiseRejectedResult).reason.message ||
|
||||||
|
'Error processing device',
|
||||||
|
}
|
||||||
|
: (result as PromiseFulfilledResult<any>).value,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Return results if there are successful operations
|
||||||
|
if (successfulResults?.length > 0) {
|
||||||
|
return {
|
||||||
|
successOperations: successfulResults,
|
||||||
|
failedOperations: failedResults,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throw an error if all operations failed
|
||||||
|
throw new HttpException(
|
||||||
|
failedResults.map((res) => res.error).join('; ') ||
|
||||||
|
'All device operations failed',
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'Error adding online temporary password from Tuya',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async getPasswords() {
|
||||||
|
const deviceIds = await this.deviceRepository.find({
|
||||||
|
where: {
|
||||||
|
productDevice: {
|
||||||
|
prodType: ProductType.DL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = [];
|
||||||
|
deviceIds.forEach((deviceId) => {
|
||||||
|
data.push(
|
||||||
|
this.doorLockService
|
||||||
|
.getOnlineTemporaryPasswordsOneTime(deviceId.uuid, true)
|
||||||
|
.catch(() => {}),
|
||||||
|
this.doorLockService
|
||||||
|
.getOnlineTemporaryPasswords(deviceId.uuid, true)
|
||||||
|
.catch(() => {}),
|
||||||
|
this.doorLockService
|
||||||
|
.getOfflineOneTimeTemporaryPasswords(deviceId.uuid, true)
|
||||||
|
.catch(() => {}),
|
||||||
|
this.doorLockService
|
||||||
|
.getOfflineMultipleTimeTemporaryPasswords(deviceId.uuid, true)
|
||||||
|
.catch(() => {}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return (await Promise.all(data)).flat().filter((datum) => {
|
||||||
|
return datum != null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllPassDevices() {
|
||||||
|
const devices = await this.deviceRepository.find({
|
||||||
|
where: {
|
||||||
|
productDevice: {
|
||||||
|
prodType: ProductType.DL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
relations: ['productDevice'],
|
||||||
|
});
|
||||||
|
const devicesData = await Promise.all(
|
||||||
|
devices?.map(async (device) => {
|
||||||
|
return {
|
||||||
|
productUuid: device.productDevice.uuid,
|
||||||
|
productType: device.productDevice.prodType,
|
||||||
|
...(await this.deviceService.getDeviceDetailsByDeviceIdTuya(
|
||||||
|
device.deviceTuyaUuid,
|
||||||
|
)),
|
||||||
|
uuid: device.uuid,
|
||||||
|
} as GetDeviceDetailsInterface;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return devicesData;
|
||||||
|
}
|
||||||
|
|
||||||
|
async addOnlineTemporaryPasswordOneTime(
|
||||||
|
addDoorLockOnlineOneTimeDto: AddDoorLockOnlineOneTimeDto,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const deviceResults = await Promise.allSettled(
|
||||||
|
addDoorLockOnlineOneTimeDto.devicesUuid.map(async (deviceUuid) => {
|
||||||
|
try {
|
||||||
|
const passwordData = await this.getTicketAndEncryptedPassword(
|
||||||
|
deviceUuid,
|
||||||
|
addDoorLockOnlineOneTimeDto.password,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!passwordData.ticketKey ||
|
||||||
|
!passwordData.encryptedPassword ||
|
||||||
|
!passwordData.deviceTuyaUuid
|
||||||
|
) {
|
||||||
|
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
const addDeviceObj: addDeviceObjectInterface = {
|
||||||
|
...addDoorLockOnlineOneTimeDto,
|
||||||
|
...passwordData,
|
||||||
|
};
|
||||||
|
|
||||||
|
const createPass = await this.addOnlineTemporaryPasswordOneTimeTuya(
|
||||||
|
addDeviceObj,
|
||||||
|
passwordData.deviceTuyaUuid,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!createPass.success) {
|
||||||
|
throw new HttpException(createPass.msg, HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send email if the password creation is successful
|
||||||
|
if (createPass.result.id) {
|
||||||
|
const emailSubject = 'Password Creation Success';
|
||||||
|
const emailBody = `Your temporary password "${addDoorLockOnlineOneTimeDto.password}" has been successfully created for the device with UUID: ${deviceUuid}.\n\nThank you for using our service.\n\nBest Regards,\nSyncrow`;
|
||||||
|
|
||||||
|
await this.emailService.sendEmail(
|
||||||
|
addDoorLockOnlineOneTimeDto.email,
|
||||||
|
emailSubject,
|
||||||
|
emailBody,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
id: createPass.result.id,
|
||||||
|
deviceUuid,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
deviceUuid,
|
||||||
|
error: error.message || 'Error processing device',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Process results: separate successful and failed operations
|
||||||
|
const successfulResults = deviceResults
|
||||||
|
.filter(
|
||||||
|
(result) =>
|
||||||
|
result.status === 'fulfilled' &&
|
||||||
|
(result as PromiseFulfilledResult<any>).value.success,
|
||||||
|
)
|
||||||
|
.map((result) => (result as PromiseFulfilledResult<any>).value);
|
||||||
|
|
||||||
|
const failedResults = deviceResults
|
||||||
|
.filter(
|
||||||
|
(result) =>
|
||||||
|
result.status === 'rejected' ||
|
||||||
|
!(result as PromiseFulfilledResult<any>).value.success,
|
||||||
|
)
|
||||||
|
.map((result) =>
|
||||||
|
result.status === 'rejected'
|
||||||
|
? {
|
||||||
|
success: false,
|
||||||
|
error:
|
||||||
|
(result as PromiseRejectedResult).reason.message ||
|
||||||
|
'Error processing device',
|
||||||
|
}
|
||||||
|
: (result as PromiseFulfilledResult<any>).value,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Return results if there are successful operations
|
||||||
|
if (successfulResults.length > 0) {
|
||||||
|
return {
|
||||||
|
successOperations: successfulResults,
|
||||||
|
failedOperations: failedResults,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throw an error if all operations failed
|
||||||
|
throw new HttpException(
|
||||||
|
failedResults.map((res) => res.error).join('; ') ||
|
||||||
|
'All device operations failed',
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(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 addOnlineTemporaryPasswordMultipleTuya(
|
||||||
|
addDeviceObj: addDeviceObjectInterface,
|
||||||
|
doorLockUuid: string,
|
||||||
|
): Promise<createTickInterface> {
|
||||||
|
try {
|
||||||
|
const path = `/v1.0/devices/${doorLockUuid}/door-lock/temp-password`;
|
||||||
|
let scheduleList;
|
||||||
|
if (addDeviceObj?.scheduleList?.length > 0) {
|
||||||
|
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.passwordName,
|
||||||
|
password: addDeviceObj.encryptedPassword,
|
||||||
|
effective_time: addDeviceObj.effectiveTime,
|
||||||
|
invalid_time: addDeviceObj.invalidTime,
|
||||||
|
password_type: 'ticket',
|
||||||
|
ticket_id: addDeviceObj.ticketId,
|
||||||
|
...(addDeviceObj?.scheduleList?.length > 0 && {
|
||||||
|
schedule_list: scheduleList,
|
||||||
|
}),
|
||||||
|
|
||||||
|
type: '0',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async addOnlineTemporaryPasswordOneTimeTuya(
|
||||||
|
addDeviceObj: addDeviceObjectInterface,
|
||||||
|
doorLockUuid: string,
|
||||||
|
): Promise<createTickInterface> {
|
||||||
|
try {
|
||||||
|
const path = `/v1.0/devices/${doorLockUuid}/door-lock/temp-password`;
|
||||||
|
const response = await this.tuya.request({
|
||||||
|
method: 'POST',
|
||||||
|
path,
|
||||||
|
body: {
|
||||||
|
name: addDeviceObj.passwordName,
|
||||||
|
password: addDeviceObj.encryptedPassword,
|
||||||
|
effective_time: addDeviceObj.effectiveTime,
|
||||||
|
invalid_time: addDeviceObj.invalidTime,
|
||||||
|
password_type: 'ticket',
|
||||||
|
ticket_id: addDeviceObj.ticketId,
|
||||||
|
type: '1',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return response as createTickInterface;
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
error.msg || 'Error adding online temporary password from Tuya',
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/vistor-password/visitor-password.module.ts
Normal file
29
src/vistor-password/visitor-password.module.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { VisitorPasswordService } from './services/visitor-password.service';
|
||||||
|
import { VisitorPasswordController } from './controllers/visitor-password.controller';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { DeviceRepositoryModule } from '@app/common/modules/device';
|
||||||
|
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
||||||
|
import { EmailService } from '@app/common/util/email.service';
|
||||||
|
import { PasswordEncryptionService } from 'src/door-lock/services/encryption.services';
|
||||||
|
import { DoorLockModule } from 'src/door-lock/door.lock.module';
|
||||||
|
import { DeviceService } from 'src/device/services';
|
||||||
|
import { ProductRepository } from '@app/common/modules/product/repositories';
|
||||||
|
import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service';
|
||||||
|
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||||
|
@Module({
|
||||||
|
imports: [ConfigModule, DeviceRepositoryModule, DoorLockModule],
|
||||||
|
controllers: [VisitorPasswordController],
|
||||||
|
providers: [
|
||||||
|
VisitorPasswordService,
|
||||||
|
EmailService,
|
||||||
|
PasswordEncryptionService,
|
||||||
|
DeviceService,
|
||||||
|
ProductRepository,
|
||||||
|
DeviceStatusFirebaseService,
|
||||||
|
SpaceRepository,
|
||||||
|
DeviceRepository,
|
||||||
|
],
|
||||||
|
exports: [VisitorPasswordService],
|
||||||
|
})
|
||||||
|
export class VisitorPasswordModule {}
|
||||||
Reference in New Issue
Block a user