feat(297): add channelId to PlayoutListItemResponseModel; SPA reset keys directly
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 11s
PR Gates / Docs update reminder (pull_request) Successful in 12s
PR Gates / decisions lifecycle (pull_request) Successful in 20s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m37s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 21s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 4m54s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 19m41s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 16m43s

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) <noreply@anthropic.com>
This commit is contained in:
2026-07-22 18:57:54 +02:00
co-authored by Claude Opus 4.8
parent 1681ae4e60
commit 1a3c8e277f
15 changed files with 24 additions and 12 deletions
@@ -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,
@@ -50,6 +50,7 @@ public class UpdatePlayoutHandler : IRequestHandler<UpdatePlayout, Either<BaseEr
playout.ScheduleKind,
playout.Channel.Name,
playout.Channel.Number,
playout.Channel.Id,
playout.Channel.PlayoutMode,
playout.ProgramSchedule?.Name ?? string.Empty,
playout.ScheduleFile,
@@ -53,6 +53,7 @@ public class
playout.ScheduleKind,
playout.Channel.Name,
playout.Channel.Number,
playout.Channel.Id,
playout.Channel.PlayoutMode,
playout.ProgramSchedule?.Name ?? string.Empty,
playout.ScheduleFile,
@@ -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,
+1
View File
@@ -11,6 +11,7 @@ internal static class Mapper
playout.ScheduleKind,
playout.Channel.Name,
playout.Channel.Number,
playout.Channel.Id,
playout.Channel.PlayoutMode,
playout.ProgramScheduleId == null ? string.Empty : playout.ProgramSchedule.Name,
playout.ScheduleFile,
@@ -7,6 +7,7 @@ public record PlayoutNameViewModel(
PlayoutScheduleKind ScheduleKind,
string ChannelName,
string ChannelNumber,
int ChannelId,
ChannelPlayoutMode PlayoutMode,
string ScheduleName,
string ScheduleFile,
@@ -24,6 +24,7 @@ public class GetPlayoutByIdHandler(IDbContextFactory<TvContext> 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,
@@ -7,6 +7,7 @@ public record PlayoutListItemResponseModel(
int Id,
string ChannelNumber,
string ChannelName,
int ChannelId,
PlayoutScheduleKind ScheduleKind,
string ScheduleName,
TimeSpan? DailyRebuildTime,
@@ -520,6 +520,7 @@ public class ChannelControllerTests
scheduleKind,
"Channel",
"5",
1,
ChannelPlayoutMode.Continuous,
"Schedule",
string.Empty,
@@ -1415,6 +1415,7 @@ public class PlayoutControllerTests
PlayoutScheduleKind.Classic,
"Channel",
"101",
1,
ChannelPlayoutMode.Continuous,
"Schedule",
string.Empty,
@@ -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,
+5
View File
@@ -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"
},
+1
View File
@@ -1140,6 +1140,7 @@ export interface components {
"id": number;
"channelNumber": string;
"channelName": string;
"channelId": number;
"scheduleKind": components["schemas"]["PlayoutScheduleKind"];
"scheduleName": string;
"dailyRebuildTime": null | string;
+4 -2
View File
@@ -17,6 +17,7 @@ function listPlayout(overrides: Record<string, unknown> = {}): Record<string, un
message: null,
success: true
},
channelId: 42,
channelName: 'Retro Cartoons',
channelNumber: '5.1',
dailyRebuildTime: '04:00:00',
@@ -564,7 +565,7 @@ describe('PlayoutsScreen', () => {
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(<PlayoutsScreen />);
@@ -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' })
+3 -10
View File
@@ -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)
);
};