fix(api): rework from-lineup to generated playlist design + review fixes (#63)
Adversarial review found the previous multi-flood design non-viable:
PlayoutModeSchedulerFlood never yields to a following Dynamic-start
schedule item, so only the first item ever played, and grouping
media items into a Collection silently dropped the requested order.
Redesign:
- Single-item lineup: one ProgramScheduleItemFlood referencing the
target directly (media item / collection / smart / multi / rerun /
playlist); no generated collection or playlist. Response PlaylistId
is null.
- Multi-item lineup (>= 2): one generated IsSystem Playlist in a
get-or-created IsSystem PlaylistGroup ("Channel Lineups"), one
PlaylistItem per entry in lineup order with PlayAll=true, referenced
by a single Flood schedule item. Rerun collections and playlists are
rejected (422) in multi lineups (PlaylistItem/CollectionKey lack the
fields to enumerate them).
Review fixes:
- Normalize + strict-validate MediaType<->CollectionType pairs once up
front (422 on mismatch / wrong id / not exactly one id).
- Reject MultiCollection with non-Shuffle order (mirrors
PlayoutModeMustBeValid), Mirror playout source, all via 422.
- OnDemand parity: queue TimeShiftOnDemandPlayout post-commit.
- De-collide generated ProgramSchedule and Playlist names against their
unique indexes instead of leaking a UNIQUE-constraint DbUpdateException.
- Generic 422 on save failure + ILogger; AnyAsync existence checks;
Either/Validation unwrap via Match; XML doc on request DTO + endpoint.
- Response model: ChannelId, PlaylistId (nullable), ProgramScheduleId,
PlayoutId (CollectionId removed). Regenerated OpenAPI v1.json + v1.d.ts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -83,8 +83,15 @@ public class ChannelController(ChannelWriter<IBackgroundServiceRequest> workerCh
|
||||
[Tags("Channels")]
|
||||
[EndpointSummary("Create a channel from a library lineup")]
|
||||
[EndpointDescription(
|
||||
"Atomically creates the channel, generated lineup collection, program schedule, schedule items, " +
|
||||
"and classic playout. Template defaults are stamped at create time; advanced overrides win.")]
|
||||
"Atomically creates the channel, program schedule, a classic playout, and (for multi-item lineups) a " +
|
||||
"generated system playlist. A single-item lineup produces one flood schedule item that references the " +
|
||||
"target directly (movie, show, season, artist, collection, smart/multi collection, rerun collection, or " +
|
||||
"playlist) and no generated playlist. A lineup with two or more items produces one generated system " +
|
||||
"playlist whose entries play in the given order (each entry played in full before the next) referenced by " +
|
||||
"one flood schedule item; only movies, shows, seasons, artists, collections, smart collections and multi " +
|
||||
"collections are allowed there (rerun collections and playlists are single-item only). playbackOrder sets " +
|
||||
"how items within each lineup entry are ordered. Template defaults are stamped at create time; advanced " +
|
||||
"overrides win.")]
|
||||
[EndpointGroupName("general")]
|
||||
[ProducesResponseType(typeof(CreateChannelFromLineupResponseModel), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
|
||||
@@ -8,6 +8,12 @@ using ErsatzTV.Core.Scheduling;
|
||||
|
||||
namespace ErsatzTV.Controllers.Api.Requests;
|
||||
|
||||
/// <summary>
|
||||
/// Composite request to create a channel, its generated schedule/playlist and a classic playout in one call.
|
||||
/// Template defaults are stamped at create time; any value set in <see cref="Advanced" /> overrides the template.
|
||||
/// A single-item lineup references its target directly; a multi-item lineup is played in order via a generated
|
||||
/// system playlist.
|
||||
/// </summary>
|
||||
public record CreateChannelFromLineupRequest(
|
||||
string Name,
|
||||
string Number,
|
||||
|
||||
@@ -537,7 +537,7 @@
|
||||
"Channels"
|
||||
],
|
||||
"summary": "Create a channel from a library lineup",
|
||||
"description": "Atomically creates the channel, generated lineup collection, program schedule, schedule items, and classic playout. Template defaults are stamped at create time; advanced overrides win.",
|
||||
"description": "Atomically creates the channel, program schedule, a classic playout, and (for multi-item lineups) a generated system playlist. A single-item lineup produces one flood schedule item that references the target directly (movie, show, season, artist, collection, smart/multi collection, rerun collection, or playlist) and no generated playlist. A lineup with two or more items produces one generated system playlist whose entries play in the given order (each entry played in full before the next) referenced by one flood schedule item; only movies, shows, seasons, artists, collections, smart collections and multi collections are allowed there (rerun collections and playlists are single-item only). playbackOrder sets how items within each lineup entry are ordered. Template defaults are stamped at create time; advanced overrides win.",
|
||||
"operationId": "CreateChannelFromLineup",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
@@ -5607,7 +5607,7 @@
|
||||
"CreateChannelFromLineupResponseModel": {
|
||||
"required": [
|
||||
"channelId",
|
||||
"collectionId",
|
||||
"playlistId",
|
||||
"programScheduleId",
|
||||
"playoutId"
|
||||
],
|
||||
@@ -5617,8 +5617,11 @@
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"collectionId": {
|
||||
"type": "integer",
|
||||
"playlistId": {
|
||||
"type": [
|
||||
"null",
|
||||
"integer"
|
||||
],
|
||||
"format": "int32"
|
||||
},
|
||||
"programScheduleId": {
|
||||
|
||||
Reference in New Issue
Block a user