fix: fluxo de paginas e preview
All checks were successful
BCards Deployment Pipeline / Run Tests (push) Successful in 2s
BCards Deployment Pipeline / PR Validation (push) Has been skipped
BCards Deployment Pipeline / Build and Push Image (push) Successful in 16m26s
BCards Deployment Pipeline / Deploy to Production (ARM - OCI) (push) Successful in 2m20s
BCards Deployment Pipeline / Deploy to Staging (x86 - Local) (push) Has been skipped
BCards Deployment Pipeline / Cleanup Old Resources (push) Has been skipped
BCards Deployment Pipeline / Deployment Summary (push) Successful in 0s

This commit is contained in:
Ricardo Carneiro 2025-09-13 21:51:37 -03:00
parent f28dc8daa8
commit 930ce8dab3
6 changed files with 105 additions and 7 deletions

13
.dockerignore Normal file
View File

@ -0,0 +1,13 @@
bin/
obj/
.git/
.gitignore
*.md
README.md
tests/
docs/
.vs/
.vscode/
**/.DS_Store
**/Thumbs.db

View File

@ -92,6 +92,16 @@ public class UserPage : IPageDisplay
[BsonElement("previewViewCount")] [BsonElement("previewViewCount")]
public int PreviewViewCount { get; set; } = 0; public int PreviewViewCount { get; set; } = 0;
// Exclusão lógica
[BsonElement("deletedAt")]
public DateTime? DeletedAt { get; set; }
[BsonElement("deletionReason")]
public string? DeletionReason { get; set; } // "trial_expired", "user_requested", "moderation_violation"
[BsonIgnore]
public bool IsDeleted => DeletedAt.HasValue;
public string FullUrl => $"page/{Category}/{Slug}"; public string FullUrl => $"page/{Category}/{Slug}";
/// <summary> /// <summary>

View File

@ -564,7 +564,6 @@ app.UseAuthorization();
app.UseMiddleware<SmartCacheMiddleware>(); app.UseMiddleware<SmartCacheMiddleware>();
app.UseMiddleware<AuthCacheMiddleware>(); app.UseMiddleware<AuthCacheMiddleware>();
app.UseMiddleware<PlanLimitationMiddleware>(); app.UseMiddleware<PlanLimitationMiddleware>();
app.UseMiddleware<PreviewTokenMiddleware>();
app.UseMiddleware<PageStatusMiddleware>(); app.UseMiddleware<PageStatusMiddleware>();
app.UseMiddleware<ModerationAuthMiddleware>(); app.UseMiddleware<ModerationAuthMiddleware>();

View File

@ -27,7 +27,7 @@ public class ModerationService : IModerationService
public async Task<string> GeneratePreviewTokenAsync(string pageId) public async Task<string> GeneratePreviewTokenAsync(string pageId)
{ {
var token = Guid.NewGuid().ToString("N")[..16]; var token = Guid.NewGuid().ToString("N")[..16];
var expiry = DateTime.UtcNow.AddDays(30); // Token válido por 30 dias var expiry = DateTime.UtcNow.AddHours(4); // Token válido por 4 horas
var page = await _userPageRepository.GetByIdAsync(pageId); var page = await _userPageRepository.GetByIdAsync(pageId);
page.PreviewToken = token; page.PreviewToken = token;
page.PreviewTokenExpiry = expiry; page.PreviewTokenExpiry = expiry;

View File

@ -1,5 +1,6 @@
using BCards.Web.Models; using BCards.Web.Models;
using BCards.Web.Repositories; using BCards.Web.Repositories;
using BCards.Web.ViewModels;
using MongoDB.Driver; using MongoDB.Driver;
namespace BCards.Web.Services; namespace BCards.Web.Services;
@ -75,7 +76,7 @@ public class TrialExpirationService : BackgroundService
_logger.LogInformation("Checking for expired trials..."); _logger.LogInformation("Checking for expired trials...");
// Get all active trial subscriptions // Process trial expirations
var trialSubscriptions = await subscriptionRepository.GetTrialSubscriptionsAsync(); var trialSubscriptions = await subscriptionRepository.GetTrialSubscriptionsAsync();
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
@ -118,6 +119,9 @@ public class TrialExpirationService : BackgroundService
} }
_logger.LogInformation("Finished checking trial expirations"); _logger.LogInformation("Finished checking trial expirations");
// Process permanent deletions (pages deleted for more than 30 days)
await ProcessPermanentDeletionsAsync(userPageRepository);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -127,15 +131,17 @@ public class TrialExpirationService : BackgroundService
} }
private async Task HandleTrialExpiredAsync( private async Task HandleTrialExpiredAsync(
User user, User user,
Subscription subscription, Subscription subscription,
IUserPageRepository userPageRepository) IUserPageRepository userPageRepository)
{ {
// Deactivate user page // Mark user page as expired (logical deletion)
var userPage = await userPageRepository.GetByUserIdAsync(user.Id); var userPage = await userPageRepository.GetByUserIdAsync(user.Id);
if (userPage != null) if (userPage != null)
{ {
userPage.IsActive = false; userPage.Status = PageStatus.Expired;
userPage.DeletedAt = DateTime.UtcNow;
userPage.DeletionReason = "trial_expired";
userPage.UpdatedAt = DateTime.UtcNow; userPage.UpdatedAt = DateTime.UtcNow;
await userPageRepository.UpdateAsync(userPage); await userPageRepository.UpdateAsync(userPage);
} }
@ -218,4 +224,48 @@ public class TrialExpirationService : BackgroundService
// TODO: Get from configuration // TODO: Get from configuration
return "https://bcards.com.br/pricing"; return "https://bcards.com.br/pricing";
} }
private async Task ProcessPermanentDeletionsAsync(IUserPageRepository userPageRepository)
{
try
{
_logger.LogInformation("Checking for pages to permanently delete...");
// Find pages that have been logically deleted for more than 30 days
var cutoffDate = DateTime.UtcNow.AddDays(-30);
// Get all expired pages older than 30 days
var filter = MongoDB.Driver.Builders<UserPage>.Filter.And(
MongoDB.Driver.Builders<UserPage>.Filter.Eq(p => p.Status, PageStatus.Expired),
MongoDB.Driver.Builders<UserPage>.Filter.Ne(p => p.DeletedAt, null),
MongoDB.Driver.Builders<UserPage>.Filter.Lt(p => p.DeletedAt, cutoffDate)
);
var pagesToDelete = await userPageRepository.GetManyAsync(filter);
_logger.LogInformation($"Found {pagesToDelete.Count} pages to permanently delete");
foreach (var page in pagesToDelete)
{
try
{
_logger.LogInformation($"Permanently deleting page {page.Id} ({page.DisplayName}) - deleted at {page.DeletedAt}");
await userPageRepository.DeleteAsync(page.Id);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error permanently deleting page {page.Id}");
}
}
if (pagesToDelete.Count > 0)
{
_logger.LogInformation($"Permanently deleted {pagesToDelete.Count} pages");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing permanent deletions");
}
}
} }

View File

@ -74,6 +74,9 @@
case BCards.Web.ViewModels.PageStatus.Rejected: case BCards.Web.ViewModels.PageStatus.Rejected:
<span class="badge bg-danger">Rejeitada</span> <span class="badge bg-danger">Rejeitada</span>
break; break;
case BCards.Web.ViewModels.PageStatus.Expired:
<span class="badge bg-warning"><i class="fas fa-clock me-1"></i>Trial Expirado</span>
break;
case BCards.Web.ViewModels.PageStatus.Creating: case BCards.Web.ViewModels.PageStatus.Creating:
<span class="badge bg-info"><i class="fas fa-edit me-1"></i>Em Criação</span> <span class="badge bg-info"><i class="fas fa-edit me-1"></i>Em Criação</span>
break; break;
@ -83,6 +86,20 @@
} }
</div> </div>
@* Exibir histórico de rejeição se existir *@
@if (pageItem.Status == BCards.Web.ViewModels.PageStatus.Rejected && !string.IsNullOrEmpty(pageItem.Motive))
{
<div class="alert alert-danger alert-sm mb-3 p-2">
<div class="d-flex align-items-start">
<i class="fas fa-exclamation-triangle me-2 mt-1 flex-shrink-0"></i>
<div class="flex-grow-1 small">
<strong>Motivo da rejeição:</strong><br>
@pageItem.Motive
</div>
</div>
</div>
}
@if (Model.CurrentPlan.AllowsAnalytics) @if (Model.CurrentPlan.AllowsAnalytics)
{ {
<div class="row text-center small mb-3"> <div class="row text-center small mb-3">
@ -129,6 +146,15 @@
<i class="fas fa-hourglass-half me-1"></i>Aguardando Moderação <i class="fas fa-hourglass-half me-1"></i>Aguardando Moderação
</button> </button>
} }
else if (pageItem.Status == BCards.Web.ViewModels.PageStatus.Expired)
{
<a href="@Url.Action("Pricing", "Home")" class="btn btn-warning">
<i class="fas fa-crown me-1"></i>Reativar Página
</a>
<small class="text-muted d-block mt-2">
<i class="fas fa-info-circle me-1"></i>Sua página foi pausada. Escolha um plano para reativá-la.
</small>
}
else else
{ {
<button class="btn btn-secondary" disabled> <button class="btn btn-secondary" disabled>