fix: google search
All checks were successful
Deploy QR Rapido / test (push) Successful in 4m22s
Deploy QR Rapido / build-and-push (push) Successful in 9m2s
Deploy QR Rapido / deploy-staging (push) Has been skipped
Deploy QR Rapido / deploy-production (push) Successful in 2m52s

This commit is contained in:
Ricardo Carneiro 2025-11-14 21:46:21 -03:00
parent 707dab8075
commit ea6eacc6c6
5 changed files with 138 additions and 137 deletions

View File

@ -205,134 +205,91 @@ namespace QRRapidoApp.Controllers
return Ok(new { status = "healthy", timestamp = DateTime.UtcNow }); return Ok(new { status = "healthy", timestamp = DateTime.UtcNow });
} }
// Sitemap endpoint for SEO // Sitemap endpoint for SEO
[Route("sitemap.xml")] [Route("sitemap.xml")]
public async Task<IActionResult> Sitemap() public async Task<IActionResult> Sitemap()
{ {
var baseUrl = "https://qrrapido.site"; var baseUrl = "https://qrrapido.site";
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();
sitemapBuilder.AppendLine(@"<?xml version=""1.0"" encoding=""UTF-8""?>"); sitemapBuilder.AppendLine(@"<?xml version=""1.0"" encoding=""UTF-8""?>");
sitemapBuilder.AppendLine(@"<urlset xmlns=""http://www.sitemaps.org/schemas/sitemap/0.9"">"); sitemapBuilder.AppendLine(@"<urlset xmlns=""http://www.sitemaps.org/schemas/sitemap/0.9"">");
// Static pages void AppendUrl(string relativePath, string changeFreq, string priority, string? lastModOverride = null)
sitemapBuilder.AppendLine($@" {
<url> var normalizedPath = relativePath.StartsWith("/")
<loc>{baseUrl}/</loc> ? relativePath
<lastmod>{now}</lastmod> : $"/{relativePath}";
<changefreq>daily</changefreq>
<priority>1.0</priority> var lastModValue = lastModOverride ?? now;
</url>
<url> sitemapBuilder.AppendLine($@"
<loc>{baseUrl}/pt/</loc> <url>
<lastmod>{now}</lastmod> <loc>{baseUrl}{normalizedPath}</loc>
<changefreq>daily</changefreq> <lastmod>{lastModValue}</lastmod>
<priority>0.9</priority> <changefreq>{changeFreq}</changefreq>
</url> <priority>{priority}</priority>
<url> </url>");
<loc>{baseUrl}/es/</loc> }
<lastmod>{now}</lastmod>
<changefreq>daily</changefreq> // Core entry points
<priority>0.9</priority> AppendUrl("/", "daily", "1.0");
</url> AppendUrl("/pt-BR", "daily", "0.9");
<url> AppendUrl("/es-PY", "daily", "0.9");
<loc>{baseUrl}/pt-BR/About</loc>
<lastmod>{now}</lastmod> var cultures = new[] { "pt-BR", "es-PY" };
<changefreq>monthly</changefreq> var informationalPages = new[]
<priority>0.8</priority> {
</url> new { Path = "Home/About", ChangeFreq = "monthly", Priority = "0.8" },
<url> new { Path = "Home/Contact", ChangeFreq = "monthly", Priority = "0.8" },
<loc>{baseUrl}/es-PY/About</loc> new { Path = "Home/FAQ", ChangeFreq = "weekly", Priority = "0.9" },
<lastmod>{now}</lastmod> new { Path = "Home/HowToUse", ChangeFreq = "weekly", Priority = "0.8" },
<changefreq>monthly</changefreq> new { Path = "Home/Privacy", ChangeFreq = "monthly", Priority = "0.5" },
<priority>0.8</priority> new { Path = "Home/Terms", ChangeFreq = "monthly", Priority = "0.5" }
</url> };
<url>
<loc>{baseUrl}/pt-BR/Contact</loc> foreach (var page in informationalPages)
<lastmod>{now}</lastmod> {
<changefreq>monthly</changefreq> AppendUrl($"/{page.Path}", page.ChangeFreq, page.Priority);
<priority>0.8</priority> }
</url>
<url> foreach (var culture in cultures)
<loc>{baseUrl}/es-PY/Contact</loc> {
<lastmod>{now}</lastmod> foreach (var page in informationalPages)
<changefreq>monthly</changefreq> {
<priority>0.8</priority> AppendUrl($"/{culture}/{page.Path}", page.ChangeFreq, page.Priority);
</url> }
<url> }
<loc>{baseUrl}/pt-BR/FAQ</loc>
<lastmod>{now}</lastmod> // Dynamic tutorial pages
<changefreq>weekly</changefreq> try
<priority>0.9</priority> {
</url> var allArticles = await _markdownService.GetAllArticlesForSitemapAsync();
<url>
<loc>{baseUrl}/es-PY/FAQ</loc> foreach (var article in allArticles)
<lastmod>{now}</lastmod> {
<changefreq>weekly</changefreq> var slug = !string.IsNullOrWhiteSpace(article.Slug)
<priority>0.9</priority> ? article.Slug
</url> : article.Title.ToLower().Replace(" ", "-");
<url>
<loc>{baseUrl}/pt-BR/HowToUse</loc> var encodedSlug = System.Uri.EscapeDataString(slug);
<lastmod>{now}</lastmod> var lastMod = article.LastMod.ToString("yyyy-MM-dd");
<changefreq>weekly</changefreq>
<priority>0.8</priority> AppendUrl($"/{article.Culture}/tutoriais/{encodedSlug}", "weekly", "0.8", lastMod);
</url> }
<url>
<loc>{baseUrl}/es-PY/HowToUse</loc> _logger.LogInformation("Generated sitemap with {Count} tutorial articles", allArticles.Count);
<lastmod>{now}</lastmod> }
<changefreq>weekly</changefreq> catch (Exception ex)
<priority>0.8</priority> {
</url> _logger.LogError(ex, "Error adding tutorials to sitemap");
<url> }
<loc>{baseUrl}/Pagamento/SelecaoPlano</loc>
<lastmod>{now}</lastmod> sitemapBuilder.AppendLine("</urlset>");
<changefreq>weekly</changefreq>
<priority>0.8</priority> return Content(sitemapBuilder.ToString(), "application/xml");
</url> }
<url>
<loc>{baseUrl}/privacy</loc>
<lastmod>{now}</lastmod>
<changefreq>monthly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>{baseUrl}/terms</loc>
<lastmod>{now}</lastmod>
<changefreq>monthly</changefreq>
<priority>0.5</priority>
</url>");
// Dynamic tutorial pages
try
{
var allArticles = await _markdownService.GetAllArticlesForSitemapAsync();
foreach (var article in allArticles)
{
var slug = article.Title.ToLower().Replace(" ", "-");
var lastMod = article.LastMod.ToString("yyyy-MM-dd");
sitemapBuilder.AppendLine($@"
<url>
<loc>{baseUrl}/{article.Culture}/tutoriais/{slug}</loc>
<lastmod>{lastMod}</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>");
}
_logger.LogInformation("Generated sitemap with {Count} tutorial articles", allArticles.Count);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error adding tutorials to sitemap");
}
sitemapBuilder.AppendLine("</urlset>");
return Content(sitemapBuilder.ToString(), "application/xml");
}
} }
//public class ErrorViewModel //public class ErrorViewModel
@ -340,4 +297,4 @@ namespace QRRapidoApp.Controllers
// public string? RequestId { get; set; } // public string? RequestId { get; set; }
// public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); // public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
//} //}
} }

View File

@ -1,4 +1,5 @@
using System.Globalization; using System.Globalization;
using System.Linq;
namespace QRRapidoApp.Middleware namespace QRRapidoApp.Middleware
{ {
@ -7,6 +8,12 @@ namespace QRRapidoApp.Middleware
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private readonly ILogger<LanguageRedirectionMiddleware> _logger; private readonly ILogger<LanguageRedirectionMiddleware> _logger;
private readonly string[] _supportedCultures = { "pt-BR", "es-PY" }; private readonly string[] _supportedCultures = { "pt-BR", "es-PY" };
private readonly Dictionary<string, string> _cultureAliases = new(StringComparer.OrdinalIgnoreCase)
{
{ "pt", "pt-BR" },
{ "pt-br", "pt-BR" },
{ "es", "es-PY" }
};
private const string DefaultCulture = "pt-BR"; private const string DefaultCulture = "pt-BR";
public LanguageRedirectionMiddleware(RequestDelegate next, ILogger<LanguageRedirectionMiddleware> logger) public LanguageRedirectionMiddleware(RequestDelegate next, ILogger<LanguageRedirectionMiddleware> logger)
@ -19,6 +26,11 @@ namespace QRRapidoApp.Middleware
{ {
var path = context.Request.Path.Value?.TrimStart('/') ?? ""; var path = context.Request.Path.Value?.TrimStart('/') ?? "";
if (TryHandleCultureAlias(context, path))
{
return;
}
if (HasCultureInPath(path) || IsSpecialRoute(path)) if (HasCultureInPath(path) || IsSpecialRoute(path))
{ {
await _next(context); await _next(context);
@ -63,6 +75,40 @@ namespace QRRapidoApp.Middleware
return _supportedCultures.Contains(segments[0]); return _supportedCultures.Contains(segments[0]);
} }
private bool TryHandleCultureAlias(HttpContext context, string path)
{
if (string.IsNullOrEmpty(path))
{
return false;
}
var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries);
if (segments.Length == 0)
{
return false;
}
var firstSegment = segments[0];
if (_cultureAliases.TryGetValue(firstSegment, out var mappedCulture))
{
var remainingSegments = segments.Length > 1
? "/" + string.Join('/', segments.Skip(1))
: string.Empty;
var redirectUrl = $"/{mappedCulture}{remainingSegments}";
if (context.Request.QueryString.HasValue)
{
redirectUrl += context.Request.QueryString.Value;
}
_logger.LogInformation("Redirecting alias '{Alias}' to canonical culture URL: {RedirectUrl}", firstSegment, redirectUrl);
context.Response.Redirect(redirectUrl, permanent: true);
return true;
}
return false;
}
private bool IsSpecialRoute(string path) private bool IsSpecialRoute(string path)
{ {
var specialRoutes = new[] var specialRoutes = new[]
@ -110,4 +156,4 @@ namespace QRRapidoApp.Middleware
return DefaultCulture; return DefaultCulture;
} }
} }
} }

View File

@ -8,6 +8,7 @@
@{ @{
var isDevelopment = HostEnvironment?.IsDevelopment() ?? false; var isDevelopment = HostEnvironment?.IsDevelopment() ?? false;
var isPremiumUser = false; var isPremiumUser = false;
var currentCulture = System.Globalization.CultureInfo.CurrentUICulture?.Name ?? "pt-BR";
if (User?.Identity?.IsAuthenticated == true) if (User?.Identity?.IsAuthenticated == true)
{ {
@ -26,7 +27,7 @@
} }
} }
<!DOCTYPE html> <!DOCTYPE html>
<html lang="pt-BR"> <html lang="@currentCulture">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
@ -47,9 +48,8 @@
<link rel="canonical" href="@Context.Request.GetDisplayUrl()"> <link rel="canonical" href="@Context.Request.GetDisplayUrl()">
<!-- Hreflang for multilingual --> <!-- Hreflang for multilingual -->
<link rel="alternate" hreflang="pt-BR" href="https://qrrapido.site/pt/"> <link rel="alternate" hreflang="pt-BR" href="https://qrrapido.site/pt-BR/">
<link rel="alternate" hreflang="es" href="https://qrrapido.site/es/"> <link rel="alternate" hreflang="es-PY" href="https://qrrapido.site/es-PY/">
<link rel="alternate" hreflang="en" href="https://qrrapido.site/en/">
<link rel="alternate" hreflang="x-default" href="https://qrrapido.site/"> <link rel="alternate" hreflang="x-default" href="https://qrrapido.site/">
<!-- Open Graph --> <!-- Open Graph -->

View File

@ -176,7 +176,7 @@
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
@foreach (var related in Model.RelatedArticles) @foreach (var related in Model.RelatedArticles)
{ {
<a href="/@ViewBag.Culture/tutoriais/@related.Title.ToLower().Replace(" ", "-")" <a href="/@ViewBag.Culture/tutoriais/@related.Slug"
class="list-group-item list-group-item-action"> class="list-group-item list-group-item-action">
<h6 class="mb-1">@related.Title</h6> <h6 class="mb-1">@related.Title</h6>
<p class="mb-1 text-muted small">@related.Description</p> <p class="mb-1 text-muted small">@related.Description</p>

View File

@ -2,8 +2,6 @@ User-agent: *
Allow: / Allow: /
Sitemap: https://qrrapido.site/sitemap.xml Sitemap: https://qrrapido.site/sitemap.xml
Sitemap: https://qrrapido.site/pt-BR/sitemap.xml
Sitemap: https://qrrapido.site/es-PY/sitemap.xml
Disallow: /Admin/ Disallow: /Admin/
Disallow: /Identity/ Disallow: /Identity/