fix: ajustes de rota
This commit is contained in:
parent
72bbbeea4a
commit
7dccfc10f0
@ -299,7 +299,7 @@ namespace QRRapidoApp.Controllers
|
|||||||
[Route("sitemap.xml")]
|
[Route("sitemap.xml")]
|
||||||
public async Task<IActionResult> Sitemap()
|
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 now = DateTime.UtcNow.ToString("yyyy-MM-dd");
|
||||||
|
|
||||||
var sitemapBuilder = new System.Text.StringBuilder();
|
var sitemapBuilder = new System.Text.StringBuilder();
|
||||||
@ -324,22 +324,15 @@ namespace QRRapidoApp.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Core entry points
|
// Core entry points
|
||||||
// "/" is canonical for Portuguese, "/es-PY" for Spanish
|
|
||||||
AppendUrl("/", "daily", "1.0");
|
AppendUrl("/", "daily", "1.0");
|
||||||
AppendUrl("/es-PY", "daily", "0.9");
|
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" };
|
var tools = new[] { "pix", "wifi", "vcard", "whatsapp", "email", "sms", "texto", "url" };
|
||||||
|
|
||||||
// Portuguese tools (canonical, no prefix)
|
|
||||||
foreach (var tool in tools)
|
foreach (var tool in tools)
|
||||||
{
|
{
|
||||||
AppendUrl($"/{tool}", "weekly", "0.9");
|
AppendUrl($"/{tool}", "weekly", "0.9");
|
||||||
}
|
|
||||||
|
|
||||||
// Spanish tools (with /es-PY prefix)
|
|
||||||
foreach (var tool in tools)
|
|
||||||
{
|
|
||||||
AppendUrl($"/es-PY/{tool}", "weekly", "0.8");
|
AppendUrl($"/es-PY/{tool}", "weekly", "0.8");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,41 +347,34 @@ namespace QRRapidoApp.Controllers
|
|||||||
new { Path = "Home/Terms", ChangeFreq = "monthly", Priority = "0.4" }
|
new { Path = "Home/Terms", ChangeFreq = "monthly", Priority = "0.4" }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Portuguese informational pages (no prefix)
|
|
||||||
foreach (var page in informationalPages)
|
foreach (var page in informationalPages)
|
||||||
{
|
{
|
||||||
AppendUrl($"/{page.Path}", page.ChangeFreq, page.Priority);
|
AppendUrl($"/{page.Path}", page.ChangeFreq, page.Priority);
|
||||||
}
|
|
||||||
|
|
||||||
// Spanish informational pages
|
|
||||||
foreach (var page in informationalPages)
|
|
||||||
{
|
|
||||||
AppendUrl($"/es-PY/{page.Path}", page.ChangeFreq, page.Priority);
|
AppendUrl($"/es-PY/{page.Path}", page.ChangeFreq, page.Priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic tutorial pages
|
// Dynamic tutorial pages from Markdown
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var allArticles = await _markdownService.GetAllArticlesForSitemapAsync();
|
var allArticles = await _markdownService.GetAllArticlesForSitemapAsync();
|
||||||
|
|
||||||
foreach (var article in allArticles)
|
foreach (var article in allArticles)
|
||||||
{
|
{
|
||||||
|
// Use slug from metadata or generate from title if missing
|
||||||
var slug = !string.IsNullOrWhiteSpace(article.Slug)
|
var slug = !string.IsNullOrWhiteSpace(article.Slug)
|
||||||
? article.Slug
|
? article.Slug
|
||||||
: article.Title.ToLower().Replace(" ", "-");
|
: article.Title.ToLower().Replace(" ", "-");
|
||||||
|
|
||||||
var encodedSlug = System.Uri.EscapeDataString(slug);
|
// Ensure slug is only encoded once and special characters are handled
|
||||||
var lastMod = article.LastMod.ToString("yyyy-MM-dd");
|
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"
|
var tutorialPath = article.Culture == "pt-BR"
|
||||||
? $"/tutoriais/{encodedSlug}"
|
? $"/tutoriais/{encodedSlug}"
|
||||||
: $"/{article.Culture}/tutoriais/{encodedSlug}";
|
: $"/es-PY/tutoriais/{encodedSlug}";
|
||||||
|
|
||||||
|
var lastMod = article.LastMod.ToString("yyyy-MM-dd");
|
||||||
AppendUrl(tutorialPath, "weekly", "0.8", lastMod);
|
AppendUrl(tutorialPath, "weekly", "0.8", lastMod);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("Generated sitemap with {Count} tutorial articles", allArticles.Count);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -397,7 +383,7 @@ namespace QRRapidoApp.Controllers
|
|||||||
|
|
||||||
sitemapBuilder.AppendLine("</urlset>");
|
sitemapBuilder.AppendLine("</urlset>");
|
||||||
|
|
||||||
return Content(sitemapBuilder.ToString(), "application/xml");
|
return Content(sitemapBuilder.ToString(), "application/xml", System.Text.Encoding.UTF8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -40,113 +40,94 @@ namespace QRRapidoApp.Middleware
|
|||||||
var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||||
var firstSegment = segments.Length > 0 ? segments[0] : "";
|
var firstSegment = segments.Length > 0 ? segments[0] : "";
|
||||||
|
|
||||||
// Check if URL starts with a culture prefix
|
// Check if URL starts with a culture segment (supported or unsupported)
|
||||||
if (IsCultureSegment(firstSegment))
|
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))
|
if (string.Equals(firstSegment, "pt-BR", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var remainingPath = segments.Length > 1
|
return RedirectToCanonical(context, segments);
|
||||||
? "/" + string.Join('/', segments.Skip(1))
|
|
||||||
: "/";
|
|
||||||
|
|
||||||
if (context.Request.QueryString.HasValue)
|
|
||||||
{
|
|
||||||
remainingPath += context.Request.QueryString.Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("Redirecting /pt-BR{Path} to {RedirectUrl} (canonical)",
|
// Supported: es-PY
|
||||||
remainingPath == "/" ? "" : remainingPath, remainingPath);
|
|
||||||
|
|
||||||
context.Response.Redirect(remainingPath, permanent: true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// /es-PY/* → Continue normally (Spanish has its own URLs)
|
|
||||||
if (string.Equals(firstSegment, "es-PY", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(firstSegment, "es-PY", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// Set culture for the request
|
|
||||||
SetCulture(context, "es-PY");
|
SetCulture(context, "es-PY");
|
||||||
await _next(context);
|
await _next(context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle lowercase aliases: /pt-br → /pt-BR, /es-py → /es-PY
|
// Handle aliases or unsupported cultures (e.g. /en/, /pt/, /es/)
|
||||||
if (TryHandleCultureAlias(context, firstSegment, segments))
|
if (TryHandleCultureAliasOrUnknown(context, firstSegment, segments))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No culture prefix → Serve in Portuguese (default)
|
// No culture prefix or unknown segment -> Serve in Portuguese (default)
|
||||||
// URLs like /, /pix, /wifi, /vcard etc.
|
|
||||||
SetCulture(context, DefaultCulture);
|
SetCulture(context, DefaultCulture);
|
||||||
await _next(context);
|
await _next(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsCultureSegment(string segment)
|
private bool IsCultureLikeSegment(string segment)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(segment)) return false;
|
if (string.IsNullOrEmpty(segment)) return false;
|
||||||
|
|
||||||
// Check exact matches and aliases
|
// Matches xx (like en, pt, es) or xx-XX (like pt-BR, en-US)
|
||||||
return string.Equals(segment, "pt-BR", StringComparison.OrdinalIgnoreCase) ||
|
return segment.Length == 2 || (segment.Length == 5 && segment[2] == '-');
|
||||||
string.Equals(segment, "es-PY", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
string.Equals(segment, "pt", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
string.Equals(segment, "es", StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryHandleCultureAlias(HttpContext context, string firstSegment, string[] segments)
|
private bool TryHandleCultureAliasOrUnknown(HttpContext context, string firstSegment, string[] segments)
|
||||||
{
|
{
|
||||||
string? targetCulture = null;
|
// Map known aliases to canonical forms
|
||||||
string? targetPrefix = null;
|
if (string.Equals(firstSegment, "es", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(firstSegment, "es-py", StringComparison.OrdinalIgnoreCase))
|
||||||
// Map aliases to canonical forms
|
|
||||||
if (string.Equals(firstSegment, "pt", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
(string.Equals(firstSegment, "pt-br", StringComparison.Ordinal) && firstSegment != "pt-BR"))
|
|
||||||
{
|
{
|
||||||
// /pt/* or /pt-br/* → Redirect to /* (Portuguese is canonical without prefix)
|
return RedirectToLanguage(context, "es-PY", segments);
|
||||||
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";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
var remainingPath = segments.Length > 1
|
||||||
? "/" + string.Join('/', segments.Skip(1))
|
? "/" + string.Join('/', segments.Skip(1))
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
var redirectUrl = targetPrefix + remainingPath;
|
var redirectUrl = "/" + culture + remainingPath;
|
||||||
if (string.IsNullOrEmpty(redirectUrl)) redirectUrl = "/";
|
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)
|
if (context.Request.QueryString.HasValue)
|
||||||
{
|
{
|
||||||
redirectUrl += context.Request.QueryString.Value;
|
url += context.Request.QueryString.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("Redirecting alias '{Alias}' to canonical URL: {RedirectUrl}",
|
_logger.LogInformation("SEO Redirect: {Source} -> {Dest}", context.Request.Path, url);
|
||||||
firstSegment, redirectUrl);
|
context.Response.Redirect(url, permanent: true);
|
||||||
|
|
||||||
context.Response.Redirect(redirectUrl, permanent: true);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetCulture(HttpContext context, string culture)
|
private void SetCulture(HttpContext context, string culture)
|
||||||
{
|
{
|
||||||
var cultureInfo = new CultureInfo(culture);
|
var cultureInfo = new CultureInfo(culture);
|
||||||
CultureInfo.CurrentCulture = cultureInfo;
|
CultureInfo.CurrentCulture = cultureInfo;
|
||||||
CultureInfo.CurrentUICulture = cultureInfo;
|
CultureInfo.CurrentUICulture = cultureInfo;
|
||||||
|
|
||||||
// Store in HttpContext for downstream use
|
|
||||||
context.Items["Culture"] = culture;
|
context.Items["Culture"] = culture;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,10 +136,8 @@ namespace QRRapidoApp.Middleware
|
|||||||
var specialRoutes = new[]
|
var specialRoutes = new[]
|
||||||
{
|
{
|
||||||
"api/", "health", "_framework/", "lib/", "css/", "js/", "images/",
|
"api/", "health", "_framework/", "lib/", "css/", "js/", "images/",
|
||||||
"favicon.ico", "robots.txt", "sitemap.xml",
|
"favicon.ico", "robots.txt", "sitemap.xml", "ads.txt",
|
||||||
"signin-microsoft", "signin-google", "signout-callback-oidc",
|
"signin-", "signout-", "Account/", "Pagamento/", "Home/Error"
|
||||||
"Account/ExternalLoginCallback", "Account/Logout", "Pagamento/CreateCheckout",
|
|
||||||
"Pagamento/StripeWebhook", "api/QR", "Home/Error", "ads.txt"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return specialRoutes.Any(route => path.StartsWith(route, StringComparison.OrdinalIgnoreCase));
|
return specialRoutes.Any(route => path.StartsWith(route, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user