From aea64c0b8ed4c45672e2521f8616788eada2d7cf Mon Sep 17 00:00:00 2001 From: Ricardo Carneiro Date: Fri, 12 Sep 2025 08:44:05 -0300 Subject: [PATCH] fix: bug para excluir trial --- .../Controllers/SitemapController.cs | 48 +++---- .../Services/TrialExpirationService.cs | 117 ++++++++++++------ 2 files changed, 106 insertions(+), 59 deletions(-) diff --git a/src/BCards.Web/Controllers/SitemapController.cs b/src/BCards.Web/Controllers/SitemapController.cs index f38d67e..674b8fd 100644 --- a/src/BCards.Web/Controllers/SitemapController.cs +++ b/src/BCards.Web/Controllers/SitemapController.cs @@ -30,40 +30,44 @@ public class SitemapController : Controller // 🔥 NOVA FUNCIONALIDADE: Usar LivePages em vez de UserPages var livePages = await _livePageService.GetAllActiveAsync(); + // Define namespace corretamente para evitar conflitos + XNamespace ns = "http://www.sitemaps.org/schemas/sitemap/0.9"; + + // Construir URLs das páginas dinâmicas separadamente para evitar problemas + var dynamicUrls = livePages.Select(page => + new XElement(ns + "url", + new XElement(ns + "loc", $"{Request.Scheme}://{Request.Host}/page/{page.Category?.Replace(" ", "-")?.ToLower()}/{page.Slug}"), + new XElement(ns + "lastmod", page.LastSyncAt.ToString("yyyy-MM-dd")), + new XElement(ns + "changefreq", "weekly"), + new XElement(ns + "priority", "0.8") + ) + ).ToList(); + var sitemap = new XDocument( new XDeclaration("1.0", "utf-8", "yes"), - new XElement("urlset", - new XAttribute("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9"), - + new XElement(ns + "urlset", // Add static pages - new XElement("url", - new XElement("loc", $"{Request.Scheme}://{Request.Host}/"), - new XElement("lastmod", DateTime.UtcNow.ToString("yyyy-MM-dd")), - new XElement("changefreq", "daily"), - new XElement("priority", "1.0") + new XElement(ns + "url", + new XElement(ns + "loc", $"{Request.Scheme}://{Request.Host}/"), + new XElement(ns + "lastmod", DateTime.UtcNow.ToString("yyyy-MM-dd")), + new XElement(ns + "changefreq", "daily"), + new XElement(ns + "priority", "1.0") ), - new XElement("url", - new XElement("loc", $"{Request.Scheme}://{Request.Host}/Home/Pricing"), - new XElement("lastmod", DateTime.UtcNow.ToString("yyyy-MM-dd")), - new XElement("changefreq", "weekly"), - new XElement("priority", "0.9") + new XElement(ns + "url", + new XElement(ns + "loc", $"{Request.Scheme}://{Request.Host}/Home/Pricing"), + new XElement(ns + "lastmod", DateTime.UtcNow.ToString("yyyy-MM-dd")), + new XElement(ns + "changefreq", "weekly"), + new XElement(ns + "priority", "0.9") ), // Add live pages (SEO-optimized URLs only) - livePages.Select(page => - new XElement("url", - new XElement("loc", $"{Request.Scheme}://{Request.Host}/page/{page.Category}/{page.Slug}"), - new XElement("lastmod", page.LastSyncAt.ToString("yyyy-MM-dd")), - new XElement("changefreq", "weekly"), - new XElement("priority", "0.8") - ) - ) + dynamicUrls ) ); _logger.LogInformation($"Generated sitemap with {livePages.Count} live pages"); - return Content(sitemap.ToString(), "application/xml", Encoding.UTF8); + return Content(sitemap.ToString(SaveOptions.DisableFormatting), "application/xml", Encoding.UTF8); } catch (Exception ex) { diff --git a/src/BCards.Web/Services/TrialExpirationService.cs b/src/BCards.Web/Services/TrialExpirationService.cs index 3123999..d2f5f9f 100644 --- a/src/BCards.Web/Services/TrialExpirationService.cs +++ b/src/BCards.Web/Services/TrialExpirationService.cs @@ -20,67 +20,110 @@ public class TrialExpirationService : BackgroundService protected override async Task ExecuteAsync(CancellationToken stoppingToken) { + _logger.LogInformation("TrialExpirationService started"); + while (!stoppingToken.IsCancellationRequested) { try { await ProcessTrialExpirationsAsync(); + + // Verificar cancelamento antes de fazer delay + if (stoppingToken.IsCancellationRequested) + break; + await Task.Delay(_checkInterval, stoppingToken); } + catch (OperationCanceledException) + { + // Cancelamento normal - não é erro + _logger.LogInformation("TrialExpirationService is being cancelled"); + break; + } catch (Exception ex) { _logger.LogError(ex, "Error processing trial expirations"); - await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken); // Wait 5 minutes on error + + // Verificar cancelamento antes de fazer delay de erro + if (stoppingToken.IsCancellationRequested) + break; + + try + { + await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken); // Wait 5 minutes on error + } + catch (OperationCanceledException) + { + // Cancelamento durante delay de erro - também é normal + _logger.LogInformation("TrialExpirationService cancelled during error delay"); + break; + } } } + + _logger.LogInformation("TrialExpirationService stopped"); } private async Task ProcessTrialExpirationsAsync() { - using var scope = _serviceProvider.CreateScope(); - var subscriptionRepository = scope.ServiceProvider.GetRequiredService(); - var userPageRepository = scope.ServiceProvider.GetRequiredService(); - var userRepository = scope.ServiceProvider.GetRequiredService(); - - _logger.LogInformation("Checking for expired trials..."); - - // Get all active trial subscriptions - var trialSubscriptions = await subscriptionRepository.GetTrialSubscriptionsAsync(); - var now = DateTime.UtcNow; - - foreach (var subscription in trialSubscriptions) + try { - try + using var scope = _serviceProvider.CreateScope(); + var subscriptionRepository = scope.ServiceProvider.GetRequiredService(); + var userPageRepository = scope.ServiceProvider.GetRequiredService(); + var userRepository = scope.ServiceProvider.GetRequiredService(); + + _logger.LogInformation("Checking for expired trials..."); + + // Get all active trial subscriptions + var trialSubscriptions = await subscriptionRepository.GetTrialSubscriptionsAsync(); + var now = DateTime.UtcNow; + + _logger.LogInformation($"Found {trialSubscriptions.Count} trial subscriptions to process"); + + foreach (var subscription in trialSubscriptions) { - var user = await userRepository.GetByIdAsync(subscription.UserId); - if (user == null) continue; - - var daysUntilExpiration = (subscription.CurrentPeriodEnd - now).TotalDays; - - if (daysUntilExpiration <= 0) + try { - // Trial expired - deactivate page - _logger.LogInformation($"Trial expired for user {user.Email}"); - await HandleTrialExpiredAsync(user, subscription, userPageRepository); + var user = await userRepository.GetByIdAsync(subscription.UserId); + if (user == null) + { + _logger.LogWarning($"User not found for subscription {subscription.Id}"); + continue; + } + + var daysUntilExpiration = (subscription.CurrentPeriodEnd - now).TotalDays; + + if (daysUntilExpiration <= 0) + { + // Trial expired - deactivate page + _logger.LogInformation($"Trial expired for user {user.Email}"); + await HandleTrialExpiredAsync(user, subscription, userPageRepository); + } + else if (daysUntilExpiration <= 2 && !user.NotifiedOfExpiration) + { + // Trial expiring soon - send notification + _logger.LogInformation($"Trial expiring in {daysUntilExpiration:F1} days for user {user.Email}"); + await SendExpirationWarningAsync(user, subscription, daysUntilExpiration); + + // Mark as notified + user.NotifiedOfExpiration = true; + await userRepository.UpdateAsync(user); + } } - else if (daysUntilExpiration <= 2 && !user.NotifiedOfExpiration) + catch (Exception ex) { - // Trial expiring soon - send notification - _logger.LogInformation($"Trial expiring in {daysUntilExpiration:F1} days for user {user.Email}"); - await SendExpirationWarningAsync(user, subscription, daysUntilExpiration); - - // Mark as notified - user.NotifiedOfExpiration = true; - await userRepository.UpdateAsync(user); + _logger.LogError(ex, $"Error processing trial for subscription {subscription.Id}"); } } - catch (Exception ex) - { - _logger.LogError(ex, $"Error processing trial for subscription {subscription.Id}"); - } + + _logger.LogInformation("Finished checking trial expirations"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Critical error in ProcessTrialExpirationsAsync"); + throw; // Re-throw para ser tratado pelo ExecuteAsync } - - _logger.LogInformation("Finished checking trial expirations"); } private async Task HandleTrialExpiredAsync(