From dbd3c8851b70deb4c489cb411bbf06d1ea7cf73a Mon Sep 17 00:00:00 2001 From: Ricardo Carneiro Date: Sun, 25 Jan 2026 00:44:34 -0300 Subject: [PATCH] fix: ajuste de warnings e combos --- .../DockerSecretsConfigurationProvider.cs | 2 +- Controllers/AccountController.cs | 13 +- Controllers/HomeController.cs | 282 ++++++++++++------ Middleware/LanguageRedirectionMiddleware.cs | 10 +- Program.cs | 16 +- QRRapidoApp.csproj | 2 +- Resources/SharedResource.es.resx | 3 + Resources/SharedResource.pt-BR.resx | 3 + Services/StripeService.cs | 70 +++-- Views/Home/Index.cshtml | 18 ++ Views/Shared/_Layout.cshtml | 15 +- wwwroot/js/qr-speed-generator.js | 4 +- 12 files changed, 282 insertions(+), 156 deletions(-) diff --git a/Configuration/DockerSecretsConfigurationProvider.cs b/Configuration/DockerSecretsConfigurationProvider.cs index bd43b12..64e9b80 100644 --- a/Configuration/DockerSecretsConfigurationProvider.cs +++ b/Configuration/DockerSecretsConfigurationProvider.cs @@ -38,7 +38,7 @@ namespace QRRapidoApp.Configuration { try { - var secretValue = File.ReadAllText(secretFilePath).Trim(); + var secretValue = (string?)File.ReadAllText(secretFilePath).Trim(); if (!string.IsNullOrEmpty(secretValue)) { Data[configKey] = secretValue; diff --git a/Controllers/AccountController.cs b/Controllers/AccountController.cs index a5ce319..e44d94e 100644 --- a/Controllers/AccountController.cs +++ b/Controllers/AccountController.cs @@ -170,7 +170,9 @@ namespace QRRapidoApp.Controllers var user = await _userService.GetUserByProviderAsync(scheme, providerId); if (user == null) { - user = await _userService.CreateUserAsync(email, name ?? email, scheme, providerId); + // Fix CS8625: Ensure name is not null + var safeName = !string.IsNullOrEmpty(name) ? name : (email ?? "User"); + user = await _userService.CreateUserAsync(email, safeName, scheme, providerId); } else { @@ -180,10 +182,10 @@ namespace QRRapidoApp.Controllers // Create application claims var claims = new List { - new Claim(ClaimTypes.NameIdentifier, user.Id), - new Claim(ClaimTypes.Email, user.Email), - new Claim(ClaimTypes.Name, user.Name), - new Claim("Provider", user.Provider), + new Claim(ClaimTypes.NameIdentifier, user.Id ?? string.Empty), // Fix CS8625 + new Claim(ClaimTypes.Email, user.Email ?? string.Empty), // Fix CS8625 + new Claim(ClaimTypes.Name, user.Name ?? string.Empty), // Fix CS8625 + new Claim("Provider", user.Provider ?? string.Empty), new Claim("IsPremium", user.IsPremium.ToString()) }; @@ -230,6 +232,7 @@ namespace QRRapidoApp.Controllers return RedirectToAction("Login"); } + // Ensure we are passing a non-null userId ViewBag.QRHistory = await _userService.GetUserQRHistoryAsync(userId, 10); ViewBag.MonthlyQRCount = await _userService.GetQRCountThisMonthAsync(userId); ViewBag.IsPremium = await _adDisplayService.HasValidPremiumSubscription(userId); diff --git a/Controllers/HomeController.cs b/Controllers/HomeController.cs index 652c97b..487db03 100644 --- a/Controllers/HomeController.cs +++ b/Controllers/HomeController.cs @@ -33,21 +33,69 @@ namespace QRRapidoApp.Controllers _markdownService = markdownService; } - public async Task Index() + // Default fallback route handled by Program.cs map + [HttpGet] + public async Task Index(string? qrType = null) { var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; + // Pass the requested QR type to the view to auto-select in combo + ViewBag.SelectedQRType = qrType; + + // Set SEO Meta Tags based on QR Type + if (!string.IsNullOrEmpty(qrType)) + { + switch (qrType.ToLower()) + { + case "pix": + ViewBag.Title = "Gerador de Pix Grátis"; + ViewBag.Description = "Crie QR Code PIX estático gratuitamente. Ideal para receber pagamentos e doações."; + ViewBag.Keywords = "pix, qr code pix, gerador pix, pix estatico, receber pix"; + break; + case "wifi": + ViewBag.Title = _localizer["WiFiQRTitle"]; + ViewBag.Description = _localizer["WiFiQRDescription"]; + break; + case "vcard": + ViewBag.Title = _localizer["VCardQRTitle"] ?? "Gerador de Cartão de Visita Digital"; + ViewBag.Description = _localizer["VCardQRDescription"]; + break; + case "whatsapp": + ViewBag.Title = "Gerador de Link WhatsApp"; + ViewBag.Description = _localizer["WhatsAppQRDescription"]; + break; + case "email": + ViewBag.Title = "Gerador de QR Code Email"; + ViewBag.Description = _localizer["EmailQRDescription"]; + break; + case "sms": + ViewBag.Title = "Gerador de QR Code SMS"; + ViewBag.Description = _localizer["SMSQRDescription"]; + break; + case "text": + ViewBag.Title = "Gerador de Texto para QR"; + ViewBag.Description = _localizer["TextQRDescription"]; + break; + case "url": + ViewBag.Title = "Gerador de URL para QR"; + ViewBag.Description = _localizer["URLQRDescription"]; + break; + } + } + else + { + // Default SEO + ViewBag.Title = _config["App:TaglinePT"]; + ViewBag.Keywords = _config["SEO:KeywordsPT"]; + ViewBag.Description = _localizer["QRGenerateDescription"]; + } + ViewBag.ShowAds = await _adDisplayService.ShouldShowAds(userId); ViewBag.IsPremium = await _adDisplayService.HasValidPremiumSubscription(userId ?? ""); - ViewBag.IsAuthenticated = User.Identity?.IsAuthenticated ?? false; - ViewBag.UserName = User.Identity?.Name ?? ""; + ViewBag.IsAuthenticated = User?.Identity?.IsAuthenticated ?? false; + ViewBag.UserName = User?.Identity?.Name ?? ""; _adDisplayService.SetViewBagAds(ViewBag); - // SEO and Analytics data - ViewBag.Title = _config["App:TaglinePT"]; - ViewBag.Keywords = _config["SEO:KeywordsPT"]; - ViewBag.Description = _localizer["QRGenerateDescription"]; - // User stats for logged in users if (!string.IsNullOrEmpty(userId)) { @@ -55,9 +103,42 @@ namespace QRRapidoApp.Controllers ViewBag.MonthlyQRCount = await _userService.GetQRCountThisMonthAsync(userId); } - return View(); + return View("Index"); } + // Dedicated SEO Routes - These act as virtual pages + [Route("pix")] + [Route("{culture}/pix")] + public async Task Pix() => await Index("pix"); + + [Route("wifi")] + [Route("{culture}/wifi")] + public async Task Wifi() => await Index("wifi"); + + [Route("vcard")] + [Route("{culture}/vcard")] + public async Task VCard() => await Index("vcard"); + + [Route("whatsapp")] + [Route("{culture}/whatsapp")] + public async Task WhatsApp() => await Index("whatsapp"); + + [Route("email")] + [Route("{culture}/email")] + public async Task Email() => await Index("email"); + + [Route("sms")] + [Route("{culture}/sms")] + public async Task Sms() => await Index("sms"); + + [Route("text")] + [Route("{culture}/text")] + public async Task Text() => await Index("text"); + + [Route("url")] + [Route("{culture}/url")] + public async Task UrlType() => await Index("url"); + public IActionResult Privacy() { var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; @@ -205,91 +286,102 @@ namespace QRRapidoApp.Controllers return Ok(new { status = "healthy", timestamp = DateTime.UtcNow }); } - // Sitemap endpoint for SEO - [Route("sitemap.xml")] - public async Task Sitemap() - { - var baseUrl = "https://qrrapido.site"; - var now = DateTime.UtcNow.ToString("yyyy-MM-dd"); - - var sitemapBuilder = new System.Text.StringBuilder(); - sitemapBuilder.AppendLine(@""); - sitemapBuilder.AppendLine(@""); - - void AppendUrl(string relativePath, string changeFreq, string priority, string? lastModOverride = null) - { - var normalizedPath = relativePath.StartsWith("/") - ? relativePath - : $"/{relativePath}"; - - var lastModValue = lastModOverride ?? now; - - sitemapBuilder.AppendLine($@" - - {baseUrl}{normalizedPath} - {lastModValue} - {changeFreq} - {priority} - "); - } - - // Core entry points - AppendUrl("/", "daily", "1.0"); - AppendUrl("/pt-BR", "daily", "0.9"); - AppendUrl("/es-PY", "daily", "0.9"); - - var cultures = new[] { "pt-BR", "es-PY" }; - var informationalPages = new[] - { - new { Path = "Home/About", ChangeFreq = "monthly", Priority = "0.8" }, - new { Path = "Home/Contact", ChangeFreq = "monthly", Priority = "0.8" }, - new { Path = "Home/FAQ", ChangeFreq = "weekly", Priority = "0.9" }, - new { Path = "Home/HowToUse", ChangeFreq = "weekly", Priority = "0.8" }, - new { Path = "Home/Privacy", ChangeFreq = "monthly", Priority = "0.5" }, - new { Path = "Home/Terms", ChangeFreq = "monthly", Priority = "0.5" } - }; - - foreach (var page in informationalPages) - { - AppendUrl($"/{page.Path}", page.ChangeFreq, page.Priority); - } - - foreach (var culture in cultures) - { - foreach (var page in informationalPages) - { - AppendUrl($"/{culture}/{page.Path}", page.ChangeFreq, page.Priority); - } - } - - // Dynamic tutorial pages - try - { - var allArticles = await _markdownService.GetAllArticlesForSitemapAsync(); - - foreach (var article in allArticles) - { - var slug = !string.IsNullOrWhiteSpace(article.Slug) - ? article.Slug - : article.Title.ToLower().Replace(" ", "-"); - - var encodedSlug = System.Uri.EscapeDataString(slug); - var lastMod = article.LastMod.ToString("yyyy-MM-dd"); - - AppendUrl($"/{article.Culture}/tutoriais/{encodedSlug}", "weekly", "0.8", lastMod); - } - - _logger.LogInformation("Generated sitemap with {Count} tutorial articles", allArticles.Count); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error adding tutorials to sitemap"); - } - - sitemapBuilder.AppendLine(""); - - return Content(sitemapBuilder.ToString(), "application/xml"); - } + // Sitemap endpoint for SEO + [Route("sitemap.xml")] + public async Task Sitemap() + { + var baseUrl = "https://qrrapido.site"; + var now = DateTime.UtcNow.ToString("yyyy-MM-dd"); + + var sitemapBuilder = new System.Text.StringBuilder(); + sitemapBuilder.AppendLine(@""); + sitemapBuilder.AppendLine(@""); + + void AppendUrl(string relativePath, string changeFreq, string priority, string? lastModOverride = null) + { + var normalizedPath = relativePath.StartsWith("/") + ? relativePath + : $"/{relativePath}"; + + var lastModValue = lastModOverride ?? now; + + sitemapBuilder.AppendLine($@" + + {baseUrl}{normalizedPath} + {lastModValue} + {changeFreq} + {priority} + "); + } + + // Core entry points + AppendUrl("/", "daily", "1.0"); + AppendUrl("/pt-BR", "daily", "0.9"); + AppendUrl("/es-PY", "daily", "0.9"); + + // Tools (Virtual Pages) + var tools = new[] { "pix", "wifi", "vcard", "whatsapp", "email", "sms", "text", "url" }; + var cultures = new[] { "pt-BR", "es-PY" }; + + foreach (var culture in cultures) + { + foreach (var tool in tools) + { + AppendUrl($"/{culture}/{tool}", "weekly", "0.9"); + } + } + + var informationalPages = new[] + { + new { Path = "Home/About", ChangeFreq = "monthly", Priority = "0.8" }, + new { Path = "Home/Contact", ChangeFreq = "monthly", Priority = "0.8" }, + new { Path = "Home/FAQ", ChangeFreq = "weekly", Priority = "0.9" }, + new { Path = "Home/HowToUse", ChangeFreq = "weekly", Priority = "0.8" }, + new { Path = "Home/Privacy", ChangeFreq = "monthly", Priority = "0.5" }, + new { Path = "Home/Terms", ChangeFreq = "monthly", Priority = "0.5" } + }; + + foreach (var page in informationalPages) + { + AppendUrl($"/{page.Path}", page.ChangeFreq, page.Priority); + } + + foreach (var culture in cultures) + { + foreach (var page in informationalPages) + { + AppendUrl($"/{culture}/{page.Path}", page.ChangeFreq, page.Priority); + } + } + + // Dynamic tutorial pages + try + { + var allArticles = await _markdownService.GetAllArticlesForSitemapAsync(); + + foreach (var article in allArticles) + { + var slug = !string.IsNullOrWhiteSpace(article.Slug) + ? article.Slug + : article.Title.ToLower().Replace(" ", "-"); + + var encodedSlug = System.Uri.EscapeDataString(slug); + var lastMod = article.LastMod.ToString("yyyy-MM-dd"); + + AppendUrl($"/{article.Culture}/tutoriais/{encodedSlug}", "weekly", "0.8", lastMod); + } + + _logger.LogInformation("Generated sitemap with {Count} tutorial articles", allArticles.Count); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error adding tutorials to sitemap"); + } + + sitemapBuilder.AppendLine(""); + + return Content(sitemapBuilder.ToString(), "application/xml"); + } } //public class ErrorViewModel @@ -297,4 +389,4 @@ namespace QRRapidoApp.Controllers // public string? RequestId { get; set; } // public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); //} -} +} diff --git a/Middleware/LanguageRedirectionMiddleware.cs b/Middleware/LanguageRedirectionMiddleware.cs index 9d5fecc..58a6e13 100644 --- a/Middleware/LanguageRedirectionMiddleware.cs +++ b/Middleware/LanguageRedirectionMiddleware.cs @@ -40,13 +40,9 @@ namespace QRRapidoApp.Middleware var detectedCulture = DetectBrowserLanguage(context); - // If the detected culture is the default, do not redirect. - if (detectedCulture == DefaultCulture) - { - await _next(context); - return; - } - + // ALWAYS Redirect to include culture in path for consistency and SEO + // This ensures /pix becomes /pt-BR/pix or /es-PY/pix + var redirectUrl = $"/{detectedCulture}"; if (!string.IsNullOrEmpty(path)) { diff --git a/Program.cs b/Program.cs index 6f12c37..89448ca 100644 --- a/Program.cs +++ b/Program.cs @@ -30,22 +30,10 @@ using System.Threading.RateLimiting; using Microsoft.AspNetCore.Server.Kestrel.Core; using AspNetCore.DataProtection.MongoDb; -// Fix for WSL path issues - disable StaticWebAssets completely -var options = new WebApplicationOptions -{ - Args = args, - ContentRootPath = Directory.GetCurrentDirectory(), - WebRootPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot") -}; - -// Disable StaticWebAssets for WSL compatibility +// Disable StaticWebAssets for WSL compatibility (keeping this as it might still be needed for your mixed env) Environment.SetEnvironmentVariable("ASPNETCORE_HOSTINGSTARTUP__STATICWEBASSETS__ENABLED", "false"); -var builder = WebApplication.CreateBuilder(options); - -// Add Docker Secrets as configuration source (for Swarm deployments) -// Secrets override values from appsettings.json -builder.Configuration.AddDockerSecrets(); +var builder = WebApplication.CreateBuilder(args); // Configure Serilog var loggerConfig = new LoggerConfiguration() diff --git a/QRRapidoApp.csproj b/QRRapidoApp.csproj index 63d65be..ae8f60f 100644 --- a/QRRapidoApp.csproj +++ b/QRRapidoApp.csproj @@ -22,7 +22,7 @@ - + diff --git a/Resources/SharedResource.es.resx b/Resources/SharedResource.es.resx index 3c0a60f..e0d97df 100644 --- a/Resources/SharedResource.es.resx +++ b/Resources/SharedResource.es.resx @@ -760,6 +760,9 @@ El generador de códigos QR pya'eve (ás rápido) de la web. Rei rehe (Gratis), seguro y confiable. + + Herramientas + Enlaces Útiles diff --git a/Resources/SharedResource.pt-BR.resx b/Resources/SharedResource.pt-BR.resx index 6163c52..a6f40a8 100644 --- a/Resources/SharedResource.pt-BR.resx +++ b/Resources/SharedResource.pt-BR.resx @@ -813,6 +813,9 @@ O gerador de QR codes mais rápido da web. Grátis, seguro e confiável. + + Ferramentas + Links Úteis diff --git a/Services/StripeService.cs b/Services/StripeService.cs index 39a7adb..1dd187e 100644 --- a/Services/StripeService.cs +++ b/Services/StripeService.cs @@ -97,47 +97,61 @@ namespace QRRapidoApp.Services switch (stripeEvent.Type) { case "checkout.session.completed": - var session = stripeEvent.Data.Object as Session; - if (session?.SubscriptionId != null) + if (stripeEvent.Data.Object is Session session) { - var subscriptionService = new SubscriptionService(); - var subscription = await subscriptionService.GetAsync(session.SubscriptionId); - await ProcessSubscriptionActivation(session.ClientReferenceId, subscription); + if (session.SubscriptionId != null) + { + var subscriptionService = new SubscriptionService(); + var subscription = await subscriptionService.GetAsync(session.SubscriptionId); + // Fix CS8604: Ensure ClientReferenceId is not null + var userId = session.ClientReferenceId ?? + (session.Metadata != null && session.Metadata.ContainsKey("user_id") ? session.Metadata["user_id"] : null); + + if (!string.IsNullOrEmpty(userId)) + { + await ProcessSubscriptionActivation(userId, subscription); + } + else + { + _logger.LogWarning($"Missing userId in checkout session {session.Id}"); + } + } } break; case "invoice.finalized": - var invoice = stripeEvent.Data.Object as Invoice; - var subscriptionLineItem = invoice.Lines?.Data - .FirstOrDefault(line => - !string.IsNullOrEmpty(line.SubscriptionId) || - line.Subscription != null - ); - - string subscriptionId = null; - - if (subscriptionLineItem != null) + if (stripeEvent.Data.Object is Invoice invoice) { - // Tenta obter o ID da assinatura de duas formas diferentes - subscriptionId = subscriptionLineItem.SubscriptionId - ?? subscriptionLineItem.Subscription?.Id; - } + var subscriptionLineItem = invoice.Lines?.Data + .FirstOrDefault(line => + !string.IsNullOrEmpty(line.SubscriptionId) || + line.Subscription != null + ); - if (subscriptionId != null) - { - var subscriptionService = new SubscriptionService(); - var subscription = await subscriptionService.GetAsync(subscriptionId); - var user = await _userService.GetUserByStripeCustomerIdAsync(subscription.CustomerId); - if (user != null) + string? subscriptionId = null; + + if (subscriptionLineItem != null) { - await ProcessSubscriptionActivation(user.Id, subscription); + // Tenta obter o ID da assinatura de duas formas diferentes + subscriptionId = subscriptionLineItem.SubscriptionId + ?? subscriptionLineItem.Subscription?.Id; + } + + if (subscriptionId != null) + { + var subscriptionService = new SubscriptionService(); + var subscription = await subscriptionService.GetAsync(subscriptionId); + var user = await _userService.GetUserByStripeCustomerIdAsync(subscription.CustomerId); + if (user != null) + { + await ProcessSubscriptionActivation(user.Id, subscription); + } } } break; case "customer.subscription.deleted": - var deletedSubscription = stripeEvent.Data.Object as Subscription; - if (deletedSubscription != null) + if (stripeEvent.Data.Object is Subscription deletedSubscription) { await _userService.DeactivatePremiumStatus(deletedSubscription.Id); } diff --git a/Views/Home/Index.cshtml b/Views/Home/Index.cshtml index 4c38b06..83f3af5 100644 --- a/Views/Home/Index.cshtml +++ b/Views/Home/Index.cshtml @@ -1580,6 +1580,24 @@ } }); } + + // Auto-select QR Type from Server Route (SEO) + const serverSelectedType = '@(ViewBag.SelectedQRType ?? "")'; + if (serverSelectedType) { + const qrTypeSelect = document.getElementById('qr-type'); + if (qrTypeSelect) { + // Find option with this value (case-insensitive) + const option = Array.from(qrTypeSelect.options).find(opt => + opt.value.toLowerCase() === serverSelectedType.toLowerCase() + ); + + if (option) { + qrTypeSelect.value = option.value; + // Trigger change event to load correct interface + qrTypeSelect.dispatchEvent(new Event('change')); + } + } + } }); } diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml index 56fea90..0000c0d 100644 --- a/Views/Shared/_Layout.cshtml +++ b/Views/Shared/_Layout.cshtml @@ -404,15 +404,24 @@
QR Rapido

@Localizer["FastestQRGeneratorDescription"]

-
+
+
@Localizer["Tools"]
+ +
+ -
+
@Localizer["Support"]
  • Contato
  • diff --git a/wwwroot/js/qr-speed-generator.js b/wwwroot/js/qr-speed-generator.js index c376e2a..9aa1b05 100644 --- a/wwwroot/js/qr-speed-generator.js +++ b/wwwroot/js/qr-speed-generator.js @@ -1994,8 +1994,8 @@ class QRRapidoGenerator { const urlPreview = document.getElementById('url-preview'); // Helper to safely hide - const safeHide = (el) => { if (el) el.style.display = 'none'; }; - const safeShow = (el) => { if (el) el.style.display = 'block'; }; + const safeHide = (el) => { if (el) { el.style.display = 'none'; el.classList.add('disabled-state'); } }; + const safeShow = (el) => { if (el) { el.style.display = 'block'; el.classList.remove('disabled-state'); } }; // 1. Hide EVERYTHING specific first safeHide(vcardInterface);