Files
TeleWave/backend/src/TeleWave.Api/Endpoints/ChannelEndpoints.cs
T

254 lines
8.9 KiB
C#

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;
/// <summary>
/// Админ-эндпоинты канала: создание, список, настройки, время и чтение расписания. Заставки —
/// в <c>ChannelEndpoints.Bumpers.cs</c>. Что и когда идёт в эфире, задаёт шаблон сетки
/// (<c>TemplateEndpoints</c>).
/// </summary>
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<CreatedIdResponse>(StatusCodes.Status201Created);
admin.MapGet("", ListChannels).Produces<IReadOnlyList<ChannelSummaryDto>>();
admin.MapGet("/{id:guid}", GetChannel).Produces<ChannelDto>();
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<CreatedIdResponse>(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<CreatedIdResponse>(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<IReadOnlyList<ScheduleEntryDto>>();
admin
.MapPut("/{id:guid}/viewer", UpdateViewerSettings)
.Produces(StatusCodes.Status204NoContent);
// «Почему это здесь»: цепочка происхождения записи, записанная в момент генерации.
admin.MapGet("/entries/{entryId:guid}/trace", GetEntryTrace).Produces<EntryTraceDto>();
return app;
}
private static async Task<IResult> 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<IResult> GetEntryTrace(
Guid entryId,
ISender sender,
CancellationToken cancellationToken
)
{
var result = await sender.Send(new GetEntryTraceQuery(entryId), cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> 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<IResult> ListChannels(
ISender sender,
CancellationToken cancellationToken
)
{
var result = await sender.Send(new ListChannelsQuery(), cancellationToken);
return Results.Ok(result);
}
private static async Task<IResult> GetChannel(
Guid id,
ISender sender,
CancellationToken cancellationToken
)
{
var result = await sender.Send(new GetChannelQuery(id), cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> 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<IResult> 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<IResult> 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();
}
}
/// <summary>Номер канала и его время: смещение от UTC и начало вещательных суток.</summary>
public sealed record UpdateChannelTimeBody(
int? Number,
int UtcOffsetMinutes,
TimeOnly DayStartTime
);
public sealed record UpdateChannelSettingsBody(
string Name,
bool IsEnabled,
bool BumpersEnabled,
BumperSettingsInput Bumper,
Guid? FillerAssetId
);
/// <summary>Оверлеи и аналоговый фильтр — как канал выглядит у зрителя (см. 6.8).</summary>
public sealed record UpdateViewerSettingsBody(
Guid? LogoImageId,
LogoCorner LogoCorner,
double LogoOpacity,
bool ShowClock,
double AnalogFilterStrength
);