forked from Yara724/api
138 lines
4.0 KiB
TypeScript
138 lines
4.0 KiB
TypeScript
import { Injectable, Logger } from "@nestjs/common";
|
|
import { InjectModel } from "@nestjs/mongoose";
|
|
import { isAxiosError } from "axios";
|
|
import { randomBytes } from "node:crypto";
|
|
import { Model, Types } from "mongoose";
|
|
import {
|
|
FanavaranAuditLog,
|
|
FanavaranAuditLogDocument,
|
|
FanavaranAuditStatus,
|
|
FanavaranAuditStep,
|
|
} from "./schema/fanavaran-audit-log.schema";
|
|
import type { FanavaranAuditSession } from "./fanavaran-audit.types";
|
|
|
|
export interface RecordFanavaranAuditStepInput {
|
|
session: FanavaranAuditSession;
|
|
step: FanavaranAuditStep;
|
|
status: FanavaranAuditStatus;
|
|
requestUrl?: string;
|
|
httpStatus?: number;
|
|
requestMeta?: Record<string, unknown>;
|
|
responseMeta?: Record<string, unknown>;
|
|
errorMessage?: string;
|
|
errorDetails?: Record<string, unknown>;
|
|
durationMs?: number;
|
|
}
|
|
|
|
@Injectable()
|
|
export class FanavaranAuditService {
|
|
private readonly logger = new Logger(FanavaranAuditService.name);
|
|
|
|
constructor(
|
|
@InjectModel(FanavaranAuditLog.name)
|
|
private readonly auditModel: Model<FanavaranAuditLogDocument>,
|
|
) {}
|
|
|
|
generateTrackingCode(): string {
|
|
const date = new Date().toISOString().slice(0, 10).replace(/-/g, "");
|
|
const suffix = randomBytes(3).toString("hex").toUpperCase();
|
|
return `FNV-${date}-${suffix}`;
|
|
}
|
|
|
|
maskNationalCode(nationalCode: string): string {
|
|
const trimmed = nationalCode.trim();
|
|
if (trimmed.length <= 4) {
|
|
return "****";
|
|
}
|
|
return `${trimmed.slice(0, 3)}****${trimmed.slice(-2)}`;
|
|
}
|
|
|
|
sanitizeErrorDetails(error: unknown): Record<string, unknown> {
|
|
if (isAxiosError(error)) {
|
|
const data = error.response?.data;
|
|
return {
|
|
type: "axios",
|
|
status: error.response?.status,
|
|
statusText: error.response?.statusText,
|
|
data:
|
|
typeof data === "object" && data !== null
|
|
? data
|
|
: typeof data === "string"
|
|
? data.slice(0, 2000)
|
|
: data,
|
|
};
|
|
}
|
|
if (error instanceof Error) {
|
|
return { type: "error", name: error.name, message: error.message };
|
|
}
|
|
return { type: "unknown", value: String(error) };
|
|
}
|
|
|
|
extractErrorMessage(error: unknown): string {
|
|
if (isAxiosError(error)) {
|
|
const data = error.response?.data as
|
|
| { Message?: string; message?: string }
|
|
| string
|
|
| undefined;
|
|
if (typeof data === "string") {
|
|
return data;
|
|
}
|
|
return (
|
|
data?.Message ||
|
|
data?.message ||
|
|
error.message ||
|
|
"Fanavaran request failed"
|
|
);
|
|
}
|
|
if (error instanceof Error) {
|
|
return error.message;
|
|
}
|
|
return "Fanavaran request failed";
|
|
}
|
|
|
|
formatErrorWithTrackingCode(message: string, trackingCode?: string): string {
|
|
if (!trackingCode) {
|
|
return message;
|
|
}
|
|
return `${message} (trackingCode: ${trackingCode})`;
|
|
}
|
|
|
|
async recordStep(input: RecordFanavaranAuditStepInput): Promise<void> {
|
|
try {
|
|
await this.auditModel.create({
|
|
trackingCode: input.session.trackingCode,
|
|
step: input.step,
|
|
status: input.status,
|
|
clientKey: input.session.clientKey,
|
|
source: input.session.source,
|
|
...(input.session.claimCaseId
|
|
? { claimCaseId: new Types.ObjectId(input.session.claimCaseId) }
|
|
: {}),
|
|
...(input.session.claimRequestId
|
|
? { claimRequestId: new Types.ObjectId(input.session.claimRequestId) }
|
|
: {}),
|
|
requestUrl: input.requestUrl,
|
|
httpStatus: input.httpStatus,
|
|
requestMeta: input.requestMeta,
|
|
responseMeta: input.responseMeta,
|
|
errorMessage: input.errorMessage,
|
|
errorDetails: input.errorDetails,
|
|
durationMs: input.durationMs,
|
|
});
|
|
} catch (error) {
|
|
this.logger.error(
|
|
`Failed to persist Fanavaran audit step ${input.step} (${input.status})`,
|
|
error,
|
|
);
|
|
}
|
|
}
|
|
|
|
async findByTrackingCode(trackingCode: string) {
|
|
return this.auditModel
|
|
.find({ trackingCode })
|
|
.sort({ createdAt: 1 })
|
|
.lean()
|
|
.exec();
|
|
}
|
|
}
|