Files
ersatztv/ErsatzTV/Controllers/Api/Requests/ScheduleItemRequest.cs
T
timothyandClaude Opus 4.8 ef2bd65c27
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 10s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 10s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m12s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 3m4s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m17s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 10m36s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
feat(api): #286 — mount the whole /api surface at /api/v1
Version every /api route to /api/v1 (251 controller routes + ~24 Location
headers + the scanner callback URL + the Startup request-log literal),
uniform across the machine API, auth, scanner and scripted-build surfaces.

Add ApiVersionRewriteMiddleware: a legacy unversioned /api/* request is
rewritten (NOT redirected) to /api/v1/* in-pipeline — method, body, auth
headers and query survive — carrying RFC 8594 Deprecation/Sunset headers,
so curl / the future MCP server / bookmarks keep working. An already-
versioned path passes through; a future /api/v2 is never forced to v1.

Standardize the route convention (leading-slash absolute route per method,
no class-[Route] — except the two Scanner/Scripted controllers whose ~all
actions share a parametrized {id} prefix), enforced by ApiRouteVersioningTests
(^/api/v\d+/ over the whole Controllers.Api surface; browser-nav
/auth/oidc/login is out of scope).

Regenerate v1.json (160 paths, all /api/v1)/endpoint-index/v1.d.ts; sweep 945
SPA request literals + the test mocks (regex + positional URL parsers). /api/v1
is additive-only after freeze; the legacy-rewrite shim sunsets in ~2 releases
(owner decision) with removal tracked as a Phase-3 follow-up.

Docs: decisions.md 2026-07-13, api-conventions §1/§9, rest-api/spa-conventions/
blazor-route-parity/e2e-local/domain-model.

fixes #286
refs #197

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 00:30:20 +02:00

138 lines
4.2 KiB
C#

using ErsatzTV.Application.ProgramSchedules;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Scheduling;
namespace ErsatzTV.Controllers.Api.Requests;
/// <param name="Id">
/// Server-assigned identity of an existing schedule item, as returned by GET /api/v1/schedules/{id}/items.
/// Omit (or send null / 0) for a new item. On a replace, the item carrying this id keeps its persisted
/// fill-group/shuffle progression even when moved to a different position. Unknown or duplicated ids are
/// rejected with 422 — never fabricate an id.
/// </param>
public record ScheduleItemRequest(
int? Id,
StartType StartType,
TimeSpan? StartTime,
FixedStartTimeBehavior? FixedStartTimeBehavior,
PlayoutMode PlayoutMode,
CollectionType CollectionType,
int? CollectionId,
int? MultiCollectionId,
int? SmartCollectionId,
int? RerunCollectionId,
int? MediaItemId,
int? PlaylistId,
string SearchTitle,
string SearchQuery,
PlaybackOrder PlaybackOrder,
MarathonGroupBy MarathonGroupBy,
bool MarathonShuffleGroups,
bool MarathonShuffleItems,
int? MarathonBatchSize,
FillWithGroupMode FillWithGroupMode,
MultipleMode MultipleMode,
string MultipleCount,
TimeSpan? PlayoutDuration,
TailMode TailMode,
int? DiscardToFillAttempts,
string CustomTitle,
GuideMode GuideMode,
int? PreRollFillerId,
int? MidRollFillerId,
int? PostRollFillerId,
int? TailFillerId,
int? FallbackFillerId,
List<int> WatermarkIds,
List<int> GraphicsElementIds,
string PreferredAudioLanguageCode,
string PreferredAudioTitle,
string PreferredSubtitleLanguageCode,
ChannelSubtitleMode? SubtitleMode)
{
public AddProgramScheduleItem ToAddCommand(int scheduleId) =>
new(
scheduleId,
StartType,
StartTime,
FixedStartTimeBehavior,
PlayoutMode,
CollectionType,
CollectionId,
MultiCollectionId,
SmartCollectionId,
RerunCollectionId,
MediaItemId,
PlaylistId,
SearchTitle,
SearchQuery,
PlaybackOrder,
MarathonGroupBy,
MarathonShuffleGroups,
MarathonShuffleItems,
MarathonBatchSize,
FillWithGroupMode,
MultipleMode,
MultipleCount,
PlayoutDuration,
TailMode,
DiscardToFillAttempts,
CustomTitle,
GuideMode,
PreRollFillerId,
MidRollFillerId,
PostRollFillerId,
TailFillerId,
FallbackFillerId,
WatermarkIds ?? [],
GraphicsElementIds ?? [],
PreferredAudioLanguageCode,
PreferredAudioTitle,
PreferredSubtitleLanguageCode,
SubtitleMode);
public ReplaceProgramScheduleItem ToReplaceCommand(int index) =>
new(
// normalize the two-state client value (null / 0 / positive) to the handler's null-or-real
// contract so 0-defaulting clients read as "new item" rather than "existing item 0"
Id is > 0 ? Id : null,
index,
StartType,
StartTime,
FixedStartTimeBehavior,
PlayoutMode,
CollectionType,
CollectionId,
MultiCollectionId,
SmartCollectionId,
RerunCollectionId,
MediaItemId,
PlaylistId,
SearchTitle,
SearchQuery,
PlaybackOrder,
MarathonGroupBy,
MarathonShuffleGroups,
MarathonShuffleItems,
MarathonBatchSize,
FillWithGroupMode,
MultipleMode,
MultipleCount,
PlayoutDuration,
TailMode,
DiscardToFillAttempts,
CustomTitle,
GuideMode,
PreRollFillerId,
MidRollFillerId,
PostRollFillerId,
TailFillerId,
FallbackFillerId,
WatermarkIds ?? [],
GraphicsElementIds ?? [],
PreferredAudioLanguageCode,
PreferredAudioTitle,
PreferredSubtitleLanguageCode,
SubtitleMode);
}