Merge pull request 'YARA-834' (#36) from s.yahyaee/yara724-api:main into main

Reviewed-on: Yara724/api#36
This commit is contained in:
2026-04-22 15:03:28 +03:30
16 changed files with 311 additions and 138 deletions

49
package-lock.json generated
View File

@@ -14,6 +14,7 @@
"@nestjs-modules/mailer": "^1.8.1", "@nestjs-modules/mailer": "^1.8.1",
"@nestjs/axios": "^3.1.3", "@nestjs/axios": "^3.1.3",
"@nestjs/common": "^10.4.15", "@nestjs/common": "^10.4.15",
"@nestjs/config": "^4.0.4",
"@nestjs/core": "^10.4.15", "@nestjs/core": "^10.4.15",
"@nestjs/jwt": "^10.2.0", "@nestjs/jwt": "^10.2.0",
"@nestjs/mapped-types": "*", "@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": { "node_modules/@nestjs/core": {
"version": "10.4.15", "version": "10.4.15",
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.15.tgz", "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.15.tgz",
@@ -8441,6 +8475,21 @@
"url": "https://dotenvx.com" "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": { "node_modules/dunder-proto": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",

View File

@@ -25,6 +25,7 @@
"@nestjs-modules/mailer": "^1.8.1", "@nestjs-modules/mailer": "^1.8.1",
"@nestjs/axios": "^3.1.3", "@nestjs/axios": "^3.1.3",
"@nestjs/common": "^10.4.15", "@nestjs/common": "^10.4.15",
"@nestjs/config": "^4.0.4",
"@nestjs/core": "^10.4.15", "@nestjs/core": "^10.4.15",
"@nestjs/jwt": "^10.2.0", "@nestjs/jwt": "^10.2.0",
"@nestjs/mapped-types": "*", "@nestjs/mapped-types": "*",

View File

@@ -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 { UserDbService } from "src/users/entities/db-service/user.db.service";
import { HashService } from "src/utils/hash/hash.service"; import { HashService } from "src/utils/hash/hash.service";
import { OtpService } from "src/utils/otp/otp.service"; import { OtpService } from "src/utils/otp/otp.service";
import { SmsManagerService } from "src/utils/sms-manager/sms-manager.service"; import { SmsOrchestrationService } from "src/sms-orchestration/sms-orchestration.service";
import { describeSmsError } from "src/utils/sms-manager/sms-provider.exception";
// TODO FIX REGISTER TO USER.SERVICE AND AUTH IN THIS MODULE // TODO FIX REGISTER TO USER.SERVICE AND AUTH IN THIS MODULE
@Injectable() @Injectable()
@@ -26,7 +25,7 @@ export class UserAuthService {
private readonly userDbService: UserDbService, private readonly userDbService: UserDbService,
private readonly hashService: HashService, private readonly hashService: HashService,
private readonly otpCreator: OtpService, private readonly otpCreator: OtpService,
private readonly smsManagerService: SmsManagerService, private readonly smsOrchestrationService: SmsOrchestrationService,
) {} ) {}
async validateUser(username: string, pass: string): Promise<any> { async validateUser(username: string, pass: string): Promise<any> {
@@ -118,26 +117,19 @@ export class UserAuthService {
} }
private async smsSender(otp: string, mobile: string) { private async smsSender(otp: string, mobile: string) {
try { const ok = await this.smsOrchestrationService.sendAuthOtp(
await this.smsManagerService.verifyLookUp({ mobile,
token: otp, otp,
template: process.env.AUTH_SMS_TEMPLATE, process.env.AUTH_SMS_TEMPLATE,
receptor: mobile, );
}); if (!ok) {
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;
}
throw new HttpException( throw new HttpException(
"auth sms send failed", "auth sms send failed",
HttpStatus.BAD_GATEWAY, HttpStatus.BAD_GATEWAY,
); );
} }
this.logger.log(
`Auth OTP SMS accepted by provider phone=${mobile} otp=${otp}`,
);
} }
} }

View File

@@ -12,7 +12,7 @@ import { UsersModule } from "src/users/users.module";
import { HashModule } from "src/utils/hash/hash.module"; import { HashModule } from "src/utils/hash/hash.module";
// import { MailModule } from "src/utils/mail/mail.module"; // import { MailModule } from "src/utils/mail/mail.module";
import { OtpModule } from "src/utils/otp/otp.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({ @Module({
imports: [ imports: [
@@ -22,7 +22,7 @@ import { SmsManagerModule } from "src/utils/sms-manager/sms-manager.module";
HashModule, HashModule,
OtpModule, OtpModule,
PassportModule, PassportModule,
SmsManagerModule, SmsOrchestrationModule,
JwtModule.register({ JwtModule.register({
signOptions: { expiresIn: "1h" }, signOptions: { expiresIn: "1h" },
global: true, global: true,

View File

@@ -35,9 +35,14 @@ export class ResendRequestDto {
description: "Array of resend requests (can include first party, second party, or both)", description: "Array of resend requests (can include first party, second party, or both)",
example: [ example: [
{ {
partyId: "507f1f77bcf86cd799439011", partyId: "681b3b3fb237e5856429e444",
requestedItems: ["drivingLicense", "carCertificate"], requestedItems: ["drivingLicense"],
description: "Please resend with better quality", description: "DESC TEST1",
},
{
partyId: "68b6ba552a6e897df34e0f90",
requestedItems: ["carCertificate"],
description: "DESC TEST2",
}, },
], ],
}) })

View File

@@ -10,9 +10,9 @@ import { BlameRequestDbService } from "src/request-management/entities/db-servic
import { SandHubModule } from "src/sand-hub/sand-hub.module"; import { SandHubModule } from "src/sand-hub/sand-hub.module";
import { UsersModule } from "src/users/users.module"; import { UsersModule } from "src/users/users.module";
import { CronModule } from "src/utils/cron/cron.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 { PublicIdModule } from "src/utils/public-id/public-id.module";
import { HashModule } from "src/utils/hash/hash.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 { WorkflowStepManagementModule } from "src/workflow-step-management/workflow-step-management.module";
import { AuthModule } from "src/auth/auth.module"; import { AuthModule } from "src/auth/auth.module";
import { BlameDocumentDbService } from "./entities/db-service/blame-document.db.service"; import { BlameDocumentDbService } from "./entities/db-service/blame-document.db.service";
@@ -45,7 +45,7 @@ import { RegistrarInitiatedController } from "./registrar-initiated.controller";
UsersModule, UsersModule,
ClientModule, ClientModule,
SandHubModule, SandHubModule,
SmsManagerModule, SmsOrchestrationModule,
PublicIdModule, PublicIdModule,
HashModule, HashModule,
WorkflowStepManagementModule, WorkflowStepManagementModule,

View File

@@ -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 { ExpertDbService } from "src/users/entities/db-service/expert.db.service";
import { UserDbService } from "src/users/entities/db-service/user.db.service"; import { UserDbService } from "src/users/entities/db-service/user.db.service";
import { AutoCloseRequestService } from "src/utils/cron/cron.service"; import { AutoCloseRequestService } from "src/utils/cron/cron.service";
import { SmsManagerService } from "src/utils/sms-manager/sms-manager.service"; import { SmsOrchestrationService } from "src/sms-orchestration/sms-orchestration.service";
import { describeSmsError } from "src/utils/sms-manager/sms-provider.exception";
import { import {
DescriptionDto, DescriptionDto,
LocationDto, LocationDto,
@@ -308,7 +307,7 @@ export class RequestManagementService {
private readonly clientService: ClientService, private readonly clientService: ClientService,
private readonly blameVoiceDbService: BlameVoiceDbService, private readonly blameVoiceDbService: BlameVoiceDbService,
private readonly userSign: UserSignDbService, private readonly userSign: UserSignDbService,
private readonly smsManagerService: SmsManagerService, private readonly smsOrchestrationService: SmsOrchestrationService,
private readonly expertDbService: ExpertDbService, private readonly expertDbService: ExpertDbService,
private readonly autoCloseRequestService: AutoCloseRequestService, private readonly autoCloseRequestService: AutoCloseRequestService,
private readonly claimRequestManagementDbService: ClaimRequestManagementDbService, private readonly claimRequestManagementDbService: ClaimRequestManagementDbService,
@@ -1305,21 +1304,14 @@ export class RequestManagementService {
metadata: { secondPartyPhone, expertInitiatedLink: true }, metadata: { secondPartyPhone, expertInitiatedLink: true },
} as any); } as any);
await (req as any).save(); await (req as any).save();
const url = `${process.env.URL}/${frontendRoute}?token=${requestId}`; const url = this.smsOrchestrationService.buildInviteLink(
try { frontendRoute,
await this.smsManagerService.verifyLookUp({ requestId,
token: url, );
template: "yara724-invite-link", await this.smsOrchestrationService.sendInviteLink(
receptor: secondPartyPhone, secondPartyPhone,
}); url,
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)}`,
);
}
return { return {
requestId: req._id, requestId: req._id,
publicId: req.publicId, publicId: req.publicId,
@@ -1362,22 +1354,11 @@ export class RequestManagementService {
await (req as any).save(); await (req as any).save();
// Send SMS invitation // Send SMS invitation
const url = `${process.env.URL}/${frontendRoute}?token=${requestId}`; const url = this.smsOrchestrationService.buildInviteLink(
try { frontendRoute,
await this.smsManagerService.verifyLookUp({ requestId,
token: url, );
template: "yara724-invite-link", await this.smsOrchestrationService.sendInviteLink(phoneNumber, url);
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
}
return { return {
requestId: req._id, requestId: req._id,
@@ -2295,21 +2276,11 @@ export class RequestManagementService {
} }
// Send the SMS after the database update is complete. // Send the SMS after the database update is complete.
const URL = `${process.env.URL}/${frontendRoutes}?token=${requestId}`; const URL = this.smsOrchestrationService.buildInviteLink(
try { frontendRoutes,
await this.smsManagerService.verifyLookUp({ requestId,
token: URL, );
template: "yara724-invite-link", await this.smsOrchestrationService.sendInviteLink(phoneNumber, URL);
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)}`,
);
}
return { url: URL }; return { url: URL };
} }
@@ -2838,17 +2809,11 @@ export class RequestManagementService {
// TODO SMS Sending // TODO SMS Sending
if (phoneNumberToNotify) { if (phoneNumberToNotify) {
const message = `متاسفانه طرف مقابل با نظر کارشناس مخالفت کرد. فرآیند آنلاین این پرونده بسته شده است و جهت پیگیری حضوری اقدام نمایید.`; await this.smsOrchestrationService.sendTextByKey(
try { phoneNumberToNotify,
await this.smsManagerService.sendMessage({ "parties_disagree_notify",
receptor: phoneNumberToNotify, "متاسفانه طرف مقابل با نظر کارشناس مخالفت کرد. فرآیند آنلاین این پرونده بسته شده است و جهت پیگیری حضوری اقدام نمایید.",
message, );
});
} catch (err) {
this.logger.error(
`[SMS] PartiesDisagree notify failed receptor=${phoneNumberToNotify}: ${describeSmsError(err)}`,
);
}
} }
} }
@@ -3028,17 +2993,15 @@ export class RequestManagementService {
: originalReq.firstPartyDetails?.firstPartyPhoneNumber; : originalReq.firstPartyDetails?.firstPartyPhoneNumber;
if (phoneNumber) { if (phoneNumber) {
// TODO FIX SMS SENDING FOR PARTIES const key =
try { message.includes("24 ساعت")
await this.smsManagerService.sendMessage({ ? "one_party_accepted_wait_24h"
receptor: phoneNumber, : "one_party_signed_wait_signature";
message, await this.smsOrchestrationService.sendTextByKey(
}); phoneNumber,
} catch (err) { key,
this.logger.error( message,
`[SMS] One-party-replied notify failed receptor=${phoneNumber}: ${describeSmsError(err)}`, );
);
}
} }
await this.autoCloseRequestService.scheduleAutoClose({ await this.autoCloseRequestService.scheduleAutoClose({
@@ -3764,33 +3727,21 @@ export class RequestManagementService {
"URL environment variable is not configured", "URL environment variable is not configured",
); );
} }
const linkUrl = `${process.env.URL}/${dto.frontendRoute}?token=${requestId}`; const linkUrl = this.smsOrchestrationService.buildInviteLink(
const typeToken = dto.frontendRoute,
req.type === BlameRequestType.CAR_BODY ? "بدنه" : "ثالث"; requestId,
);
const expertName = const expertName =
`${expert?.lastName || ""}`.trim() || "کارشناس"; `${expert?.lastName || ""}`.trim() || "کارشناس";
const sentTo: { role: string; phoneNumber: string; smsSent: boolean }[] = []; const sentTo: { role: string; phoneNumber: string; smsSent: boolean }[] = [];
const template = "yara-field-expert-link";
const sendSms = async (phone: string, role: PartyRole): Promise<boolean> => { const sendSms = async (phone: string, role: PartyRole): Promise<boolean> => {
try { return this.smsOrchestrationService.sendFieldExpertLink({
await this.smsManagerService.verifyLookUp({ receptor: phone,
template, type: req.type,
receptor: phone, expertLastName: expertName,
token: typeToken, link: linkUrl,
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;
}
}; };
const firstIdx = this.getPartyIndex(req, PartyRole.FIRST); const firstIdx = this.getPartyIndex(req, PartyRole.FIRST);
@@ -3816,7 +3767,12 @@ export class RequestManagementService {
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(), actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
actorType: "field_expert", 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(); await (req as any).save();

View File

@@ -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<SmsTextDocument>,
) {}
async findOne(filter: FilterQuery<SmsText>): Promise<SmsTextDocument | null> {
return this.smsTextModel.findOne(filter);
}
async upsert(key: string, text: string): Promise<void> {
await this.smsTextModel.updateOne(
{ key },
{ $setOnInsert: { key, text, active: true } },
{ upsert: true },
);
}
}

View File

@@ -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<SmsText>;
export const SmsTextSchema = SchemaFactory.createForClass(SmsText);

View File

@@ -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 {}

View File

@@ -14,7 +14,6 @@ import {
export interface SendMessage { export interface SendMessage {
message: string; message: string;
// sender: string;
receptor: string; receptor: string;
} }
@@ -27,8 +26,8 @@ export interface VerifyLookUpMessage {
} }
@Injectable() @Injectable()
export class SmsManagerService { export class SmsGatewayService {
private readonly logger = new Logger(SmsManagerService.name); private readonly logger = new Logger(SmsGatewayService.name);
constructor(private readonly sender: KavenegarService) {} constructor(private readonly sender: KavenegarService) {}

View File

@@ -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 {}

View File

@@ -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<void> {
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<boolean> {
return this.sendTemplate({
template: "yara724-invite-link",
receptor: phoneNumber,
token: link,
});
}
async sendAuthOtp(
mobile: string,
otp: string,
template?: string,
): Promise<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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;
}
}
}

View File

@@ -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 {}