Update scheduling parameters and refactor channel endpoints: extend HorizonDays to 7 and RetentionDays to 90 in appsettings.json. Consolidate channel-related endpoint logic by removing obsolete files and enhancing the ShowEndpoints with audience and genre management capabilities. Improve error handling and streamline command handlers for channel operations.
build / backend (push) Successful in 7m40s
build / frontend (push) Failing after 39s
tests / backend-tests (push) Successful in 6m9s

This commit is contained in:
Leonid Pershin
2026-07-26 13:32:13 +03:00
parent c4ef954dea
commit 66040a8841
272 changed files with 27944 additions and 8699 deletions
@@ -2,4 +2,10 @@ using LiteCqrs;
namespace TeleWave.Application.Library.ListShows;
public sealed record ListShowsQuery : IQuery<IReadOnlyList<ShowSummaryDto>>;
/// <summary>
/// <paramref name="GenreId"/> — оставить только шоу с этим жанром (основным или нет).
/// <paramref name="Interstitials"/> — вернуть ролики-врезки вместо контента: у них своя страница,
/// и в общей библиотеке они только мешали бы.
/// </summary>
public sealed record ListShowsQuery(Guid? GenreId = null, bool Interstitials = false)
: IQuery<IReadOnlyList<ShowSummaryDto>>;
@@ -1,59 +1,85 @@
using LiteCqrs;
using Microsoft.EntityFrameworkCore;
using TeleWave.Application.Common.Interfaces;
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 shows = await dbContext
.Shows.AsNoTracking()
.Include(s => s.Episodes)
.OrderBy(s => s.Name)
.ToListAsync(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
);
})
.ToList();
}
}
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)
.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();
}
}