254 lines
8.9 KiB
C#
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
|
|
);
|