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

Reviewed-on: Yara724/api#90
This commit is contained in:
2026-05-30 15:02:35 +03:30
10 changed files with 71 additions and 10 deletions

View File

@@ -84,8 +84,10 @@ import * as Joi from "joi";
useFactory: (configService: ConfigService) => { useFactory: (configService: ConfigService) => {
return { return {
uri: `mongodb://${configService.get<string>("MONGO_USER")}:${configService.get<string>("MONGO_PASS")}@${configService.get<string>("MONGO_HOST")}:${configService.get<string>("MONGO_PORT")}/${configService.get<string>("MONGO_DB_NAME")}?authSource=admin&${configService.get<string>("MONGO_OPTIONS")}`, uri: `mongodb://${configService.get<string>("MONGO_USER")}:${configService.get<string>("MONGO_PASS")}@${configService.get<string>("MONGO_HOST")}:${configService.get<string>("MONGO_PORT")}/${configService.get<string>("MONGO_DB_NAME")}?authSource=admin&${configService.get<string>("MONGO_OPTIONS")}`,
tls: true, tls: configService.get<string>("MONGO_TLS") === "true",
tlsAllowInvalidCertificates: true, tlsAllowInvalidCertificates:
configService.get<string>("MONGO_TLS_ALLOW_INVALID_CERTS") ===
"true",
autoIndex: configService.get<string>("NODE_ENV") !== "production", autoIndex: configService.get<string>("NODE_ENV") !== "production",
connectionFactory: (connection) => { connectionFactory: (connection) => {
applyIranFaTimestampPlugin(connection); applyIranFaTimestampPlugin(connection);

View File

@@ -30,6 +30,7 @@ export class UserAuthController {
linkToken: body.linkToken, linkToken: body.linkToken,
linkContext: body.linkContext, linkContext: body.linkContext,
}); });
if (res) { if (res) {
throw new HttpException(res, HttpStatus.ACCEPTED); throw new HttpException(res, HttpStatus.ACCEPTED);
} }

View File

@@ -34,9 +34,9 @@ export function userAuthErrorBody(code: UserAuthErrorCode) {
} }
export function throwUserAuthError(code: UserAuthErrorCode): never { export function throwUserAuthError(code: UserAuthErrorCode): never {
if (code === UserAuthErrorCode.OTP_REQUEST_TOO_SOON) { // if (code === UserAuthErrorCode.OTP_REQUEST_TOO_SOON) {
throw new BadRequestException(userAuthErrorBody(code)); // throw new BadRequestException(userAuthErrorBody(code));
} // }
if (code === UserAuthErrorCode.LINK_MOBILE_MISMATCH) { if (code === UserAuthErrorCode.LINK_MOBILE_MISMATCH) {
throw new ForbiddenException(userAuthErrorBody(code)); throw new ForbiddenException(userAuthErrorBody(code));

View File

@@ -139,7 +139,8 @@ export class UserAuthService {
} }
if (isOtpExpiryActive(userExist.otpExpire, nowMs)) { if (isOtpExpiryActive(userExist.otpExpire, nowMs)) {
throwUserAuthError(UserAuthErrorCode.OTP_REQUEST_TOO_SOON); // throwUserAuthError(UserAuthErrorCode.OTP_REQUEST_TOO_SOON);
return new LoginDtoRs(userExist, "OTP Still valid");
} }
await this.smsSender(otp, canonicalMobile); await this.smsSender(otp, canonicalMobile);

View File

@@ -35,7 +35,9 @@ export class UserLoginDto {
} }
export class LoginDtoRs extends UserModel { export class LoginDtoRs extends UserModel {
@ApiProperty({ type: "string" })
message: string; message: string;
@ApiProperty({ @ApiProperty({
example: "09226187419", example: "09226187419",
type: "string", type: "string",
@@ -61,8 +63,10 @@ export class LoginDtoRs extends UserModel {
username: string; username: string;
mobile: string; mobile: string;
nationalCode: string; nationalCode: string;
constructor(loginData) {
constructor(loginData, message: string = "") {
super(); super();
this.mobile = loginData.mobile; this.mobile = loginData.mobile;
this.message = message;
} }
} }

View File

@@ -8,9 +8,11 @@ import {
CaptchaChallenge, CaptchaChallenge,
CaptchaChallengeSchema, CaptchaChallengeSchema,
} from "./entities/schema/captcha-challenge.schema"; } from "./entities/schema/captcha-challenge.schema";
import { ConfigModule } from "@nestjs/config";
@Module({ @Module({
imports: [ imports: [
ConfigModule,
HashModule, HashModule,
MongooseModule.forFeature([ MongooseModule.forFeature([
{ name: CaptchaChallenge.name, schema: CaptchaChallengeSchema }, { name: CaptchaChallenge.name, schema: CaptchaChallengeSchema },

View File

@@ -1,14 +1,17 @@
import { Injectable } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import * as svgCaptcha from "svg-captcha"; import * as svgCaptcha from "svg-captcha";
export interface GeneratedCaptcha { export interface GeneratedCaptcha {
text: string; text?: string;
image: string; image: string;
expiresAt: number; expiresAt: number;
} }
@Injectable() @Injectable()
export class CaptchaService { export class CaptchaService {
constructor(private readonly configService: ConfigService) {}
generate(): GeneratedCaptcha { generate(): GeneratedCaptcha {
const captcha = svgCaptcha.create({ const captcha = svgCaptcha.create({
size: 5, size: 5,
@@ -21,6 +24,13 @@ export class CaptchaService {
fontSize: 52, fontSize: 52,
}); });
if (this.configService.get<string>("NODE_ENV") === "production") {
return {
image: `data:image/svg+xml;base64,${Buffer.from(captcha.data, "utf8").toString("base64")}`,
expiresAt: this.buildExpireAt(),
};
}
return { return {
text: captcha.text, text: captcha.text,
image: `data:image/svg+xml;base64,${Buffer.from(captcha.data, "utf8").toString("base64")}`, image: `data:image/svg+xml;base64,${Buffer.from(captcha.data, "utf8").toString("base64")}`,

View File

@@ -3850,7 +3850,7 @@ export class ExpertClaimService {
}; };
} }
// Build damaged parts list (aligned with normalized selected parts) // Build damaged parts list (aligned with normalized selected parts and enriched with evaluation context)
const carTypeExpert = claim.vehicle?.carType as const carTypeExpert = claim.vehicle?.carType as
| ClaimVehicleTypeV2 | ClaimVehicleTypeV2
| undefined; | undefined;
@@ -3860,6 +3860,13 @@ export class ExpertClaimService {
(claim.damage as any)?.selectedOuterParts, (claim.damage as any)?.selectedOuterParts,
); );
const damagedPartsDataExpert = claim.media?.damagedParts as any; const damagedPartsDataExpert = claim.media?.damagedParts as any;
// Extract optimization lookups from the DB document safely
const evaluationBlock = (claim as any).evaluation;
const priceDropLines = evaluationBlock?.priceDrop?.partLines || [];
const objectionParts = evaluationBlock?.objection?.objectionParts || [];
const newObjectionParts = evaluationBlock?.objection?.newParts || [];
const damagedParts = selectedNormExpert.map((sp, index) => { const damagedParts = selectedNormExpert.map((sp, index) => {
const ck = sp.catalogKey ?? catalogLikeKeyFromPart(sp); const ck = sp.catalogKey ?? catalogLikeKeyFromPart(sp);
const cap = getDamagedPartCaptureBlob( const cap = getDamagedPartCaptureBlob(
@@ -3867,6 +3874,22 @@ export class ExpertClaimService {
ck, ck,
selectedNormExpert, selectedNormExpert,
) as { url?: string; path?: string } | undefined; ) as { url?: string; path?: string } | undefined;
// 1. Cross-reference Price Drop items via partId or name match
const matchingPriceDrop = priceDropLines.find(
(line: any) =>
line.partId === sp.id ||
line.priceDropPartKey?.toLowerCase() === sp.name?.toLowerCase(),
);
// 2. Evaluate if this specific part is flag-linked within an active user objection
const isObjected = objectionParts.some(
(objPart: any) => objPart.id === sp.id || objPart.name === sp.name,
);
const isNewAddedPart = newObjectionParts.some(
(newPart: any) => newPart.id === sp.id || newPart.name === sp.name,
);
return { return {
index, index,
partId: catalogPartIdFromSelectedPart(sp), partId: catalogPartIdFromSelectedPart(sp),
@@ -3880,6 +3903,15 @@ export class ExpertClaimService {
selectedNormExpert, selectedNormExpert,
), ),
url: cap?.url || (cap?.path ? buildFileLink(cap.path) : undefined), url: cap?.url || (cap?.path ? buildFileLink(cap.path) : undefined),
// --- Added Consolidated Production Enrichments ---
severity: matchingPriceDrop ? matchingPriceDrop.severity : null,
coefficient: matchingPriceDrop ? matchingPriceDrop.coefficient : null,
objectionStatus: {
hasObjection: isObjected,
isNewlyAddedByObjection: isNewAddedPart,
// You can map extra parameters here if the front-end requires reason texts
},
}; };
}); });

View File

@@ -1068,10 +1068,19 @@ export class RequestManagementService {
const companyCode = inquiryMapped?.CompanyCode; const companyCode = inquiryMapped?.CompanyCode;
const client = const client =
await this.clientService.findClientWithCompanyCode(+companyCode); await this.clientService.findClientWithCompanyCode(+companyCode);
if (!client) { if (!client) {
throw new HttpException("Client not found", HttpStatus.CONFLICT); await this.clientService.addClient({
clientName,
clientCode: companyCode,
useExpertMode: "legal",
});
} }
// if (!client) {
// throw new HttpException("Client not found", HttpStatus.CONFLICT);
// }
// Persist inquiry/body data into new model // Persist inquiry/body data into new model
if (!party.person) party.person = {} as any; if (!party.person) party.person = {} as any;
const resolvedClientId = (client as any)?._id ?? (client as any)?._doc?._id; const resolvedClientId = (client as any)?._id ?? (client as any)?._doc?._id;