123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- 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 = `
- <div>
- <h1>Ваш временный код:</h1>
- <h4> <a href="${confirmPin}">${confirmPin}</a> </h4>
- </div>
- `;
- 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();
|