import { Injectable, BadRequestException } from '@nestjs/common'; import { randomInt } from 'node:crypto'; import { PinoLogger } from 'nestjs-pino'; import { PrismaService } from '../prisma/prisma.service'; import { MailService } from '../mail/mail.service'; @Injectable() export class VerificationService { constructor( private readonly prisma: PrismaService, private readonly mailService: MailService, private readonly logger: PinoLogger, ) { this.logger.setContext(VerificationService.name); } generateCode(): string { return randomInt(100000, 999999).toString(); } async sendEmailCode(email: string): Promise { const code = this.generateCode(); await this.prisma.verificationCode.create({ data: { target: email, channel: 'EMAIL', code, purpose: 'AUTH', expiresAt: new Date(Date.now() + 5 * 60 * 1000), }, }); await this.mailService.sendCode(email, code, 'AUTH'); this.logger.info({ email }, 'Email verification code sent'); } async sendPhoneCode(phone: string): Promise { const code = this.generateCode(); await this.prisma.verificationCode.create({ data: { target: phone, channel: 'SMS', code, purpose: 'AUTH', expiresAt: new Date(Date.now() + 5 * 60 * 1000), }, }); this.logger.info({ phone }, 'SMS verification code would be sent (mock)'); } async verifyCode(target: string, code: string): Promise { const record = await this.prisma.verificationCode.findFirst({ where: { target, code, usedAt: null, expiresAt: { gt: new Date() }, }, orderBy: { createdAt: 'desc' }, }); if (!record) { throw new BadRequestException('Invalid or expired code'); } await this.prisma.verificationCode.update({ where: { id: record.id }, data: { usedAt: new Date() }, }); return true; } }