chore: initial commit - baseline before redesign

This commit is contained in:
2026-03-20 12:03:38 +01:00
commit 85ee66750a
31 changed files with 3426 additions and 0 deletions

View File

@@ -0,0 +1,109 @@
using CertReports.Syncfusion.Services.Implementations;
using Microsoft.AspNetCore.Mvc;
using Syncfusion.Pdf;
using Syncfusion.Pdf.Graphics;
namespace CertReports.Syncfusion.Controllers;
/// <summary>
/// API per la generazione standalone del grafico certificato.
/// Richiamabile da qualsiasi progetto esterno.
///
/// Endpoint:
/// GET /api/chart/{isin} → PNG inline
/// GET /api/chart/{isin}?format=png → PNG inline
/// GET /api/chart/{isin}?format=pdf → PDF inline
/// GET /api/chart/{isin}?width=1100&height=700&format=png
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class ChartController : ControllerBase
{
private readonly IChartDataService _chartDataService;
private readonly ILogger<ChartController> _logger;
public ChartController(IChartDataService chartDataService, ILogger<ChartController> logger)
{
_chartDataService = chartDataService;
_logger = logger;
}
[HttpGet("{isin}")]
public async Task<IActionResult> GenerateChart(
string isin,
[FromQuery] int width = 1100,
[FromQuery] int height = 700,
[FromQuery] string format = "png")
{
if (string.IsNullOrWhiteSpace(isin))
return BadRequest("ISIN non valido.");
// Limiti ragionevoli
width = Math.Clamp(width, 400, 2000);
height = Math.Clamp(height, 300, 1500);
try
{
var chartData = await _chartDataService.GetChartDataAsync(isin);
if (chartData == null || chartData.Series.Count == 0)
{
return NotFound(new
{
status = "KO",
message = $"Nessun dato per il grafico di {isin} (meno di 30 prezzi EOD?)."
});
}
// Genera PNG
byte[] pngBytes = SkiaChartRenderer.RenderToPng(chartData, width, height);
if (format.Equals("pdf", StringComparison.OrdinalIgnoreCase))
{
// Converti in PDF landscape
byte[] pdfBytes = WrapPngInPdf(pngBytes);
Response.Headers.Append("Content-Disposition", $"inline; filename=chart_{isin}.pdf");
return File(pdfBytes, "application/pdf");
}
// Default: PNG
Response.Headers.Append("Content-Disposition", $"inline; filename=chart_{isin}.png");
return File(pngBytes, "image/png");
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore generazione chart per ISIN {Isin}", isin);
return StatusCode(500, new { status = "KO", message = "Errore nella generazione del grafico." });
}
}
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();
}
}

View File

@@ -0,0 +1,139 @@
using CertReports.Syncfusion.Helpers;
using CertReports.Syncfusion.Services.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace CertReports.Syncfusion.Controllers;
/// <summary>
/// Controller REST per la generazione dei report certificati.
/// Sostituisce la vecchia WebForm ReportFSSiteCrypt.aspx.
///
/// Endpoint:
/// GET /api/report?p={encrypted_isin}
/// GET /api/report?alias={alias_id}
/// GET /api/report/by-isin/{isin} (uso interno/debug)
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class ReportController : ControllerBase
{
private readonly IReportOrchestrator _orchestrator;
private readonly ICertificateDataService _dataService;
private readonly CryptoHelper _crypto;
private readonly ILogger<ReportController> _logger;
public ReportController(
IReportOrchestrator orchestrator,
ICertificateDataService dataService,
CryptoHelper crypto,
ILogger<ReportController> logger)
{
_orchestrator = orchestrator;
_dataService = dataService;
_crypto = crypto;
_logger = logger;
}
/// <summary>
/// Endpoint principale - compatibile con la vecchia URL.
/// Accetta parametri 'p' (ISIN cifrato) o 'alias' (alias ID).
/// Restituisce il PDF inline (visualizzabile nel browser).
/// </summary>
[HttpGet]
public async Task<IActionResult> GenerateReport(
[FromQuery(Name = "p")] string? encryptedIsin = null,
[FromQuery(Name = "alias")] string? aliasId = null)
{
string? isin = null;
// Risolvi ISIN
if (!string.IsNullOrEmpty(encryptedIsin))
{
try
{
isin = _crypto.DecryptIsin(encryptedIsin);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Errore nella decodifica ISIN cifrato");
return BadRequest("Parametro 'p' non valido.");
}
}
else if (!string.IsNullOrEmpty(aliasId))
{
isin = await _dataService.FindIsinByAliasIdAsync(aliasId);
}
if (string.IsNullOrEmpty(isin))
{
return BadRequest("Specificare il parametro 'p' (ISIN cifrato) o 'alias' (alias ID).");
}
return await GenerateAndReturnPdf(isin);
}
/// <summary>
/// Endpoint diretto per ISIN (uso interno / debug).
/// In produzione proteggere con autenticazione.
/// </summary>
[HttpGet("by-isin/{isin}")]
public async Task<IActionResult> GenerateReportByIsin(string isin)
{
if (string.IsNullOrWhiteSpace(isin) || isin.Length < 12)
{
return BadRequest("ISIN non valido.");
}
return await GenerateAndReturnPdf(isin);
}
/// <summary>
/// Endpoint per download come allegato (content-disposition: attachment)
/// </summary>
[HttpGet("download")]
public async Task<IActionResult> DownloadReport(
[FromQuery(Name = "p")] string? encryptedIsin = null,
[FromQuery(Name = "alias")] string? aliasId = null)
{
string? isin = null;
if (!string.IsNullOrEmpty(encryptedIsin))
isin = _crypto.DecryptIsin(encryptedIsin);
else if (!string.IsNullOrEmpty(aliasId))
isin = await _dataService.FindIsinByAliasIdAsync(aliasId);
if (string.IsNullOrEmpty(isin))
return BadRequest("Specificare il parametro 'p' o 'alias'.");
try
{
var pdfBytes = await _orchestrator.GenerateReportAsync(isin);
return File(pdfBytes, "application/pdf", $"{isin}.pdf");
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore generazione report download per ISIN {Isin}", isin);
return StatusCode(500, "Errore nella generazione del report.");
}
}
// ─── Helper ────────────────────────────────────────────────────────
private async Task<IActionResult> GenerateAndReturnPdf(string isin)
{
try
{
_logger.LogInformation("Richiesta report per ISIN {Isin}", isin);
var pdfBytes = await _orchestrator.GenerateReportAsync(isin);
// Inline: il PDF si apre direttamente nel browser
Response.Headers.Append("Content-Disposition", $"inline; filename={isin}.pdf");
return File(pdfBytes, "application/pdf");
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore generazione report per ISIN {Isin}", isin);
return StatusCode(500, "Errore nella generazione del report.");
}
}
}

View File

@@ -0,0 +1,47 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.SqlClient;
namespace CertReports.Syncfusion.Controllers;
[ApiController]
[Route("api/[controller]")]
public class TestController : ControllerBase
{
private readonly IConfiguration _config;
public TestController(IConfiguration config)
{
_config = config;
}
[HttpGet("db")]
public async Task<IActionResult> TestDb()
{
var connStr = _config.GetConnectionString("CertDb");
try
{
await using var conn = new SqlConnection(connStr);
await conn.OpenAsync();
await using var cmd = new SqlCommand("SELECT 1", conn);
var result = await cmd.ExecuteScalarAsync();
return Ok(new
{
status = "OK",
result = result?.ToString(),
connectionString = connStr?.Replace(_config["ConnectionStrings:CertDb"]?.Split("Password=").LastOrDefault()?.Split(";").FirstOrDefault() ?? "", "***")
});
}
catch (Exception ex)
{
return Ok(new
{
status = "ERRORE",
message = ex.Message,
innerMessage = ex.InnerException?.Message,
connectionString = connStr
});
}
}
}