152 lines
6.3 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|