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 { const subject = `[SIM ACTION] ${action} - ${status}`; const toAddress = this.configService.get("SIM_ALERT_EMAIL_TO"); const fromAddress = this.configService.get("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): Record { const sanitized: Record = {}; 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."; } }