fix: ajustes de rota
Some checks failed
Deploy QR Rapido / test (push) Failing after 1m18s
Deploy QR Rapido / build-and-push (push) Has been skipped
Deploy QR Rapido / deploy-staging (push) Has been skipped
Deploy QR Rapido / deploy-production (push) Has been skipped

This commit is contained in:
Ricardo Carneiro 2026-02-15 21:05:57 -03:00
parent 72bbbeea4a
commit 7dccfc10f0
2 changed files with 59 additions and 94 deletions

View File

@ -299,7 +299,7 @@ namespace QRRapidoApp.Controllers
[Route("sitemap.xml")]
public async Task<IActionResult> 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("</urlset>");
return Content(sitemapBuilder.ToString(), "application/xml");
return Content(sitemapBuilder.ToString(), "application/xml", System.Text.Encoding.UTF8);
}
}

View File

@ -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));