From 1a3c8e277fb5ef4f3fa916f8e642198c1ef03127 Mon Sep 17 00:00:00 2001 From: Timothy Date: Wed, 22 Jul 2026 18:56:56 +0200 Subject: [PATCH] feat(297): add channelId to PlayoutListItemResponseModel; SPA reset keys directly Added ChannelId to PlayoutNameViewModel and all 6 construction sites (Mapper, GetPlayoutByIdHandler, and the Update{,Scripted,ExternalJson,Sequential} PlayoutHandler commands), plus the list DTO PlayoutListItemResponseModel and the PlayoutController list projection. Regenerated OpenAPI (v1.json) and the TS client (v1.d.ts); endpoint-index.md unchanged (no endpoint/operation delta). Simplified PlayoutsScreen resetSelectedChannel to key directly on selectedSummary.channelId instead of resolving via channelStates. Updated controller + SPA tests. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Commands/UpdateExternalJsonPlayoutHandler.cs | 1 + .../Playouts/Commands/UpdatePlayoutHandler.cs | 1 + .../Commands/UpdateScriptedPlayoutHandler.cs | 1 + .../Commands/UpdateSequentialPlayoutHandler.cs | 1 + ErsatzTV.Application/Playouts/Mapper.cs | 1 + .../Playouts/PlayoutNameViewModel.cs | 1 + .../Playouts/Queries/GetPlayoutByIdHandler.cs | 1 + .../Api/Playouts/PlayoutListItemResponseModel.cs | 1 + .../Controllers/ChannelControllerTests.cs | 1 + .../Controllers/PlayoutControllerTests.cs | 1 + ErsatzTV/Controllers/Api/PlayoutController.cs | 1 + ErsatzTV/wwwroot/openapi/v1.json | 5 +++++ web/src/api/generated/v1.d.ts | 1 + web/src/screens/PlayoutsScreen.test.tsx | 6 ++++-- web/src/screens/PlayoutsScreen.tsx | 13 +++---------- 15 files changed, 24 insertions(+), 12 deletions(-) diff --git a/ErsatzTV.Application/Playouts/Commands/UpdateExternalJsonPlayoutHandler.cs b/ErsatzTV.Application/Playouts/Commands/UpdateExternalJsonPlayoutHandler.cs index 494bddd7d..a85ef3aa7 100644 --- a/ErsatzTV.Application/Playouts/Commands/UpdateExternalJsonPlayoutHandler.cs +++ b/ErsatzTV.Application/Playouts/Commands/UpdateExternalJsonPlayoutHandler.cs @@ -58,6 +58,7 @@ public class playout.ScheduleKind, playout.Channel.Name, playout.Channel.Number, + playout.Channel.Id, playout.Channel.PlayoutMode, playout.ProgramSchedule?.Name ?? string.Empty, playout.ScheduleFile, diff --git a/ErsatzTV.Application/Playouts/Commands/UpdatePlayoutHandler.cs b/ErsatzTV.Application/Playouts/Commands/UpdatePlayoutHandler.cs index f92ba186b..419fc0979 100644 --- a/ErsatzTV.Application/Playouts/Commands/UpdatePlayoutHandler.cs +++ b/ErsatzTV.Application/Playouts/Commands/UpdatePlayoutHandler.cs @@ -50,6 +50,7 @@ public class UpdatePlayoutHandler : IRequestHandler dbContextFactory p.ScheduleKind, p.Channel.Name, p.Channel.Number, + p.Channel.Id, p.Channel.PlayoutMode, p.ProgramScheduleId == null ? string.Empty : p.ProgramSchedule.Name, p.ScheduleFile, diff --git a/ErsatzTV.Core/Api/Playouts/PlayoutListItemResponseModel.cs b/ErsatzTV.Core/Api/Playouts/PlayoutListItemResponseModel.cs index 2b1da4754..4806490c4 100644 --- a/ErsatzTV.Core/Api/Playouts/PlayoutListItemResponseModel.cs +++ b/ErsatzTV.Core/Api/Playouts/PlayoutListItemResponseModel.cs @@ -7,6 +7,7 @@ public record PlayoutListItemResponseModel( int Id, string ChannelNumber, string ChannelName, + int ChannelId, PlayoutScheduleKind ScheduleKind, string ScheduleName, TimeSpan? DailyRebuildTime, diff --git a/ErsatzTV.Tests/Controllers/ChannelControllerTests.cs b/ErsatzTV.Tests/Controllers/ChannelControllerTests.cs index d313df95e..32e3a6119 100644 --- a/ErsatzTV.Tests/Controllers/ChannelControllerTests.cs +++ b/ErsatzTV.Tests/Controllers/ChannelControllerTests.cs @@ -520,6 +520,7 @@ public class ChannelControllerTests scheduleKind, "Channel", "5", + 1, ChannelPlayoutMode.Continuous, "Schedule", string.Empty, diff --git a/ErsatzTV.Tests/Controllers/PlayoutControllerTests.cs b/ErsatzTV.Tests/Controllers/PlayoutControllerTests.cs index 29259a9aa..dc0853519 100644 --- a/ErsatzTV.Tests/Controllers/PlayoutControllerTests.cs +++ b/ErsatzTV.Tests/Controllers/PlayoutControllerTests.cs @@ -1415,6 +1415,7 @@ public class PlayoutControllerTests PlayoutScheduleKind.Classic, "Channel", "101", + 1, ChannelPlayoutMode.Continuous, "Schedule", string.Empty, diff --git a/ErsatzTV/Controllers/Api/PlayoutController.cs b/ErsatzTV/Controllers/Api/PlayoutController.cs index 2581bd78e..a52f8d85a 100644 --- a/ErsatzTV/Controllers/Api/PlayoutController.cs +++ b/ErsatzTV/Controllers/Api/PlayoutController.cs @@ -870,6 +870,7 @@ public class PlayoutController(IMediator mediator, IEntityLocker entityLocker) : vm.PlayoutId, vm.ChannelNumber, vm.ChannelName, + vm.ChannelId, vm.ScheduleKind, vm.ScheduleName, vm.DbDailyRebuildTime, diff --git a/ErsatzTV/wwwroot/openapi/v1.json b/ErsatzTV/wwwroot/openapi/v1.json index 4a314b650..dc1644a52 100644 --- a/ErsatzTV/wwwroot/openapi/v1.json +++ b/ErsatzTV/wwwroot/openapi/v1.json @@ -28709,6 +28709,7 @@ "id", "channelNumber", "channelName", + "channelId", "scheduleKind", "scheduleName", "dailyRebuildTime", @@ -28729,6 +28730,10 @@ "channelName": { "type": "string" }, + "channelId": { + "type": "integer", + "format": "int32" + }, "scheduleKind": { "$ref": "#/components/schemas/PlayoutScheduleKind" }, diff --git a/web/src/api/generated/v1.d.ts b/web/src/api/generated/v1.d.ts index 596766c03..14fd2c4b4 100644 --- a/web/src/api/generated/v1.d.ts +++ b/web/src/api/generated/v1.d.ts @@ -1140,6 +1140,7 @@ export interface components { "id": number; "channelNumber": string; "channelName": string; + "channelId": number; "scheduleKind": components["schemas"]["PlayoutScheduleKind"]; "scheduleName": string; "dailyRebuildTime": null | string; diff --git a/web/src/screens/PlayoutsScreen.test.tsx b/web/src/screens/PlayoutsScreen.test.tsx index 949accbb7..4f8a04061 100644 --- a/web/src/screens/PlayoutsScreen.test.tsx +++ b/web/src/screens/PlayoutsScreen.test.tsx @@ -17,6 +17,7 @@ function listPlayout(overrides: Record = {}): Record { playoutItems: [playoutItem()], playoutDetails: playout({ id: 20, scheduleKind: 'Classic' }), channelStates: [{ channelId: 7, channelNumber: '5.1', onAir: true, nowPlaying: null }], - playouts: { page: [listPlayout({ id: 20, channelNumber: '5.1' })], totalCount: 1 } + playouts: { page: [listPlayout({ id: 20, channelId: 7, channelNumber: '5.1' })], totalCount: 1 } }); render(); @@ -574,7 +575,8 @@ describe('PlayoutsScreen', () => { fireEvent.click(screen.getByRole('button', { name: 'Reset' })); await waitFor(() => { - // Keyed on the resolved channel id (7), not the playout id (20) or the channel number. + // Keyed on the channel id carried directly on the playout summary (7, #297), not the playout + // id (20) or the channel number. expect(window.fetch).toHaveBeenCalledWith( '/api/v1/channels/7/playout/reset', expect.objectContaining({ method: 'POST' }) diff --git a/web/src/screens/PlayoutsScreen.tsx b/web/src/screens/PlayoutsScreen.tsx index 91d9ee2c0..765b586e0 100644 --- a/web/src/screens/PlayoutsScreen.tsx +++ b/web/src/screens/PlayoutsScreen.tsx @@ -414,17 +414,10 @@ export function PlayoutsScreen() { if (!selectedSummary) { return; } - // The reset endpoint keys on the immutable channel id. The playout summary only carries the - // channel number, so resolve the id from channel state (which covers every channel). - const channelId = channelStates.find( - (state) => state.channelNumber === selectedSummary.channelNumber - )?.channelId; - if (channelId == null) { - setMutationError('Unable to resolve the channel for this playout.'); - return; - } + // The reset endpoint keys on the immutable channel id, now carried directly on the playout + // summary (#297). runMutation(`Reset the playout for ${selectedSummary.channelName}?`, () => - resetChannelPlayout(channelId) + resetChannelPlayout(selectedSummary.channelId) ); }; -- 2.47.3