113 lines
4.9 KiB
C#
113 lines
4.9 KiB
C#
using System.Globalization;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using CsvHelper;
|
|
using InboxIntel.Application.Abstractions;
|
|
using DTOs = InboxIntel.Application.DTOs;
|
|
using QuestPDF.Fluent;
|
|
using QuestPDF.Helpers;
|
|
using QuestPDF.Infrastructure;
|
|
|
|
namespace InboxIntel.Infrastructure.Export;
|
|
|
|
/// <summary>
|
|
/// Builds inbox reports as PDF (QuestPDF), CSV (CsvHelper) or JSON. The report
|
|
/// covers the inbox summary, sender stats, cleanup suggestions and storage use.
|
|
/// </summary>
|
|
public class ExportService : IExportService
|
|
{
|
|
private readonly IAnalyticsService _analytics;
|
|
private readonly IAiService _ai;
|
|
|
|
public ExportService(IAnalyticsService analytics, IAiService ai)
|
|
{
|
|
_analytics = analytics;
|
|
_ai = ai;
|
|
QuestPDF.Settings.License = LicenseType.Community;
|
|
}
|
|
|
|
public async Task<(byte[] Content, string ContentType, string FileName)> ExportReportAsync(Guid userId, ExportFormat format, CancellationToken ct = default)
|
|
{
|
|
var health = await _analytics.GetInboxHealthAsync(userId, ct);
|
|
var topSenders = await _analytics.GetTopSendersAsync(userId, 25, ct);
|
|
var suggestions = await _ai.SuggestCleanupAsync(userId, ct);
|
|
var stamp = DateTime.UtcNow.ToString("yyyyMMdd-HHmm");
|
|
|
|
return format switch
|
|
{
|
|
ExportFormat.Json => (BuildJson(health, topSenders, suggestions), "application/json", $"inbox-report-{stamp}.json"),
|
|
ExportFormat.Csv => (BuildCsv(topSenders), "text/csv", $"sender-stats-{stamp}.csv"),
|
|
_ => (BuildPdf(health, topSenders, suggestions), "application/pdf", $"inbox-report-{stamp}.pdf")
|
|
};
|
|
}
|
|
|
|
private static byte[] BuildJson(object health, object senders, object suggestions)
|
|
{
|
|
var payload = new { generatedUtc = DateTimeOffset.UtcNow, health, topSenders = senders, cleanupSuggestions = suggestions };
|
|
return JsonSerializer.SerializeToUtf8Bytes(payload, new JsonSerializerOptions { WriteIndented = true });
|
|
}
|
|
|
|
private static byte[] BuildCsv(IEnumerable<DTOs.SenderStatDto> senders)
|
|
{
|
|
using var ms = new MemoryStream();
|
|
using (var writer = new StreamWriter(ms, Encoding.UTF8, leaveOpen: true))
|
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
|
{
|
|
csv.WriteRecords(senders);
|
|
}
|
|
return ms.ToArray();
|
|
}
|
|
|
|
private byte[] BuildPdf(DTOs.InboxHealthDto health, IReadOnlyList<DTOs.SenderStatDto> senders, IReadOnlyList<DTOs.AiCleanupSuggestionDto> suggestions)
|
|
{
|
|
var doc = Document.Create(container =>
|
|
{
|
|
container.Page(page =>
|
|
{
|
|
page.Margin(40);
|
|
page.Size(PageSizes.A4);
|
|
page.DefaultTextStyle(t => t.FontSize(10));
|
|
|
|
page.Header().Text("InboxIntel — Inbox Report").FontSize(20).Bold();
|
|
|
|
page.Content().PaddingVertical(10).Column(col =>
|
|
{
|
|
col.Item().Text($"Generated: {DateTime.UtcNow:u}").FontColor(Colors.Grey.Medium);
|
|
col.Item().PaddingTop(10).Text($"Inbox Health: {health.Score}/100 (Grade {health.Grade})").FontSize(14).Bold();
|
|
col.Item().Text($"Total emails: {health.TotalEmails:N0} Unread: {health.UnreadEmails:N0} Newsletters: {health.NewsletterCount:N0}");
|
|
col.Item().Text($"Estimated storage: {health.EstimatedStorageBytes / 1_048_576.0:N1} MB");
|
|
|
|
col.Item().PaddingTop(14).Text("Recommendations").FontSize(13).Bold();
|
|
foreach (var rec in health.Recommendations)
|
|
col.Item().Text($"• {rec}");
|
|
|
|
col.Item().PaddingTop(14).Text("Top Senders").FontSize(13).Bold();
|
|
col.Item().Table(table =>
|
|
{
|
|
table.ColumnsDefinition(c => { c.RelativeColumn(3); c.RelativeColumn(1); c.RelativeColumn(1); });
|
|
table.Header(h =>
|
|
{
|
|
h.Cell().Text("Sender").Bold();
|
|
h.Cell().Text("Emails").Bold();
|
|
h.Cell().Text("Unread").Bold();
|
|
});
|
|
foreach (var s in senders)
|
|
{
|
|
table.Cell().Text(s.Address);
|
|
table.Cell().Text(s.EmailCount.ToString("N0"));
|
|
table.Cell().Text(s.UnreadCount.ToString("N0"));
|
|
}
|
|
});
|
|
|
|
col.Item().PaddingTop(14).Text("Cleanup Suggestions").FontSize(13).Bold();
|
|
foreach (var sug in suggestions)
|
|
col.Item().Text($"• {sug.Title} — {sug.Rationale}");
|
|
});
|
|
|
|
page.Footer().AlignCenter().Text(t => { t.Span("InboxIntel • "); t.CurrentPageNumber(); t.Span(" / "); t.TotalPages(); });
|
|
});
|
|
});
|
|
return doc.GeneratePdf();
|
|
}
|
|
}
|