using Microsoft.EntityFrameworkCore; using PnvPanel.Application.Auth; using PnvPanel.Application.Common.Interfaces; using LiteCqrs; using PnvPanel.Application.Common.Models; using PnvPanel.Domain.Audit; using PnvPanel.Domain.Support; namespace PnvPanel.Application.Admin.Maintenance; public sealed class DeleteClosedTicketsCommandHandler( IAppDbContext dbContext, IFileStorage fileStorage, ICurrentUser currentUser ) : ICommandHandler> { public async Task> Handle( DeleteClosedTicketsCommand command, CancellationToken cancellationToken ) { if (currentUser.UserId is not { } adminId) return Result.Failure(AuthErrors.Unauthorized); var ticketIds = await dbContext .SupportTickets.AsNoTracking() .Where(t => t.Status == TicketStatus.Closed) .Select(t => t.Id) .ToListAsync(cancellationToken); if (ticketIds.Count == 0) return Result.Success(0); // Тикет/комментарий/вложение — плоские сущности без FK-каскада (см. SupportTicket), поэтому // порядок удаления важен: вложения (+ файлы на диске) -> комментарии -> тикеты. var comments = await dbContext .TicketComments.Where(c => ticketIds.Contains(c.TicketId)) .ToListAsync(cancellationToken); var commentIds = comments.Select(c => c.Id).ToList(); var attachments = await dbContext .TicketAttachments.Where(a => commentIds.Contains(a.CommentId)) .ToListAsync(cancellationToken); foreach (var attachment in attachments) await fileStorage.DeleteAsync(attachment.StoredFileName, cancellationToken); dbContext.TicketAttachments.RemoveRange(attachments); dbContext.TicketComments.RemoveRange(comments); var tickets = await dbContext .SupportTickets.Where(t => ticketIds.Contains(t.Id)) .ToListAsync(cancellationToken); dbContext.SupportTickets.RemoveRange(tickets); dbContext.AuditLogs.Add( AuditLog.Create( adminId, "ClosedTicketsCleanedUp", "SupportTicket", "bulk", metadata: $"{{\"count\":{ticketIds.Count}}}", AuditSource.Web ) ); return Result.Success(ticketIds.Count); } }