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
{
///
/// QRRapido public API v1 — generate QR codes programmatically.
/// Authenticate with your API key using the X-API-Key header.
/// Get your key at https://qrrapido.site/Developer.
///
[ApiController]
[Route("api/v1/[controller]")]
[EnableCors("ApiPolicy")]
[ApiKeyAuthorize]
[Produces("application/json")]
public class QRManagerController : ControllerBase
{
private readonly IQRBusinessManager _qrBusinessManager;
private readonly ILogger _logger;
private static readonly HashSet _validFormats = new(StringComparer.OrdinalIgnoreCase)
{ "png", "webp", "svg" };
private static readonly HashSet _validTypes = new(StringComparer.OrdinalIgnoreCase)
{ "url", "pix", "wifi", "vcard", "whatsapp", "email", "sms", "texto" };
public QRManagerController(IQRBusinessManager qrBusinessManager, ILogger logger)
{
_qrBusinessManager = qrBusinessManager;
_logger = logger;
}
/// Health check — no API key required.
/// API is running.
[HttpGet("ping")]
[AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)]
public IActionResult Ping() =>
Ok(new { status = "QRRapido API v1 is up", timestamp = DateTime.UtcNow });
///
/// Generate a QR code and receive it as a base64-encoded image.
///
///
/// 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`.
///
/// QR code generated successfully.
/// Invalid request parameters.
/// Missing or invalid API key.
/// Insufficient credits.
/// Rate limit or monthly quota exceeded.
/// Internal server error.
[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 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);
}
///
/// Smoke-test endpoint — generates a QR code without an API key (GET, for quick browser testing).
///
[HttpGet("generate-get-test")]
[AllowAnonymous]
[ApiExplorerSettings(IgnoreApi = true)] // Hidden from Swagger docs
public async Task 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);
}
}
}