import { MailService } from "#services/mail-service.js"; import { logger } from "#logger"; import { db } from "#db"; import { sql } from "slonik"; import { ZDbShema } from "#db-shema"; import { z } from "zod"; // dayjs import { dayjs, DayjsUtils } from "#dayjs"; class confirmPinsService { // privalte private genRandom4DigitNumber() { return Math.floor(1000 + Math.random() * 9000); } private async deleteConfirmPin(transactionId: string, email: string) { await db.any( sql.unsafe`delete from users.user_confirm_pins where transaction_id = ${transactionId} and email = ${email}`, ); } private async pinTriesIncrement(transactionId: string, email: string) { await db.any( sql.unsafe`update users.user_confirm_pins set wrong_pin_tries = wrong_pin_tries + 1 where transaction_id = ${transactionId} and email = ${email}`, ); } // // public // async sendConfirmPin(transactionId: string, email: string) { const confirmPin = this.genRandom4DigitNumber(); // удаляем если пин уже есть await this.deleteConfirmPin(transactionId, email); // отправка const mailBody = `

Ваш временный код:

${confirmPin}

`; await MailService.sendMail(email, "Ваш код для EVENT", mailBody); // бд await db.any( sql.unsafe` insert into users.user_confirm_pins ( transaction_id, email, confirm_pin, create_time) values ( ${transactionId}, ${email}, ${confirmPin}, ${DayjsUtils.createDayjsUtcWithoutOffset().toISOString()})`, ); logger.info("Отправлен временный код: ", { email, confirmPin, }); } async checkConfirmPin( transactionId: string, email: string, confirmPin: number, ): Promise< | "correct" | "rotten" | "tooManyTries" | { status: "wrong"; triesRemained?: number; } > { const pinInfo = await db.maybeOne( sql.type( z.object({ confirm_pin: ZDbShema.users.user_confirm_pins.confirm_pin, create_time: ZDbShema.users.user_confirm_pins.create_time, wrong_pin_tries: ZDbShema.users.user_confirm_pins.wrong_pin_tries, }), )`select confirm_pin, create_time, wrong_pin_tries from users.user_confirm_pins where transaction_id = ${transactionId} and email = ${email}`, ); // не существует if (!pinInfo) { return "rotten"; } // много попыток if ( pinInfo.wrong_pin_tries > Number(process.env.CONFIRM_PIN_MAX_TRIES) - 1 ) { return "tooManyTries"; } // просрочка if ( DayjsUtils.createDayjsUtcWithoutOffset().isAfter( dayjs .utc(pinInfo.create_time) .add(Number(process.env.CONFIRM_PIN_LIFETIME_MINS), "minutes"), ) ) { await this.deleteConfirmPin(transactionId, email); return "rotten"; } // неправильный if (pinInfo.confirm_pin !== confirmPin) { await this.pinTriesIncrement(transactionId, email); const triesRemained = Number(process.env.CONFIRM_PIN_MAX_TRIES) - 1 - pinInfo.wrong_pin_tries; return { status: "wrong", triesRemained, }; } // правильный await this.deleteConfirmPin(transactionId, email); return "correct"; } } export const ConfirmPinsService = new confirmPinsService();