feat(api): expose PlayoutItem row id on the playout-items list DTO (#210)
The SPA needs the item's row id to call
GET /api/playouts/items/{id}/scheduling-context. Plumbed through
PlayoutItemViewModel -> PlayoutItemResponseModel as a nullable Id
(null for synthesized UNSCHEDULED gap rows, which are PlayoutGaps,
not PlayoutItems). Additive for existing consumers (Playouts.razor
reads the VM by property). Regenerated v1.json + v1.d.ts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,7 @@ internal static class Mapper
|
|||||||
|
|
||||||
internal static PlayoutItemViewModel ProjectToViewModel(PlayoutItem playoutItem) =>
|
internal static PlayoutItemViewModel ProjectToViewModel(PlayoutItem playoutItem) =>
|
||||||
new(
|
new(
|
||||||
|
playoutItem.Id,
|
||||||
GetDisplayTitle(playoutItem.MediaItem, playoutItem.ChapterTitle),
|
GetDisplayTitle(playoutItem.MediaItem, playoutItem.ChapterTitle),
|
||||||
playoutItem.StartOffset,
|
playoutItem.StartOffset,
|
||||||
playoutItem.FinishOffset,
|
playoutItem.FinishOffset,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using ErsatzTV.Core.Domain.Filler;
|
|||||||
namespace ErsatzTV.Application.Playouts;
|
namespace ErsatzTV.Application.Playouts;
|
||||||
|
|
||||||
public record PlayoutItemViewModel(
|
public record PlayoutItemViewModel(
|
||||||
|
int? Id,
|
||||||
string Title,
|
string Title,
|
||||||
DateTimeOffset Start,
|
DateTimeOffset Start,
|
||||||
DateTimeOffset Finish,
|
DateTimeOffset Finish,
|
||||||
|
|||||||
@@ -106,7 +106,9 @@ public class GetFuturePlayoutItemsByIdHandler(IDbContextFactory<TvContext> dbCon
|
|||||||
|
|
||||||
var gap = playoutGaps.Single(g => g.Id == c.Id);
|
var gap = playoutGaps.Single(g => g.Id == c.Id);
|
||||||
TimeSpan gapDuration = gap.Finish - gap.Start;
|
TimeSpan gapDuration = gap.Finish - gap.Start;
|
||||||
|
// gaps are synthesized rows, not PlayoutItems, so they carry no row id
|
||||||
return new PlayoutItemViewModel(
|
return new PlayoutItemViewModel(
|
||||||
|
null,
|
||||||
"UNSCHEDULED",
|
"UNSCHEDULED",
|
||||||
gap.StartOffset,
|
gap.StartOffset,
|
||||||
gap.FinishOffset,
|
gap.FinishOffset,
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
#nullable enable
|
||||||
using ErsatzTV.Core.Domain.Filler;
|
using ErsatzTV.Core.Domain.Filler;
|
||||||
|
|
||||||
namespace ErsatzTV.Core.Api.Playouts;
|
namespace ErsatzTV.Core.Api.Playouts;
|
||||||
|
|
||||||
public record PlayoutItemResponseModel(
|
public record PlayoutItemResponseModel(
|
||||||
|
int? Id,
|
||||||
string Title,
|
string Title,
|
||||||
DateTimeOffset Start,
|
DateTimeOffset Start,
|
||||||
DateTimeOffset Finish,
|
DateTimeOffset Finish,
|
||||||
|
|||||||
@@ -519,6 +519,7 @@ public class PlayoutControllerTests
|
|||||||
.Returns(Option<PlayoutNameViewModel>.Some(MakePlayout(9)));
|
.Returns(Option<PlayoutNameViewModel>.Some(MakePlayout(9)));
|
||||||
|
|
||||||
var item = new PlayoutItemViewModel(
|
var item = new PlayoutItemViewModel(
|
||||||
|
77,
|
||||||
"Movie",
|
"Movie",
|
||||||
new DateTimeOffset(2026, 7, 2, 12, 0, 0, TimeSpan.Zero),
|
new DateTimeOffset(2026, 7, 2, 12, 0, 0, TimeSpan.Zero),
|
||||||
new DateTimeOffset(2026, 7, 2, 13, 0, 0, TimeSpan.Zero),
|
new DateTimeOffset(2026, 7, 2, 13, 0, 0, TimeSpan.Zero),
|
||||||
@@ -526,6 +527,7 @@ public class PlayoutControllerTests
|
|||||||
string.Empty,
|
string.Empty,
|
||||||
Some(FillerKind.MidRoll));
|
Some(FillerKind.MidRoll));
|
||||||
var gap = new PlayoutItemViewModel(
|
var gap = new PlayoutItemViewModel(
|
||||||
|
null,
|
||||||
"UNSCHEDULED",
|
"UNSCHEDULED",
|
||||||
new DateTimeOffset(2026, 7, 2, 13, 0, 0, TimeSpan.Zero),
|
new DateTimeOffset(2026, 7, 2, 13, 0, 0, TimeSpan.Zero),
|
||||||
new DateTimeOffset(2026, 7, 2, 13, 30, 0, TimeSpan.Zero),
|
new DateTimeOffset(2026, 7, 2, 13, 30, 0, TimeSpan.Zero),
|
||||||
@@ -539,9 +541,11 @@ public class PlayoutControllerTests
|
|||||||
|
|
||||||
var result = actionResult.ShouldBeOfType<OkObjectResult>().Value.ShouldBeOfType<PagedPlayoutItemsResponseModel>();
|
var result = actionResult.ShouldBeOfType<OkObjectResult>().Value.ShouldBeOfType<PagedPlayoutItemsResponseModel>();
|
||||||
result.TotalCount.ShouldBe(2);
|
result.TotalCount.ShouldBe(2);
|
||||||
|
result.Page[0].Id.ShouldBe(77);
|
||||||
result.Page[0].Title.ShouldBe("Movie");
|
result.Page[0].Title.ShouldBe("Movie");
|
||||||
result.Page[0].Duration.ShouldBe("1:00:00");
|
result.Page[0].Duration.ShouldBe("1:00:00");
|
||||||
result.Page[0].FillerKind.ShouldBe(FillerKind.MidRoll);
|
result.Page[0].FillerKind.ShouldBe(FillerKind.MidRoll);
|
||||||
|
result.Page[1].Id.ShouldBeNull();
|
||||||
result.Page[1].Title.ShouldBe("UNSCHEDULED");
|
result.Page[1].Title.ShouldBe("UNSCHEDULED");
|
||||||
result.Page[1].FillerKind.ShouldBeNull();
|
result.Page[1].FillerKind.ShouldBeNull();
|
||||||
|
|
||||||
@@ -558,6 +562,7 @@ public class PlayoutControllerTests
|
|||||||
.Returns(Option<PlayoutNameViewModel>.Some(MakePlayout(9)));
|
.Returns(Option<PlayoutNameViewModel>.Some(MakePlayout(9)));
|
||||||
|
|
||||||
var withContext = new PlayoutItemViewModel(
|
var withContext = new PlayoutItemViewModel(
|
||||||
|
42,
|
||||||
"Movie",
|
"Movie",
|
||||||
new DateTimeOffset(2026, 7, 2, 12, 0, 0, TimeSpan.Zero),
|
new DateTimeOffset(2026, 7, 2, 12, 0, 0, TimeSpan.Zero),
|
||||||
new DateTimeOffset(2026, 7, 2, 13, 0, 0, TimeSpan.Zero),
|
new DateTimeOffset(2026, 7, 2, 13, 0, 0, TimeSpan.Zero),
|
||||||
|
|||||||
@@ -743,6 +743,7 @@ public class PlayoutController(IMediator mediator) : ControllerBase
|
|||||||
|
|
||||||
private static PlayoutItemResponseModel ToItemResponse(PlayoutItemViewModel vm) =>
|
private static PlayoutItemResponseModel ToItemResponse(PlayoutItemViewModel vm) =>
|
||||||
new(
|
new(
|
||||||
|
vm.Id,
|
||||||
vm.Title,
|
vm.Title,
|
||||||
vm.Start,
|
vm.Start,
|
||||||
vm.Finish,
|
vm.Finish,
|
||||||
|
|||||||
@@ -19334,6 +19334,7 @@
|
|||||||
},
|
},
|
||||||
"PlayoutItemResponseModel": {
|
"PlayoutItemResponseModel": {
|
||||||
"required": [
|
"required": [
|
||||||
|
"id",
|
||||||
"title",
|
"title",
|
||||||
"start",
|
"start",
|
||||||
"finish",
|
"finish",
|
||||||
@@ -19343,11 +19344,15 @@
|
|||||||
],
|
],
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"title": {
|
"id": {
|
||||||
"type": [
|
"type": [
|
||||||
"null",
|
"null",
|
||||||
"string"
|
"integer"
|
||||||
]
|
],
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
},
|
},
|
||||||
"start": {
|
"start": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -19358,10 +19363,7 @@
|
|||||||
"format": "date-time"
|
"format": "date-time"
|
||||||
},
|
},
|
||||||
"duration": {
|
"duration": {
|
||||||
"type": [
|
"type": "string"
|
||||||
"null",
|
|
||||||
"string"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"fillerKind": {
|
"fillerKind": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
|
|||||||
Vendored
+3
-2
@@ -1044,10 +1044,11 @@ export interface components {
|
|||||||
"details": string;
|
"details": string;
|
||||||
};
|
};
|
||||||
"PlayoutItemResponseModel": {
|
"PlayoutItemResponseModel": {
|
||||||
"title": null | string;
|
"id": null | number;
|
||||||
|
"title": string;
|
||||||
"start": string;
|
"start": string;
|
||||||
"finish": string;
|
"finish": string;
|
||||||
"duration": null | string;
|
"duration": string;
|
||||||
"fillerKind": null | components["schemas"]["FillerKind"];
|
"fillerKind": null | components["schemas"]["FillerKind"];
|
||||||
"hasSchedulingContext": boolean;
|
"hasSchedulingContext": boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user