fix(api): reject unsupported CollectionType on rerun-collection create/update (#152 review)
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 4m25s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m42s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped

The rerun request mapping's catch-all routed Playlist/RerunFirstRun/RerunRerun/
SearchQuery/Fake* to MediaItemId, so POST/PUT with those persisted a wrong-kind id
and returned 201 instead of 422. Guard both verbs with IsSupportedSelectionType.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-08 18:30:06 +02:00
co-authored by Claude Opus 4.8
parent 16c8789638
commit dc459c6cd5
3 changed files with 71 additions and 1 deletions
@@ -14,6 +14,27 @@ namespace ErsatzTV.Controllers.Api.Requests;
// doing extra lookup queries. The remaining VMs are left null (the handler expects that).
internal static class RerunCollectionRequestMapping
{
/// <summary>
/// The only <see cref="CollectionType" /> values valid as a rerun collection's selected
/// source. Excludes Playlist / RerunFirstRun / RerunRerun / SearchQuery / Fake* — those
/// are not media-item-backed collection types and must not be resolved via
/// <see cref="ResolveSelection" />. Callers (the controller) must check this first.
/// </summary>
public static bool IsSupportedSelectionType(CollectionType collectionType) =>
collectionType is CollectionType.Collection
or CollectionType.MultiCollection
or CollectionType.SmartCollection
or CollectionType.TelevisionShow
or CollectionType.TelevisionSeason
or CollectionType.Artist
or CollectionType.Movie
or CollectionType.Episode
or CollectionType.MusicVideo
or CollectionType.OtherVideo
or CollectionType.Song
or CollectionType.Image
or CollectionType.RemoteStream;
public static (MediaCollectionViewModel Collection,
MultiCollectionViewModel MultiCollection,
SmartCollectionViewModel SmartCollection,
@@ -43,7 +64,9 @@ internal static class RerunCollectionRequestMapping
// Any media-item-backed collection type (TelevisionShow, TelevisionSeason,
// Artist, Movie, Episode, MusicVideo, OtherVideo, Song, Image, ...) resolves to
// the MediaItem VM keyed by media item id.
// the MediaItem VM keyed by media item id. The caller (RerunCollectionController)
// has already validated CollectionType via IsSupportedSelectionType, so this
// catch-all only ever receives a real media-item type.
_ => (
null,
null,
@@ -62,6 +62,13 @@ public class RerunCollectionController(IMediator mediator) : ControllerBase
[Required] [FromBody] CreateRerunCollectionRequest request,
CancellationToken cancellationToken)
{
if (!RerunCollectionRequestMapping.IsSupportedSelectionType(request.CollectionType))
{
return BaseError.New(
$"Unsupported collection type '{request.CollectionType}' for a rerun collection")
.ToErrorResult();
}
Either<BaseError, RerunCollectionViewModel> result =
await mediator.Send(request.ToCommand(), cancellationToken);
return result.ToCreatedResult(
@@ -81,6 +88,13 @@ public class RerunCollectionController(IMediator mediator) : ControllerBase
[Required] [FromBody] UpdateRerunCollectionRequest request,
CancellationToken cancellationToken)
{
if (!RerunCollectionRequestMapping.IsSupportedSelectionType(request.CollectionType))
{
return BaseError.New(
$"Unsupported collection type '{request.CollectionType}' for a rerun collection")
.ToErrorResult();
}
Either<BaseError, Unit> result = await mediator.Send(request.ToCommand(id), cancellationToken);
return await result.Match(
Left: error => Task.FromResult(error.ToErrorResult()),