using BCards.Web.Models; using BCards.Web.Repositories; using BCards.Web.ViewModels; using MongoDB.Driver; namespace BCards.Web.Services; public class ModerationService : IModerationService { private readonly IUserPageRepository _userPageRepository; private readonly IUserRepository _userRepository; private readonly ILivePageService _livePageService; private readonly ILogger _logger; public ModerationService( IUserPageRepository userPageRepository, IUserRepository userRepository, ILivePageService livePageService, ILogger logger) { _userPageRepository = userPageRepository; _userRepository = userRepository; _livePageService = livePageService; _logger = logger; } public async Task GeneratePreviewTokenAsync(string pageId) { var token = Guid.NewGuid().ToString("N")[..16]; var expiry = DateTime.UtcNow.AddMinutes(5); // Token válido por 5 minutos // LOG ANTES da busca _logger.LogInformation("Generating token for page {PageId} - searching page in DB", pageId); var page = await _userPageRepository.GetByIdAsync(pageId); // LOG TOKEN ANTERIOR _logger.LogInformation("Page {PageId} - Old token: {OldToken}, New token: {NewToken}", pageId, page.PreviewToken, token); page.PreviewToken = token; page.PreviewTokenExpiry = expiry; page.PreviewViewCount = 0; // LOG ANTES do update _logger.LogInformation("Updating page {PageId} with new token {Token}", pageId, token); await _userPageRepository.UpdateAsync(page); // LOG APÓS update _logger.LogInformation("Successfully updated page {PageId} with token {Token}", pageId, token); return token; } public async Task ValidatePreviewTokenAsync(string pageId, string token) { var page = await _userPageRepository.GetByIdAsync(pageId); if (page == null) return false; var isValid = page.PreviewToken == token && page.PreviewTokenExpiry > DateTime.UtcNow && page.PreviewViewCount < 50; return isValid; } public async Task> GetPendingModerationAsync(int skip = 0, int take = 20, string? filter = null) { var filterBuilder = Builders.Filter; var baseFilter = filterBuilder.Eq(p => p.Status, PageStatus.PendingModeration); FilterDefinition finalFilter = baseFilter; // Aplicar filtro de moderação especial if (!string.IsNullOrEmpty(filter)) { switch (filter.ToLower()) { case "special": // Filtrar apenas páginas com moderação especial (Premium + Afiliados) var specialFilter = filterBuilder.Eq("planLimitations.specialModeration", true); finalFilter = filterBuilder.And(baseFilter, specialFilter); break; case "normal": // Filtrar apenas páginas sem moderação especial var normalFilter = filterBuilder.Or( filterBuilder.Eq("planLimitations.specialModeration", false), filterBuilder.Exists("planLimitations.specialModeration", false) ); finalFilter = filterBuilder.And(baseFilter, normalFilter); break; default: // "all" ou qualquer outro valor: sem filtro adicional break; } } // Ordenar por moderação especial primeiro (SLA reduzido), depois por data var sort = Builders.Sort .Descending("planLimitations.specialModeration") .Ascending(p => p.CreatedAt); var pages = await _userPageRepository.GetManyAsync(finalFilter, sort, skip, take); return pages.ToList(); } public async Task GetPageForModerationAsync(string pageId) { var page = await _userPageRepository.GetByIdAsync(pageId); if (page?.Status != PageStatus.PendingModeration) return null; return page; } public async Task DeleteForModerationAsync(string pageId) { var page = await _userPageRepository.GetByIdAsync(pageId); await _userPageRepository.DeleteAsync(pageId); } public async Task ApprovePageAsync(string pageId, string moderatorId, string? notes = null) { var page = await _userPageRepository.GetByIdAsync(pageId); if (page == null) throw new ArgumentException("Page not found"); var moderationEntry = new ModerationHistory { Attempt = page.ModerationAttempts + 1, Status = "approved", ModeratorId = moderatorId, Date = DateTime.UtcNow, Reason = notes }; page.ModerationHistory.Add(moderationEntry); var update = Builders.Update .Set(p => p.Status, PageStatus.Active) .Set(p => p.ApprovedAt, DateTime.UtcNow) .Set(p => p.ModerationAttempts, page.ModerationAttempts + 1) .Set(p => p.ModerationHistory, page.ModerationHistory) .Set(p => p.PublishedAt, DateTime.UtcNow) .Unset(p => p.PreviewToken) .Unset(p => p.PreviewTokenExpiry); await _userPageRepository.UpdateAsync(pageId, update); _logger.LogInformation("Page {PageId} approved by moderator {ModeratorId}", pageId, moderatorId); // 🔥 NOVA FUNCIONALIDADE: Sincronizar para LivePage try { await _livePageService.SyncFromUserPageAsync(pageId); _logger.LogInformation("Page {PageId} synced to LivePages successfully", pageId); } catch (Exception ex) { _logger.LogError(ex, "Failed to sync page {PageId} to LivePages. Approval completed but sync failed.", pageId); // Não falhar a aprovação se sync falhar } } public async Task RejectPageAsync(string pageId, string moderatorId, string reason, List issues) { var page = await _userPageRepository.GetByIdAsync(pageId); if (page == null) throw new ArgumentException("Page not found"); var moderationEntry = new ModerationHistory { Attempt = page.ModerationAttempts + 1, Status = "rejected", ModeratorId = moderatorId, Date = DateTime.UtcNow, Reason = reason, Issues = issues }; page.ModerationHistory.Add(moderationEntry); var newStatus = page.ModerationAttempts >= 2 ? PageStatus.Rejected : PageStatus.Inactive; var userScoreDeduction = Math.Min(20, page.UserScore / 5); var update = Builders.Update .Set(p => p.Status, newStatus) .Set(p => p.ModerationAttempts, page.ModerationAttempts + 1) .Set(p => p.ModerationHistory, page.ModerationHistory) .Set(p => p.UserScore, Math.Max(0, page.UserScore - userScoreDeduction)) .Unset(p => p.PreviewToken) .Unset(p => p.PreviewTokenExpiry); await _userPageRepository.UpdateAsync(pageId, update); _logger.LogInformation("Page {PageId} rejected by moderator {ModeratorId}. Reason: {Reason}", pageId, moderatorId, reason); // Remover da LivePages se existir try { await _livePageService.DeleteByOriginalPageIdAsync(pageId); _logger.LogInformation("LivePage removed for rejected UserPage {PageId}", pageId); } catch (Exception ex) { _logger.LogError(ex, "Failed to remove LivePage for UserPage {PageId}", pageId); } } public async Task CanUserCreatePageAsync(string userId) { var user = await _userRepository.GetByIdAsync(userId); if (user == null) return false; //var rejectedPages = userPages.Count(p => p.Status == PageStatus.Rejected); var filter = Builders.Filter.Eq(p => p.UserId, userId); filter &= Builders.Filter.Eq(p => p.Status, PageStatus.Rejected); var rejectedPages = await _userPageRepository.CountAsync(filter); // Usuários com mais de 2 páginas rejeitadas não podem criar novas return rejectedPages < 2; } public async Task IncrementPreviewViewAsync(string pageId) { var page = await _userPageRepository.GetByIdAsync(pageId); if (page == null || page.PreviewViewCount >= 50) return false; var update = Builders.Update .Inc(p => p.PreviewViewCount, 1); await _userPageRepository.UpdateAsync(pageId, update); return true; } public async Task> GetModerationStatsAsync() { var stats = new Dictionary(); var pendingCount = await _userPageRepository.CountAsync( Builders.Filter.Eq(p => p.Status, PageStatus.PendingModeration)); var approvedToday = await _userPageRepository.CountAsync( Builders.Filter.And( Builders.Filter.Eq(p => p.Status, PageStatus.Active), Builders.Filter.Gte(p => p.ApprovedAt, DateTime.UtcNow.Date))); var rejectedToday = await _userPageRepository.CountAsync( Builders.Filter.And( Builders.Filter.Eq(p => p.Status, PageStatus.Rejected), Builders.Filter.Gte(p => p.UpdatedAt, DateTime.UtcNow.Date))); stats["pending"] = (int)pendingCount; stats["approvedToday"] = (int)approvedToday; stats["rejectedToday"] = (int)rejectedToday; return stats; } public async Task> GetModerationHistoryAsync(int skip = 0, int take = 20) { var filter = Builders.Filter.Or( Builders.Filter.Eq(p => p.Status, PageStatus.Active), Builders.Filter.Eq(p => p.Status, PageStatus.Rejected)); var sort = Builders.Sort.Descending(p => p.UpdatedAt); var pages = await _userPageRepository.GetManyAsync(filter, sort, skip, take); return pages.ToList(); } public async Task GetPageByPreviewTokenAsync(string token) { var filter = Builders.Filter.And( Builders.Filter.Eq(p => p.PreviewToken, token), Builders.Filter.Gt(p => p.PreviewTokenExpiry, DateTime.UtcNow)); var pages = await _userPageRepository.GetManyAsync(filter); return pages.FirstOrDefault(); } }