forked from Yara724/api
Merge pull request 'YARA-941' (#79) from s.yahyaee/yara724-api:main into main
Reviewed-on: Yara724/api#79
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
export enum ClaimRequiredDocumentType {
|
||||
// Car green card
|
||||
CAR_GREEN_CARD = "car_green_card",
|
||||
|
||||
CAR_CERTIFICATE = "car_certificate",
|
||||
/** National ID card (or similar); may be requested on resend even if not in the initial upload set. */
|
||||
NATIONAL_CARD = "national_card",
|
||||
|
||||
|
||||
@@ -77,14 +77,14 @@ dotenv.config({ path: `.${process.env.NODE_ENV}.env` });
|
||||
provide: APP_INTERCEPTOR,
|
||||
useClass: UnicodeDigitsNormalizeInterceptor,
|
||||
},
|
||||
// {
|
||||
// provide: APP_PIPE,
|
||||
// useValue: new ValidationPipe({
|
||||
// transform: true,
|
||||
// whitelist: true,
|
||||
// forbidNonWhitelisted: false,
|
||||
// }),
|
||||
// },
|
||||
{
|
||||
provide: APP_PIPE,
|
||||
useValue: new ValidationPipe({
|
||||
transform: true,
|
||||
whitelist: true,
|
||||
forbidNonWhitelisted: false,
|
||||
}),
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
LightCondition,
|
||||
} from "src/Types&Enums/blame-request-management/accident-conditions.enum";
|
||||
import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum";
|
||||
import { IsEnum } from "class-validator";
|
||||
|
||||
export class InitialFormDto {
|
||||
@ApiProperty({ required: false, default: false })
|
||||
@@ -161,16 +162,17 @@ export class LocationDto {
|
||||
lon: number;
|
||||
}
|
||||
|
||||
export class CreateRequestDto {
|
||||
@ApiProperty({ type: String, required: false })
|
||||
type?: "THIRD_PARTY" | "CAR_BODY";
|
||||
}
|
||||
// export class CreateRequestDto {
|
||||
// @ApiProperty({ type: String, required: false })
|
||||
// type?: "THIRD_PARTY" | "CAR_BODY";
|
||||
// }
|
||||
|
||||
/**
|
||||
* V2 create blame request (new model `BlameRequest`).
|
||||
*/
|
||||
export class CreateBlameRequestDtoV2 {
|
||||
@ApiProperty({ enum: BlameRequestType })
|
||||
@IsEnum(BlameRequestType)
|
||||
@ApiProperty({ enum: BlameRequestType, description: "Type of blame request" })
|
||||
type: BlameRequestType;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ import { BlameDocumentType } from "./entities/schema/blame-document.schema";
|
||||
import {
|
||||
CarBodyFormDto,
|
||||
CarBodySecondForm,
|
||||
CreateRequestDto,
|
||||
DescriptionDto,
|
||||
InitialFormDto,
|
||||
LocationDto,
|
||||
@@ -53,18 +52,18 @@ export class RequestManagementController {
|
||||
private readonly requestManagementService: RequestManagementService,
|
||||
) {}
|
||||
|
||||
@ApiOperation({ deprecated: true })
|
||||
@Post()
|
||||
@UseGuards(GlobalGuard)
|
||||
async createRequest(
|
||||
@CurrentUser() user,
|
||||
@Body() createRequestDto: CreateRequestDto,
|
||||
) {
|
||||
return await this.requestManagementService.createRequest(
|
||||
user,
|
||||
createRequestDto.type,
|
||||
);
|
||||
}
|
||||
// @ApiOperation({ deprecated: true })
|
||||
// @Post()
|
||||
// @UseGuards(GlobalGuard)
|
||||
// async createRequest(
|
||||
// @CurrentUser() user,
|
||||
// @Body() createRequestDto: CreateRequestDto,
|
||||
// ) {
|
||||
// return await this.requestManagementService.createRequest(
|
||||
// user,
|
||||
// createRequestDto.type,
|
||||
// );
|
||||
// }
|
||||
|
||||
@ApiOperation({ deprecated: true })
|
||||
@Post("/initial-form/:requestId")
|
||||
|
||||
98
src/sms-orchestration/provider/kavenegar-sms.gateway.ts
Normal file
98
src/sms-orchestration/provider/kavenegar-sms.gateway.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { KavenegarService } from "@fraybabak/kavenegar_nest";
|
||||
import { Injectable, Logger } from "@nestjs/common";
|
||||
import {
|
||||
isKavenegarSuccess,
|
||||
KavenegarNormalized,
|
||||
normalizeKavenegarBody,
|
||||
safeJsonStringify,
|
||||
unwrapKavenegarTransportError,
|
||||
} from "./kavenegar-response";
|
||||
import {
|
||||
SmsProviderException,
|
||||
SmsTransportException,
|
||||
} from "./sms-provider.exception";
|
||||
import { SendMessage, VerifyLookUpMessage } from "./sms-gateway.types";
|
||||
|
||||
@Injectable()
|
||||
export class KavenegarSmsGateway {
|
||||
private readonly logger = new Logger(KavenegarSmsGateway.name);
|
||||
|
||||
constructor(private readonly sender: KavenegarService) {}
|
||||
|
||||
async sendMessage(data: SendMessage) {
|
||||
try {
|
||||
const body = await this.sender.Send({
|
||||
sender: "10008663",
|
||||
...data,
|
||||
});
|
||||
if (!isKavenegarSuccess(body)) {
|
||||
const normalized = normalizeKavenegarBody(body);
|
||||
this.logProviderRejection("send", data.receptor, undefined, normalized);
|
||||
throw new SmsProviderException("send", {
|
||||
receptor: data.receptor,
|
||||
providerBody: body,
|
||||
normalized,
|
||||
});
|
||||
}
|
||||
this.logger.log(
|
||||
`Kavenegar Send ok receptor=${data.receptor} status=200`,
|
||||
);
|
||||
return body;
|
||||
} catch (e) {
|
||||
if (e instanceof SmsProviderException) throw e;
|
||||
const detail = safeJsonStringify(unwrapKavenegarTransportError(e));
|
||||
this.logger.error(
|
||||
`Kavenegar Send transport error receptor=${data.receptor} detail=${detail}`,
|
||||
);
|
||||
throw new SmsTransportException("send", { receptor: data.receptor }, e);
|
||||
}
|
||||
}
|
||||
|
||||
async verifyLookUp(data: VerifyLookUpMessage) {
|
||||
try {
|
||||
const body = await this.sender.verifyLookup(data);
|
||||
if (!isKavenegarSuccess(body)) {
|
||||
const normalized = normalizeKavenegarBody(body);
|
||||
this.logProviderRejection(
|
||||
"verifyLookUp",
|
||||
data.receptor,
|
||||
data.template,
|
||||
normalized,
|
||||
);
|
||||
throw new SmsProviderException("verifyLookup", {
|
||||
receptor: data.receptor,
|
||||
template: data.template,
|
||||
providerBody: body,
|
||||
normalized,
|
||||
});
|
||||
}
|
||||
this.logger.log(
|
||||
`Kavenegar verifyLookUp ok receptor=${data.receptor} template=${data.template} status=200`,
|
||||
);
|
||||
return body;
|
||||
} catch (e) {
|
||||
if (e instanceof SmsProviderException) throw e;
|
||||
const detail = safeJsonStringify(unwrapKavenegarTransportError(e));
|
||||
this.logger.error(
|
||||
`Kavenegar verifyLookUp transport error receptor=${data.receptor} template=${data.template} detail=${detail}`,
|
||||
);
|
||||
throw new SmsTransportException(
|
||||
"verifyLookup",
|
||||
{ receptor: data.receptor, template: data.template },
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private logProviderRejection(
|
||||
op: "send" | "verifyLookUp",
|
||||
receptor: string,
|
||||
template: string | undefined,
|
||||
normalized: KavenegarNormalized,
|
||||
) {
|
||||
const t = template != null && template !== "" ? ` template=${template}` : "";
|
||||
this.logger.warn(
|
||||
`Kavenegar ${op} rejected receptor=${receptor}${t} providerStatus=${normalized.httpLikeStatus ?? "unknown"} providerMessage=${normalized.message ?? "n/a"} body=${normalized.raw}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
90
src/sms-orchestration/provider/parsian-sms.gateway.ts
Normal file
90
src/sms-orchestration/provider/parsian-sms.gateway.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Injectable, Logger } from "@nestjs/common";
|
||||
import axios, { isAxiosError } from "axios";
|
||||
import { buildParsianTemplateMessage } from "./parsian-template-messages";
|
||||
import {
|
||||
SmsProviderException,
|
||||
SmsTransportException,
|
||||
} from "./sms-provider.exception";
|
||||
import { SendMessage, VerifyLookUpMessage } from "./sms-gateway.types";
|
||||
|
||||
@Injectable()
|
||||
export class ParsianSmsGateway {
|
||||
private readonly logger = new Logger(ParsianSmsGateway.name);
|
||||
|
||||
async sendMessage(data: SendMessage) {
|
||||
try {
|
||||
const body = await this.requestSend(data.receptor, data.message);
|
||||
this.logger.log(`Parsian Send ok receptor=${data.receptor}`);
|
||||
return body;
|
||||
} catch (e) {
|
||||
if (e instanceof SmsProviderException) throw e;
|
||||
const detail = isAxiosError(e)
|
||||
? { status: e.response?.status, data: e.response?.data, message: e.message }
|
||||
: e;
|
||||
this.logger.error(
|
||||
`Parsian Send transport error receptor=${data.receptor} detail=${JSON.stringify(detail)}`,
|
||||
);
|
||||
throw new SmsTransportException("send", { receptor: data.receptor }, e);
|
||||
}
|
||||
}
|
||||
|
||||
async verifyLookUp(data: VerifyLookUpMessage) {
|
||||
const message = buildParsianTemplateMessage(data);
|
||||
try {
|
||||
const body = await this.requestSend(data.receptor, message);
|
||||
this.logger.log(
|
||||
`Parsian verifyLookUp ok receptor=${data.receptor} template=${data.template}`,
|
||||
);
|
||||
return body;
|
||||
} catch (e) {
|
||||
if (e instanceof SmsProviderException) throw e;
|
||||
const detail = isAxiosError(e)
|
||||
? { status: e.response?.status, data: e.response?.data, message: e.message }
|
||||
: e;
|
||||
this.logger.error(
|
||||
`Parsian verifyLookUp transport error receptor=${data.receptor} template=${data.template} detail=${JSON.stringify(detail)}`,
|
||||
);
|
||||
throw new SmsTransportException(
|
||||
"verifyLookup",
|
||||
{ receptor: data.receptor, template: data.template },
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private parsianHeaders() {
|
||||
return {
|
||||
"Content-Type": "application/json",
|
||||
"X-PACKAGE-API-KEY": process.env.PARSIAN_API_KEY ?? "",
|
||||
Authorization: `Basic ${process.env.PARSIAN_BASIC_TOKEN ?? ""}`,
|
||||
};
|
||||
}
|
||||
|
||||
private async requestSend(receptor: string, message: string): Promise<unknown> {
|
||||
const baseUrl = process.env.PARSIAN_SMS_URL;
|
||||
if (!baseUrl?.trim()) {
|
||||
throw new SmsTransportException(
|
||||
"send",
|
||||
{ receptor },
|
||||
new Error("PARSIAN_SMS_URL is not configured"),
|
||||
);
|
||||
}
|
||||
|
||||
const url = `${baseUrl}=${receptor}&Message=${encodeURIComponent(message)}`;
|
||||
const response = await axios.get(url, { headers: this.parsianHeaders() });
|
||||
|
||||
if (response.status < 200 || response.status >= 300) {
|
||||
throw new SmsProviderException("send", {
|
||||
receptor,
|
||||
providerBody: response.data,
|
||||
normalized: {
|
||||
httpLikeStatus: response.status,
|
||||
message: String(response.statusText ?? ""),
|
||||
raw: JSON.stringify(response.data),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
65
src/sms-orchestration/provider/parsian-template-messages.ts
Normal file
65
src/sms-orchestration/provider/parsian-template-messages.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { VerifyLookUpMessage } from "./sms-gateway.types";
|
||||
|
||||
const TOKEN_KEYS = ["token", "token2", "token3", "token10"] as const;
|
||||
|
||||
/** Kavenegar template name → plain-text body for Parsian (placeholders: {token}, {token2}, …). */
|
||||
const PARSIAN_TEMPLATE_BODIES: Record<string, string> = {
|
||||
"yara724-invite-link":
|
||||
"کاربر محترم، لینک ورود به پرونده یارا724:\nشناسه: {token}\n{token2}",
|
||||
"yara-field-expert-link":
|
||||
"کارشناس میدانی بیمه {token} ({token2}) پرونده شما را بررسی میکند.\nلینک: {token3}",
|
||||
"yara-blame-agreement":
|
||||
"پرونده تصادف شما ({token}) آماده امضاست.\nلینک: {token2}",
|
||||
"yara-claim-link":
|
||||
"پرونده خسارت شما ({token}) آماده ثبت است.\nلینک: {token2}",
|
||||
"yara-expert-lock":
|
||||
"کارشناس ({token3}) پرونده {token} ({token2}) را در حال بررسی دارد.",
|
||||
"yara-resend-documents":
|
||||
"لطفاً مدارک پرونده {token} ({token2}) را مجدداً ارسال کنید.\nلینک: {token3}",
|
||||
"yara-signature":
|
||||
"امضای پرونده {token} ({token2}) توسط کارشناس {token3} بررسی شد.\nلینک: {token10}",
|
||||
};
|
||||
|
||||
function applyTokens(
|
||||
pattern: string,
|
||||
data: Pick<
|
||||
VerifyLookUpMessage,
|
||||
"token" | "token2" | "token3" | "token10"
|
||||
>,
|
||||
): string {
|
||||
let out = pattern;
|
||||
for (const key of TOKEN_KEYS) {
|
||||
const value = data[key];
|
||||
if (value != null && value !== "") {
|
||||
out = out.split(`{${key}}`).join(value);
|
||||
}
|
||||
|
||||
}
|
||||
return out.replace(/\{token\d*\}/g, "").trim();
|
||||
}
|
||||
|
||||
export function buildParsianTemplateMessage(
|
||||
data: VerifyLookUpMessage,
|
||||
): string {
|
||||
const authTemplate = process.env.AUTH_SMS_TEMPLATE?.trim();
|
||||
if (authTemplate && data.template === authTemplate) {
|
||||
const authMessage =
|
||||
process.env.AUTH_SMS_MESSAGE?.trim() ??
|
||||
"کاربر محترم رمز یکبار مصرف شما:\nCode: {token}";
|
||||
return applyTokens(authMessage, data);
|
||||
}
|
||||
|
||||
const body = PARSIAN_TEMPLATE_BODIES[data.template];
|
||||
if (body) {
|
||||
return applyTokens(body, data);
|
||||
}
|
||||
|
||||
const parts = [
|
||||
`پیام سیستم (${data.template})`,
|
||||
data.token ? `کد: ${data.token}` : "",
|
||||
data.token2 ?? "",
|
||||
data.token3 ?? "",
|
||||
data.token10 ?? "",
|
||||
].filter(Boolean);
|
||||
return parts.join("\n");
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { KavenegarModule } from "@fraybabak/kavenegar_nest";
|
||||
import { Module } from "@nestjs/common";
|
||||
import * as dotenv from "dotenv";
|
||||
import { KavenegarSmsGateway } from "./kavenegar-sms.gateway";
|
||||
import { ParsianSmsGateway } from "./parsian-sms.gateway";
|
||||
import { SmsGatewayService } from "./sms-gateway.service";
|
||||
|
||||
dotenv.config();
|
||||
@@ -12,7 +14,11 @@ dotenv.config({ path: `.${process.env.NODE_ENV}.env` });
|
||||
apikey: process.env.SMS_API_KEY || "",
|
||||
}),
|
||||
],
|
||||
providers: [SmsGatewayService],
|
||||
providers: [
|
||||
KavenegarSmsGateway,
|
||||
ParsianSmsGateway,
|
||||
SmsGatewayService,
|
||||
],
|
||||
exports: [SmsGatewayService],
|
||||
})
|
||||
export class SmsGatewayModule {}
|
||||
|
||||
@@ -1,112 +1,33 @@
|
||||
import { KavenegarService } from "@fraybabak/kavenegar_nest";
|
||||
import { Injectable, Logger } from "@nestjs/common";
|
||||
import {
|
||||
isKavenegarSuccess,
|
||||
KavenegarNormalized,
|
||||
normalizeKavenegarBody,
|
||||
safeJsonStringify,
|
||||
unwrapKavenegarTransportError,
|
||||
} from "./kavenegar-response";
|
||||
import {
|
||||
SmsProviderException,
|
||||
SmsTransportException,
|
||||
} from "./sms-provider.exception";
|
||||
import { Injectable, Logger, OnModuleInit } from "@nestjs/common";
|
||||
import { KavenegarSmsGateway } from "./kavenegar-sms.gateway";
|
||||
import { ParsianSmsGateway } from "./parsian-sms.gateway";
|
||||
import { getSmsProvider } from "./sms-provider.config";
|
||||
import { SendMessage, VerifyLookUpMessage } from "./sms-gateway.types";
|
||||
|
||||
export interface SendMessage {
|
||||
message: string;
|
||||
receptor: string;
|
||||
}
|
||||
|
||||
export interface VerifyLookUpMessage {
|
||||
template: string;
|
||||
token: string;
|
||||
token2?: string;
|
||||
token3?: string;
|
||||
token10?: string;
|
||||
receptor: string;
|
||||
}
|
||||
export type { SendMessage, VerifyLookUpMessage } from "./sms-gateway.types";
|
||||
|
||||
@Injectable()
|
||||
export class SmsGatewayService {
|
||||
export class SmsGatewayService implements OnModuleInit {
|
||||
private readonly logger = new Logger(SmsGatewayService.name);
|
||||
|
||||
constructor(private readonly sender: KavenegarService) {}
|
||||
constructor(
|
||||
private readonly kavenegar: KavenegarSmsGateway,
|
||||
private readonly parsian: ParsianSmsGateway,
|
||||
) {}
|
||||
|
||||
onModuleInit() {
|
||||
this.logger.log(`SMS provider active: ${getSmsProvider()}`);
|
||||
}
|
||||
|
||||
async sendMessage(data: SendMessage) {
|
||||
try {
|
||||
const body = await this.sender.Send({
|
||||
sender: "10008663",
|
||||
...data,
|
||||
});
|
||||
if (!isKavenegarSuccess(body)) {
|
||||
const normalized = normalizeKavenegarBody(body);
|
||||
this.logProviderRejection("send", data.receptor, undefined, normalized);
|
||||
throw new SmsProviderException("send", {
|
||||
receptor: data.receptor,
|
||||
providerBody: body,
|
||||
normalized,
|
||||
});
|
||||
}
|
||||
this.logger.log(
|
||||
`Kavenegar Send ok receptor=${data.receptor} status=200`,
|
||||
);
|
||||
return body;
|
||||
} catch (e) {
|
||||
if (e instanceof SmsProviderException) throw e;
|
||||
const detail = safeJsonStringify(unwrapKavenegarTransportError(e));
|
||||
this.logger.error(
|
||||
`Kavenegar Send transport error receptor=${data.receptor} detail=${detail}`,
|
||||
);
|
||||
throw new SmsTransportException("send", { receptor: data.receptor }, e);
|
||||
}
|
||||
return this.activeGateway().sendMessage(data);
|
||||
}
|
||||
|
||||
async verifyLookUp(data: VerifyLookUpMessage) {
|
||||
try {
|
||||
console.log(data)
|
||||
const body = await this.sender.verifyLookup(data);
|
||||
if (!isKavenegarSuccess(body)) {
|
||||
const normalized = normalizeKavenegarBody(body);
|
||||
this.logProviderRejection(
|
||||
"verifyLookUp",
|
||||
data.receptor,
|
||||
data.template,
|
||||
normalized,
|
||||
);
|
||||
throw new SmsProviderException("verifyLookup", {
|
||||
receptor: data.receptor,
|
||||
template: data.template,
|
||||
providerBody: body,
|
||||
normalized,
|
||||
});
|
||||
}
|
||||
this.logger.log(
|
||||
`Kavenegar verifyLookUp ok receptor=${data.receptor} template=${data.template} status=200`,
|
||||
);
|
||||
return body;
|
||||
} catch (e) {
|
||||
if (e instanceof SmsProviderException) throw e;
|
||||
const detail = safeJsonStringify(unwrapKavenegarTransportError(e));
|
||||
this.logger.error(
|
||||
`Kavenegar verifyLookUp transport error receptor=${data.receptor} template=${data.template} detail=${detail}`,
|
||||
);
|
||||
throw new SmsTransportException(
|
||||
"verifyLookup",
|
||||
{ receptor: data.receptor, template: data.template },
|
||||
e,
|
||||
);
|
||||
}
|
||||
return this.activeGateway().verifyLookUp(data);
|
||||
}
|
||||
|
||||
private logProviderRejection(
|
||||
op: "send" | "verifyLookUp",
|
||||
receptor: string,
|
||||
template: string | undefined,
|
||||
normalized: KavenegarNormalized,
|
||||
) {
|
||||
const t = template != null && template !== "" ? ` template=${template}` : "";
|
||||
this.logger.warn(
|
||||
`Kavenegar ${op} rejected receptor=${receptor}${t} providerStatus=${normalized.httpLikeStatus ?? "unknown"} providerMessage=${normalized.message ?? "n/a"} body=${normalized.raw}`,
|
||||
);
|
||||
private activeGateway(): KavenegarSmsGateway | ParsianSmsGateway {
|
||||
return getSmsProvider() === "parsian" ? this.parsian : this.kavenegar;
|
||||
}
|
||||
}
|
||||
|
||||
13
src/sms-orchestration/provider/sms-gateway.types.ts
Normal file
13
src/sms-orchestration/provider/sms-gateway.types.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export interface SendMessage {
|
||||
message: string;
|
||||
receptor: string;
|
||||
}
|
||||
|
||||
export interface VerifyLookUpMessage {
|
||||
template: string;
|
||||
token: string;
|
||||
token2?: string;
|
||||
token3?: string;
|
||||
token10?: string;
|
||||
receptor: string;
|
||||
}
|
||||
18
src/sms-orchestration/provider/sms-provider.config.ts
Normal file
18
src/sms-orchestration/provider/sms-provider.config.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export type SmsProviderId = "kavenegar" | "parsian";
|
||||
|
||||
/** Reads `SMS` (e.g. `parsian`) or `SMS_PROVIDER`; defaults to kavenegar. */
|
||||
export function getSmsProvider(): SmsProviderId {
|
||||
const raw = (
|
||||
process.env.SMS ??
|
||||
process.env.SMS_PROVIDER ??
|
||||
"kavenegar"
|
||||
)
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
if (raw === "parsian") return "parsian";
|
||||
return "kavenegar";
|
||||
}
|
||||
|
||||
export function isParsianSmsProvider(): boolean {
|
||||
return getSmsProvider() === "parsian";
|
||||
}
|
||||
Reference in New Issue
Block a user