128 lines
4.7 KiB
C#
128 lines
4.7 KiB
C#
|
|
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";
|
||
|
|
|
||
|
|
// Rutas estáticas públicas del frontend con sus prioridades
|
||
|
|
private static readonly (string Path, string Priority, string ChangeFreq)[] StaticRoutes =
|
||
|
|
[
|
||
|
|
("/", "1.0", "daily"),
|
||
|
|
("/explorar", "0.8", "daily"),
|
||
|
|
("/publicar", "0.6", "monthly"),
|
||
|
|
("/vender", "0.6", "monthly"),
|
||
|
|
("/condiciones","0.3", "yearly"),
|
||
|
|
];
|
||
|
|
|
||
|
|
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();
|
||
|
|
|
||
|
|
var now = DateTime.UtcNow.ToString("yyyy-MM-dd");
|
||
|
|
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\">");
|
||
|
|
|
||
|
|
// 1. Rutas estáticas
|
||
|
|
foreach (var (path, priority, changeFreq) in StaticRoutes)
|
||
|
|
{
|
||
|
|
sb.AppendLine(" <url>");
|
||
|
|
sb.AppendLine($" <loc>{BaseUrl}{path}</loc>");
|
||
|
|
sb.AppendLine($" <lastmod>{now}</lastmod>");
|
||
|
|
sb.AppendLine($" <changefreq>{changeFreq}</changefreq>");
|
||
|
|
sb.AppendLine($" <priority>{priority}</priority>");
|
||
|
|
sb.AppendLine(" </url>");
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2. Rutas dinámicas (vehículos activos)
|
||
|
|
foreach (var ad in activeAds)
|
||
|
|
{
|
||
|
|
var lastmod = ad.PublishedAt?.ToString("yyyy-MM-dd") ?? now;
|
||
|
|
|
||
|
|
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);
|
||
|
|
|
||
|
|
var totalUrls = StaticRoutes.Length + activeAds.Count;
|
||
|
|
_logger.LogInformation("Sitemap generado con {TotalUrls} URLs ({StaticCount} estáticas + {DynamicCount} vehículos). Archivo: {Path}",
|
||
|
|
totalUrls, StaticRoutes.Length, activeAds.Count, outputPath);
|
||
|
|
}
|
||
|
|
}
|