import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { VerificationModel } from '../models/verification.model'; import { BookingVerification } from '../../domain/entities/booking-verification.entity'; import { UnprocessableEntityException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { WhatsappService } from 'src/services/whatsapp/whatsapp.service'; export class VerificationService { constructor( @InjectRepository(VerificationModel) private readonly verificationRepository: Repository, private readonly jwtService: JwtService, ) {} maxAttempts = 3; expiredTime = 5 * 60 * 1000; expiredTimeRegister = 1 * 60 * 1000; async generateToken(payload: BookingVerification) { return this.jwtService.sign({ phone_number: payload.phone_number, name: payload.name, created_at: payload.created_at, }); } async register(data: BookingVerification) { const isProduction = process.env.NODE_ENV === 'true'; const currentTime = Math.floor(Date.now()); // current time in seconds // Generate a 4 digit OTP code const otpCode = Math.floor(1000 + Math.random() * 9000).toString(); let verification = await this.verificationRepository.findOne({ where: { phone_number: data.phone_number }, }); if ( isProduction && verification.updated_at && currentTime - verification.updated_at < this.expiredTimeRegister ) { throw new UnprocessableEntityException('Please try again in 1 minute'); } data.code = otpCode; data.tried = 0; data.updated_at = currentTime; if (verification) { // Update existing record verification = this.verificationRepository.merge(verification, data); } else { // Create new record verification = this.verificationRepository.create(data); } const payload = await this.verificationRepository.save(verification); const notificationService = new WhatsappService(); notificationService.sendOtpNotification({ phone: data.phone_number, code: otpCode, }); return payload; } async findByPhoneNumber(phoneNumber: string) { return this.verificationRepository.findOne({ where: { phone_number: phoneNumber }, }); } async verify(data: BookingVerification): Promise { const verification = await this.findByPhoneNumber(data.phone_number); if (!verification) { throw new UnprocessableEntityException('Phone number not found'); } if (verification.tried >= this.maxAttempts) { throw new UnprocessableEntityException( 'Too many attempts, please resend OTP Code', ); } if (verification.code != data.code) { verification.tried++; await this.verificationRepository.save(verification); throw new UnprocessableEntityException('Invalid verification code'); } const currentTime = Math.floor(Date.now()); if ( verification.updated_at && currentTime - verification.updated_at > this.expiredTime ) { throw new UnprocessableEntityException('Verification code expired'); } return verification; } async update(id: string, data: BookingVerification) { return this.verificationRepository.update(id, data); } async delete(id: string) { return this.verificationRepository.delete(id); } }