feat: add FundChartController (/api/chart/fund/{isin})

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-08 17:54:45 +02:00
parent 14bc7f772b
commit 3e6c5c6dff

View File

@@ -0,0 +1,139 @@
using CertReports.Syncfusion.Services.Implementations;
using CertReports.Syncfusion.Services.Interfaces;
using Microsoft.AspNetCore.Mvc;
using Syncfusion.Pdf;
using Syncfusion.Pdf.Graphics;
namespace CertReports.Syncfusion.Controllers;
/// <summary>
/// Endpoint:
/// GET /api/chart/fund/{isin}[?format=png|jpg|jpeg|pdf&amp;width=&amp;height=&amp;save=true]
/// </summary>
[ApiController]
[Route("api/chart/fund")]
public class FundChartController : ControllerBase
{
private readonly IFundDataService _dataService;
private readonly IConfiguration _configuration;
private readonly ILogger<FundChartController> _logger;
public FundChartController(
IFundDataService dataService,
IConfiguration configuration,
ILogger<FundChartController> logger)
{
_dataService = dataService;
_configuration = configuration;
_logger = logger;
}
[HttpGet("{isin}")]
public async Task<IActionResult> GetChart(
string isin,
[FromQuery] string format = "png",
[FromQuery] int width = 1100,
[FromQuery] int height = 700,
[FromQuery] bool save = false)
{
if (string.IsNullOrWhiteSpace(isin))
return BadRequest("ISIN richiesto.");
width = Math.Clamp(width, 400, 2000);
height = Math.Clamp(height, 300, 1500);
try
{
var info = await _dataService.GetFundInfoAsync(isin);
var points = await _dataService.GetChartPricesAsync(isin);
if (info == null || points.Count == 0)
return NotFound($"Nessun dato per ISIN {isin}");
bool isJpeg = format.Equals("jpg", StringComparison.OrdinalIgnoreCase)
|| format.Equals("jpeg", StringComparison.OrdinalIgnoreCase);
if (format.Equals("pdf", StringComparison.OrdinalIgnoreCase))
{
var pngBytes = FundSkiaChartRenderer.Render(points, info.Strumento, width, height);
var pdfBytes = WrapPngInPdf(pngBytes);
Response.Headers.Append("Content-Disposition",
$"inline; filename=chart_fund_{isin}.pdf");
return File(pdfBytes, "application/pdf");
}
var imgBytes = FundSkiaChartRenderer.Render(points, info.Strumento, width, height, jpeg: isJpeg);
if (save && isJpeg)
await SaveChartToDiskAsync(isin, imgBytes);
if (isJpeg)
{
Response.Headers.Append("Content-Disposition",
$"inline; filename=chart_fund_{isin}.jpg");
return File(imgBytes, "image/jpeg");
}
Response.Headers.Append("Content-Disposition",
$"inline; filename=chart_fund_{isin}.png");
return File(imgBytes, "image/png");
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore generazione chart fondo per ISIN {Isin}", isin);
return StatusCode(500, new { status = "KO", message = "Errore nella generazione del grafico." });
}
}
private async Task SaveChartToDiskAsync(string isin, byte[] imgBytes)
{
try
{
string? folder = _configuration["ChartSettings:SavePath"];
if (string.IsNullOrEmpty(folder))
{
_logger.LogWarning("Salvataggio chart fondo saltato: ChartSettings:SavePath non configurato");
return;
}
Directory.CreateDirectory(folder);
var fullPath = Path.Combine(folder, $"{isin}_fund.jpg");
await System.IO.File.WriteAllBytesAsync(fullPath, imgBytes);
_logger.LogInformation("Chart fondo salvato in {Path}", fullPath);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Salvataggio chart fondo fallito per {Isin}", isin);
}
}
private static byte[] WrapPngInPdf(byte[] pngBytes)
{
var doc = new PdfDocument();
doc.PageSettings.Size = PdfPageSize.A4;
doc.PageSettings.Orientation = PdfPageOrientation.Landscape;
doc.PageSettings.Margins.All = 30;
var page = doc.Pages.Add();
var g = page.Graphics;
float pw = page.GetClientSize().Width;
float ph = page.GetClientSize().Height;
using var stream = new MemoryStream(pngBytes);
var img = new PdfBitmap(stream);
float ratio = (float)img.Width / img.Height;
float dw = pw;
float dh = dw / ratio;
if (dh > ph) { dh = ph; dw = dh * ratio; }
float x = (pw - dw) / 2;
float y = (ph - dh) / 2;
g.DrawImage(img, x, y, dw, dh);
using var output = new MemoryStream();
doc.Save(output);
doc.Close(true);
return output.ToArray();
}
}