QrRapido/Controllers/QRManagerController.cs
Ricardo Carneiro 7a0c12f8d2
Some checks failed
Deploy QR Rapido / test (push) Failing after 17s
Deploy QR Rapido / build-and-push (push) Has been skipped
Deploy QR Rapido / deploy-staging (push) Has been skipped
Deploy QR Rapido / deploy-production (push) Has been skipped
feat: api separada do front-end e area do desenvolvedor.
2026-03-08 12:40:51 -03:00

152 lines
6.3 KiB
C#

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using QRRapidoApp.Filters;
using QRRapidoApp.Models.DTOs;
using QRRapidoApp.Models.ViewModels;
using QRRapidoApp.Services;
namespace QRRapidoApp.Controllers
{
/// <summary>
/// QRRapido public API v1 — generate QR codes programmatically.
/// Authenticate with your API key using the <c>X-API-Key</c> header.
/// Get your key at <c>https://qrrapido.site/Developer</c>.
/// </summary>
[ApiController]
[Route("api/v1/[controller]")]
[EnableCors("ApiPolicy")]
[ApiKeyAuthorize]
[Produces("application/json")]
public class QRManagerController : ControllerBase
{
private readonly IQRBusinessManager _qrBusinessManager;
private readonly ILogger<QRManagerController> _logger;
private static readonly HashSet<string> _validFormats = new(StringComparer.OrdinalIgnoreCase)
{ "png", "webp", "svg" };
private static readonly HashSet<string> _validTypes = new(StringComparer.OrdinalIgnoreCase)
{ "url", "pix", "wifi", "vcard", "whatsapp", "email", "sms", "texto" };
public QRManagerController(IQRBusinessManager qrBusinessManager, ILogger<QRManagerController> logger)
{
_qrBusinessManager = qrBusinessManager;
_logger = logger;
}
/// <summary>Health check — no API key required.</summary>
/// <response code="200">API is running.</response>
[HttpGet("ping")]
[AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)]
public IActionResult Ping() =>
Ok(new { status = "QRRapido API v1 is up", timestamp = DateTime.UtcNow });
/// <summary>
/// Generate a QR code and receive it as a base64-encoded image.
/// </summary>
/// <remarks>
/// Supported output formats: **png** (default), **webp** (recommended — ~40% smaller), **svg**.
///
/// Rate limits and monthly quotas are returned in response headers:
/// `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, `X-Quota-Limit`, `X-Quota-Remaining`.
/// </remarks>
/// <response code="200">QR code generated successfully.</response>
/// <response code="400">Invalid request parameters.</response>
/// <response code="401">Missing or invalid API key.</response>
/// <response code="402">Insufficient credits.</response>
/// <response code="429">Rate limit or monthly quota exceeded.</response>
/// <response code="500">Internal server error.</response>
[HttpPost("generate")]
[ProducesResponseType(typeof(QRResponseDto), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(object), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(object), StatusCodes.Status402PaymentRequired)]
[ProducesResponseType(typeof(object), StatusCodes.Status429TooManyRequests)]
public async Task<IActionResult> Generate([FromBody] QRGenerationRequest request)
{
// Input validation
if (string.IsNullOrWhiteSpace(request.Content))
return BadRequest(new { error = "Field 'content' is required." });
if (request.Content.Length > 2048)
return BadRequest(new { error = "Field 'content' exceeds the maximum length of 2048 characters." });
var format = (request.OutputFormat ?? "png").ToLowerInvariant();
if (!_validFormats.Contains(format))
return BadRequest(new { error = $"Invalid 'outputFormat'. Allowed values: {string.Join(", ", _validFormats)}." });
var type = (request.Type ?? "url").ToLowerInvariant();
if (!_validTypes.Contains(type))
return BadRequest(new { error = $"Invalid 'type'. Allowed values: {string.Join(", ", _validTypes)}." });
request.OutputFormat = format;
request.Type = type;
var userId = HttpContext.Items["ApiKeyUserId"] as string;
var context = new UserRequesterContext
{
UserId = userId,
IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "api"
};
var result = await _qrBusinessManager.ProcessGenerationAsync(request, context);
if (!result.Success)
{
return result.ErrorCode switch
{
"INSUFFICIENT_CREDITS" => StatusCode(402, result),
_ => BadRequest(result)
};
}
// Map format and mimeType into the response
result.Format = format;
result.MimeType = format switch
{
"webp" => "image/webp",
"svg" => "image/svg+xml",
_ => "image/png"
};
return Ok(result);
}
/// <summary>
/// Smoke-test endpoint — generates a QR code without an API key (GET, for quick browser testing).
/// </summary>
[HttpGet("generate-get-test")]
[AllowAnonymous]
[ApiExplorerSettings(IgnoreApi = true)] // Hidden from Swagger docs
public async Task<IActionResult> GenerateGetTest(
string content = "https://qrrapido.site",
string color = "000000",
string format = "png")
{
var request = new QRGenerationRequest
{
Content = content,
PrimaryColor = "#" + color.Replace("#", ""),
Type = "url",
Size = 400,
OutputFormat = format
};
var context = new UserRequesterContext
{
UserId = "browser-test",
IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString()
};
var result = await _qrBusinessManager.ProcessGenerationAsync(request, context);
if (!result.Success) return BadRequest(result.Message);
byte[] imageBytes = Convert.FromBase64String(result.QRCodeBase64!);
var mimeType = format == "webp" ? "image/webp" : "image/png";
return File(imageBytes, mimeType);
}
}
}