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 = `
`;
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();