using LiteCqrs; using TeleWave.Api.Common; using TeleWave.Application.Broadcast; using TeleWave.Application.Broadcast.CreateChannel; using TeleWave.Application.Broadcast.GetChannel; using TeleWave.Application.Broadcast.GetSchedule; using TeleWave.Application.Broadcast.ListChannels; using TeleWave.Application.Broadcast.UpdateChannelSettings; using TeleWave.Application.Broadcast.UpdateChannelTime; using TeleWave.Domain.Broadcast; using TeleWave.Infrastructure.Identity; using TeleWave.Application.Broadcast.UpdateViewerSettings; using TeleWave.Application.Programming.Planning.Trace; namespace TeleWave.Api.Endpoints; /// /// Админ-эндпоинты канала: создание, список, настройки, время и чтение расписания. Заставки — /// в ChannelEndpoints.Bumpers.cs. Что и когда идёт в эфире, задаёт шаблон сетки /// (TemplateEndpoints). /// public static partial class ChannelEndpoints { public static IEndpointRouteBuilder MapChannelEndpoints(this IEndpointRouteBuilder app) { var admin = app.MapGroup("/api/admin/channels") .WithTags("Admin.Channels") .RequireAuthorization(policy => policy.RequireRole(RoleNames.Admin)); admin.MapPost("", CreateChannel).Produces(StatusCodes.Status201Created); admin.MapGet("", ListChannels).Produces>(); admin.MapGet("/{id:guid}", GetChannel).Produces(); admin .MapPut("/{id:guid}/settings", UpdateSettings) .Produces(StatusCodes.Status204NoContent); admin.MapPut("/{id:guid}/time", UpdateTime).Produces(StatusCodes.Status204NoContent); admin .MapPost("/{id:guid}/bumper/templates", AddBumperTemplate) .Produces(StatusCodes.Status201Created); admin .MapPut("/{id:guid}/bumper/templates/{templateId:guid}", UpdateBumperTemplate) .Produces(StatusCodes.Status204NoContent); admin .MapDelete("/{id:guid}/bumper/templates/{templateId:guid}", RemoveBumperTemplate) .Produces(StatusCodes.Status204NoContent); admin .MapPut("/{id:guid}/bumper/templates/{templateId:guid}/audio", UploadTemplateAudio) .Produces(StatusCodes.Status204NoContent); admin .MapDelete("/{id:guid}/bumper/templates/{templateId:guid}/audio", ClearTemplateAudio) .Produces(StatusCodes.Status204NoContent); admin .MapPut( "/{id:guid}/bumper/templates/{templateId:guid}/background", SetTemplateBackground ) .Produces(StatusCodes.Status204NoContent); admin .MapDelete( "/{id:guid}/bumper/templates/{templateId:guid}/background", ClearTemplateBackground ) .Produces(StatusCodes.Status204NoContent); admin .MapPost("/{id:guid}/bumper/templates/{templateId:guid}/preview", RenderPreview) .Produces(StatusCodes.Status204NoContent); admin.MapGet( "/{id:guid}/bumper/templates/{templateId:guid}/preview/{variantId:guid}/index.m3u8", PreviewPlaylist ); admin.MapGet( "/{id:guid}/bumper/templates/{templateId:guid}/preview/{variantId:guid}/{file}", PreviewSegment ); admin .MapPost("/{id:guid}/bumper/templates/{templateId:guid}/variants", AddBumperVariant) .Produces(StatusCodes.Status201Created); admin .MapPut( "/{id:guid}/bumper/templates/{templateId:guid}/variants/{variantId:guid}", UpdateBumperVariant ) .Produces(StatusCodes.Status204NoContent); admin .MapDelete( "/{id:guid}/bumper/templates/{templateId:guid}/variants/{variantId:guid}", RemoveBumperVariant ) .Produces(StatusCodes.Status204NoContent); admin .MapGet("/{id:guid}/schedule", GetSchedule) .Produces>(); admin .MapPut("/{id:guid}/viewer", UpdateViewerSettings) .Produces(StatusCodes.Status204NoContent); // «Почему это здесь»: цепочка происхождения записи, записанная в момент генерации. admin.MapGet("/entries/{entryId:guid}/trace", GetEntryTrace).Produces(); return app; } private static async Task UpdateViewerSettings( Guid id, UpdateViewerSettingsBody body, ISender sender, CancellationToken cancellationToken ) { var result = await sender.Send( new UpdateViewerSettingsCommand( id, body.LogoImageId, body.LogoCorner, body.LogoOpacity, body.ShowClock, body.AnalogFilterStrength ), cancellationToken ); return result.ToHttpResult(); } private static async Task GetEntryTrace( Guid entryId, ISender sender, CancellationToken cancellationToken ) { var result = await sender.Send(new GetEntryTraceQuery(entryId), cancellationToken); return result.ToHttpResult(); } private static async Task CreateChannel( CreateChannelCommand command, ISender sender, CancellationToken cancellationToken ) { var result = await sender.Send(command, cancellationToken); return result.IsSuccess ? Results.Created( $"/api/admin/channels/{result.Value}", new CreatedIdResponse(result.Value) ) : result.ToHttpResult(); } private static async Task ListChannels( ISender sender, CancellationToken cancellationToken ) { var result = await sender.Send(new ListChannelsQuery(), cancellationToken); return Results.Ok(result); } private static async Task GetChannel( Guid id, ISender sender, CancellationToken cancellationToken ) { var result = await sender.Send(new GetChannelQuery(id), cancellationToken); return result.ToHttpResult(); } private static async Task UpdateTime( Guid id, UpdateChannelTimeBody body, ISender sender, CancellationToken cancellationToken ) { var result = await sender.Send( new UpdateChannelTimeCommand( id, body.Number, body.UtcOffsetMinutes, body.DayStartTime ), cancellationToken ); return result.ToHttpResult(); } private static async Task UpdateSettings( Guid id, UpdateChannelSettingsBody body, ISender sender, CancellationToken cancellationToken ) { var result = await sender.Send( new UpdateChannelSettingsCommand( id, body.Name, body.IsEnabled, body.BumpersEnabled, body.Bumper, body.FillerAssetId ), cancellationToken ); return result.ToHttpResult(); } private static async Task GetSchedule( Guid id, DateTimeOffset? from, DateTimeOffset? to, ISender sender, CancellationToken cancellationToken ) { var fromUtc = from ?? DateTimeOffset.UtcNow; var toUtc = to ?? fromUtc.AddDays(1); var result = await sender.Send( new GetChannelScheduleQuery(id, fromUtc, toUtc), cancellationToken ); return result.ToHttpResult(); } } /// Номер канала и его время: смещение от UTC и начало вещательных суток. public sealed record UpdateChannelTimeBody( int? Number, int UtcOffsetMinutes, TimeOnly DayStartTime ); public sealed record UpdateChannelSettingsBody( string Name, bool IsEnabled, bool BumpersEnabled, BumperSettingsInput Bumper, Guid? FillerAssetId ); /// Оверлеи и аналоговый фильтр — как канал выглядит у зрителя (см. 6.8). public sealed record UpdateViewerSettingsBody( Guid? LogoImageId, LogoCorner LogoCorner, double LogoOpacity, bool ShowClock, double AnalogFilterStrength );