From d9b1537ee46a5f1866e353da2cbc348ce6994458 Mon Sep 17 00:00:00 2001 From: SepehrYahyaee <7heycallmegray@gmail.com> Date: Wed, 22 Apr 2026 15:02:08 +0330 Subject: [PATCH] Centralized SMS services YARA-834 --- package-lock.json | 49 ++++++ package.json | 1 + src/auth/auth-services/user.auth.service.ts | 30 ++-- src/auth/auth.module.ts | 4 +- src/expert-blame/dto/resend.dto.ts | 11 +- .../request-management.module.ts | 4 +- .../request-management.service.ts | 144 ++++++------------ .../db-service/sms-text.db.service.ts | 24 +++ .../entities/schema/sms-text.schema.ts | 17 +++ .../provider}/kavenegar-response.ts | 0 .../provider/sms-gateway.module.ts | 18 +++ .../provider/sms-gateway.service.ts} | 5 +- .../provider}/sms-provider.exception.ts | 0 .../sms-orchestration.module.ts | 16 ++ .../sms-orchestration.service.ts | 111 ++++++++++++++ src/utils/sms-manager/sms-manager.module.ts | 15 -- 16 files changed, 311 insertions(+), 138 deletions(-) create mode 100644 src/sms-orchestration/entities/db-service/sms-text.db.service.ts create mode 100644 src/sms-orchestration/entities/schema/sms-text.schema.ts rename src/{utils/sms-manager => sms-orchestration/provider}/kavenegar-response.ts (100%) create mode 100644 src/sms-orchestration/provider/sms-gateway.module.ts rename src/{utils/sms-manager/sms-manager.service.ts => sms-orchestration/provider/sms-gateway.service.ts} (96%) rename src/{utils/sms-manager => sms-orchestration/provider}/sms-provider.exception.ts (100%) create mode 100644 src/sms-orchestration/sms-orchestration.module.ts create mode 100644 src/sms-orchestration/sms-orchestration.service.ts delete mode 100644 src/utils/sms-manager/sms-manager.module.ts diff --git a/package-lock.json b/package-lock.json index 34f9792..ad90e3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@nestjs-modules/mailer": "^1.8.1", "@nestjs/axios": "^3.1.3", "@nestjs/common": "^10.4.15", + "@nestjs/config": "^4.0.4", "@nestjs/core": "^10.4.15", "@nestjs/jwt": "^10.2.0", "@nestjs/mapped-types": "*", @@ -4651,6 +4652,39 @@ } } }, + "node_modules/@nestjs/config": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.4.tgz", + "integrity": "sha512-CJPjNitr0bAufSEnRe2N+JbnVmMmDoo6hvKCPzXgZoGwJSmp/dZPk9f/RMbuD/+Q1ZJPjwsRpq0vxna++Knwow==", + "license": "MIT", + "dependencies": { + "dotenv": "17.4.1", + "dotenv-expand": "12.0.3", + "lodash": "4.18.1" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/config/node_modules/dotenv": { + "version": "17.4.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.1.tgz", + "integrity": "sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@nestjs/config/node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, "node_modules/@nestjs/core": { "version": "10.4.15", "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.15.tgz", @@ -8441,6 +8475,21 @@ "url": "https://dotenvx.com" } }, + "node_modules/dotenv-expand": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz", + "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==", + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", diff --git a/package.json b/package.json index 846b83a..0d369c6 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@nestjs-modules/mailer": "^1.8.1", "@nestjs/axios": "^3.1.3", "@nestjs/common": "^10.4.15", + "@nestjs/config": "^4.0.4", "@nestjs/core": "^10.4.15", "@nestjs/jwt": "^10.2.0", "@nestjs/mapped-types": "*", diff --git a/src/auth/auth-services/user.auth.service.ts b/src/auth/auth-services/user.auth.service.ts index 77f1e39..6aea3fd 100644 --- a/src/auth/auth-services/user.auth.service.ts +++ b/src/auth/auth-services/user.auth.service.ts @@ -13,8 +13,7 @@ import { LoginDtoRs } from "src/auth/dto/user/login.dto"; import { UserDbService } from "src/users/entities/db-service/user.db.service"; import { HashService } from "src/utils/hash/hash.service"; import { OtpService } from "src/utils/otp/otp.service"; -import { SmsManagerService } from "src/utils/sms-manager/sms-manager.service"; -import { describeSmsError } from "src/utils/sms-manager/sms-provider.exception"; +import { SmsOrchestrationService } from "src/sms-orchestration/sms-orchestration.service"; // TODO FIX REGISTER TO USER.SERVICE AND AUTH IN THIS MODULE @Injectable() @@ -26,7 +25,7 @@ export class UserAuthService { private readonly userDbService: UserDbService, private readonly hashService: HashService, private readonly otpCreator: OtpService, - private readonly smsManagerService: SmsManagerService, + private readonly smsOrchestrationService: SmsOrchestrationService, ) {} async validateUser(username: string, pass: string): Promise { @@ -118,26 +117,19 @@ export class UserAuthService { } private async smsSender(otp: string, mobile: string) { - try { - await this.smsManagerService.verifyLookUp({ - token: otp, - template: process.env.AUTH_SMS_TEMPLATE, - receptor: mobile, - }); - this.logger.log( - `Auth OTP SMS accepted by provider phone=${mobile} otp=${otp}`, - ); - } catch (err) { - this.logger.error( - `Auth OTP SMS failed phone=${mobile} otp=${otp} ${describeSmsError(err)}`, - ); - if (err instanceof HttpException) { - throw err; - } + const ok = await this.smsOrchestrationService.sendAuthOtp( + mobile, + otp, + process.env.AUTH_SMS_TEMPLATE, + ); + if (!ok) { throw new HttpException( "auth sms send failed", HttpStatus.BAD_GATEWAY, ); } + this.logger.log( + `Auth OTP SMS accepted by provider phone=${mobile} otp=${otp}`, + ); } } diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 230996c..34b25e4 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -12,7 +12,7 @@ import { UsersModule } from "src/users/users.module"; import { HashModule } from "src/utils/hash/hash.module"; // import { MailModule } from "src/utils/mail/mail.module"; import { OtpModule } from "src/utils/otp/otp.module"; -import { SmsManagerModule } from "src/utils/sms-manager/sms-manager.module"; +import { SmsOrchestrationModule } from "src/sms-orchestration/sms-orchestration.module"; @Module({ imports: [ @@ -22,7 +22,7 @@ import { SmsManagerModule } from "src/utils/sms-manager/sms-manager.module"; HashModule, OtpModule, PassportModule, - SmsManagerModule, + SmsOrchestrationModule, JwtModule.register({ signOptions: { expiresIn: "1h" }, global: true, diff --git a/src/expert-blame/dto/resend.dto.ts b/src/expert-blame/dto/resend.dto.ts index 3a28474..2c86798 100644 --- a/src/expert-blame/dto/resend.dto.ts +++ b/src/expert-blame/dto/resend.dto.ts @@ -35,9 +35,14 @@ export class ResendRequestDto { description: "Array of resend requests (can include first party, second party, or both)", example: [ { - partyId: "507f1f77bcf86cd799439011", - requestedItems: ["drivingLicense", "carCertificate"], - description: "Please resend with better quality", + partyId: "681b3b3fb237e5856429e444", + requestedItems: ["drivingLicense"], + description: "DESC TEST1", + }, + { + partyId: "68b6ba552a6e897df34e0f90", + requestedItems: ["carCertificate"], + description: "DESC TEST2", }, ], }) diff --git a/src/request-management/request-management.module.ts b/src/request-management/request-management.module.ts index c346df9..23542da 100644 --- a/src/request-management/request-management.module.ts +++ b/src/request-management/request-management.module.ts @@ -10,9 +10,9 @@ import { BlameRequestDbService } from "src/request-management/entities/db-servic import { SandHubModule } from "src/sand-hub/sand-hub.module"; import { UsersModule } from "src/users/users.module"; import { CronModule } from "src/utils/cron/cron.module"; -import { SmsManagerModule } from "src/utils/sms-manager/sms-manager.module"; import { PublicIdModule } from "src/utils/public-id/public-id.module"; import { HashModule } from "src/utils/hash/hash.module"; +import { SmsOrchestrationModule } from "src/sms-orchestration/sms-orchestration.module"; import { WorkflowStepManagementModule } from "src/workflow-step-management/workflow-step-management.module"; import { AuthModule } from "src/auth/auth.module"; import { BlameDocumentDbService } from "./entities/db-service/blame-document.db.service"; @@ -45,7 +45,7 @@ import { RegistrarInitiatedController } from "./registrar-initiated.controller"; UsersModule, ClientModule, SandHubModule, - SmsManagerModule, + SmsOrchestrationModule, PublicIdModule, HashModule, WorkflowStepManagementModule, diff --git a/src/request-management/request-management.service.ts b/src/request-management/request-management.service.ts index 9d7de28..0a3b1e6 100644 --- a/src/request-management/request-management.service.ts +++ b/src/request-management/request-management.service.ts @@ -39,8 +39,7 @@ import { StepsEnum } from "src/Types&Enums/blame-request-management/steps.enum"; import { ExpertDbService } from "src/users/entities/db-service/expert.db.service"; import { UserDbService } from "src/users/entities/db-service/user.db.service"; import { AutoCloseRequestService } from "src/utils/cron/cron.service"; -import { SmsManagerService } from "src/utils/sms-manager/sms-manager.service"; -import { describeSmsError } from "src/utils/sms-manager/sms-provider.exception"; +import { SmsOrchestrationService } from "src/sms-orchestration/sms-orchestration.service"; import { DescriptionDto, LocationDto, @@ -308,7 +307,7 @@ export class RequestManagementService { private readonly clientService: ClientService, private readonly blameVoiceDbService: BlameVoiceDbService, private readonly userSign: UserSignDbService, - private readonly smsManagerService: SmsManagerService, + private readonly smsOrchestrationService: SmsOrchestrationService, private readonly expertDbService: ExpertDbService, private readonly autoCloseRequestService: AutoCloseRequestService, private readonly claimRequestManagementDbService: ClaimRequestManagementDbService, @@ -1305,21 +1304,14 @@ export class RequestManagementService { metadata: { secondPartyPhone, expertInitiatedLink: true }, } as any); await (req as any).save(); - const url = `${process.env.URL}/${frontendRoute}?token=${requestId}`; - try { - await this.smsManagerService.verifyLookUp({ - token: url, - template: "yara724-invite-link", - receptor: secondPartyPhone, - }); - this.logger.log( - `[SMS] Invitation resent to ${secondPartyPhone} for expert-initiated request ${req.publicId}`, - ); - } catch (err) { - this.logger.error( - `[SMS] Failed to send invitation to ${secondPartyPhone}: ${describeSmsError(err)}`, - ); - } + const url = this.smsOrchestrationService.buildInviteLink( + frontendRoute, + requestId, + ); + await this.smsOrchestrationService.sendInviteLink( + secondPartyPhone, + url, + ); return { requestId: req._id, publicId: req.publicId, @@ -1362,22 +1354,11 @@ export class RequestManagementService { await (req as any).save(); // Send SMS invitation - const url = `${process.env.URL}/${frontendRoute}?token=${requestId}`; - try { - await this.smsManagerService.verifyLookUp({ - token: url, - template: "yara724-invite-link", - receptor: phoneNumber, - }); - this.logger.log( - `[SMS] Invitation sent to ${phoneNumber} for request ${req.publicId}`, - ); - } catch (err) { - this.logger.error( - `[SMS] Failed to send invitation to ${phoneNumber}: ${describeSmsError(err)}`, - ); - // Don't block the request flow if SMS fails - } + const url = this.smsOrchestrationService.buildInviteLink( + frontendRoute, + requestId, + ); + await this.smsOrchestrationService.sendInviteLink(phoneNumber, url); return { requestId: req._id, @@ -2295,21 +2276,11 @@ export class RequestManagementService { } // Send the SMS after the database update is complete. - const URL = `${process.env.URL}/${frontendRoutes}?token=${requestId}`; - try { - await this.smsManagerService.verifyLookUp({ - token: URL, - template: "yara724-invite-link", - receptor: phoneNumber, - }); - this.logger.log( - `[SMS] Invite link verifyLookUp ok receptor=${phoneNumber}`, - ); - } catch (er) { - this.logger.error( - `[SMS] Invite link failed receptor=${phoneNumber}: ${describeSmsError(er)}`, - ); - } + const URL = this.smsOrchestrationService.buildInviteLink( + frontendRoutes, + requestId, + ); + await this.smsOrchestrationService.sendInviteLink(phoneNumber, URL); return { url: URL }; } @@ -2838,17 +2809,11 @@ export class RequestManagementService { // TODO SMS Sending if (phoneNumberToNotify) { - const message = `متاسفانه طرف مقابل با نظر کارشناس مخالفت کرد. فرآیند آنلاین این پرونده بسته شده است و جهت پیگیری حضوری اقدام نمایید.`; - try { - await this.smsManagerService.sendMessage({ - receptor: phoneNumberToNotify, - message, - }); - } catch (err) { - this.logger.error( - `[SMS] PartiesDisagree notify failed receptor=${phoneNumberToNotify}: ${describeSmsError(err)}`, - ); - } + await this.smsOrchestrationService.sendTextByKey( + phoneNumberToNotify, + "parties_disagree_notify", + "متاسفانه طرف مقابل با نظر کارشناس مخالفت کرد. فرآیند آنلاین این پرونده بسته شده است و جهت پیگیری حضوری اقدام نمایید.", + ); } } @@ -3028,17 +2993,15 @@ export class RequestManagementService { : originalReq.firstPartyDetails?.firstPartyPhoneNumber; if (phoneNumber) { - // TODO FIX SMS SENDING FOR PARTIES - try { - await this.smsManagerService.sendMessage({ - receptor: phoneNumber, - message, - }); - } catch (err) { - this.logger.error( - `[SMS] One-party-replied notify failed receptor=${phoneNumber}: ${describeSmsError(err)}`, - ); - } + const key = + message.includes("24 ساعت") + ? "one_party_accepted_wait_24h" + : "one_party_signed_wait_signature"; + await this.smsOrchestrationService.sendTextByKey( + phoneNumber, + key, + message, + ); } await this.autoCloseRequestService.scheduleAutoClose({ @@ -3764,33 +3727,21 @@ export class RequestManagementService { "URL environment variable is not configured", ); } - const linkUrl = `${process.env.URL}/${dto.frontendRoute}?token=${requestId}`; - const typeToken = - req.type === BlameRequestType.CAR_BODY ? "بدنه" : "ثالث"; + const linkUrl = this.smsOrchestrationService.buildInviteLink( + dto.frontendRoute, + requestId, + ); const expertName = `${expert?.lastName || ""}`.trim() || "کارشناس"; const sentTo: { role: string; phoneNumber: string; smsSent: boolean }[] = []; - const template = "yara-field-expert-link"; const sendSms = async (phone: string, role: PartyRole): Promise => { - try { - await this.smsManagerService.verifyLookUp({ - template, - receptor: phone, - token: typeToken, - token2: expertName, - token3: linkUrl, - }); - this.logger.log( - `[SMS] Field-expert link sent role=${role} receptor=${phone} template=${template}`, - ); - return true; - } catch (er) { - this.logger.error( - `[SMS] Field-expert link failed role=${role} receptor=${phone}: ${describeSmsError(er)}`, - ); - return false; - } + return this.smsOrchestrationService.sendFieldExpertLink({ + receptor: phone, + type: req.type, + expertLastName: expertName, + link: linkUrl, + }); }; const firstIdx = this.getPartyIndex(req, PartyRole.FIRST); @@ -3816,7 +3767,12 @@ export class RequestManagementService { actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(), actorType: "field_expert", }, - metadata: { linkUrl, sentTo, template, typeToken, frontendRoute: dto.frontendRoute }, + metadata: { + linkUrl, + sentTo, + template: "yara-field-expert-link", + frontendRoute: dto.frontendRoute, + }, }); await (req as any).save(); diff --git a/src/sms-orchestration/entities/db-service/sms-text.db.service.ts b/src/sms-orchestration/entities/db-service/sms-text.db.service.ts new file mode 100644 index 0000000..a5b49fb --- /dev/null +++ b/src/sms-orchestration/entities/db-service/sms-text.db.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from "@nestjs/common"; +import { InjectModel } from "@nestjs/mongoose"; +import { FilterQuery, Model } from "mongoose"; +import { SmsText, SmsTextDocument } from "../schema/sms-text.schema"; + +@Injectable() +export class SmsTextDbService { + constructor( + @InjectModel(SmsText.name) + private readonly smsTextModel: Model, + ) {} + + async findOne(filter: FilterQuery): Promise { + return this.smsTextModel.findOne(filter); + } + + async upsert(key: string, text: string): Promise { + await this.smsTextModel.updateOne( + { key }, + { $setOnInsert: { key, text, active: true } }, + { upsert: true }, + ); + } +} diff --git a/src/sms-orchestration/entities/schema/sms-text.schema.ts b/src/sms-orchestration/entities/schema/sms-text.schema.ts new file mode 100644 index 0000000..79af8c8 --- /dev/null +++ b/src/sms-orchestration/entities/schema/sms-text.schema.ts @@ -0,0 +1,17 @@ +import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose"; +import { HydratedDocument } from "mongoose"; + +@Schema({ collection: "sms-texts", timestamps: true, versionKey: false }) +export class SmsText { + @Prop({ required: true, unique: true, index: true }) + key: string; + + @Prop({ required: true }) + text: string; + + @Prop({ default: true }) + active: boolean; +} + +export type SmsTextDocument = HydratedDocument; +export const SmsTextSchema = SchemaFactory.createForClass(SmsText); diff --git a/src/utils/sms-manager/kavenegar-response.ts b/src/sms-orchestration/provider/kavenegar-response.ts similarity index 100% rename from src/utils/sms-manager/kavenegar-response.ts rename to src/sms-orchestration/provider/kavenegar-response.ts diff --git a/src/sms-orchestration/provider/sms-gateway.module.ts b/src/sms-orchestration/provider/sms-gateway.module.ts new file mode 100644 index 0000000..5c104a6 --- /dev/null +++ b/src/sms-orchestration/provider/sms-gateway.module.ts @@ -0,0 +1,18 @@ +import { KavenegarModule } from "@fraybabak/kavenegar_nest"; +import { Module } from "@nestjs/common"; +import * as dotenv from "dotenv"; +import { SmsGatewayService } from "./sms-gateway.service"; + +dotenv.config(); +dotenv.config({ path: `.${process.env.NODE_ENV}.env` }); + +@Module({ + imports: [ + KavenegarModule.forRoot({ + apikey: process.env.SMS_API_KEY || "", + }), + ], + providers: [SmsGatewayService], + exports: [SmsGatewayService], +}) +export class SmsGatewayModule {} diff --git a/src/utils/sms-manager/sms-manager.service.ts b/src/sms-orchestration/provider/sms-gateway.service.ts similarity index 96% rename from src/utils/sms-manager/sms-manager.service.ts rename to src/sms-orchestration/provider/sms-gateway.service.ts index fd31b73..90cd7a8 100644 --- a/src/utils/sms-manager/sms-manager.service.ts +++ b/src/sms-orchestration/provider/sms-gateway.service.ts @@ -14,7 +14,6 @@ import { export interface SendMessage { message: string; - // sender: string; receptor: string; } @@ -27,8 +26,8 @@ export interface VerifyLookUpMessage { } @Injectable() -export class SmsManagerService { - private readonly logger = new Logger(SmsManagerService.name); +export class SmsGatewayService { + private readonly logger = new Logger(SmsGatewayService.name); constructor(private readonly sender: KavenegarService) {} diff --git a/src/utils/sms-manager/sms-provider.exception.ts b/src/sms-orchestration/provider/sms-provider.exception.ts similarity index 100% rename from src/utils/sms-manager/sms-provider.exception.ts rename to src/sms-orchestration/provider/sms-provider.exception.ts diff --git a/src/sms-orchestration/sms-orchestration.module.ts b/src/sms-orchestration/sms-orchestration.module.ts new file mode 100644 index 0000000..bcc1d20 --- /dev/null +++ b/src/sms-orchestration/sms-orchestration.module.ts @@ -0,0 +1,16 @@ +import { Module } from "@nestjs/common"; +import { MongooseModule } from "@nestjs/mongoose"; +import { SmsTextDbService } from "./entities/db-service/sms-text.db.service"; +import { SmsText, SmsTextSchema } from "./entities/schema/sms-text.schema"; +import { SmsGatewayModule } from "./provider/sms-gateway.module"; +import { SmsOrchestrationService } from "./sms-orchestration.service"; + +@Module({ + imports: [ + SmsGatewayModule, + MongooseModule.forFeature([{ name: SmsText.name, schema: SmsTextSchema }]), + ], + providers: [SmsTextDbService, SmsOrchestrationService], + exports: [SmsOrchestrationService], +}) +export class SmsOrchestrationModule {} diff --git a/src/sms-orchestration/sms-orchestration.service.ts b/src/sms-orchestration/sms-orchestration.service.ts new file mode 100644 index 0000000..61ecd8c --- /dev/null +++ b/src/sms-orchestration/sms-orchestration.service.ts @@ -0,0 +1,111 @@ +import { Injectable, Logger, OnModuleInit } from "@nestjs/common"; +import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum"; +import { SmsTextDbService } from "./entities/db-service/sms-text.db.service"; +import { SmsGatewayService } from "./provider/sms-gateway.service"; +import { describeSmsError } from "./provider/sms-provider.exception"; + +type TemplateArgs = { + template: string; + receptor: string; + token: string; + token2?: string; + token3?: string; +}; + +@Injectable() +export class SmsOrchestrationService implements OnModuleInit { + private readonly logger = new Logger(SmsOrchestrationService.name); + + constructor( + private readonly smsGatewayService: SmsGatewayService, + private readonly smsTextDbService: SmsTextDbService, + ) {} + + async onModuleInit(): Promise { + await this.smsTextDbService.upsert( + "parties_disagree_notify", + "متاسفانه طرف مقابل با نظر کارشناس مخالفت کرد. فرآیند آنلاین این پرونده بسته شده است و جهت پیگیری حضوری اقدام نمایید.", + ); + await this.smsTextDbService.upsert( + "one_party_signed_wait_signature", + "طرف مقابل توافق را امضا کرده است. لطفا جهت نهایی کردن پرونده، امضای خود را ثبت نمایید.", + ); + await this.smsTextDbService.upsert( + "one_party_accepted_wait_24h", + "طرف مقابل نظر کارشناس را قبول و امضا کرده است. در طی 24 ساعت آینده فرصت دارید تا پرونده را تکمیل کرده یا پرونده بسته خواهد شد.", + ); + } + + buildInviteLink(frontendRoute: string, requestId: string): string { + return `${process.env.URL}/${frontendRoute}?token=${requestId}`; + } + + async sendInviteLink(phoneNumber: string, link: string): Promise { + return this.sendTemplate({ + template: "yara724-invite-link", + receptor: phoneNumber, + token: link, + }); + } + + async sendAuthOtp( + mobile: string, + otp: string, + template?: string, + ): Promise { + return this.sendTemplate({ + template: template || process.env.AUTH_SMS_TEMPLATE || "", + receptor: mobile, + token: otp, + }); + } + + async sendFieldExpertLink(params: { + receptor: string; + type: BlameRequestType; + expertLastName: string; + link: string; + }): Promise { + return this.sendTemplate({ + template: "yara-field-expert-link", + receptor: params.receptor, + token: params.type === BlameRequestType.CAR_BODY ? "بدنه" : "ثالث", + token2: params.expertLastName || "کارشناس", + token3: params.link, + }); + } + + async sendTextByKey( + receptor: string, + key: string, + fallbackText: string, + ): Promise { + const row = await this.smsTextDbService.findOne({ key, active: true }); + const message = row?.text?.trim() || fallbackText; + try { + await this.smsGatewayService.sendMessage({ receptor, message }); + this.logger.log(`[SMS] text sent key=${key} receptor=${receptor}`); + return true; + } catch (err) { + this.logger.error( + `[SMS] text send failed key=${key} receptor=${receptor}: ${describeSmsError(err)}`, + ); + return false; + } + } + + private async sendTemplate(args: TemplateArgs): Promise { + try { + await this.smsGatewayService.verifyLookUp(args); + this.logger.log( + `[SMS] template sent template=${args.template} receptor=${args.receptor}`, + ); + return true; + } catch (err) { + this.logger.error( + `[SMS] template send failed template=${args.template} receptor=${args.receptor}: ${describeSmsError(err)}`, + ); + return false; + } + } +} diff --git a/src/utils/sms-manager/sms-manager.module.ts b/src/utils/sms-manager/sms-manager.module.ts deleted file mode 100644 index 90b0c52..0000000 --- a/src/utils/sms-manager/sms-manager.module.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { KavenegarModule } from "@fraybabak/kavenegar_nest"; -import { Module } from "@nestjs/common"; -import { SmsManagerService } from "./sms-manager.service"; - -@Module({ - imports: [ - KavenegarModule.forRoot({ - apikey: - "75776C717969412B4B52306A5956462F4A714E6F6C65544D6A2B654B7566786E", - }), - ], - providers: [SmsManagerService], - exports: [SmsManagerService], -}) -export class SmsManagerModule {}