diff --git a/README.md b/README.md index 6efb3e7..cb6b1a2 100644 --- a/README.md +++ b/README.md @@ -1 +1,15 @@ # api + +Current JWT payload: +```json +{ + "username": "saman_insurer@gmail.com", + "sub": "6a144979799f3c63aa63f67c", + "fullName": "بیمه گر سامان", + "role": "company", + "userType": "UserType", + "clientKey": "67f0fd0e53868dc1ff8a2738", + "iat": 1781339791, + "exp": 1781343391 +} +``` diff --git a/src/claim-request-management/claim-request-management.service.ts b/src/claim-request-management/claim-request-management.service.ts index a6ff6f8..28a77d0 100644 --- a/src/claim-request-management/claim-request-management.service.ts +++ b/src/claim-request-management/claim-request-management.service.ts @@ -15,7 +15,7 @@ import { isAxiosError } from "axios"; import { unlink } from "node:fs/promises"; import { existsSync } from "node:fs"; import { AiService } from "src/ai/ai.service"; -import { buildFileLink } from "src/helpers/urlCreator"; +import { buildFileLink, resolveStoredFileUrl } from "src/helpers/urlCreator"; import { RequestManagementDbService } from "src/request-management/entities/db-service/request-management.db.service"; import { ReqBlameStatus } from "src/Types&Enums/blame-request-management/status.enum"; import { ReqClaimStatus } from "src/Types&Enums/claim-request-management/status.enum"; @@ -6644,7 +6644,7 @@ export class ClaimRequestManagementService { ) as { url?: string; path?: string } | undefined; carAngles[k] = { captured: !!cap, - url: cap?.url || (cap?.path ? buildFileLink(cap.path) : undefined), + url: resolveStoredFileUrl(cap), }; } diff --git a/src/core/config/config.schema.ts b/src/core/config/config.schema.ts index 60caa61..1e6753a 100644 --- a/src/core/config/config.schema.ts +++ b/src/core/config/config.schema.ts @@ -22,7 +22,9 @@ export class EnvironmentVariables { message: "NODE_ENV should be development or production", }) NODE_ENV: Environment; + // --------------------------------------------------------- // + @IsNumber( { allowNaN: false, @@ -36,7 +38,9 @@ export class EnvironmentVariables { @Min(0, { message: "PORT must be between 0 and 65535" }) @Max(65535, { message: "PORT must be between 0 and 65535" }) PORT: number; + // --------------------------------------------------------- // + @IsString({ message: "MONGO_URI must be string" }) @IsUrl( { @@ -48,23 +52,32 @@ export class EnvironmentVariables { }, ) MONGO_URI: string; + // --------------------------------------------------------- // + @IsBooleanString({ message: "MONGO_TLS must be boolean" }) MONGO_TLS: string; + // --------------------------------------------------------- // + @IsBooleanString({ message: "MONGO_TLS_ALLOW_INVALID_CERTS must be boolean" }) MONGO_TLS_ALLOW_INVALID_CERTS: string; + // --------------------------------------------------------- // + @IsString({ message: "JWT_SECRET must be string" }) @MinLength(32, { message: "JWT_SECRET must be at least 32 characters", }) JWT_SECRET: string; + // --------------------------------------------------------- // + @Matches(/^\d+(ms|s|m|h|d|w|y)$/, { message: "JWT_EXPIRY must be in the correct format", }) JWT_EXPIRY: StringValue; + // --------------------------------------------------------- // // @IsString({ message: "HASH_PEPPER must be string" }) // @MinLength(32, { diff --git a/src/core/database/plugins/iran-timestamp.plugin.ts b/src/core/database/plugins/iran-timestamp.plugin.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/expert-claim/dto/claim-damaged-part.enricher.ts b/src/expert-claim/dto/claim-damaged-part.enricher.ts index 6e47ce2..1177ad4 100644 --- a/src/expert-claim/dto/claim-damaged-part.enricher.ts +++ b/src/expert-claim/dto/claim-damaged-part.enricher.ts @@ -1,5 +1,6 @@ // claim-damaged-part.enricher.ts +import { resolveStoredFileUrl } from "src/helpers/urlCreator"; import { DamagedPartCaptureStatus, EnrichedDamagedPart, @@ -14,6 +15,9 @@ export function buildEnrichedDamagedParts(params: { hasDamagedPartCapture: (data: any, key: string, parts: any[]) => boolean; catalogLikeKeyFromPart: (part: any) => string; buildFileLink: (path: string) => string; + resolveStoredFileUrl?: ( + stored?: { url?: string; path?: string } | null, + ) => string | undefined; }): EnrichedDamagedPart[] { const { selectedParts, @@ -24,6 +28,7 @@ export function buildEnrichedDamagedParts(params: { hasDamagedPartCapture, catalogLikeKeyFromPart, buildFileLink, + resolveStoredFileUrl: resolveUrl = resolveStoredFileUrl, } = params; const priceDropLines: any[] = evaluationBlock?.priceDrop?.partLines ?? []; @@ -109,17 +114,13 @@ export function buildEnrichedDamagedParts(params: { : false); const capUrl = - cap?.url || - (cap?.path ? buildFileLink(cap.path) : undefined) || + resolveUrl(cap) || (sp.id == null && Array.isArray(damagedPartsData) ? (() => { const match = (damagedPartsData as any[]).find( (d) => d.name === sp.name && d.side === sp.side && d.path, ); - return ( - match?.url || - (match?.path ? buildFileLink(match.path) : undefined) - ); + return resolveUrl(match); })() : undefined); diff --git a/src/expert-claim/expert-claim.service.ts b/src/expert-claim/expert-claim.service.ts index 87aaf9f..6fa6dd7 100644 --- a/src/expert-claim/expert-claim.service.ts +++ b/src/expert-claim/expert-claim.service.ts @@ -20,7 +20,7 @@ import { ClaimSignDbService } from "src/claim-request-management/entites/db-serv import { DamageImageDbService } from "src/claim-request-management/entites/db-service/damage-image.db.service"; import { VideoCaptureDbService } from "src/claim-request-management/entites/db-service/video-capture.db.service"; import { ClientDbService } from "src/client/entities/db-service/client.db.service"; -import { buildFileLink } from "src/helpers/urlCreator"; +import { buildFileLink, resolveStoredFileUrl } from "src/helpers/urlCreator"; import { toJalaliDateAndTime } from "src/helpers/date-jalali"; import { BlameDocumentDbService } from "src/request-management/entities/db-service/blame-document.db.service"; import { BlameVideoDbService } from "src/request-management/entities/db-service/blame-video.db.service"; @@ -3941,7 +3941,7 @@ export class ExpertClaimService { ) as { url?: string; path?: string } | undefined; carAngles[k] = { captured: !!cap, - url: cap?.url || (cap?.path ? buildFileLink(cap.path) : undefined), + url: resolveStoredFileUrl(cap), }; } @@ -3965,6 +3965,7 @@ export class ExpertClaimService { hasDamagedPartCapture, catalogLikeKeyFromPart, buildFileLink, + resolveStoredFileUrl, }); // Vehicle payload — fall back to blame inquiry if claim vehicle is sparse diff --git a/src/expert-insurer/expert-insurer.service.ts b/src/expert-insurer/expert-insurer.service.ts index a3c3315..016386c 100644 --- a/src/expert-insurer/expert-insurer.service.ts +++ b/src/expert-insurer/expert-insurer.service.ts @@ -33,7 +33,7 @@ import { BlameVideoDbService } from "src/request-management/entities/db-service/ import { BlameVoiceDbService } from "src/request-management/entities/db-service/blame.voice.db.service"; import { VideoCaptureDbService } from "src/claim-request-management/entites/db-service/video-capture.db.service"; import { ClientDbService } from "src/client/entities/db-service/client.db.service"; -import { buildFileLink } from "src/helpers/urlCreator"; +import { buildFileLink, resolveStoredFileUrl } from "src/helpers/urlCreator"; import { toJalaliDateAndTime } from "src/helpers/date-jalali"; import { enrichBlamePartiesForAgreementView } from "src/helpers/blame-party-agreement-decision"; import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum"; @@ -422,7 +422,7 @@ export class ExpertInsurerService { ck, selectedNormExpert, ) as { url?: string; path?: string; fileName?: string } | undefined; - const url = cap?.url || (cap?.path ? buildFileLink(cap.path) : undefined); + const url = resolveStoredFileUrl(cap); const link = url; return { index, @@ -633,7 +633,7 @@ export class ExpertInsurerService { ) as { url?: string; path?: string } | undefined; carAngles[k] = { captured: !!cap, - url: cap?.url || (cap?.path ? buildFileLink(cap.path) : undefined), + url: resolveStoredFileUrl(cap), }; } diff --git a/src/helpers/claim-expert-resend.ts b/src/helpers/claim-expert-resend.ts index 78373e0..4511ac4 100644 --- a/src/helpers/claim-expert-resend.ts +++ b/src/helpers/claim-expert-resend.ts @@ -9,6 +9,7 @@ import { } from "./outer-damage-parts"; import { ClaimVehicleTypeV2 } from "src/static/outer-car-parts-catalog"; import { canonicalizeResendDocumentKey } from "./claim-resend-document-keys"; +import { resolveStoredFileUrl } from "./urlCreator"; export type ExpertResendCarPartV2 = { id?: number | null; @@ -161,8 +162,7 @@ export function mapExpertResendCarPartsForClient( const capturedFromStore = typeof stored.captured === "boolean" ? stored.captured : undefined; const url = - cap?.url || - (cap?.path && options.buildUrl ? options.buildUrl(cap.path) : undefined); + resolveStoredFileUrl(cap); return { id: sp.id, diff --git a/src/helpers/urlCreator.ts b/src/helpers/urlCreator.ts index 510e04e..5d00ae7 100644 --- a/src/helpers/urlCreator.ts +++ b/src/helpers/urlCreator.ts @@ -1,9 +1,38 @@ +type StoredFileCapture = { + url?: string; + path?: string; +}; + +/** + * Build a public file URL from a relative storage path (e.g. `files/...`). + * Uses the current `URL` env so deployments with custom routing stay correct. + */ export function buildFileLink(path: string): string { - const baseUrl = process.env.URL; - if(process.env.NODE_ENV === 'local'){ - return baseUrl + "/" + path; - }else{ - return baseUrl + "/api/" + path; + const normalized = String(path ?? "").trim(); + if (!normalized) return normalized; + if (/^https?:\/\//i.test(normalized)) { + return normalized; } + const baseUrl = (process.env.URL ?? "").replace(/\/+$/, ""); + const relativePath = normalized.replace(/^\/+/, ""); + if (process.env.NODE_ENV === "local") { + return `${baseUrl}/${relativePath}`; + } + return `${baseUrl}/api/${relativePath}`; +} + +/** + * Resolve a file URL from persisted capture metadata. + * Prefer rebuilding from `path` so stale baked-in URLs (e.g. user-portal prefix) + * do not leak into expert/other API responses. + */ +export function resolveStoredFileUrl( + stored?: StoredFileCapture | null, +): string | undefined { + const path = stored?.path?.trim(); + if (path) { + return buildFileLink(path); + } + return stored?.url; }