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); } } }