Files
MotoresArgentinosV2/Backend/MotoresArgentinosV2.Infrastructure/Services/SitemapGeneratorService.cs

148 lines
5.7 KiB
C#
Raw Normal View History

2026-03-21 20:11:50 -03:00
using System.Text;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using MotoresArgentinosV2.Core.Entities;
using MotoresArgentinosV2.Infrastructure.Data;
namespace MotoresArgentinosV2.Infrastructure.Services;
/// <summary>
/// Servicio de fondo que genera sitemap.xml dinámicamente.
/// Ejecuta al iniciar la app y luego cada 6 horas.
/// Incluye rutas estáticas del frontend + vehículos activos.
/// </summary>
public class SitemapGeneratorService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<SitemapGeneratorService> _logger;
private static readonly TimeSpan Interval = TimeSpan.FromHours(6);
private const string BaseUrl = "https://motoresargentinos.com";
2026-03-21 20:17:27 -03:00
// Rutas estáticas cuyo contenido cambia con cada aviso nuevo (usan lastmod dinámico)
private static readonly (string Path, string Priority, string ChangeFreq)[] DynamicDateRoutes =
2026-03-21 20:11:50 -03:00
[
2026-03-21 20:17:27 -03:00
("/", "1.0", "daily"),
("/explorar", "0.8", "daily"),
];
// Rutas estáticas cuyo contenido rara vez cambia (sin lastmod para no mentirle a Google)
private static readonly (string Path, string Priority, string ChangeFreq)[] FixedRoutes =
[
("/publicar", "0.6", "monthly"),
("/vender", "0.6", "monthly"),
("/condiciones", "0.3", "yearly"),
2026-03-21 20:11:50 -03:00
];
public SitemapGeneratorService(IServiceProvider serviceProvider, ILogger<SitemapGeneratorService> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("SitemapGeneratorService iniciado.");
while (!stoppingToken.IsCancellationRequested)
{
try
{
await GenerateSitemapAsync();
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error generando sitemap.");
}
await Task.Delay(Interval, stoppingToken);
}
}
private async Task GenerateSitemapAsync()
{
using var scope = _serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<MotoresV2DbContext>();
var config = scope.ServiceProvider.GetRequiredService<IConfiguration>();
// Ruta de salida: configurable para Docker (volumen compartido) o dev local
var outputPath = config["AppSettings:SitemapOutputPath"] ?? Path.Combine("wwwroot", "sitemap.xml");
// Obtener todos los avisos activos
var activeAds = await context.Ads
.AsNoTracking()
.Where(a => a.StatusID == (int)AdStatusEnum.Active)
.Select(a => new { a.AdID, a.PublishedAt })
.ToListAsync();
2026-03-21 20:17:27 -03:00
// Fecha del aviso más reciente (para rutas cuyo contenido cambia con nuevos avisos)
var latestPublished = activeAds
.Where(a => a.PublishedAt.HasValue)
.Max(a => a.PublishedAt)
?.ToString("yyyy-MM-dd") ?? DateTime.UtcNow.ToString("yyyy-MM-dd");
2026-03-21 20:11:50 -03:00
var sb = new StringBuilder();
sb.AppendLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
sb.AppendLine("<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">");
2026-03-21 20:17:27 -03:00
// 1. Rutas cuyo contenido cambia con cada aviso nuevo (/, /explorar)
foreach (var (path, priority, changeFreq) in DynamicDateRoutes)
{
sb.AppendLine(" <url>");
sb.AppendLine($" <loc>{BaseUrl}{path}</loc>");
sb.AppendLine($" <lastmod>{latestPublished}</lastmod>");
sb.AppendLine($" <changefreq>{changeFreq}</changefreq>");
sb.AppendLine($" <priority>{priority}</priority>");
sb.AppendLine(" </url>");
}
// 2. Rutas fijas (sin lastmod — no mentirle a Google)
foreach (var (path, priority, changeFreq) in FixedRoutes)
2026-03-21 20:11:50 -03:00
{
sb.AppendLine(" <url>");
sb.AppendLine($" <loc>{BaseUrl}{path}</loc>");
sb.AppendLine($" <changefreq>{changeFreq}</changefreq>");
sb.AppendLine($" <priority>{priority}</priority>");
sb.AppendLine(" </url>");
}
2026-03-21 20:17:27 -03:00
// 3. Rutas dinámicas (vehículos activos — lastmod = fecha de publicación real)
2026-03-21 20:11:50 -03:00
foreach (var ad in activeAds)
{
2026-03-21 20:17:27 -03:00
var lastmod = ad.PublishedAt?.ToString("yyyy-MM-dd") ?? latestPublished;
2026-03-21 20:11:50 -03:00
sb.AppendLine(" <url>");
sb.AppendLine($" <loc>{BaseUrl}/vehiculo/{ad.AdID}</loc>");
sb.AppendLine($" <lastmod>{lastmod}</lastmod>");
sb.AppendLine(" <changefreq>weekly</changefreq>");
sb.AppendLine(" <priority>0.7</priority>");
sb.AppendLine(" </url>");
}
sb.AppendLine("</urlset>");
// Escritura atómica: escribir en .tmp, luego mover
var dir = Path.GetDirectoryName(outputPath);
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
var tempPath = outputPath + ".tmp";
await File.WriteAllTextAsync(tempPath, sb.ToString(), Encoding.UTF8);
File.Move(tempPath, outputPath, overwrite: true);
2026-03-21 20:17:27 -03:00
var totalUrls = DynamicDateRoutes.Length + FixedRoutes.Length + activeAds.Count;
2026-03-21 20:11:50 -03:00
_logger.LogInformation("Sitemap generado con {TotalUrls} URLs ({StaticCount} estáticas + {DynamicCount} vehículos). Archivo: {Path}",
2026-03-21 20:17:27 -03:00
totalUrls, DynamicDateRoutes.Length + FixedRoutes.Length, activeAds.Count, outputPath);
2026-03-21 20:11:50 -03:00
}
}