QrRapido/Services/QuotaValidator.cs
Ricardo Carneiro a3238ca6c5 feat: MCP server + landing page + OAuth returnUrl fix
- Add Node.js MCP server (stdio + HTTP/SSE) with generate_qr and generate_pix_qr tools
- Add landing pages PT/EN at /mcp and /mcp/en with hreflang SEO
- Fix OAuth returnUrl via RedirectUri query param (state was always null in callback)
- Fix API key requests bypassing web credit check (use rate limiter instead)
- Add /api/mcp nginx route + Docker Swarm service for n8n cloud integration
- Auto-create API key on first OAuth login with TempData display
- Add UseDefaultFiles() for /mcp → /mcp/index.html serving
- Fix Serilog console log level in Development (was Error, now Info for app logs)
- Add /api/v1/QRManager/me endpoint for API key validation
- Update CI/CD to build and deploy qrrapido-mcp image alongside .NET app

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 21:23:50 -03:00

75 lines
2.9 KiB
C#

using QRRapidoApp.Models.DTOs;
using QRRapidoApp.Services;
namespace QRRapidoApp.Services
{
public class QuotaValidator : IQuotaValidator
{
private readonly IUserService _userService;
private readonly ILogger<QuotaValidator> _logger;
public QuotaValidator(IUserService userService, ILogger<QuotaValidator> logger)
{
_userService = userService;
_logger = logger;
}
public async Task<(bool CanProceed, string? ErrorCode, string? ErrorMessage)> ValidateQuotaAsync(UserRequesterContext context)
{
if (!context.IsAuthenticated)
{
// Se não tem Cookie de DeviceId, permitimos (limpar cookie funciona)
if (string.IsNullOrEmpty(context.DeviceId))
{
return (true, null, null);
}
// Verifica no banco se este DeviceId já estourou o limite de 3
// (Removi a trava por IP aqui para permitir seu teste com limpeza de cookies)
var canGenerate = await _userService.CheckAnonymousLimitAsync("ignore_ip", context.DeviceId);
if (!canGenerate)
{
return (false, "LIMIT_REACHED", "Limite diário atingido. Limpe os cookies ou cadastre-se!");
}
return (true, null, null);
}
// API key requests have their own monthly rate limiting — skip credit check
if (context.IsApiKeyRequest) return (true, null, null);
var user = await _userService.GetUserAsync(context.UserId!);
if (user == null) return (false, "UNAUTHORIZED", "Usuário não encontrado.");
if (user.FreeQRsUsed < 5 || user.Credits > 0) return (true, null, null);
return (false, "INSUFFICIENT_CREDITS", "Saldo insuficiente. Adquira mais créditos.");
}
public async Task RegisterUsageAsync(UserRequesterContext context, string qrId, int cost = 1)
{
try
{
if (!context.IsAuthenticated)
{
// Registra apenas via DeviceId para o seu teste funcionar
await _userService.RegisterAnonymousUsageAsync("ignore_ip", context.DeviceId ?? "temp_id", qrId);
return;
}
if (cost <= 0) return;
var user = await _userService.GetUserAsync(context.UserId!);
if (user == null) return;
if (user.FreeQRsUsed < 5)
await _userService.IncrementFreeUsageAsync(context.UserId!);
else
await _userService.DeductCreditAsync(context.UserId!);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error registering usage in QuotaValidator");
}
}
}
}