- GET /api/playouts — paged list over GetPagedPlayouts (query/pageNum/pageSize), list DTO with buildStatus (#100, #107) - GET /api/playouts/{id}/items — paged future items + UNSCHEDULED gaps over GetFuturePlayoutItemsById; 404 ProblemDetails for unknown playout (matches /api/schedules/{id}/items precedent) (#101) - buildStatus {lastBuild, success, message} on GET /api/playouts/{id} (BuildStatus now included by GetPlayoutByIdHandler) (#107) - GET /api/playouts/warnings/count — failed-build count for the warnings badge (#107) - POST /api/playouts/reset-all — 202 Accepted, wraps ResetAllPlayouts (#110) - POST /api/channels/{channelNumber}/playout/reset — optional ?mode= override; default branches by ScheduleKind (Classic→Refresh, others→Reset) to match Blazor semantics (#110) - Option<FillerKind>→FillerKind? projection uses MatchUnsafe (Match throws on null-returning branch; idiom per Health/Mapper.cs) - Tests: controller unit tests (routes, projections, 404s, reset-mode defaults), OpenAPI ProblemDetails contract entry for /api/playouts/{id}/items get 404 - Regenerated wwwroot/openapi/v1.json Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ public class GetPlayoutByIdHandler(IDbContextFactory<TvContext> dbContextFactory
|
|||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(p => p.ProgramSchedule)
|
.Include(p => p.ProgramSchedule)
|
||||||
.Include(p => p.Channel)
|
.Include(p => p.Channel)
|
||||||
|
.Include(p => p.BuildStatus)
|
||||||
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId, cancellationToken)
|
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId, cancellationToken)
|
||||||
.MapT(p => new PlayoutNameViewModel(
|
.MapT(p => new PlayoutNameViewModel(
|
||||||
p.Id,
|
p.Id,
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
namespace ErsatzTV.Core.Api.Playouts;
|
||||||
|
|
||||||
|
public record PagedPlayoutItemsResponseModel(
|
||||||
|
int TotalCount,
|
||||||
|
List<PlayoutItemResponseModel> Page);
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
namespace ErsatzTV.Core.Api.Playouts;
|
||||||
|
|
||||||
|
public record PagedPlayoutsResponseModel(
|
||||||
|
int TotalCount,
|
||||||
|
List<PlayoutListItemResponseModel> Page);
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace ErsatzTV.Core.Api.Playouts;
|
||||||
|
|
||||||
|
public record PlayoutBuildStatusResponseModel(
|
||||||
|
DateTimeOffset LastBuild,
|
||||||
|
bool Success,
|
||||||
|
string Message);
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using ErsatzTV.Core.Domain.Filler;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Api.Playouts;
|
||||||
|
|
||||||
|
public record PlayoutItemResponseModel(
|
||||||
|
string Title,
|
||||||
|
DateTimeOffset Start,
|
||||||
|
DateTimeOffset Finish,
|
||||||
|
string Duration,
|
||||||
|
FillerKind? FillerKind);
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#nullable enable
|
||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Api.Playouts;
|
||||||
|
|
||||||
|
public record PlayoutListItemResponseModel(
|
||||||
|
int Id,
|
||||||
|
string ChannelNumber,
|
||||||
|
string ChannelName,
|
||||||
|
PlayoutScheduleKind ScheduleKind,
|
||||||
|
string ScheduleName,
|
||||||
|
TimeSpan? DailyRebuildTime,
|
||||||
|
PlayoutBuildStatusResponseModel? BuildStatus);
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#nullable enable
|
||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
|
|
||||||
namespace ErsatzTV.Core.Api.Playouts;
|
namespace ErsatzTV.Core.Api.Playouts;
|
||||||
@@ -9,8 +10,9 @@ public record PlayoutResponseModel(
|
|||||||
string ChannelNumber,
|
string ChannelNumber,
|
||||||
ChannelPlayoutMode PlayoutMode,
|
ChannelPlayoutMode PlayoutMode,
|
||||||
string ScheduleName,
|
string ScheduleName,
|
||||||
string ScheduleFile,
|
string? ScheduleFile,
|
||||||
TimeSpan? DailyRebuildTime)
|
TimeSpan? DailyRebuildTime,
|
||||||
|
PlayoutBuildStatusResponseModel? BuildStatus)
|
||||||
{
|
{
|
||||||
public static PlayoutResponseModel From(
|
public static PlayoutResponseModel From(
|
||||||
int id,
|
int id,
|
||||||
@@ -19,8 +21,9 @@ public record PlayoutResponseModel(
|
|||||||
string channelNumber,
|
string channelNumber,
|
||||||
ChannelPlayoutMode playoutMode,
|
ChannelPlayoutMode playoutMode,
|
||||||
string scheduleName,
|
string scheduleName,
|
||||||
string scheduleFile,
|
string? scheduleFile,
|
||||||
TimeSpan? dailyRebuildTime) =>
|
TimeSpan? dailyRebuildTime,
|
||||||
|
PlayoutBuildStatusResponseModel? buildStatus) =>
|
||||||
new(
|
new(
|
||||||
id,
|
id,
|
||||||
scheduleKind,
|
scheduleKind,
|
||||||
@@ -29,5 +32,6 @@ public record PlayoutResponseModel(
|
|||||||
playoutMode,
|
playoutMode,
|
||||||
scheduleName,
|
scheduleName,
|
||||||
scheduleFile,
|
scheduleFile,
|
||||||
dailyRebuildTime);
|
dailyRebuildTime,
|
||||||
|
buildStatus);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using ErsatzTV.Controllers.Api.Requests;
|
|||||||
using ErsatzTV.Core;
|
using ErsatzTV.Core;
|
||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
using ErsatzTV.Core.Errors;
|
using ErsatzTV.Core.Errors;
|
||||||
|
using ErsatzTV.Core.Scheduling;
|
||||||
using LanguageExt;
|
using LanguageExt;
|
||||||
using static LanguageExt.Prelude;
|
using static LanguageExt.Prelude;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
@@ -23,15 +24,15 @@ namespace ErsatzTV.Tests.Controllers;
|
|||||||
public class ChannelControllerTests
|
public class ChannelControllerTests
|
||||||
{
|
{
|
||||||
private IMediator _mediator = null!;
|
private IMediator _mediator = null!;
|
||||||
|
private Channel<IBackgroundServiceRequest> _workerChannel = null!;
|
||||||
private ChannelController _controller = null!;
|
private ChannelController _controller = null!;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp()
|
public void SetUp()
|
||||||
{
|
{
|
||||||
_mediator = Substitute.For<IMediator>();
|
_mediator = Substitute.For<IMediator>();
|
||||||
ChannelWriter<IBackgroundServiceRequest> writer =
|
_workerChannel = System.Threading.Channels.Channel.CreateUnbounded<IBackgroundServiceRequest>();
|
||||||
System.Threading.Channels.Channel.CreateUnbounded<IBackgroundServiceRequest>().Writer;
|
_controller = new ChannelController(_workerChannel.Writer, _mediator);
|
||||||
_controller = new ChannelController(writer, _mediator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -157,7 +158,7 @@ public class ChannelControllerTests
|
|||||||
_mediator.Send(Arg.Any<GetPlayoutIdByChannelNumber>(), Arg.Any<CancellationToken>())
|
_mediator.Send(Arg.Any<GetPlayoutIdByChannelNumber>(), Arg.Any<CancellationToken>())
|
||||||
.Returns(Option<int>.None);
|
.Returns(Option<int>.None);
|
||||||
|
|
||||||
IActionResult result = await _controller.ResetPlayout("404");
|
IActionResult result = await _controller.ResetPlayout("404", mode: null, CancellationToken.None);
|
||||||
|
|
||||||
var notFound = result.ShouldBeOfType<NotFoundObjectResult>();
|
var notFound = result.ShouldBeOfType<NotFoundObjectResult>();
|
||||||
var problemDetails = notFound.Value.ShouldBeOfType<ProblemDetails>();
|
var problemDetails = notFound.Value.ShouldBeOfType<ProblemDetails>();
|
||||||
@@ -165,6 +166,53 @@ public class ChannelControllerTests
|
|||||||
problemDetails.Title.ShouldBe("Resource not found");
|
problemDetails.Title.ShouldBe("Resource not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase(PlayoutScheduleKind.Classic, PlayoutBuildMode.Refresh)]
|
||||||
|
[TestCase(PlayoutScheduleKind.Block, PlayoutBuildMode.Reset)]
|
||||||
|
[TestCase(PlayoutScheduleKind.Sequential, PlayoutBuildMode.Reset)]
|
||||||
|
public async Task ResetPlayout_Should_Default_Mode_By_ScheduleKind(
|
||||||
|
PlayoutScheduleKind scheduleKind,
|
||||||
|
PlayoutBuildMode expectedMode)
|
||||||
|
{
|
||||||
|
_mediator.Send(Arg.Any<GetPlayoutIdByChannelNumber>(), Arg.Any<CancellationToken>())
|
||||||
|
.Returns(Option<int>.Some(9));
|
||||||
|
_mediator.Send(Arg.Any<GetPlayoutById>(), Arg.Any<CancellationToken>())
|
||||||
|
.Returns(Option<PlayoutNameViewModel>.Some(MakePlayout(9, scheduleKind)));
|
||||||
|
|
||||||
|
IActionResult result = await _controller.ResetPlayout("5", mode: null, CancellationToken.None);
|
||||||
|
|
||||||
|
result.ShouldBeOfType<OkResult>();
|
||||||
|
_workerChannel.Reader.TryRead(out IBackgroundServiceRequest? request).ShouldBeTrue();
|
||||||
|
var buildPlayout = request.ShouldBeOfType<BuildPlayout>();
|
||||||
|
buildPlayout.PlayoutId.ShouldBe(9);
|
||||||
|
buildPlayout.Mode.ShouldBe(expectedMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task ResetPlayout_Should_Honor_Explicit_Mode()
|
||||||
|
{
|
||||||
|
_mediator.Send(Arg.Any<GetPlayoutIdByChannelNumber>(), Arg.Any<CancellationToken>())
|
||||||
|
.Returns(Option<int>.Some(9));
|
||||||
|
|
||||||
|
IActionResult result = await _controller.ResetPlayout("5", PlayoutBuildMode.Continue, CancellationToken.None);
|
||||||
|
|
||||||
|
result.ShouldBeOfType<OkResult>();
|
||||||
|
_workerChannel.Reader.TryRead(out IBackgroundServiceRequest? request).ShouldBeTrue();
|
||||||
|
request.ShouldBeOfType<BuildPlayout>().Mode.ShouldBe(PlayoutBuildMode.Continue);
|
||||||
|
await _mediator.DidNotReceive().Send(Arg.Any<GetPlayoutById>(), Arg.Any<CancellationToken>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PlayoutNameViewModel MakePlayout(int id, PlayoutScheduleKind scheduleKind) =>
|
||||||
|
new(
|
||||||
|
id,
|
||||||
|
scheduleKind,
|
||||||
|
"Channel",
|
||||||
|
"5",
|
||||||
|
ChannelPlayoutMode.Continuous,
|
||||||
|
"Schedule",
|
||||||
|
string.Empty,
|
||||||
|
null,
|
||||||
|
null);
|
||||||
|
|
||||||
private static ChannelViewModel MakeVm(int id) =>
|
private static ChannelViewModel MakeVm(int id) =>
|
||||||
new(
|
new(
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ public class OpenApiErrorResponseContractTests
|
|||||||
[TestCase("/api/playouts", "post", "422")]
|
[TestCase("/api/playouts", "post", "422")]
|
||||||
[TestCase("/api/playouts/{id}", "delete", "404")]
|
[TestCase("/api/playouts/{id}", "delete", "404")]
|
||||||
[TestCase("/api/playouts/{id}", "delete", "422")]
|
[TestCase("/api/playouts/{id}", "delete", "422")]
|
||||||
|
[TestCase("/api/playouts/{id}/items", "get", "404")]
|
||||||
[TestCase("/api/ffmpeg/profiles/{id}", "get", "404")]
|
[TestCase("/api/ffmpeg/profiles/{id}", "get", "404")]
|
||||||
[TestCase("/api/ffmpeg/profiles", "post", "404")]
|
[TestCase("/api/ffmpeg/profiles", "post", "404")]
|
||||||
[TestCase("/api/ffmpeg/profiles", "post", "401")]
|
[TestCase("/api/ffmpeg/profiles", "post", "401")]
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using ErsatzTV.Controllers.Api.Requests;
|
|||||||
using ErsatzTV.Core;
|
using ErsatzTV.Core;
|
||||||
using ErsatzTV.Core.Api.Playouts;
|
using ErsatzTV.Core.Api.Playouts;
|
||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
|
using ErsatzTV.Core.Domain.Filler;
|
||||||
using ErsatzTV.Core.Errors;
|
using ErsatzTV.Core.Errors;
|
||||||
using LanguageExt;
|
using LanguageExt;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
@@ -34,8 +35,12 @@ public class PlayoutControllerTests
|
|||||||
[Test]
|
[Test]
|
||||||
public void Controller_Should_Expose_Idiomatic_Rest_Routes()
|
public void Controller_Should_Expose_Idiomatic_Rest_Routes()
|
||||||
{
|
{
|
||||||
|
ShouldHaveActionRoute(nameof(PlayoutController.GetAll), "GET", "/api/playouts");
|
||||||
ShouldHaveActionRoute(nameof(PlayoutController.GetById), "GET", "/api/playouts/{id:int}");
|
ShouldHaveActionRoute(nameof(PlayoutController.GetById), "GET", "/api/playouts/{id:int}");
|
||||||
|
ShouldHaveActionRoute(nameof(PlayoutController.GetItems), "GET", "/api/playouts/{id:int}/items");
|
||||||
|
ShouldHaveActionRoute(nameof(PlayoutController.GetWarningsCount), "GET", "/api/playouts/warnings/count");
|
||||||
ShouldHaveActionRoute(nameof(PlayoutController.Create), "POST", "/api/playouts");
|
ShouldHaveActionRoute(nameof(PlayoutController.Create), "POST", "/api/playouts");
|
||||||
|
ShouldHaveActionRoute(nameof(PlayoutController.ResetAll), "POST", "/api/playouts/reset-all");
|
||||||
ShouldHaveActionRoute(nameof(PlayoutController.Delete), "DELETE", "/api/playouts/{id:int}");
|
ShouldHaveActionRoute(nameof(PlayoutController.Delete), "DELETE", "/api/playouts/{id:int}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,6 +167,130 @@ public class PlayoutControllerTests
|
|||||||
problem.Title.ShouldBe("Resource not found");
|
problem.Title.ShouldBe("Resource not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetAll_Should_Project_Paged_List_With_BuildStatus()
|
||||||
|
{
|
||||||
|
var buildStatus = new PlayoutBuildStatus
|
||||||
|
{
|
||||||
|
LastBuild = new DateTimeOffset(2026, 7, 2, 10, 0, 0, TimeSpan.Zero),
|
||||||
|
Success = false,
|
||||||
|
Message = "boom"
|
||||||
|
};
|
||||||
|
PlayoutNameViewModel vm = MakePlayout(9) with
|
||||||
|
{
|
||||||
|
BuildStatus = buildStatus,
|
||||||
|
DbDailyRebuildTime = TimeSpan.FromHours(4)
|
||||||
|
};
|
||||||
|
_mediator.Send(Arg.Any<GetPagedPlayouts>(), Arg.Any<CancellationToken>())
|
||||||
|
.Returns(new PagedPlayoutsViewModel(1, [vm]));
|
||||||
|
|
||||||
|
PagedPlayoutsResponseModel result = await _controller.GetAll("q", 2, 25, CancellationToken.None);
|
||||||
|
|
||||||
|
result.TotalCount.ShouldBe(1);
|
||||||
|
PlayoutListItemResponseModel item = result.Page.Single();
|
||||||
|
item.Id.ShouldBe(9);
|
||||||
|
item.ChannelNumber.ShouldBe("101");
|
||||||
|
item.ChannelName.ShouldBe("Channel");
|
||||||
|
item.ScheduleKind.ShouldBe(PlayoutScheduleKind.Classic);
|
||||||
|
item.ScheduleName.ShouldBe("Schedule");
|
||||||
|
item.DailyRebuildTime.ShouldBe(TimeSpan.FromHours(4));
|
||||||
|
item.BuildStatus.ShouldNotBeNull();
|
||||||
|
item.BuildStatus.Success.ShouldBeFalse();
|
||||||
|
item.BuildStatus.Message.ShouldBe("boom");
|
||||||
|
item.BuildStatus.LastBuild.ShouldBe(buildStatus.LastBuild);
|
||||||
|
|
||||||
|
await _mediator.Received(1).Send(
|
||||||
|
Arg.Is<GetPagedPlayouts>(q => q.Query == "q" && q.PageNum == 2 && q.PageSize == 25),
|
||||||
|
Arg.Any<CancellationToken>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetAll_Should_Emit_Null_BuildStatus_When_Absent()
|
||||||
|
{
|
||||||
|
PlayoutNameViewModel vm = MakePlayout(9) with { BuildStatus = null };
|
||||||
|
_mediator.Send(Arg.Any<GetPagedPlayouts>(), Arg.Any<CancellationToken>())
|
||||||
|
.Returns(new PagedPlayoutsViewModel(1, [vm]));
|
||||||
|
|
||||||
|
PagedPlayoutsResponseModel result = await _controller.GetAll("", 0, 100, CancellationToken.None);
|
||||||
|
|
||||||
|
result.Page.Single().BuildStatus.ShouldBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetItems_Should_Project_Items_And_Null_FillerKind_For_Gaps()
|
||||||
|
{
|
||||||
|
_mediator.Send(Arg.Any<GetPlayoutById>(), Arg.Any<CancellationToken>())
|
||||||
|
.Returns(Option<PlayoutNameViewModel>.Some(MakePlayout(9)));
|
||||||
|
|
||||||
|
var item = new PlayoutItemViewModel(
|
||||||
|
"Movie",
|
||||||
|
new DateTimeOffset(2026, 7, 2, 12, 0, 0, TimeSpan.Zero),
|
||||||
|
new DateTimeOffset(2026, 7, 2, 13, 0, 0, TimeSpan.Zero),
|
||||||
|
"1:00:00",
|
||||||
|
string.Empty,
|
||||||
|
Some(FillerKind.MidRoll));
|
||||||
|
var gap = new PlayoutItemViewModel(
|
||||||
|
"UNSCHEDULED",
|
||||||
|
new DateTimeOffset(2026, 7, 2, 13, 0, 0, TimeSpan.Zero),
|
||||||
|
new DateTimeOffset(2026, 7, 2, 13, 30, 0, TimeSpan.Zero),
|
||||||
|
"30:00",
|
||||||
|
string.Empty,
|
||||||
|
Option<FillerKind>.None);
|
||||||
|
_mediator.Send(Arg.Any<GetFuturePlayoutItemsById>(), Arg.Any<CancellationToken>())
|
||||||
|
.Returns(new PagedPlayoutItemsViewModel(2, [item, gap]));
|
||||||
|
|
||||||
|
IActionResult actionResult = await _controller.GetItems(9, showFiller: true, 1, 10, CancellationToken.None);
|
||||||
|
|
||||||
|
var result = actionResult.ShouldBeOfType<OkObjectResult>().Value.ShouldBeOfType<PagedPlayoutItemsResponseModel>();
|
||||||
|
result.TotalCount.ShouldBe(2);
|
||||||
|
result.Page[0].Title.ShouldBe("Movie");
|
||||||
|
result.Page[0].Duration.ShouldBe("1:00:00");
|
||||||
|
result.Page[0].FillerKind.ShouldBe(FillerKind.MidRoll);
|
||||||
|
result.Page[1].Title.ShouldBe("UNSCHEDULED");
|
||||||
|
result.Page[1].FillerKind.ShouldBeNull();
|
||||||
|
|
||||||
|
await _mediator.Received(1).Send(
|
||||||
|
Arg.Is<GetFuturePlayoutItemsById>(q =>
|
||||||
|
q.PlayoutId == 9 && q.ShowFiller && q.PageNum == 1 && q.PageSize == 10),
|
||||||
|
Arg.Any<CancellationToken>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetItems_Should_Return_404_For_Unknown_Playout_With_ProblemDetails()
|
||||||
|
{
|
||||||
|
_mediator.Send(Arg.Any<GetPlayoutById>(), Arg.Any<CancellationToken>())
|
||||||
|
.Returns(Option<PlayoutNameViewModel>.None);
|
||||||
|
|
||||||
|
IActionResult result = await _controller.GetItems(404, showFiller: false, 0, 100, CancellationToken.None);
|
||||||
|
|
||||||
|
var notFound = result.ShouldBeOfType<NotFoundObjectResult>();
|
||||||
|
var problem = notFound.Value.ShouldBeOfType<ProblemDetails>();
|
||||||
|
problem.Status.ShouldBe(404);
|
||||||
|
problem.Title.ShouldBe("Resource not found");
|
||||||
|
|
||||||
|
await _mediator.DidNotReceive().Send(Arg.Any<GetFuturePlayoutItemsById>(), Arg.Any<CancellationToken>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetWarningsCount_Should_Return_Count()
|
||||||
|
{
|
||||||
|
_mediator.Send(Arg.Any<GetPlayoutWarningsCount>(), Arg.Any<CancellationToken>())
|
||||||
|
.Returns(7);
|
||||||
|
|
||||||
|
int result = await _controller.GetWarningsCount(CancellationToken.None);
|
||||||
|
|
||||||
|
result.ShouldBe(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task ResetAll_Should_Return_202_And_Send_Command()
|
||||||
|
{
|
||||||
|
IActionResult result = await _controller.ResetAll(CancellationToken.None);
|
||||||
|
|
||||||
|
result.ShouldBeOfType<AcceptedResult>();
|
||||||
|
await _mediator.Received(1).Send(Arg.Any<ResetAllPlayouts>(), Arg.Any<CancellationToken>());
|
||||||
|
}
|
||||||
|
|
||||||
private static PlayoutNameViewModel MakePlayout(int id) =>
|
private static PlayoutNameViewModel MakePlayout(int id) =>
|
||||||
new(
|
new(
|
||||||
id,
|
id,
|
||||||
@@ -183,7 +312,13 @@ public class PlayoutControllerTests
|
|||||||
vm.PlayoutMode,
|
vm.PlayoutMode,
|
||||||
vm.ScheduleName,
|
vm.ScheduleName,
|
||||||
vm.ScheduleFile,
|
vm.ScheduleFile,
|
||||||
vm.DbDailyRebuildTime);
|
vm.DbDailyRebuildTime,
|
||||||
|
vm.BuildStatus is null
|
||||||
|
? null
|
||||||
|
: new PlayoutBuildStatusResponseModel(
|
||||||
|
vm.BuildStatus.LastBuild,
|
||||||
|
vm.BuildStatus.Success,
|
||||||
|
vm.BuildStatus.Message));
|
||||||
|
|
||||||
private static void ShouldHaveActionRoute(string actionName, string httpMethod, string route)
|
private static void ShouldHaveActionRoute(string actionName, string httpMethod, string route)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using ErsatzTV.Application.Playouts;
|
|||||||
using ErsatzTV.Controllers.Api.Requests;
|
using ErsatzTV.Controllers.Api.Requests;
|
||||||
using ErsatzTV.Core;
|
using ErsatzTV.Core;
|
||||||
using ErsatzTV.Core.Api.Channels;
|
using ErsatzTV.Core.Api.Channels;
|
||||||
|
using ErsatzTV.Core.Domain;
|
||||||
using ErsatzTV.Core.Scheduling;
|
using ErsatzTV.Core.Scheduling;
|
||||||
using ErsatzTV.Extensions;
|
using ErsatzTV.Extensions;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
@@ -90,18 +91,42 @@ public class ChannelController(ChannelWriter<IBackgroundServiceRequest> workerCh
|
|||||||
[HttpPost("/api/channels/{channelNumber}/playout/reset")]
|
[HttpPost("/api/channels/{channelNumber}/playout/reset")]
|
||||||
[Tags("Channels")]
|
[Tags("Channels")]
|
||||||
[EndpointSummary("Reset channel playout")]
|
[EndpointSummary("Reset channel playout")]
|
||||||
|
[EndpointDescription(
|
||||||
|
"When mode is omitted, classic playouts use Refresh (rebuild while maintaining collection " +
|
||||||
|
"progress) and all other playout kinds use Reset (rebuild from scratch), matching the Blazor UI. " +
|
||||||
|
"Pass mode to force a specific PlayoutBuildMode.")]
|
||||||
[EndpointGroupName("general")]
|
[EndpointGroupName("general")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||||
public async Task<IActionResult> ResetPlayout(string channelNumber)
|
public async Task<IActionResult> ResetPlayout(
|
||||||
|
string channelNumber,
|
||||||
|
[FromQuery] PlayoutBuildMode? mode,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Option<int> maybePlayoutId = await mediator.Send(new GetPlayoutIdByChannelNumber(channelNumber));
|
Option<int> maybePlayoutId =
|
||||||
|
await mediator.Send(new GetPlayoutIdByChannelNumber(channelNumber), cancellationToken);
|
||||||
foreach (int playoutId in maybePlayoutId)
|
foreach (int playoutId in maybePlayoutId)
|
||||||
{
|
{
|
||||||
await workerChannel.WriteAsync(new BuildPlayout(playoutId, PlayoutBuildMode.Reset));
|
PlayoutBuildMode buildMode = mode ?? await DefaultResetMode(playoutId, cancellationToken);
|
||||||
|
await workerChannel.WriteAsync(new BuildPlayout(playoutId, buildMode), cancellationToken);
|
||||||
return new OkResult();
|
return new OkResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
return ApiResults.NotFoundProblem();
|
return ApiResults.NotFoundProblem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Match Blazor's Playouts.razor reset semantics: classic playouts refresh (preserve progress),
|
||||||
|
// every other kind resets from scratch.
|
||||||
|
private async Task<PlayoutBuildMode> DefaultResetMode(int playoutId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Option<PlayoutNameViewModel> maybePlayout =
|
||||||
|
await mediator.Send(new GetPlayoutById(playoutId), cancellationToken);
|
||||||
|
return maybePlayout.Match(
|
||||||
|
Some: vm => vm.ScheduleKind switch
|
||||||
|
{
|
||||||
|
PlayoutScheduleKind.Classic => PlayoutBuildMode.Refresh,
|
||||||
|
_ => PlayoutBuildMode.Reset
|
||||||
|
},
|
||||||
|
None: () => PlayoutBuildMode.Reset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ using ErsatzTV.Application.Playouts;
|
|||||||
using ErsatzTV.Controllers.Api.Requests;
|
using ErsatzTV.Controllers.Api.Requests;
|
||||||
using ErsatzTV.Core;
|
using ErsatzTV.Core;
|
||||||
using ErsatzTV.Core.Api.Playouts;
|
using ErsatzTV.Core.Api.Playouts;
|
||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
using ErsatzTV.Core.Domain.Filler;
|
||||||
using ErsatzTV.Extensions;
|
using ErsatzTV.Extensions;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
@@ -13,6 +15,32 @@ namespace ErsatzTV.Controllers.Api;
|
|||||||
[ApiController]
|
[ApiController]
|
||||||
public class PlayoutController(IMediator mediator) : ControllerBase
|
public class PlayoutController(IMediator mediator) : ControllerBase
|
||||||
{
|
{
|
||||||
|
[HttpGet("/api/playouts", Name = "GetPlayouts")]
|
||||||
|
[Tags("Playouts")]
|
||||||
|
[EndpointSummary("List playouts")]
|
||||||
|
[EndpointGroupName("general")]
|
||||||
|
[ProducesResponseType(typeof(PagedPlayoutsResponseModel), StatusCodes.Status200OK)]
|
||||||
|
public async Task<PagedPlayoutsResponseModel> GetAll(
|
||||||
|
[FromQuery] string query = "",
|
||||||
|
[FromQuery] int pageNum = 0,
|
||||||
|
[FromQuery] int pageSize = 100,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
PagedPlayoutsViewModel result =
|
||||||
|
await mediator.Send(new GetPagedPlayouts(query, pageNum, pageSize), cancellationToken);
|
||||||
|
return new PagedPlayoutsResponseModel(
|
||||||
|
result.TotalCount,
|
||||||
|
result.Page.Map(ToListItemResponse).ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("/api/playouts/warnings/count", Name = "GetPlayoutWarningsCount")]
|
||||||
|
[Tags("Playouts")]
|
||||||
|
[EndpointSummary("Count playouts with a failed build")]
|
||||||
|
[EndpointGroupName("general")]
|
||||||
|
[ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
|
||||||
|
public async Task<int> GetWarningsCount(CancellationToken cancellationToken) =>
|
||||||
|
await mediator.Send(new GetPlayoutWarningsCount(), cancellationToken);
|
||||||
|
|
||||||
[HttpGet("/api/playouts/{id:int}", Name = "GetPlayoutById")]
|
[HttpGet("/api/playouts/{id:int}", Name = "GetPlayoutById")]
|
||||||
[Tags("Playouts")]
|
[Tags("Playouts")]
|
||||||
[EndpointSummary("Get a playout by id")]
|
[EndpointSummary("Get a playout by id")]
|
||||||
@@ -25,6 +53,34 @@ public class PlayoutController(IMediator mediator) : ControllerBase
|
|||||||
return result.Map(ToResponse).ToGetResult();
|
return result.Map(ToResponse).ToGetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("/api/playouts/{id:int}/items", Name = "GetPlayoutItems")]
|
||||||
|
[Tags("Playouts")]
|
||||||
|
[EndpointSummary("Get upcoming items (and unscheduled gaps) for a playout")]
|
||||||
|
[EndpointGroupName("general")]
|
||||||
|
[ProducesResponseType(typeof(PagedPlayoutItemsResponseModel), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetItems(
|
||||||
|
int id,
|
||||||
|
[FromQuery] bool showFiller = false,
|
||||||
|
[FromQuery] int pageNum = 0,
|
||||||
|
[FromQuery] int pageSize = 100,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
Option<PlayoutNameViewModel> maybePlayout = await mediator.Send(new GetPlayoutById(id), cancellationToken);
|
||||||
|
if (maybePlayout.IsNone)
|
||||||
|
{
|
||||||
|
return ApiResults.NotFoundProblem();
|
||||||
|
}
|
||||||
|
|
||||||
|
PagedPlayoutItemsViewModel result = await mediator.Send(
|
||||||
|
new GetFuturePlayoutItemsById(id, showFiller, pageNum, pageSize),
|
||||||
|
cancellationToken);
|
||||||
|
return new OkObjectResult(
|
||||||
|
new PagedPlayoutItemsResponseModel(
|
||||||
|
result.TotalCount,
|
||||||
|
result.Page.Map(ToItemResponse).ToList()));
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost("/api/playouts")]
|
[HttpPost("/api/playouts")]
|
||||||
[Tags("Playouts")]
|
[Tags("Playouts")]
|
||||||
[EndpointSummary("Create a classic playout")]
|
[EndpointSummary("Create a classic playout")]
|
||||||
@@ -49,6 +105,17 @@ public class PlayoutController(IMediator mediator) : ControllerBase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("/api/playouts/reset-all", Name = "ResetAllPlayouts")]
|
||||||
|
[Tags("Playouts")]
|
||||||
|
[EndpointSummary("Reset all playouts")]
|
||||||
|
[EndpointGroupName("general")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status202Accepted)]
|
||||||
|
public async Task<IActionResult> ResetAll(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await mediator.Send(new ResetAllPlayouts(), cancellationToken);
|
||||||
|
return Accepted();
|
||||||
|
}
|
||||||
|
|
||||||
[HttpDelete("/api/playouts/{id:int}")]
|
[HttpDelete("/api/playouts/{id:int}")]
|
||||||
[Tags("Playouts")]
|
[Tags("Playouts")]
|
||||||
[EndpointSummary("Delete a playout")]
|
[EndpointSummary("Delete a playout")]
|
||||||
@@ -71,5 +138,32 @@ public class PlayoutController(IMediator mediator) : ControllerBase
|
|||||||
vm.PlayoutMode,
|
vm.PlayoutMode,
|
||||||
vm.ScheduleName,
|
vm.ScheduleName,
|
||||||
vm.ScheduleFile,
|
vm.ScheduleFile,
|
||||||
vm.DbDailyRebuildTime);
|
vm.DbDailyRebuildTime,
|
||||||
|
ToBuildStatus(vm.BuildStatus));
|
||||||
|
|
||||||
|
private static PlayoutListItemResponseModel ToListItemResponse(PlayoutNameViewModel vm) =>
|
||||||
|
new(
|
||||||
|
vm.PlayoutId,
|
||||||
|
vm.ChannelNumber,
|
||||||
|
vm.ChannelName,
|
||||||
|
vm.ScheduleKind,
|
||||||
|
vm.ScheduleName,
|
||||||
|
vm.DbDailyRebuildTime,
|
||||||
|
ToBuildStatus(vm.BuildStatus));
|
||||||
|
|
||||||
|
private static PlayoutBuildStatusResponseModel ToBuildStatus(PlayoutBuildStatus buildStatus) =>
|
||||||
|
buildStatus is null
|
||||||
|
? null
|
||||||
|
: new PlayoutBuildStatusResponseModel(
|
||||||
|
buildStatus.LastBuild,
|
||||||
|
buildStatus.Success,
|
||||||
|
buildStatus.Message);
|
||||||
|
|
||||||
|
private static PlayoutItemResponseModel ToItemResponse(PlayoutItemViewModel vm) =>
|
||||||
|
new(
|
||||||
|
vm.Title,
|
||||||
|
vm.Start,
|
||||||
|
vm.Finish,
|
||||||
|
vm.Duration,
|
||||||
|
vm.FillerKind.MatchUnsafe(fk => (FillerKind?)fk, () => null));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -370,6 +370,7 @@
|
|||||||
"Channels"
|
"Channels"
|
||||||
],
|
],
|
||||||
"summary": "Reset channel playout",
|
"summary": "Reset channel playout",
|
||||||
|
"description": "When mode is omitted, classic playouts use Refresh (rebuild while maintaining collection progress) and all other playout kinds use Reset (rebuild from scratch), matching the Blazor UI. Pass mode to force a specific PlayoutBuildMode.",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "channelNumber",
|
"name": "channelNumber",
|
||||||
@@ -378,6 +379,13 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mode",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/PlayoutBuildMode"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@@ -1595,6 +1603,192 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/playouts": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Playouts"
|
||||||
|
],
|
||||||
|
"summary": "List playouts",
|
||||||
|
"operationId": "GetPlayouts",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "query",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"default": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pageNum",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32",
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pageSize",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32",
|
||||||
|
"default": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"text/plain": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/PagedPlayoutsResponseModel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/PagedPlayoutsResponseModel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"text/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/PagedPlayoutsResponseModel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Playouts"
|
||||||
|
],
|
||||||
|
"summary": "Create a classic playout",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json-patch+json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CreatePlayoutRequest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CreatePlayoutRequest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"text/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CreatePlayoutRequest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"application/*+json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CreatePlayoutRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"content": {
|
||||||
|
"text/plain": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/PlayoutResponseModel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/PlayoutResponseModel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"text/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/PlayoutResponseModel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"text/plain": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProblemDetails"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProblemDetails"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"text/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProblemDetails"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Unprocessable Entity",
|
||||||
|
"content": {
|
||||||
|
"text/plain": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProblemDetails"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProblemDetails"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"text/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProblemDetails"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/playouts/warnings/count": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Playouts"
|
||||||
|
],
|
||||||
|
"summary": "Count playouts with a failed build",
|
||||||
|
"operationId": "GetPlayoutWarningsCount",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"text/plain": {
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"text/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/playouts/{id}": {
|
"/api/playouts/{id}": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -1719,54 +1913,67 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/playouts": {
|
"/api/playouts/{id}/items": {
|
||||||
"post": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
"Playouts"
|
"Playouts"
|
||||||
],
|
],
|
||||||
"summary": "Create a classic playout",
|
"summary": "Get upcoming items (and unscheduled gaps) for a playout",
|
||||||
"requestBody": {
|
"operationId": "GetPlayoutItems",
|
||||||
"content": {
|
"parameters": [
|
||||||
"application/json-patch+json": {
|
{
|
||||||
"schema": {
|
"name": "id",
|
||||||
"$ref": "#/components/schemas/CreatePlayoutRequest"
|
"in": "path",
|
||||||
}
|
"required": true,
|
||||||
},
|
"schema": {
|
||||||
"application/json": {
|
"type": "integer",
|
||||||
"schema": {
|
"format": "int32"
|
||||||
"$ref": "#/components/schemas/CreatePlayoutRequest"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"text/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/CreatePlayoutRequest"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"application/*+json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/CreatePlayoutRequest"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": true
|
{
|
||||||
},
|
"name": "showFiller",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pageNum",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32",
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pageSize",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32",
|
||||||
|
"default": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"201": {
|
"200": {
|
||||||
"description": "Created",
|
"description": "OK",
|
||||||
"content": {
|
"content": {
|
||||||
"text/plain": {
|
"text/plain": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/PlayoutResponseModel"
|
"$ref": "#/components/schemas/PagedPlayoutItemsResponseModel"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/PlayoutResponseModel"
|
"$ref": "#/components/schemas/PagedPlayoutItemsResponseModel"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"text/json": {
|
"text/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/PlayoutResponseModel"
|
"$ref": "#/components/schemas/PagedPlayoutItemsResponseModel"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1790,26 +1997,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"422": {
|
}
|
||||||
"description": "Unprocessable Entity",
|
}
|
||||||
"content": {
|
},
|
||||||
"text/plain": {
|
"/api/playouts/reset-all": {
|
||||||
"schema": {
|
"post": {
|
||||||
"$ref": "#/components/schemas/ProblemDetails"
|
"tags": [
|
||||||
}
|
"Playouts"
|
||||||
},
|
],
|
||||||
"application/json": {
|
"summary": "Reset all playouts",
|
||||||
"schema": {
|
"operationId": "ResetAllPlayouts",
|
||||||
"$ref": "#/components/schemas/ProblemDetails"
|
"responses": {
|
||||||
}
|
"202": {
|
||||||
},
|
"description": "Accepted"
|
||||||
"text/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/ProblemDetails"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4650,6 +4851,50 @@
|
|||||||
],
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"PagedPlayoutItemsResponseModel": {
|
||||||
|
"required": [
|
||||||
|
"totalCount",
|
||||||
|
"page"
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"totalCount": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"page": {
|
||||||
|
"type": [
|
||||||
|
"null",
|
||||||
|
"array"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/PlayoutItemResponseModel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"PagedPlayoutsResponseModel": {
|
||||||
|
"required": [
|
||||||
|
"totalCount",
|
||||||
|
"page"
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"totalCount": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"page": {
|
||||||
|
"type": [
|
||||||
|
"null",
|
||||||
|
"array"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/PlayoutListItemResponseModel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"PlaybackOrder": {
|
"PlaybackOrder": {
|
||||||
"enum": [
|
"enum": [
|
||||||
"None",
|
"None",
|
||||||
@@ -4692,6 +4937,126 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"PlayoutBuildMode": {
|
||||||
|
"enum": [
|
||||||
|
"Continue",
|
||||||
|
"Refresh",
|
||||||
|
"Reset"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"PlayoutBuildStatusResponseModel": {
|
||||||
|
"required": [
|
||||||
|
"lastBuild",
|
||||||
|
"success",
|
||||||
|
"message"
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"lastBuild": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": [
|
||||||
|
"null",
|
||||||
|
"string"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"PlayoutItemResponseModel": {
|
||||||
|
"required": [
|
||||||
|
"title",
|
||||||
|
"start",
|
||||||
|
"finish",
|
||||||
|
"duration",
|
||||||
|
"fillerKind"
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": [
|
||||||
|
"null",
|
||||||
|
"string"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"finish": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"duration": {
|
||||||
|
"type": [
|
||||||
|
"null",
|
||||||
|
"string"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fillerKind": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/FillerKind"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"PlayoutListItemResponseModel": {
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"channelNumber",
|
||||||
|
"channelName",
|
||||||
|
"scheduleKind",
|
||||||
|
"scheduleName",
|
||||||
|
"dailyRebuildTime",
|
||||||
|
"buildStatus"
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"channelNumber": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"channelName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"scheduleKind": {
|
||||||
|
"$ref": "#/components/schemas/PlayoutScheduleKind"
|
||||||
|
},
|
||||||
|
"scheduleName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"dailyRebuildTime": {
|
||||||
|
"pattern": "^-?(\\d+\\.)?\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,7})?$",
|
||||||
|
"type": [
|
||||||
|
"null",
|
||||||
|
"string"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"buildStatus": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/PlayoutBuildStatusResponseModel"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"PlayoutMode": {
|
"PlayoutMode": {
|
||||||
"enum": [
|
"enum": [
|
||||||
"Flood",
|
"Flood",
|
||||||
@@ -4710,7 +5075,8 @@
|
|||||||
"playoutMode",
|
"playoutMode",
|
||||||
"scheduleName",
|
"scheduleName",
|
||||||
"scheduleFile",
|
"scheduleFile",
|
||||||
"dailyRebuildTime"
|
"dailyRebuildTime",
|
||||||
|
"buildStatus"
|
||||||
],
|
],
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -4722,25 +5088,16 @@
|
|||||||
"$ref": "#/components/schemas/PlayoutScheduleKind"
|
"$ref": "#/components/schemas/PlayoutScheduleKind"
|
||||||
},
|
},
|
||||||
"channelName": {
|
"channelName": {
|
||||||
"type": [
|
"type": "string"
|
||||||
"null",
|
|
||||||
"string"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"channelNumber": {
|
"channelNumber": {
|
||||||
"type": [
|
"type": "string"
|
||||||
"null",
|
|
||||||
"string"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"playoutMode": {
|
"playoutMode": {
|
||||||
"$ref": "#/components/schemas/ChannelPlayoutMode"
|
"$ref": "#/components/schemas/ChannelPlayoutMode"
|
||||||
},
|
},
|
||||||
"scheduleName": {
|
"scheduleName": {
|
||||||
"type": [
|
"type": "string"
|
||||||
"null",
|
|
||||||
"string"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"scheduleFile": {
|
"scheduleFile": {
|
||||||
"type": [
|
"type": [
|
||||||
@@ -4754,6 +5111,16 @@
|
|||||||
"null",
|
"null",
|
||||||
"string"
|
"string"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"buildStatus": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/PlayoutBuildStatusResponseModel"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user