forked from Yara724/api
audit fanavaran logs and missing fields null fixed
This commit is contained in:
18
src/fanavaran/fanavaran-audit.module.ts
Normal file
18
src/fanavaran/fanavaran-audit.module.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { MongooseModule } from "@nestjs/mongoose";
|
||||
import { FanavaranAuditService } from "./fanavaran-audit.service";
|
||||
import {
|
||||
FanavaranAuditLog,
|
||||
FanavaranAuditLogSchema,
|
||||
} from "./schema/fanavaran-audit-log.schema";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
MongooseModule.forFeature([
|
||||
{ name: FanavaranAuditLog.name, schema: FanavaranAuditLogSchema },
|
||||
]),
|
||||
],
|
||||
providers: [FanavaranAuditService],
|
||||
exports: [FanavaranAuditService],
|
||||
})
|
||||
export class FanavaranAuditModule {}
|
||||
137
src/fanavaran/fanavaran-audit.service.ts
Normal file
137
src/fanavaran/fanavaran-audit.service.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
10
src/fanavaran/fanavaran-audit.types.ts
Normal file
10
src/fanavaran/fanavaran-audit.types.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { FanavaranClientKey } from "src/core/config/fanavaran-client.config";
|
||||
import { FanavaranAuditSource } from "./schema/fanavaran-audit-log.schema";
|
||||
|
||||
export interface FanavaranAuditSession {
|
||||
trackingCode: string;
|
||||
clientKey: FanavaranClientKey;
|
||||
source: FanavaranAuditSource;
|
||||
claimCaseId?: string;
|
||||
claimRequestId?: string;
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { ClaimRequestManagementModule } from "src/claim-request-management/claim-request-management.module";
|
||||
import { FanavaranAuditModule } from "./fanavaran-audit.module";
|
||||
import { FanavaranController } from "./fanavaran.controller";
|
||||
|
||||
@Module({
|
||||
imports: [ClaimRequestManagementModule],
|
||||
imports: [ClaimRequestManagementModule, FanavaranAuditModule],
|
||||
controllers: [FanavaranController],
|
||||
})
|
||||
export class FanavaranModule {}
|
||||
|
||||
75
src/fanavaran/schema/fanavaran-audit-log.schema.ts
Normal file
75
src/fanavaran/schema/fanavaran-audit-log.schema.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||
import { HydratedDocument, Types } from "mongoose";
|
||||
import type { FanavaranClientKey } from "src/core/config/fanavaran-client.config";
|
||||
|
||||
export enum FanavaranAuditStep {
|
||||
GET_APP_TOKEN = "GET_APP_TOKEN",
|
||||
LOGIN = "LOGIN",
|
||||
POLICY_INQUIRY = "POLICY_INQUIRY",
|
||||
BUILD_PAYLOAD = "BUILD_PAYLOAD",
|
||||
SUBMIT_CLAIM = "SUBMIT_CLAIM",
|
||||
}
|
||||
|
||||
export enum FanavaranAuditStatus {
|
||||
STARTED = "started",
|
||||
SUCCESS = "success",
|
||||
FAILURE = "failure",
|
||||
}
|
||||
|
||||
export enum FanavaranAuditSource {
|
||||
PREVIEW = "preview",
|
||||
SUBMIT = "submit",
|
||||
AUTO_SUBMIT = "auto_submit",
|
||||
LEGACY_SUBMIT = "legacy_submit",
|
||||
}
|
||||
|
||||
@Schema({ collection: "fanavaranAuditLogs", timestamps: true })
|
||||
export class FanavaranAuditLog {
|
||||
@Prop({ type: String, required: true, index: true })
|
||||
trackingCode: string;
|
||||
|
||||
@Prop({ type: String, required: true, enum: FanavaranAuditStep, index: true })
|
||||
step: FanavaranAuditStep;
|
||||
|
||||
@Prop({ type: String, required: true, enum: FanavaranAuditStatus, index: true })
|
||||
status: FanavaranAuditStatus;
|
||||
|
||||
@Prop({ type: String, required: true, index: true })
|
||||
clientKey: FanavaranClientKey;
|
||||
|
||||
@Prop({ type: String, required: true, enum: FanavaranAuditSource, index: true })
|
||||
source: FanavaranAuditSource;
|
||||
|
||||
@Prop({ type: Types.ObjectId, required: false, index: true })
|
||||
claimCaseId?: Types.ObjectId;
|
||||
|
||||
@Prop({ type: Types.ObjectId, required: false, index: true })
|
||||
claimRequestId?: Types.ObjectId;
|
||||
|
||||
@Prop({ type: String, required: false })
|
||||
requestUrl?: string;
|
||||
|
||||
@Prop({ type: Number, required: false })
|
||||
httpStatus?: number;
|
||||
|
||||
@Prop({ type: Object, required: false })
|
||||
requestMeta?: Record<string, unknown>;
|
||||
|
||||
@Prop({ type: Object, required: false })
|
||||
responseMeta?: Record<string, unknown>;
|
||||
|
||||
@Prop({ type: String, required: false })
|
||||
errorMessage?: string;
|
||||
|
||||
@Prop({ type: Object, required: false })
|
||||
errorDetails?: Record<string, unknown>;
|
||||
|
||||
@Prop({ type: Number, required: false })
|
||||
durationMs?: number;
|
||||
}
|
||||
|
||||
export type FanavaranAuditLogDocument = HydratedDocument<FanavaranAuditLog>;
|
||||
export const FanavaranAuditLogSchema =
|
||||
SchemaFactory.createForClass(FanavaranAuditLog);
|
||||
|
||||
FanavaranAuditLogSchema.index({ trackingCode: 1, createdAt: 1 });
|
||||
Reference in New Issue
Block a user