import { Resend } from 'resend'; import prisma from '@prisma/prisma'; import { ReportData } from '@utils/types'; export class ExpenseReporter { private resend: Resend; private senderEmail: string; private recipientEmail: string; constructor() { if (!process.env.RESEND_API_KEY) { throw new Error('RESEND_API_KEY environment variable is not set'); } if (!process.env.SENDER_EMAIL) { throw new Error('SENDER_EMAIL environment variable is not set'); } if (!process.env.RECIPIENT_EMAIL) { throw new Error('RECIPIENT_EMAIL environment variable is not set'); } this.resend = new Resend(process.env.RESEND_API_KEY); this.senderEmail = process.env.SENDER_EMAIL; this.recipientEmail = process.env.RECIPIENT_EMAIL; } private async generateReport(from: Date, to: Date): Promise { const startDate = new Date(from); startDate.setHours(0, 0, 0, 0); const endDate = new Date(to); endDate.setHours(23, 59, 59, 999); const expenses = await prisma.expense.findMany({ where: { deleted: false, createdAt: { gte: startDate, lte: endDate } }, include: { category: true }, orderBy: { createdAt: 'desc' } }); const totalExpenses = expenses.reduce((sum, exp) => sum + exp.cost, 0); const categoryMap = new Map(); expenses.forEach(exp => { const current = categoryMap.get(exp.category.name) || { total: 0, count: 0 }; categoryMap.set(exp.category.name, { total: current.total + exp.cost, count: current.count + 1 }); }); const byCategory = Array.from(categoryMap.entries()) .map(([category, stats]) => ({ category, total: stats.total, count: stats.count })) .sort((a, b) => b.total - a.total); return { expenses, summary: { totalExpenses, byCategory }, dateRange: { from, to } }; } private generateHtmlReport(data: ReportData): string { const formatDate = (date: Date) => date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); const formatCurrency = (amount: number) => amount.toLocaleString('en-US', { style: 'currency', currency: 'EUR' }); return `

Expense Report

From ${formatDate(data.dateRange.from)} to ${formatDate(data.dateRange.to)}

Summary

Total Expenses: ${formatCurrency(data.summary.totalExpenses)}

Expenses by Category

${data.summary.byCategory .map( cat => ` ` ) .join('')}
Category Total Count Average
${cat.category} ${formatCurrency(cat.total)} ${cat.count} ${formatCurrency(cat.total / cat.count)}

Detailed Expenses

${data.expenses .map( exp => ` ` ) .join('')}
Date Description Category Amount
${formatDate(exp.createdAt)} ${exp.description} ${exp.category.name} ${formatCurrency(exp.cost)}
`; } async sendReport( from: Date, to: Date, includeJson: boolean = false ): Promise { const reportData = await this.generateReport(from, to); const htmlContent = this.generateHtmlReport(reportData); const attachments = []; if (includeJson) { const jsonData = JSON.stringify(reportData, null, 2); attachments.push({ filename: 'expense-report.json', content: Buffer.from(jsonData).toString('base64'), contentType: 'application/json' // Added MIME type }); } try { const response = await this.resend.emails.send({ from: this.senderEmail, to: this.recipientEmail, subject: `Expense Report: ${from.toLocaleDateString()} - ${to.toLocaleDateString()}`, html: htmlContent, attachments }); if (response.error) { throw new Error('Failed to send email: No id returned from Resend'); } } catch (error) { console.error('Failed to send email:', error); throw new Error(`Email sending failed: ${error}`); } } }