confirm-pins-service.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import { MailService } from "#services/mail-service.js";
  2. import { logger } from "#logger";
  3. import { db } from "#db";
  4. import { sql } from "slonik";
  5. import { ZDbShema } from "#db-shema";
  6. import { z } from "zod";
  7. // dayjs
  8. import { dayjs, DayjsUtils } from "#dayjs";
  9. class confirmPinsService {
  10. // privalte
  11. private genRandom4DigitNumber() {
  12. return Math.floor(1000 + Math.random() * 9000);
  13. }
  14. private async deleteConfirmPin(transactionId: string, email: string) {
  15. await db.any(
  16. sql.unsafe`delete from users.user_confirm_pins where transaction_id = ${transactionId} and email = ${email}`,
  17. );
  18. }
  19. private async pinTriesIncrement(transactionId: string, email: string) {
  20. await db.any(
  21. sql.unsafe`update users.user_confirm_pins set wrong_pin_tries = wrong_pin_tries + 1 where transaction_id = ${transactionId} and email = ${email}`,
  22. );
  23. }
  24. //
  25. // public
  26. //
  27. async sendConfirmPin(transactionId: string, email: string) {
  28. const confirmPin = this.genRandom4DigitNumber();
  29. // удаляем если пин уже есть
  30. await this.deleteConfirmPin(transactionId, email);
  31. // отправка
  32. const mailBody = `
  33. <div>
  34. <h1>Ваш временный код:</h1>
  35. <h4> <a href="${confirmPin}">${confirmPin}</a> </h4>
  36. </div>
  37. `;
  38. await MailService.sendMail(email, "Ваш код для EVENT", mailBody);
  39. // бд
  40. await db.any(
  41. sql.unsafe`
  42. insert into users.user_confirm_pins (
  43. transaction_id,
  44. email,
  45. confirm_pin,
  46. create_time)
  47. values (
  48. ${transactionId},
  49. ${email},
  50. ${confirmPin},
  51. ${DayjsUtils.createDayjsUtcWithoutOffset().toISOString()})`,
  52. );
  53. logger.info("Отправлен временный код: ", {
  54. email,
  55. confirmPin,
  56. });
  57. }
  58. async checkConfirmPin(
  59. transactionId: string,
  60. email: string,
  61. confirmPin: number,
  62. ): Promise<
  63. | "correct"
  64. | "rotten"
  65. | "tooManyTries"
  66. | {
  67. status: "wrong";
  68. triesRemained?: number;
  69. }
  70. > {
  71. const pinInfo = await db.maybeOne(
  72. sql.type(
  73. z.object({
  74. confirm_pin: ZDbShema.users.user_confirm_pins.confirm_pin,
  75. create_time: ZDbShema.users.user_confirm_pins.create_time,
  76. wrong_pin_tries: ZDbShema.users.user_confirm_pins.wrong_pin_tries,
  77. }),
  78. )`select confirm_pin, create_time, wrong_pin_tries from users.user_confirm_pins where transaction_id = ${transactionId} and email = ${email}`,
  79. );
  80. // не существует
  81. if (!pinInfo) {
  82. return "rotten";
  83. }
  84. // много попыток
  85. if (
  86. pinInfo.wrong_pin_tries >
  87. Number(process.env.CONFIRM_PIN_MAX_TRIES) - 1
  88. ) {
  89. return "tooManyTries";
  90. }
  91. // просрочка
  92. if (
  93. DayjsUtils.createDayjsUtcWithoutOffset().isAfter(
  94. dayjs
  95. .utc(pinInfo.create_time)
  96. .add(Number(process.env.CONFIRM_PIN_LIFETIME_MINS), "minutes"),
  97. )
  98. ) {
  99. await this.deleteConfirmPin(transactionId, email);
  100. return "rotten";
  101. }
  102. // неправильный
  103. if (pinInfo.confirm_pin !== confirmPin) {
  104. await this.pinTriesIncrement(transactionId, email);
  105. const triesRemained =
  106. Number(process.env.CONFIRM_PIN_MAX_TRIES) - 1 - pinInfo.wrong_pin_tries;
  107. return {
  108. status: "wrong",
  109. triesRemained,
  110. };
  111. }
  112. // правильный
  113. await this.deleteConfirmPin(transactionId, email);
  114. return "correct";
  115. }
  116. }
  117. export const ConfirmPinsService = new confirmPinsService();