chore: init project
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user