Репозиторий хранил фронтенд в CRLF, а часть бэкенда — вперемешку, хотя CI и Docker-сборка работают под Linux. Прибиваем LF атрибутом `* text=auto eol=lf` и разово нормализуем дерево, чтобы форматтеры не переписывали файлы целиком на каждом прогоне. Коммит чисто механический: изменений содержимого нет, только концы строк. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
90 lines
3.4 KiB
C#
90 lines
3.4 KiB
C#
using LiteCqrs;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using TeleWave.Application.Common.Interfaces;
|
|
using TeleWave.Domain.Library;
|
|
|
|
namespace TeleWave.Application.Library.ListShows;
|
|
|
|
public sealed class ListShowsQueryHandler(IAppDbContext dbContext)
|
|
: IQueryHandler<ListShowsQuery, IReadOnlyList<ShowSummaryDto>>
|
|
{
|
|
public async Task<IReadOnlyList<ShowSummaryDto>> Handle(
|
|
ListShowsQuery query,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
var source = dbContext
|
|
.Shows.AsNoTracking()
|
|
.Include(s => s.Episodes)
|
|
.Include(s => s.Genres)
|
|
// Серии и жанры — сиблинги: одним запросом это честное перемножение (сериал на 200 серий
|
|
// с тремя жанрами даёт 600 строк вместо 203). Разделяем.
|
|
.AsSplitQuery()
|
|
.Where(s =>
|
|
query.Interstitials
|
|
? s.Kind == ShowKind.Interstitial
|
|
: s.Kind != ShowKind.Interstitial
|
|
);
|
|
|
|
var filtered = query.GenreId is { } genreId
|
|
? source.Where(s => s.Genres.Any(g => g.GenreId == genreId))
|
|
: source;
|
|
|
|
var shows = await filtered.OrderBy(s => s.Name).ToListAsync(cancellationToken);
|
|
|
|
// Названия только для основных жанров — в списке показывается один.
|
|
var primaryIds = shows
|
|
.Select(s => s.PrimaryGenreId)
|
|
.Where(id => id is not null)
|
|
.Select(id => id!.Value)
|
|
.Distinct()
|
|
.ToList();
|
|
var genreNames = await dbContext
|
|
.Genres.AsNoTracking()
|
|
.Where(g => primaryIds.Contains(g.Id))
|
|
.ToDictionaryAsync(g => g.Id, g => g.Name, cancellationToken);
|
|
|
|
// Имена ассетов нужны, чтобы распознать сезоны (номера в модели не хранятся).
|
|
var assetIds = shows
|
|
.SelectMany(s => s.Episodes.Select(e => e.MediaAssetId))
|
|
.Distinct()
|
|
.ToList();
|
|
var names = await dbContext
|
|
.MediaAssets.AsNoTracking()
|
|
.Where(a => assetIds.Contains(a.Id))
|
|
.Select(a => new { a.Id, a.OriginalFileName })
|
|
.ToDictionaryAsync(a => a.Id, a => a.OriginalFileName, cancellationToken);
|
|
|
|
return shows
|
|
.Select(s =>
|
|
{
|
|
var seasons = s
|
|
.Episodes.Select(e =>
|
|
names.TryGetValue(e.MediaAssetId, out var n)
|
|
? EpisodeName.ParseSeason(n)
|
|
: null
|
|
)
|
|
.Where(season => season is not null)
|
|
.Distinct()
|
|
.Count();
|
|
return new ShowSummaryDto(
|
|
s.Id,
|
|
s.Name,
|
|
s.OriginalName,
|
|
s.Kind,
|
|
s.Audience,
|
|
s.Episodes.Count,
|
|
seasons,
|
|
s.Year,
|
|
s.PosterImageId is not null,
|
|
s.CreatedAt,
|
|
s.PrimaryGenreId is { } primaryId
|
|
&& genreNames.TryGetValue(primaryId, out var g)
|
|
? g
|
|
: null
|
|
);
|
|
})
|
|
.ToList();
|
|
}
|
|
}
|