120 lines
3.7 KiB
TypeScript
120 lines
3.7 KiB
TypeScript
|
|
import { Injectable, Inject } from "@nestjs/common";
|
||
|
|
import { Logger } from "nestjs-pino";
|
||
|
|
import { ConfigService } from "@nestjs/config";
|
||
|
|
import { EmailService } from "@bff/infra/email/email.service";
|
||
|
|
import { getErrorMessage } from "@bff/core/utils/error.util";
|
||
|
|
import type { SimNotificationContext } from "../interfaces/sim-base.interface";
|
||
|
|
|
||
|
|
@Injectable()
|
||
|
|
export class SimNotificationService {
|
||
|
|
constructor(
|
||
|
|
@Inject(Logger) private readonly logger: Logger,
|
||
|
|
private readonly email: EmailService,
|
||
|
|
private readonly configService: ConfigService
|
||
|
|
) {}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Send notification for SIM actions
|
||
|
|
*/
|
||
|
|
async notifySimAction(
|
||
|
|
action: string,
|
||
|
|
status: "SUCCESS" | "ERROR",
|
||
|
|
context: SimNotificationContext
|
||
|
|
): Promise<void> {
|
||
|
|
const subject = `[SIM ACTION] ${action} - ${status}`;
|
||
|
|
const toAddress = this.configService.get<string>("SIM_ALERT_EMAIL_TO");
|
||
|
|
const fromAddress = this.configService.get<string>("SIM_ALERT_EMAIL_FROM");
|
||
|
|
|
||
|
|
if (!toAddress || !fromAddress) {
|
||
|
|
this.logger.debug("SIM action notification skipped: email config missing", {
|
||
|
|
action,
|
||
|
|
status,
|
||
|
|
});
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const publicContext = this.redactSensitiveFields(context);
|
||
|
|
|
||
|
|
try {
|
||
|
|
const lines: string[] = [
|
||
|
|
`Action: ${action}`,
|
||
|
|
`Result: ${status}`,
|
||
|
|
`Timestamp: ${new Date().toISOString()}`,
|
||
|
|
"",
|
||
|
|
"Context:",
|
||
|
|
JSON.stringify(publicContext, null, 2),
|
||
|
|
];
|
||
|
|
await this.email.sendEmail({
|
||
|
|
to: toAddress,
|
||
|
|
from: fromAddress,
|
||
|
|
subject,
|
||
|
|
text: lines.join("\n"),
|
||
|
|
});
|
||
|
|
} catch (err) {
|
||
|
|
this.logger.warn("Failed to send SIM action notification email", {
|
||
|
|
action,
|
||
|
|
status,
|
||
|
|
error: getErrorMessage(err),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Redact sensitive information from notification context
|
||
|
|
*/
|
||
|
|
private redactSensitiveFields(context: Record<string, unknown>): Record<string, unknown> {
|
||
|
|
const sanitized: Record<string, unknown> = {};
|
||
|
|
for (const [key, value] of Object.entries(context)) {
|
||
|
|
if (typeof key === "string" && key.toLowerCase().includes("password")) {
|
||
|
|
sanitized[key] = "[REDACTED]";
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (typeof value === "string" && value.length > 200) {
|
||
|
|
sanitized[key] = `${value.substring(0, 200)}…`;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
sanitized[key] = value;
|
||
|
|
}
|
||
|
|
return sanitized;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Convert technical errors to user-friendly messages for SIM operations
|
||
|
|
*/
|
||
|
|
getUserFriendlySimError(technicalError: string): string {
|
||
|
|
if (!technicalError) {
|
||
|
|
return "SIM operation failed. Please try again or contact support.";
|
||
|
|
}
|
||
|
|
|
||
|
|
const errorLower = technicalError.toLowerCase();
|
||
|
|
|
||
|
|
// Freebit API errors
|
||
|
|
if (errorLower.includes("api error: ng") || errorLower.includes("account not found")) {
|
||
|
|
return "SIM account not found. Please contact support to verify your SIM configuration.";
|
||
|
|
}
|
||
|
|
|
||
|
|
if (errorLower.includes("authentication failed") || errorLower.includes("auth")) {
|
||
|
|
return "SIM service is temporarily unavailable. Please try again later.";
|
||
|
|
}
|
||
|
|
|
||
|
|
if (errorLower.includes("timeout") || errorLower.includes("network")) {
|
||
|
|
return "SIM service request timed out. Please try again.";
|
||
|
|
}
|
||
|
|
|
||
|
|
// WHMCS errors
|
||
|
|
if (errorLower.includes("invalid permissions") || errorLower.includes("not allowed")) {
|
||
|
|
return "SIM service is temporarily unavailable. Please contact support for assistance.";
|
||
|
|
}
|
||
|
|
|
||
|
|
// Generic errors
|
||
|
|
if (errorLower.includes("failed") || errorLower.includes("error")) {
|
||
|
|
return "SIM operation failed. Please try again or contact support.";
|
||
|
|
}
|
||
|
|
|
||
|
|
// Default fallback
|
||
|
|
return "SIM operation failed. Please try again or contact support.";
|
||
|
|
}
|
||
|
|
}
|