QrRapido/Controllers/QRController.cs
Ricardo Carneiro b54aa295ac
All checks were successful
Deploy QR Rapido / test (push) Successful in 44s
Deploy QR Rapido / build-and-push (push) Successful in 12m59s
Deploy QR Rapido / deploy-staging (push) Has been skipped
Deploy QR Rapido / deploy-production (push) Successful in 1m24s
fix: add Node.js to Docker build stage for frontend compilation
- Install Node.js 18.x in Docker build stage
- Add MongoDB DataProtection for Swarm compatibility
- Enables shared authentication keys across multiple replicas

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-22 16:25:54 -03:00

570 lines
26 KiB
C#

using Microsoft.AspNetCore.Mvc;
using QRRapidoApp.Models.ViewModels;
using QRRapidoApp.Services;
using System.Diagnostics;
using System.Security.Claims;
using System.Text;
using Microsoft.Extensions.Localization;
namespace QRRapidoApp.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class QRController : ControllerBase
{
private readonly IQRCodeService _qrService;
private readonly IUserService _userService;
private readonly AdDisplayService _adService;
private readonly ILogger<QRController> _logger;
private readonly IStringLocalizer<QRRapidoApp.Resources.SharedResource> _localizer;
private readonly AdDisplayService _adDisplayService;
public QRController(IQRCodeService qrService, IUserService userService, AdDisplayService adService, ILogger<QRController> logger, IStringLocalizer<QRRapidoApp.Resources.SharedResource> localizer, AdDisplayService adDisplayService)
{
_qrService = qrService;
_userService = userService;
_adService = adService;
_logger = logger;
_localizer = localizer;
_adDisplayService = adDisplayService;
}
[HttpPost("GenerateRapid")]
public async Task<IActionResult> GenerateRapid([FromBody] QRGenerationRequest request)
{
var stopwatch = Stopwatch.StartNew();
var requestId = Guid.NewGuid().ToString("N")[..8];
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var isAuthenticated = User?.Identity?.IsAuthenticated ?? false;
using (_logger.BeginScope(new Dictionary<string, object>
{
["RequestId"] = requestId,
["UserId"] = userId ?? "anonymous",
["IsAuthenticated"] = isAuthenticated,
["QRType"] = request.Type ?? "unknown",
["ContentLength"] = request.Content?.Length ?? 0,
["QRGeneration"] = true
}))
{
_logger.LogInformation("QR generation request started - Type: {QRType}, ContentLength: {ContentLength}, User: {UserType}",
request.Type, request.Content?.Length ?? 0, isAuthenticated ? "authenticated" : "anonymous");
try
{
// Quick validations
if (string.IsNullOrWhiteSpace(request.Content))
{
_logger.LogWarning("QR generation failed - empty content provided");
return BadRequest(new { error = _localizer["RequiredContent"], success = false });
}
if (request.Content.Length > 4000) // Limit to maintain speed
{
_logger.LogWarning("QR generation failed - content too long: {ContentLength} characters", request.Content.Length);
return BadRequest(new { error = _localizer["ContentTooLong"], success = false });
}
// Check user status
var user = await _userService.GetUserAsync(userId);
// Validate premium features
if (!string.IsNullOrEmpty(request.CornerStyle) && request.CornerStyle != "square" && user?.IsPremium != true)
{
_logger.LogWarning("Custom corner style attempted by non-premium user - UserId: {UserId}, CornerStyle: {CornerStyle}",
userId ?? "anonymous", request.CornerStyle);
return BadRequest(new
{
error = _localizer["PremiumCornerStyleRequired"],
requiresPremium = true,
success = false
});
}
// Rate limiting for free users
var rateLimitPassed = await CheckRateLimitAsync(userId, user);
if (!rateLimitPassed)
{
_logger.LogWarning("QR generation rate limited - User: {UserId}, IsPremium: {IsPremium}",
userId ?? "anonymous", user?.IsPremium ?? false);
return StatusCode(429, new
{
error = _localizer["RateLimitReached"],
upgradeUrl = "/Premium/Upgrade",
success = false
});
}
// Configure optimizations based on user
request.IsPremium = user?.IsPremium == true;
request.OptimizeForSpeed = true;
_logger.LogDebug("Generating QR code - IsPremium: {IsPremium}, OptimizeForSpeed: {OptimizeForSpeed}",
request.IsPremium, request.OptimizeForSpeed);
// Generate QR code
var generationStopwatch = Stopwatch.StartNew();
var result = await _qrService.GenerateRapidAsync(request);
generationStopwatch.Stop();
if (!result.Success)
{
_logger.LogError("QR generation failed - Error: {ErrorMessage}, GenerationTime: {GenerationTimeMs}ms",
result.ErrorMessage, generationStopwatch.ElapsedMilliseconds);
return StatusCode(500, new { error = result.ErrorMessage, success = false });
}
_logger.LogInformation("QR code generated successfully - GenerationTime: {GenerationTimeMs}ms, FromCache: {FromCache}, Size: {Size}px",
generationStopwatch.ElapsedMilliseconds, result.FromCache, request.Size);
// Update counter for all logged users
if (userId != null)
{
if (request.IsPremium)
{
result.RemainingQRs = int.MaxValue; // Premium users have unlimited
// Still increment the count for statistics
await _userService.IncrementDailyQRCountAsync(userId);
}
else
{
var remaining = await _userService.IncrementDailyQRCountAsync(userId);
result.RemainingQRs = remaining;
_logger.LogDebug("Updated QR count for free user - Remaining: {RemainingQRs}", remaining);
}
}
// Save to history if user is logged in (fire and forget)
if (userId != null)
{
_ = Task.Run(async () =>
{
try
{
await _userService.SaveQRToHistoryAsync(userId, result);
_logger.LogDebug("QR code saved to history successfully for user {UserId}", userId);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error saving QR to history for user {UserId}", userId);
}
});
}
stopwatch.Stop();
var totalTimeMs = stopwatch.ElapsedMilliseconds;
// Performance logging with structured data
using (_logger.BeginScope(new Dictionary<string, object>
{
["TotalRequestTimeMs"] = totalTimeMs,
["QRGenerationTimeMs"] = generationStopwatch.ElapsedMilliseconds,
["ServiceGenerationTimeMs"] = result.GenerationTimeMs,
["FromCache"] = result.FromCache,
["UserType"] = request.IsPremium ? "premium" : "free",
["QRSize"] = request.Size,
["Success"] = true
}))
{
var performanceStatus = totalTimeMs switch
{
< 500 => "excellent",
< 1000 => "good",
< 2000 => "acceptable",
_ => "slow"
};
_logger.LogInformation("QR generation completed - TotalTime: {TotalTimeMs}ms, ServiceTime: {ServiceTimeMs}ms, Performance: {PerformanceStatus}, Cache: {FromCache}",
totalTimeMs, result.GenerationTimeMs, performanceStatus, result.FromCache);
}
return Ok(result);
}
catch (Exception ex)
{
stopwatch.Stop();
_logger.LogError(ex, "QR generation failed with exception - RequestTime: {RequestTimeMs}ms, UserId: {UserId}",
stopwatch.ElapsedMilliseconds, userId ?? "anonymous");
return StatusCode(500, new { error = "Erro interno do servidor", success = false });
}
}
}
[HttpGet("Download/{qrId}")]
public async Task<IActionResult> Download(string qrId, string format = "png")
{
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var stopwatch = Stopwatch.StartNew();
using (_logger.BeginScope(new Dictionary<string, object>
{
["QRId"] = qrId,
["Format"] = format.ToLower(),
["UserId"] = userId ?? "anonymous",
["QRDownload"] = true
}))
{
_logger.LogInformation("QR download requested - QRId: {QRId}, Format: {Format}", qrId, format);
try
{
var qrData = await _userService.GetQRDataAsync(qrId);
if (qrData == null)
{
_logger.LogWarning("QR download failed - QR code not found: {QRId}", qrId);
return NotFound();
}
var contentType = format.ToLower() switch
{
"svg" => "image/svg+xml",
"pdf" => "application/pdf",
_ => "image/png"
};
var fileName = $"qrrapido-{DateTime.Now:yyyyMMdd-HHmmss}.{format}";
_logger.LogDebug("Converting QR to format - QRId: {QRId}, Format: {Format}, Size: {Size}",
qrId, format, qrData.Size);
byte[] fileContent;
if (format.ToLower() == "svg")
{
fileContent = await _qrService.ConvertToSvgAsync(qrData.QRCodeBase64);
}
else if (format.ToLower() == "pdf")
{
fileContent = await _qrService.ConvertToPdfAsync(qrData.QRCodeBase64, qrData.Size);
}
else
{
fileContent = Convert.FromBase64String(qrData.QRCodeBase64);
}
stopwatch.Stop();
_logger.LogInformation("QR download completed - QRId: {QRId}, Format: {Format}, Size: {FileSize} bytes, ProcessingTime: {ProcessingTimeMs}ms",
qrId, format, fileContent.Length, stopwatch.ElapsedMilliseconds);
return File(fileContent, contentType, fileName);
}
catch (Exception ex)
{
stopwatch.Stop();
_logger.LogError(ex, "QR download failed - QRId: {QRId}, Format: {Format}, ProcessingTime: {ProcessingTimeMs}ms",
qrId, format, stopwatch.ElapsedMilliseconds);
return StatusCode(500);
}
}
}
[HttpPost("SaveToHistory")]
public async Task<IActionResult> SaveToHistory([FromBody] SaveToHistoryRequest request)
{
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
using (_logger.BeginScope(new Dictionary<string, object>
{
["QRId"] = request.QrId,
["UserId"] = userId ?? "anonymous",
["SaveToHistory"] = true
}))
{
_logger.LogInformation("Save to history requested - QRId: {QRId}", request.QrId);
try
{
if (string.IsNullOrEmpty(userId))
{
_logger.LogWarning("Save to history failed - user not authenticated");
return Unauthorized();
}
var qrData = await _userService.GetQRDataAsync(request.QrId);
if (qrData == null)
{
_logger.LogWarning("Save to history failed - QR code not found: {QRId}", request.QrId);
return NotFound();
}
// QR is already saved when generated, just return success
_logger.LogInformation("QR code already saved in history - QRId: {QRId}", request.QrId);
return Ok(new { success = true, message = "QR Code salvo no histórico!" });
}
catch (Exception ex)
{
_logger.LogError(ex, "Save to history failed - QRId: {QRId}", request.QrId);
return StatusCode(500, new { error = "Erro ao salvar no histórico." });
}
}
}
[HttpPost("GenerateRapidWithLogo")]
public async Task<IActionResult> GenerateRapidWithLogo([FromForm] QRGenerationRequest request, IFormFile? logo)
{
var stopwatch = Stopwatch.StartNew();
var requestId = Guid.NewGuid().ToString("N")[..8];
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var isAuthenticated = User?.Identity?.IsAuthenticated ?? false;
// DEBUG: Log detalhado dos parâmetros recebidos
_logger.LogInformation("🔍 [DEBUG] GenerateRapidWithLogo called - RequestId: {RequestId}, ApplyLogoColorization: {ApplyLogoColorization}, LogoSizePercent: {LogoSizePercent}, HasLogo: {HasLogo}",
requestId, request.ApplyLogoColorization, request.LogoSizePercent, request.HasLogo);
using (_logger.BeginScope(new Dictionary<string, object>
{
["RequestId"] = requestId,
["UserId"] = userId ?? "anonymous",
["IsAuthenticated"] = isAuthenticated,
["QRType"] = request.Type ?? "unknown",
["ContentLength"] = request.Content?.Length ?? 0,
["QRGeneration"] = true,
["HasLogo"] = logo != null
}))
{
_logger.LogInformation("QR generation with logo request started - Type: {QRType}, ContentLength: {ContentLength}, HasLogo: {HasLogo}",
request.Type, request.Content?.Length ?? 0, logo != null);
try
{
// Quick validations
if (string.IsNullOrWhiteSpace(request.Content))
{
_logger.LogWarning("QR generation failed - empty content provided");
return BadRequest(new { error = _localizer["RequiredContent"], success = false });
}
if (request.Content.Length > 4000)
{
_logger.LogWarning("QR generation failed - content too long: {ContentLength} characters", request.Content.Length);
return BadRequest(new { error = _localizer["ContentTooLong"], success = false });
}
// Check user status
var user = await _userService.GetUserAsync(userId);
// Validate premium status for logo feature
if (user?.IsPremium != true)
{
_logger.LogWarning("Logo upload attempted by non-premium user - UserId: {UserId}", userId ?? "anonymous");
return BadRequest(new
{
error = _localizer["PremiumLogoRequired"],
requiresPremium = true,
success = false
});
}
// Validate premium corner styles
if (!string.IsNullOrEmpty(request.CornerStyle) && request.CornerStyle != "square")
{
_logger.LogInformation("Premium user using custom corner style - UserId: {UserId}, CornerStyle: {CornerStyle}",
userId, request.CornerStyle);
}
// Process logo upload if provided
if (logo != null && logo.Length > 0)
{
// Validate file size (2MB max)
if (logo.Length > 2 * 1024 * 1024)
{
_logger.LogWarning("Logo upload failed - file too large: {FileSize} bytes", logo.Length);
return BadRequest(new { error = _localizer["LogoTooLarge"], success = false });
}
// Validate file format
var allowedTypes = new[] { "image/png", "image/jpeg", "image/jpg" };
if (!allowedTypes.Contains(logo.ContentType?.ToLower()))
{
_logger.LogWarning("Logo upload failed - invalid format: {ContentType}", logo.ContentType);
return BadRequest(new { error = _localizer["InvalidLogoFormat"], success = false });
}
try
{
// Convert file to byte array
using var memoryStream = new MemoryStream();
await logo.CopyToAsync(memoryStream);
request.Logo = memoryStream.ToArray();
request.HasLogo = true;
_logger.LogInformation("Logo processed successfully - Size: {LogoSize} bytes, Format: {ContentType}, SizePercent: {SizePercent}%, Colorized: {Colorized}",
logo.Length, logo.ContentType, request.LogoSizePercent ?? 20, request.ApplyLogoColorization);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing logo file");
return BadRequest(new { error = _localizer["ErrorProcessingLogo"], success = false });
}
}
// Rate limiting for free users (premium users get unlimited)
var rateLimitPassed = await CheckRateLimitAsync(userId, user);
if (!rateLimitPassed)
{
_logger.LogWarning("QR generation rate limited - User: {UserId}, IsPremium: {IsPremium}",
userId ?? "anonymous", user?.IsPremium ?? false);
return StatusCode(429, new
{
error = _localizer["RateLimitReached"],
upgradeUrl = "/Premium/Upgrade",
success = false
});
}
// Configure optimizations based on user
request.IsPremium = user?.IsPremium == true;
request.OptimizeForSpeed = true;
_logger.LogDebug("Generating QR code with logo - IsPremium: {IsPremium}, HasLogo: {HasLogo}, LogoSize: {LogoSize}%, Colorized: {Colorized}",
request.IsPremium, request.HasLogo, request.LogoSizePercent ?? 20, request.ApplyLogoColorization);
// Generate QR code
var generationStopwatch = Stopwatch.StartNew();
var result = await _qrService.GenerateRapidAsync(request);
generationStopwatch.Stop();
if (!result.Success)
{
_logger.LogError("QR generation failed - Error: {ErrorMessage}, GenerationTime: {GenerationTimeMs}ms",
result.ErrorMessage, generationStopwatch.ElapsedMilliseconds);
return StatusCode(500, new { error = result.ErrorMessage, success = false });
}
_logger.LogInformation("QR code with logo generated successfully - GenerationTime: {GenerationTimeMs}ms, FromCache: {FromCache}, HasLogo: {HasLogo}, Base64Length: {Base64Length}",
generationStopwatch.ElapsedMilliseconds, result.FromCache, request.HasLogo, result.QRCodeBase64?.Length ?? 0);
// Save to history if user is logged in (fire and forget)
if (userId != null)
{
_ = Task.Run(async () =>
{
try
{
await _userService.SaveQRToHistoryAsync(userId, result);
_logger.LogDebug("QR code saved to history successfully for user {UserId}", userId);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error saving QR to history for user {UserId}", userId);
}
});
}
stopwatch.Stop();
return Ok(result);
}
catch (Exception ex)
{
stopwatch.Stop();
_logger.LogError(ex, "QR generation with logo failed with exception - RequestTime: {RequestTimeMs}ms, UserId: {UserId}",
stopwatch.ElapsedMilliseconds, userId ?? "anonymous");
return StatusCode(500, new { error = "Erro interno do servidor", success = false });
}
}
}
[HttpGet("History")]
public async Task<IActionResult> GetHistory(int limit = 20)
{
try
{
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (string.IsNullOrEmpty(userId))
{
return Unauthorized();
}
var history = await _userService.GetUserQRHistoryAsync(userId, limit);
return Ok(history);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting QR history");
return StatusCode(500);
}
}
[HttpGet("GetUserStats")]
public async Task<IActionResult> GetUserStats()
{
try
{
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (string.IsNullOrEmpty(userId))
{
return Unauthorized();
}
var user = await _userService.GetUserAsync(userId);
var isPremium = user?.IsPremium ?? false;
// For logged users (premium or not), return -1 to indicate unlimited
// For consistency with the frontend logic
var remainingCount = -1; // Unlimited for all logged users
return Ok(new
{
remainingCount = remainingCount,
isPremium = isPremium,
isUnlimited = true
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting user stats");
return StatusCode(500);
}
}
[HttpDelete("History/{qrId}")]
public async Task<IActionResult> DeleteFromHistory(string qrId)
{
try
{
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (string.IsNullOrEmpty(userId))
{
return Unauthorized();
}
var success = await _userService.DeleteQRFromHistoryAsync(userId, qrId);
if (success)
{
_logger.LogInformation("QR code deleted from history - QRId: {QRId}, UserId: {UserId}", qrId, userId);
return Ok(new { success = true, message = _localizer["QRCodeDeleted"].Value });
}
else
{
return NotFound(new { success = false, message = _localizer["ErrorDeletingQR"].Value });
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deleting QR from history - QRId: {QRId}", qrId);
return StatusCode(500, new { success = false, message = _localizer["ErrorDeletingQR"].Value });
}
}
private async Task<bool> CheckRateLimitAsync(string? userId, Models.User? user)
{
// Premium users have unlimited QR codes
if (user?.IsPremium == true) return true;
// Logged users (non-premium) have unlimited QR codes
if (userId != null) return true;
// Anonymous users have 3 QR codes per day
var dailyLimit = 3;
var currentCount = await _userService.GetDailyQRCountAsync(userId);
return currentCount < dailyLimit;
}
}
public class SaveToHistoryRequest
{
public string QrId { get; set; } = string.Empty;
}
}