confirm-pins-service.ts 3.7 KB

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