232 lines
8.1 KiB
C#
232 lines
8.1 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
|
using BCards.Web.Services;
|
|
using BCards.Web.Models;
|
|
using BCards.Web.ViewModels;
|
|
using BCards.Web.Repositories;
|
|
using System.Security.Claims;
|
|
using BCards.Web.Attributes;
|
|
|
|
namespace BCards.Web.Controllers;
|
|
|
|
[ModeratorAuthorize]
|
|
[Route("Moderation")]
|
|
public class ModerationController : Controller
|
|
{
|
|
private readonly IModerationService _moderationService;
|
|
private readonly IEmailService _emailService;
|
|
private readonly IUserRepository _userRepository;
|
|
private readonly ILogger<ModerationController> _logger;
|
|
|
|
public ModerationController(
|
|
IModerationService moderationService,
|
|
IEmailService emailService,
|
|
IUserRepository userRepository,
|
|
ILogger<ModerationController> logger)
|
|
{
|
|
_moderationService = moderationService;
|
|
_emailService = emailService;
|
|
_userRepository = userRepository;
|
|
_logger = logger;
|
|
}
|
|
|
|
[HttpGet("Dashboard")]
|
|
public async Task<IActionResult> Dashboard(int page = 1, int size = 20, string? filter = null)
|
|
{
|
|
var skip = (page - 1) * size;
|
|
var pendingPages = await _moderationService.GetPendingModerationAsync(skip, size, filter);
|
|
var stats = await _moderationService.GetModerationStatsAsync();
|
|
|
|
var viewModel = new ModerationDashboardViewModel
|
|
{
|
|
PendingPages = pendingPages.Select(p => new PendingPageViewModel
|
|
{
|
|
Id = p.Id,
|
|
DisplayName = p.DisplayName,
|
|
Category = p.Category,
|
|
Slug = p.Slug,
|
|
CreatedAt = p.CreatedAt,
|
|
ModerationAttempts = p.ModerationAttempts,
|
|
PlanType = p.PlanLimitations.PlanType.ToString(),
|
|
IsSpecialModeration = p.PlanLimitations.SpecialModeration ?? false,
|
|
PreviewUrl = !string.IsNullOrEmpty(p.PreviewToken)
|
|
? $"/page/{p.Category}/{p.Slug}?preview={p.PreviewToken}"
|
|
: null
|
|
}).ToList(),
|
|
Stats = stats,
|
|
CurrentPage = page,
|
|
PageSize = size,
|
|
HasNextPage = pendingPages.Count == size,
|
|
CurrentFilter = filter ?? "all"
|
|
};
|
|
|
|
return View(viewModel);
|
|
}
|
|
|
|
[HttpGet("Review/{id}")]
|
|
public async Task<IActionResult> Review(string id)
|
|
{
|
|
var page = await _moderationService.GetPageForModerationAsync(id);
|
|
if (page == null)
|
|
{
|
|
TempData["Error"] = "Página não encontrada ou não está pendente de moderação.";
|
|
return RedirectToAction("Dashboard");
|
|
}
|
|
|
|
var user = await _userRepository.GetByIdAsync(page.UserId);
|
|
if (user == null)
|
|
{
|
|
TempData["Error"] = "Usuário não encontrado.";
|
|
return RedirectToAction("Dashboard");
|
|
}
|
|
|
|
var viewModel = new ModerationReviewViewModel
|
|
{
|
|
Page = page,
|
|
User = user,
|
|
PreviewUrl = !string.IsNullOrEmpty(page.PreviewToken)
|
|
? $"/page/{page.Category}/{page.Slug}?preview={page.PreviewToken}"
|
|
: null,
|
|
ModerationCriteria = GetModerationCriteria()
|
|
};
|
|
|
|
return View(viewModel);
|
|
}
|
|
|
|
[HttpPost("Approve/{id}")]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Approve(string id, string notes)
|
|
{
|
|
try
|
|
{
|
|
var page = await _moderationService.GetPageForModerationAsync(id);
|
|
if (page == null)
|
|
{
|
|
TempData["Error"] = "Página não encontrada.";
|
|
return RedirectToAction("Dashboard");
|
|
}
|
|
|
|
var user = await _userRepository.GetByIdAsync(page.UserId);
|
|
var moderatorId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "system";
|
|
|
|
await _moderationService.ApprovePageAsync(id, moderatorId, notes);
|
|
|
|
if (user != null)
|
|
{
|
|
await _emailService.SendModerationStatusAsync(
|
|
user.Email,
|
|
user.Name,
|
|
page.DisplayName,
|
|
PageStatus.Active.ToString());
|
|
}
|
|
|
|
TempData["Success"] = $"Página '{page.DisplayName}' aprovada com sucesso!";
|
|
return RedirectToAction("Dashboard");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error approving page {PageId}", id);
|
|
TempData["Error"] = "Erro ao aprovar página.";
|
|
return RedirectToAction("Review", new { id });
|
|
}
|
|
}
|
|
|
|
[HttpPost("Reject/{id}")]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Reject(string id, string reason, List<string> issues)
|
|
{
|
|
try
|
|
{
|
|
var page = await _moderationService.GetPageForModerationAsync(id);
|
|
if (page == null)
|
|
{
|
|
TempData["Error"] = "Página não encontrada.";
|
|
return RedirectToAction("Dashboard");
|
|
}
|
|
|
|
var user = await _userRepository.GetByIdAsync(page.UserId);
|
|
var moderatorId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "system";
|
|
|
|
await _moderationService.RejectPageAsync(id, moderatorId, reason, issues);
|
|
|
|
if (user != null)
|
|
{
|
|
await _emailService.SendModerationStatusAsync(
|
|
user.Email,
|
|
user.Name,
|
|
page.DisplayName,
|
|
"rejected",
|
|
reason);
|
|
}
|
|
|
|
TempData["Success"] = $"Página '{page.DisplayName}' rejeitada.";
|
|
return RedirectToAction("Dashboard");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error rejecting page {PageId}", id);
|
|
TempData["Error"] = "Erro ao rejeitar página.";
|
|
return RedirectToAction("Review", new { id });
|
|
}
|
|
}
|
|
|
|
[HttpGet("History")]
|
|
public async Task<IActionResult> History(int page = 1, int size = 20)
|
|
{
|
|
var skip = (page - 1) * size;
|
|
var historyPages = await _moderationService.GetModerationHistoryAsync(skip, size);
|
|
|
|
var viewModel = new ModerationHistoryViewModel
|
|
{
|
|
Pages = historyPages.Select(p => new ModerationPageViewModel
|
|
{
|
|
Id = p.Id,
|
|
DisplayName = p.DisplayName,
|
|
Category = p.Category,
|
|
Slug = p.Slug,
|
|
CreatedAt = p.CreatedAt,
|
|
Status = p.Status.ToString(),
|
|
ModerationAttempts = p.ModerationAttempts,
|
|
PlanType = p.PlanLimitations.PlanType.ToString(),
|
|
ApprovedAt = p.ApprovedAt,
|
|
LastModerationEntry = p.ModerationHistory.LastOrDefault()
|
|
}).ToList(),
|
|
CurrentPage = page,
|
|
PageSize = size,
|
|
HasNextPage = historyPages.Count == size
|
|
};
|
|
|
|
return View(viewModel);
|
|
}
|
|
|
|
private List<ModerationCriterion> GetModerationCriteria()
|
|
{
|
|
return new List<ModerationCriterion>
|
|
{
|
|
new() { Category = "Conteúdo Proibido", Items = new List<string>
|
|
{
|
|
"Pornografia e conteúdo sexual explícito",
|
|
"Drogas ilegais e substâncias controladas",
|
|
"Armas e explosivos",
|
|
"Atividades ilegais (fraudes, pirataria)",
|
|
"Apostas e jogos de azar",
|
|
"Criptomoedas e esquemas de pirâmide",
|
|
"Conteúdo que promove violência ou ódio",
|
|
"Spam e links suspeitos/maliciosos"
|
|
}},
|
|
new() { Category = "Conteúdo Suspeito", Items = new List<string>
|
|
{
|
|
"Excesso de anúncios (>30% dos links)",
|
|
"Sites com pop-ups excessivos",
|
|
"Links encurtados suspeitos",
|
|
"Conteúdo que imita marcas conhecidas",
|
|
"Produtos \"milagrosos\""
|
|
}},
|
|
new() { Category = "Verificações Técnicas", Items = new List<string>
|
|
{
|
|
"Links funcionais (não quebrados)",
|
|
"Sites com SSL válido",
|
|
"Não redirecionamentos maliciosos"
|
|
}}
|
|
};
|
|
}
|
|
} |