using System.Text.Json; using System.Text.Json.Serialization; using LiteCqrs; using Microsoft.EntityFrameworkCore; using TeleWave.Application.Broadcast; using TeleWave.Application.Common.Interfaces; using TeleWave.Application.Common.Models; using TeleWave.Application.Programming.Templates; using TeleWave.Domain.Programming; using TeleWave.Domain.Programming.Planning; namespace TeleWave.Application.Programming.Planning.Trace; public sealed class GetEntryTraceQueryHandler(IAppDbContext dbContext) : IQueryHandler> { /// Те же настройки, что при записи трейса генератором. private static readonly JsonSerializerOptions TraceJsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, Converters = { new JsonStringEnumConverter() }, }; public async Task> Handle( GetEntryTraceQuery query, CancellationToken cancellationToken ) { var entry = await dbContext .ScheduleEntries.AsNoTracking() .FirstOrDefaultAsync(e => e.Id == query.EntryId, cancellationToken); if (entry is null) return Result.Failure(ChannelErrors.NotFound); var showName = entry.ShowId is { } showId ? await dbContext .Shows.AsNoTracking() .Where(s => s.Id == showId) .Select(s => s.Name) .FirstOrDefaultAsync(cancellationToken) : null; var collectionName = entry.CollectionId is { } collectionId ? await dbContext .Collections.AsNoTracking() .Where(c => c.Id == collectionId) .Select(c => c.Name) .FirstOrDefaultAsync(cancellationToken) : null; var trace = Parse(entry.TraceJson); if (trace is null) return Result.Success( new EntryTraceDto( entry.Id, entry.StartsAtUtc, entry.EndsAtUtc, showName, entry.EpisodeIndex, null, null, null, null, null, null, null, null, null, collectionName, null, null, null, 0, false, null ) ); // Слот мог быть удалён или изменён после генерации — трейс от этого не портится, просто // часть подписей окажется пустой. var slot = trace.SlotId is { } slotId ? await dbContext .Slots.AsNoTracking() .FirstOrDefaultAsync(s => s.Id == slotId, cancellationToken) : null; var layer = slot is null ? null : await dbContext .GridLayers.AsNoTracking() .FirstOrDefaultAsync(l => l.Id == slot.LayerId, cancellationToken); var group = slot?.GroupId is { } groupId ? await dbContext .Groups.AsNoTracking() .Where(g => g.Id == groupId) .Select(g => new { g.Name, g.ItemCount }) .FirstOrDefaultAsync(cancellationToken) : null; var strategy = SlotStrategy.FromJson(slot?.StrategyJson); var junctionId = slot?.JunctionAfterId ?? slot?.JunctionBetweenId; var junctionName = junctionId is { } id ? await dbContext .JunctionTemplates.AsNoTracking() .Where(j => j.Id == id) .Select(j => j.Name) .FirstOrDefaultAsync(cancellationToken) : null; return Result.Success( new EntryTraceDto( entry.Id, entry.StartsAtUtc, entry.EndsAtUtc, showName, entry.EpisodeIndex, layer?.Name, layer?.Priority, slot?.Title, trace.SlotKind, slot?.Weekday, slot?.TargetStart, slot?.TargetDurationMinutes, group?.Name, group?.ItemCount, collectionName, trace.Strategy, strategy?.CooldownDays, trace.CandidatesAfterCooldown, trace.DriftMinutes, trace.Snapped, junctionName ) ); } private static PlanTrace? Parse(string? json) { if (string.IsNullOrWhiteSpace(json)) return null; try { return JsonSerializer.Deserialize(json, TraceJsonOptions); } catch (JsonException) { return null; } } }