From 7dccfc10f076b85d84e5e13df5d86e3ce7a94c58 Mon Sep 17 00:00:00 2001 From: Ricardo Carneiro Date: Sun, 15 Feb 2026 21:05:57 -0300 Subject: [PATCH] fix: ajustes de rota --- Controllers/HomeController.cs | 32 ++---- Middleware/LanguageRedirectionMiddleware.cs | 121 ++++++++------------ 2 files changed, 59 insertions(+), 94 deletions(-) diff --git a/Controllers/HomeController.cs b/Controllers/HomeController.cs index 787a0a9..e1a5d75 100644 --- a/Controllers/HomeController.cs +++ b/Controllers/HomeController.cs @@ -299,7 +299,7 @@ namespace QRRapidoApp.Controllers [Route("sitemap.xml")] public async Task Sitemap() { - var baseUrl = "https://qrrapido.site"; + var baseUrl = $"{Request.Scheme}://{Request.Host}"; var now = DateTime.UtcNow.ToString("yyyy-MM-dd"); var sitemapBuilder = new System.Text.StringBuilder(); @@ -324,22 +324,15 @@ namespace QRRapidoApp.Controllers } // Core entry points - // "/" is canonical for Portuguese, "/es-PY" for Spanish AppendUrl("/", "daily", "1.0"); AppendUrl("/es-PY", "daily", "0.9"); - // Tools (Landing Pages) - Portuguese without prefix, Spanish with /es-PY + // Tools (Landing Pages) var tools = new[] { "pix", "wifi", "vcard", "whatsapp", "email", "sms", "texto", "url" }; - // Portuguese tools (canonical, no prefix) foreach (var tool in tools) { AppendUrl($"/{tool}", "weekly", "0.9"); - } - - // Spanish tools (with /es-PY prefix) - foreach (var tool in tools) - { AppendUrl($"/es-PY/{tool}", "weekly", "0.8"); } @@ -354,41 +347,34 @@ namespace QRRapidoApp.Controllers new { Path = "Home/Terms", ChangeFreq = "monthly", Priority = "0.4" } }; - // Portuguese informational pages (no prefix) foreach (var page in informationalPages) { AppendUrl($"/{page.Path}", page.ChangeFreq, page.Priority); - } - - // Spanish informational pages - foreach (var page in informationalPages) - { AppendUrl($"/es-PY/{page.Path}", page.ChangeFreq, page.Priority); } - // Dynamic tutorial pages + // Dynamic tutorial pages from Markdown try { var allArticles = await _markdownService.GetAllArticlesForSitemapAsync(); foreach (var article in allArticles) { + // Use slug from metadata or generate from title if missing 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"); + // Ensure slug is only encoded once and special characters are handled + var encodedSlug = slug; // Assuming slug is already URL-safe from filename - // pt-BR tutorials go under /tutoriais/, es-PY under /es-PY/tutoriais/ var tutorialPath = article.Culture == "pt-BR" ? $"/tutoriais/{encodedSlug}" - : $"/{article.Culture}/tutoriais/{encodedSlug}"; + : $"/es-PY/tutoriais/{encodedSlug}"; + var lastMod = article.LastMod.ToString("yyyy-MM-dd"); AppendUrl(tutorialPath, "weekly", "0.8", lastMod); } - - _logger.LogInformation("Generated sitemap with {Count} tutorial articles", allArticles.Count); } catch (Exception ex) { @@ -397,7 +383,7 @@ namespace QRRapidoApp.Controllers sitemapBuilder.AppendLine(""); - return Content(sitemapBuilder.ToString(), "application/xml"); + return Content(sitemapBuilder.ToString(), "application/xml", System.Text.Encoding.UTF8); } } diff --git a/Middleware/LanguageRedirectionMiddleware.cs b/Middleware/LanguageRedirectionMiddleware.cs index c92a218..d82cc87 100644 --- a/Middleware/LanguageRedirectionMiddleware.cs +++ b/Middleware/LanguageRedirectionMiddleware.cs @@ -40,104 +40,87 @@ namespace QRRapidoApp.Middleware var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries); var firstSegment = segments.Length > 0 ? segments[0] : ""; - // Check if URL starts with a culture prefix - if (IsCultureSegment(firstSegment)) + // Check if URL starts with a culture segment (supported or unsupported) + if (IsCultureLikeSegment(firstSegment)) { - // /pt-BR/* → Redirect 301 to /* (remove pt-BR prefix, it's the default) + // Supported: pt-BR (Default/Canonical) if (string.Equals(firstSegment, "pt-BR", StringComparison.OrdinalIgnoreCase)) { - var remainingPath = segments.Length > 1 - ? "/" + string.Join('/', segments.Skip(1)) - : "/"; - - if (context.Request.QueryString.HasValue) - { - remainingPath += context.Request.QueryString.Value; - } - - _logger.LogInformation("Redirecting /pt-BR{Path} to {RedirectUrl} (canonical)", - remainingPath == "/" ? "" : remainingPath, remainingPath); - - context.Response.Redirect(remainingPath, permanent: true); - return; + return RedirectToCanonical(context, segments); } - // /es-PY/* → Continue normally (Spanish has its own URLs) + // Supported: es-PY if (string.Equals(firstSegment, "es-PY", StringComparison.OrdinalIgnoreCase)) { - // Set culture for the request SetCulture(context, "es-PY"); await _next(context); return; } - // Handle lowercase aliases: /pt-br → /pt-BR, /es-py → /es-PY - if (TryHandleCultureAlias(context, firstSegment, segments)) + // Handle aliases or unsupported cultures (e.g. /en/, /pt/, /es/) + if (TryHandleCultureAliasOrUnknown(context, firstSegment, segments)) { return; } } - // No culture prefix → Serve in Portuguese (default) - // URLs like /, /pix, /wifi, /vcard etc. + // No culture prefix or unknown segment -> Serve in Portuguese (default) SetCulture(context, DefaultCulture); await _next(context); } - private bool IsCultureSegment(string segment) + private bool IsCultureLikeSegment(string segment) { if (string.IsNullOrEmpty(segment)) return false; - // Check exact matches and aliases - return string.Equals(segment, "pt-BR", StringComparison.OrdinalIgnoreCase) || - string.Equals(segment, "es-PY", StringComparison.OrdinalIgnoreCase) || - string.Equals(segment, "pt", StringComparison.OrdinalIgnoreCase) || - string.Equals(segment, "es", StringComparison.OrdinalIgnoreCase); + // Matches xx (like en, pt, es) or xx-XX (like pt-BR, en-US) + return segment.Length == 2 || (segment.Length == 5 && segment[2] == '-'); } - private bool TryHandleCultureAlias(HttpContext context, string firstSegment, string[] segments) + private bool TryHandleCultureAliasOrUnknown(HttpContext context, string firstSegment, string[] segments) { - string? targetCulture = null; - string? targetPrefix = null; - - // Map aliases to canonical forms - if (string.Equals(firstSegment, "pt", StringComparison.OrdinalIgnoreCase) || - (string.Equals(firstSegment, "pt-br", StringComparison.Ordinal) && firstSegment != "pt-BR")) + // Map known aliases to canonical forms + if (string.Equals(firstSegment, "es", StringComparison.OrdinalIgnoreCase) || + string.Equals(firstSegment, "es-py", StringComparison.OrdinalIgnoreCase)) { - // /pt/* or /pt-br/* → Redirect to /* (Portuguese is canonical without prefix) - targetCulture = "pt-BR"; - targetPrefix = ""; // No prefix for Portuguese - } - else if (string.Equals(firstSegment, "es", StringComparison.OrdinalIgnoreCase) || - (string.Equals(firstSegment, "es-py", StringComparison.OrdinalIgnoreCase) && firstSegment != "es-PY")) - { - // /es/* or /es-py/* → Redirect to /es-PY/* - targetCulture = "es-PY"; - targetPrefix = "/es-PY"; + return RedirectToLanguage(context, "es-PY", segments); } - if (targetCulture != null) + // For anything else that looks like a culture (en, pt, fr, etc.) + // but isn't explicitly es-PY, we redirect to the canonical (no prefix) + // This prevents 404s for /en/, /pt-BR/ (redirects to /), etc. + return RedirectToCanonical(context, segments); + } + + private bool RedirectToLanguage(HttpContext context, string culture, string[] segments) + { + var remainingPath = segments.Length > 1 + ? "/" + string.Join('/', segments.Skip(1)) + : ""; + + var redirectUrl = "/" + culture + remainingPath; + return ExecuteRedirect(context, redirectUrl); + } + + private bool RedirectToCanonical(HttpContext context, string[] segments) + { + var remainingPath = segments.Length > 1 + ? "/" + string.Join('/', segments.Skip(1)) + : "/"; + + return ExecuteRedirect(context, remainingPath); + } + + private bool ExecuteRedirect(HttpContext context, string url) + { + if (context.Request.QueryString.HasValue) { - var remainingPath = segments.Length > 1 - ? "/" + string.Join('/', segments.Skip(1)) - : ""; - - var redirectUrl = targetPrefix + remainingPath; - if (string.IsNullOrEmpty(redirectUrl)) redirectUrl = "/"; - - if (context.Request.QueryString.HasValue) - { - redirectUrl += context.Request.QueryString.Value; - } - - _logger.LogInformation("Redirecting alias '{Alias}' to canonical URL: {RedirectUrl}", - firstSegment, redirectUrl); - - context.Response.Redirect(redirectUrl, permanent: true); - return true; + url += context.Request.QueryString.Value; } - return false; + _logger.LogInformation("SEO Redirect: {Source} -> {Dest}", context.Request.Path, url); + context.Response.Redirect(url, permanent: true); + return true; } private void SetCulture(HttpContext context, string culture) @@ -145,8 +128,6 @@ namespace QRRapidoApp.Middleware var cultureInfo = new CultureInfo(culture); CultureInfo.CurrentCulture = cultureInfo; CultureInfo.CurrentUICulture = cultureInfo; - - // Store in HttpContext for downstream use context.Items["Culture"] = culture; } @@ -155,10 +136,8 @@ namespace QRRapidoApp.Middleware var specialRoutes = new[] { "api/", "health", "_framework/", "lib/", "css/", "js/", "images/", - "favicon.ico", "robots.txt", "sitemap.xml", - "signin-microsoft", "signin-google", "signout-callback-oidc", - "Account/ExternalLoginCallback", "Account/Logout", "Pagamento/CreateCheckout", - "Pagamento/StripeWebhook", "api/QR", "Home/Error", "ads.txt" + "favicon.ico", "robots.txt", "sitemap.xml", "ads.txt", + "signin-", "signout-", "Account/", "Pagamento/", "Home/Error" }; return specialRoutes.Any(route => path.StartsWith(route, StringComparison.OrdinalIgnoreCase));