568 lines
26 KiB
C#
568 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;
|
|
|
|
public QRController(IQRCodeService qrService, IUserService userService, AdDisplayService adService, ILogger<QRController> logger, IStringLocalizer<QRRapidoApp.Resources.SharedResource> localizer)
|
|
{
|
|
_qrService = qrService;
|
|
_userService = userService;
|
|
_adService = adService;
|
|
_logger = logger;
|
|
_localizer = localizer;
|
|
}
|
|
|
|
[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;
|
|
}
|
|
} |