All checks were successful
BCards Deployment Pipeline / Run Tests (push) Successful in 4s
BCards Deployment Pipeline / PR Validation (push) Has been skipped
BCards Deployment Pipeline / Build and Push Image (push) Successful in 15m22s
BCards Deployment Pipeline / Deploy to Production (ARM - OCI) (push) Successful in 1m54s
BCards Deployment Pipeline / Deploy to Test (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
🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
287 lines
11 KiB
C#
287 lines
11 KiB
C#
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<ModerationService> _logger;
|
|
|
|
public ModerationService(
|
|
IUserPageRepository userPageRepository,
|
|
IUserRepository userRepository,
|
|
ILivePageService livePageService,
|
|
ILogger<ModerationService> logger)
|
|
{
|
|
_userPageRepository = userPageRepository;
|
|
_userRepository = userRepository;
|
|
_livePageService = livePageService;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<string> 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<bool> 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<List<UserPage>> GetPendingModerationAsync(int skip = 0, int take = 20, string? filter = null)
|
|
{
|
|
var filterBuilder = Builders<UserPage>.Filter;
|
|
var baseFilter = filterBuilder.Eq(p => p.Status, PageStatus.PendingModeration);
|
|
|
|
FilterDefinition<UserPage> 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<UserPage>.Sort
|
|
.Descending("planLimitations.specialModeration")
|
|
.Ascending(p => p.CreatedAt);
|
|
|
|
var pages = await _userPageRepository.GetManyAsync(finalFilter, sort, skip, take);
|
|
return pages.ToList();
|
|
}
|
|
|
|
public async Task<UserPage?> 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<UserPage>.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<string> 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<UserPage>.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<bool> 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<UserPage>.Filter.Eq(p => p.UserId, userId);
|
|
filter &= Builders<UserPage>.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<bool> IncrementPreviewViewAsync(string pageId)
|
|
{
|
|
var page = await _userPageRepository.GetByIdAsync(pageId);
|
|
if (page == null || page.PreviewViewCount >= 50)
|
|
return false;
|
|
|
|
var update = Builders<UserPage>.Update
|
|
.Inc(p => p.PreviewViewCount, 1);
|
|
|
|
await _userPageRepository.UpdateAsync(pageId, update);
|
|
return true;
|
|
}
|
|
|
|
public async Task<Dictionary<string, int>> GetModerationStatsAsync()
|
|
{
|
|
var stats = new Dictionary<string, int>();
|
|
|
|
var pendingCount = await _userPageRepository.CountAsync(
|
|
Builders<UserPage>.Filter.Eq(p => p.Status, PageStatus.PendingModeration));
|
|
|
|
var approvedToday = await _userPageRepository.CountAsync(
|
|
Builders<UserPage>.Filter.And(
|
|
Builders<UserPage>.Filter.Eq(p => p.Status, PageStatus.Active),
|
|
Builders<UserPage>.Filter.Gte(p => p.ApprovedAt, DateTime.UtcNow.Date)));
|
|
|
|
var rejectedToday = await _userPageRepository.CountAsync(
|
|
Builders<UserPage>.Filter.And(
|
|
Builders<UserPage>.Filter.Eq(p => p.Status, PageStatus.Rejected),
|
|
Builders<UserPage>.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<List<UserPage>> GetModerationHistoryAsync(int skip = 0, int take = 20)
|
|
{
|
|
var filter = Builders<UserPage>.Filter.Or(
|
|
Builders<UserPage>.Filter.Eq(p => p.Status, PageStatus.Active),
|
|
Builders<UserPage>.Filter.Eq(p => p.Status, PageStatus.Rejected));
|
|
|
|
var sort = Builders<UserPage>.Sort.Descending(p => p.UpdatedAt);
|
|
|
|
var pages = await _userPageRepository.GetManyAsync(filter, sort, skip, take);
|
|
return pages.ToList();
|
|
}
|
|
|
|
public async Task<UserPage?> GetPageByPreviewTokenAsync(string token)
|
|
{
|
|
var filter = Builders<UserPage>.Filter.And(
|
|
Builders<UserPage>.Filter.Eq(p => p.PreviewToken, token),
|
|
Builders<UserPage>.Filter.Gt(p => p.PreviewTokenExpiry, DateTime.UtcNow));
|
|
|
|
var pages = await _userPageRepository.GetManyAsync(filter);
|
|
return pages.FirstOrDefault();
|
|
}
|
|
} |