From a7af34659b275b0088a47613db93bbb11696d29c Mon Sep 17 00:00:00 2001 From: Ricardo Carneiro Date: Mon, 4 Aug 2025 20:34:29 -0300 Subject: [PATCH] feat: delete e es-py --- Controllers/HomeController.cs | 7 +- Controllers/QRController.cs | 57 +++++-- Program.cs | 5 +- Resources/SharedResource.es-PY.resx | 217 +++++++++++++++++++++++++ Resources/SharedResource.pt-BR.resx | 90 ++++++++++ Resources/SharedResource.resx | 90 ++++++++++ Services/IUserService.cs | 1 + Services/QRRapidoService.cs | 13 +- Services/UserService.cs | 30 ++++ Tests/Services/QRRapidoServiceTests.cs | 4 +- Views/Account/History.cshtml | 122 +++++++++++++- Views/Account/Profile.cshtml | 3 +- Views/Shared/_Layout.cshtml | 38 ++++- wwwroot/js/qr-speed-generator.js | 38 ++--- wwwroot/js/simple-opcacity.js | 7 +- wwwroot/js/theme-toggle.js | 23 +-- 16 files changed, 663 insertions(+), 82 deletions(-) create mode 100644 Resources/SharedResource.es-PY.resx diff --git a/Controllers/HomeController.cs b/Controllers/HomeController.cs index d6efa8f..170b1dc 100644 --- a/Controllers/HomeController.cs +++ b/Controllers/HomeController.cs @@ -3,6 +3,7 @@ using QRRapidoApp.Models; using QRRapidoApp.Services; using System.Diagnostics; using System.Security.Claims; +using Microsoft.Extensions.Localization; namespace QRRapidoApp.Controllers { @@ -12,13 +13,15 @@ namespace QRRapidoApp.Controllers private readonly AdDisplayService _adDisplayService; private readonly IUserService _userService; private readonly IConfiguration _config; + private readonly IStringLocalizer _localizer; - public HomeController(ILogger logger, AdDisplayService adDisplayService, IUserService userService, IConfiguration config) + public HomeController(ILogger logger, AdDisplayService adDisplayService, IUserService userService, IConfiguration config, IStringLocalizer localizer) { _logger = logger; _adDisplayService = adDisplayService; _userService = userService; _config = config; + _localizer = localizer; } public async Task Index() @@ -33,7 +36,7 @@ namespace QRRapidoApp.Controllers // SEO and Analytics data ViewBag.Title = _config["App:TaglinePT"]; ViewBag.Keywords = _config["SEO:KeywordsPT"]; - ViewBag.Description = "QR Rapido: Gere códigos QR em segundos! Gerador ultrarrápido em português e espanhol. Grátis, sem cadastro obrigatório. 30 dias sem anúncios após login."; + ViewBag.Description = _localizer["QRGenerateDescription"]; // User stats for logged in users if (!string.IsNullOrEmpty(userId)) diff --git a/Controllers/QRController.cs b/Controllers/QRController.cs index 7784cd6..61e363a 100644 --- a/Controllers/QRController.cs +++ b/Controllers/QRController.cs @@ -4,6 +4,7 @@ using QRRapidoApp.Services; using System.Diagnostics; using System.Security.Claims; using System.Text; +using Microsoft.Extensions.Localization; namespace QRRapidoApp.Controllers { @@ -15,13 +16,15 @@ namespace QRRapidoApp.Controllers private readonly IUserService _userService; private readonly AdDisplayService _adService; private readonly ILogger _logger; + private readonly IStringLocalizer _localizer; - public QRController(IQRCodeService qrService, IUserService userService, AdDisplayService adService, ILogger logger) + public QRController(IQRCodeService qrService, IUserService userService, AdDisplayService adService, ILogger logger, IStringLocalizer localizer) { _qrService = qrService; _userService = userService; _adService = adService; _logger = logger; + _localizer = localizer; } [HttpPost("GenerateRapid")] @@ -51,13 +54,13 @@ namespace QRRapidoApp.Controllers if (string.IsNullOrWhiteSpace(request.Content)) { _logger.LogWarning("QR generation failed - empty content provided"); - return BadRequest(new { error = "Conteúdo é obrigatório", success = false }); + 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 = "Conteúdo muito longo. Máximo 4000 caracteres.", success = false }); + return BadRequest(new { error = _localizer["ContentTooLong"], success = false }); } // Check user status @@ -70,7 +73,7 @@ namespace QRRapidoApp.Controllers userId ?? "anonymous", request.CornerStyle); return BadRequest(new { - error = "Estilos de borda personalizados são exclusivos do plano Premium. Faça upgrade para usar esta funcionalidade.", + error = _localizer["PremiumCornerStyleRequired"], requiresPremium = true, success = false }); @@ -84,7 +87,7 @@ namespace QRRapidoApp.Controllers userId ?? "anonymous", user?.IsPremium ?? false); return StatusCode(429, new { - error = "Limite de QR codes atingido", + error = _localizer["RateLimitReached"], upgradeUrl = "/Premium/Upgrade", success = false }); @@ -325,13 +328,13 @@ namespace QRRapidoApp.Controllers if (string.IsNullOrWhiteSpace(request.Content)) { _logger.LogWarning("QR generation failed - empty content provided"); - return BadRequest(new { error = "Conteúdo é obrigatório", success = false }); + 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 = "Conteúdo muito longo. Máximo 4000 caracteres.", success = false }); + return BadRequest(new { error = _localizer["ContentTooLong"], success = false }); } // Check user status @@ -343,7 +346,7 @@ namespace QRRapidoApp.Controllers _logger.LogWarning("Logo upload attempted by non-premium user - UserId: {UserId}", userId ?? "anonymous"); return BadRequest(new { - error = "Logo personalizado é exclusivo do plano Premium. Faça upgrade para usar esta funcionalidade.", + error = _localizer["PremiumLogoRequired"], requiresPremium = true, success = false }); @@ -363,7 +366,7 @@ namespace QRRapidoApp.Controllers if (logo.Length > 2 * 1024 * 1024) { _logger.LogWarning("Logo upload failed - file too large: {FileSize} bytes", logo.Length); - return BadRequest(new { error = "Logo muito grande. Máximo 2MB.", success = false }); + return BadRequest(new { error = _localizer["LogoTooLarge"], success = false }); } // Validate file format @@ -371,7 +374,7 @@ namespace QRRapidoApp.Controllers if (!allowedTypes.Contains(logo.ContentType?.ToLower())) { _logger.LogWarning("Logo upload failed - invalid format: {ContentType}", logo.ContentType); - return BadRequest(new { error = "Formato inválido. Use PNG ou JPG.", success = false }); + return BadRequest(new { error = _localizer["InvalidLogoFormat"], success = false }); } try @@ -388,7 +391,7 @@ namespace QRRapidoApp.Controllers catch (Exception ex) { _logger.LogError(ex, "Error processing logo file"); - return BadRequest(new { error = "Erro ao processar logo.", success = false }); + return BadRequest(new { error = _localizer["ErrorProcessingLogo"], success = false }); } } @@ -400,7 +403,7 @@ namespace QRRapidoApp.Controllers userId ?? "anonymous", user?.IsPremium ?? false); return StatusCode(429, new { - error = "Limite de QR codes atingido", + error = _localizer["RateLimitReached"], upgradeUrl = "/Premium/Upgrade", success = false }); @@ -512,6 +515,36 @@ namespace QRRapidoApp.Controllers } } + [HttpDelete("History/{qrId}")] + public async Task 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"] }); + } + else + { + return NotFound(new { success = false, message = _localizer["ErrorDeletingQR"] }); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error deleting QR from history - QRId: {QRId}", qrId); + return StatusCode(500, new { success = false, message = _localizer["ErrorDeletingQR"] }); + } + } + private async Task CheckRateLimitAsync(string? userId, Models.User? user) { // Premium users have unlimited QR codes diff --git a/Program.cs b/Program.cs index 6777731..dfa2752 100644 --- a/Program.cs +++ b/Program.cs @@ -157,8 +157,7 @@ builder.Services.Configure(options => var supportedCultures = new[] { new CultureInfo("pt-BR"), - new CultureInfo("es"), - new CultureInfo("en") + new CultureInfo("es-PY") }; options.DefaultRequestCulture = new RequestCulture("pt-BR", "pt-BR"); @@ -267,7 +266,7 @@ app.MapHealthChecks("/health"); // Language routes (must be first) app.MapControllerRoute( name: "localized", - pattern: "{culture:regex(^(pt-BR|es|en)$)}/{controller=Home}/{action=Index}/{id?}"); + pattern: "{culture:regex(^(pt-BR|es-PY)$)}/{controller=Home}/{action=Index}/{id?}"); // API routes app.MapControllerRoute( diff --git a/Resources/SharedResource.es-PY.resx b/Resources/SharedResource.es-PY.resx new file mode 100644 index 0000000..a9afb29 --- /dev/null +++ b/Resources/SharedResource.es-PY.resx @@ -0,0 +1,217 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ¡Genera códigos QR en segundos! + + + Contenido es obligatorio + + + Contenido muy largo. Máximo 4000 caracteres. + + + Estilos de borde personalizados son exclusivos del plan Premium. Actualiza para usar esta funcionalidad. + + + Límite de códigos QR alcanzado + + + Logo personalizado es exclusivo del plan Premium. Actualiza para usar esta funcionalidad. + + + Logo muy grande. Máximo 2MB. + + + Formato inválido. Usa PNG o JPG. + + + Perfil del Usuario + + + Historial de Códigos QR + + + Error al guardar en el historial. + + + Función no disponible + + + ¡Código QR guardado en el historial! + + + Error al compartir. Intenta otro método. + + + ¡Enlace copiado al portapapeles! + + + Ingresa el contenido del código QR + + + Contenido debe tener al menos 3 caracteres + + + Error en la validación del VCard: + + + Código QR generado con QR Rapido - ¡el generador más rápido de Paraguay! + + + QR Rapido: ¡Genera códigos QR en segundos! Generador ultra-rápido en español y portugués. Gratis, sin registro obligatorio. 30 días sin anuncios después del login. + + + Logo no proporcionado + + + Logo muy pequeño. Mínimo 32x32 píxeles. + + + Formato de imagen inválido + + + Error al procesar logo. + + + Eliminar Código QR + + + Confirmar Eliminación + + + ¿Estás seguro de que quieres eliminar este código QR de tu historial? + + + + + + No + + + ¡Código QR eliminado exitosamente! + + + Error al eliminar código QR. Inténtalo de nuevo. + + + Eliminando + + \ No newline at end of file diff --git a/Resources/SharedResource.pt-BR.resx b/Resources/SharedResource.pt-BR.resx index 02d435f..cf681f8 100644 --- a/Resources/SharedResource.pt-BR.resx +++ b/Resources/SharedResource.pt-BR.resx @@ -783,4 +783,94 @@ Usuários anônimos: 3 QR codes por dia + + Conteúdo é obrigatório + + + Conteúdo muito longo. Máximo 4000 caracteres. + + + Estilos de borda personalizados são exclusivos do plano Premium. Faça upgrade para usar esta funcionalidade. + + + Limite de QR codes atingido + + + Logo personalizado é exclusivo do plano Premium. Faça upgrade para usar esta funcionalidade. + + + Logo muito grande. Máximo 2MB. + + + Formato inválido. Use PNG ou JPG. + + + Perfil do Usuário + + + Histórico de QR Codes + + + Erro ao salvar no histórico. + + + Funcionalidade não disponível + + + Erro ao compartilhar. Tente outro método. + + + Link copiado para a área de transferência! + + + Digite o conteúdo do QR code + + + Conteúdo deve ter pelo menos 3 caracteres + + + Erro na validação do VCard: + + + QR Code gerado com QR Rapido - o gerador mais rápido do Brasil! + + + QR Rapido: Gere códigos QR em segundos! Gerador ultrarrápido em português e espanhol. Grátis, sem cadastro obrigatório. 30 dias sem anúncios após login. + + + Logo não fornecido + + + Logo muito pequeno. Mínimo 32x32 pixels. + + + Formato de imagem inválido + + + Erro ao processar logo. + + + Excluir QR Code + + + Confirmar Exclusão + + + Tem certeza que deseja excluir este QR code do seu histórico? + + + Sim + + + Não + + + QR Code excluído com sucesso! + + + Erro ao excluir QR code. Tente novamente. + + + Excluindo + \ No newline at end of file diff --git a/Resources/SharedResource.resx b/Resources/SharedResource.resx index 55cf8f7..226b02f 100644 --- a/Resources/SharedResource.resx +++ b/Resources/SharedResource.resx @@ -336,4 +336,94 @@ Sem senha + + Content is required + + + Content too long. Maximum 4000 characters. + + + Custom corner styles are exclusive to Premium plan. Upgrade to use this functionality. + + + QR codes limit reached + + + Custom logo is exclusive to Premium plan. Upgrade to use this functionality. + + + Logo too large. Maximum 2MB. + + + Invalid format. Use PNG or JPG. + + + User Profile + + + QR Codes History + + + Error saving to history. + + + Feature not available + + + Error sharing. Try another method. + + + Link copied to clipboard! + + + Enter QR code content + + + Content must have at least 3 characters + + + VCard validation error: + + + QR Code generated with QR Rapido - the fastest generator in Brazil! + + + QR Rapido: Generate QR codes in seconds! Ultra-fast generator. Free, no registration required. 30 days ad-free after login. + + + Logo not provided + + + Logo too small. Minimum 32x32 pixels. + + + Invalid image format + + + Error processing logo. + + + Delete QR Code + + + Confirm Deletion + + + Are you sure you want to delete this QR code from your history? + + + Yes + + + No + + + QR Code deleted successfully! + + + Error deleting QR code. Please try again. + + + Deleting + \ No newline at end of file diff --git a/Services/IUserService.cs b/Services/IUserService.cs index c46df8d..cba8749 100644 --- a/Services/IUserService.cs +++ b/Services/IUserService.cs @@ -22,6 +22,7 @@ namespace QRRapidoApp.Services Task SaveQRToHistoryAsync(string? userId, QRGenerationResult qrResult); Task> GetUserQRHistoryAsync(string userId, int limit = 50); Task GetQRDataAsync(string qrId); + Task DeleteQRFromHistoryAsync(string userId, string qrId); Task GetQRCountThisMonthAsync(string userId); Task GetUserEmailAsync(string userId); Task MarkPremiumCancelledAsync(string userId, DateTime cancelledAt); diff --git a/Services/QRRapidoService.cs b/Services/QRRapidoService.cs index 0dc4ec6..3a9ca6c 100644 --- a/Services/QRRapidoService.cs +++ b/Services/QRRapidoService.cs @@ -11,6 +11,7 @@ using System.Numerics; using System.Security.Cryptography; using System.Text; using System.Text.Json; +using Microsoft.Extensions.Localization; namespace QRRapidoApp.Services { @@ -21,13 +22,15 @@ namespace QRRapidoApp.Services private readonly ILogger _logger; private readonly SemaphoreSlim _semaphore; private readonly LogoReadabilityAnalyzer _readabilityAnalyzer; + private readonly IStringLocalizer _localizer; - public QRRapidoService(IDistributedCache cache, IConfiguration config, ILogger logger, LogoReadabilityAnalyzer readabilityAnalyzer) + public QRRapidoService(IDistributedCache cache, IConfiguration config, ILogger logger, LogoReadabilityAnalyzer readabilityAnalyzer, IStringLocalizer localizer) { _cache = cache; _config = config; _logger = logger; _readabilityAnalyzer = readabilityAnalyzer; + _localizer = localizer; // Limit simultaneous generations to maintain performance var maxConcurrent = _config.GetValue("Performance:MaxConcurrentGenerations", 100); @@ -215,14 +218,14 @@ namespace QRRapidoApp.Services if (logoBytes == null || logoBytes.Length == 0) { - errorMessage = "Logo não fornecido"; + errorMessage = _localizer["LogoNotProvided"]; return false; } // Validar tamanho máximo if (logoBytes.Length > 2 * 1024 * 1024) // 2MB { - errorMessage = "Logo muito grande. Máximo 2MB."; + errorMessage = _localizer["LogoTooLarge"]; return false; } @@ -234,7 +237,7 @@ namespace QRRapidoApp.Services // Validar dimensões mínimas if (image.Width < 32 || image.Height < 32) { - errorMessage = "Logo muito pequeno. Mínimo 32x32 pixels."; + errorMessage = _localizer["LogoTooSmall"]; return false; } @@ -245,7 +248,7 @@ namespace QRRapidoApp.Services } catch (Exception ex) { - errorMessage = "Formato de imagem inválido"; + errorMessage = _localizer["InvalidImageFormat"]; _logger.LogError(ex, "Error validating logo format"); return false; } diff --git a/Services/UserService.cs b/Services/UserService.cs index 5628336..37f1109 100644 --- a/Services/UserService.cs +++ b/Services/UserService.cs @@ -276,6 +276,36 @@ namespace QRRapidoApp.Services } } + public async Task DeleteQRFromHistoryAsync(string userId, string qrId) + { + try + { + // First verify that the QR code belongs to the user + var qrCode = await _context.QRCodeHistory + .Find(q => q.Id == qrId && q.UserId == userId && q.IsActive) + .FirstOrDefaultAsync(); + + if (qrCode == null) + { + _logger.LogWarning($"QR code not found or doesn't belong to user - QRId: {qrId}, UserId: {userId}"); + return false; + } + + // Soft delete: mark as inactive instead of permanently deleting + var update = Builders.Update.Set(q => q.IsActive, false); + var result = await _context.QRCodeHistory.UpdateOneAsync( + q => q.Id == qrId && q.UserId == userId, + update); + + return result.ModifiedCount > 0; + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error deleting QR from history - QRId: {qrId}, UserId: {userId}: {ex.Message}"); + return false; + } + } + public async Task GetQRCountThisMonthAsync(string userId) { try diff --git a/Tests/Services/QRRapidoServiceTests.cs b/Tests/Services/QRRapidoServiceTests.cs index 4bd14d4..288c370 100644 --- a/Tests/Services/QRRapidoServiceTests.cs +++ b/Tests/Services/QRRapidoServiceTests.cs @@ -16,6 +16,7 @@ namespace QRRapidoApp.Tests.Services private readonly Mock _configMock; private readonly Mock> _loggerMock; private readonly Mock _readabilityAnalyzerMock; + private readonly Mock> _localizerMock; private readonly QRRapidoService _service; public QRRapidoServiceTests() @@ -24,9 +25,10 @@ namespace QRRapidoApp.Tests.Services _configMock = new Mock(); _loggerMock = new Mock>(); _readabilityAnalyzerMock = new Mock(Mock.Of>(), Mock.Of>()); + _localizerMock = new Mock>(); SetupDefaultConfiguration(); - _service = new QRRapidoService(_cacheMock.Object, _configMock.Object, _loggerMock.Object, _readabilityAnalyzerMock.Object); + _service = new QRRapidoService(_cacheMock.Object, _configMock.Object, _loggerMock.Object, _readabilityAnalyzerMock.Object, _localizerMock.Object); } private void SetupDefaultConfiguration() diff --git a/Views/Account/History.cshtml b/Views/Account/History.cshtml index 4e51618..3a3d034 100644 --- a/Views/Account/History.cshtml +++ b/Views/Account/History.cshtml @@ -3,7 +3,7 @@ @inject IStringLocalizer Localizer @{ - ViewData["Title"] = "Histórico de QR Codes"; + ViewData["Title"] = Localizer["HistoryTitle"]; Layout = "~/Views/Shared/_Layout.cshtml"; var userId = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; } @@ -29,7 +29,15 @@ @foreach (var qr in Model) {
-
+
+ +
+ + + @section Scripts { @@ -307,13 +327,13 @@