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

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

${confirmPin}

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