Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m43s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 12s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 9m52s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Adversarial review follow-ups on the #193 rerun selection validation: - RerunCollectionEditor.razor: remove the CollectionType.Playlist dropdown option — RerunCollection has no PlaylistId column, so the option previously persisted a broken row and now fails validation. - RerunCollectionSelectionValidation: replace FindAsync with a non-tracking AnyAsync(EF.Property<int>("Id") == id) existence check so nothing is materialized/tracked into the save-path context. - Tests: pin the deliberate 422-not-404 choice (ShouldNotBeOfType<NotFoundError>) and cover the unsupported-type default arm (Playlist -> "Unsupported collection type"), which was reachable via the Blazor editor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
113 lines
4.2 KiB
C#
113 lines
4.2 KiB
C#
using ErsatzTV.Core;
|
|
using ErsatzTV.Core.Domain;
|
|
using ErsatzTV.Infrastructure.Data;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace ErsatzTV.Application.MediaCollections;
|
|
|
|
/// <summary>
|
|
/// Shared existence check for the id a rerun collection selects via <see cref="CollectionType" />
|
|
/// (see <c>RerunCollectionRequestMapping.ResolveSelection</c> in the API layer). Used by both
|
|
/// <see cref="CreateRerunCollectionHandler" /> and <see cref="UpdateRerunCollectionHandler" /> so
|
|
/// a bad/stale id fails validation (422) instead of persisting a dangling FK or 500ing at
|
|
/// SaveChanges.
|
|
/// </summary>
|
|
internal static class RerunCollectionSelectionValidation
|
|
{
|
|
public static Task<Validation<BaseError, Unit>> SelectedEntityMustExist(
|
|
TvContext dbContext,
|
|
CollectionType collectionType,
|
|
int? collectionId,
|
|
int? multiCollectionId,
|
|
int? smartCollectionId,
|
|
int? mediaItemId,
|
|
CancellationToken cancellationToken) =>
|
|
collectionType switch
|
|
{
|
|
CollectionType.Collection => Exists(
|
|
dbContext.Collections,
|
|
collectionId,
|
|
"Collection does not exist.",
|
|
cancellationToken),
|
|
CollectionType.MultiCollection => Exists(
|
|
dbContext.MultiCollections,
|
|
multiCollectionId,
|
|
"Multi collection does not exist.",
|
|
cancellationToken),
|
|
CollectionType.SmartCollection => Exists(
|
|
dbContext.SmartCollections,
|
|
smartCollectionId,
|
|
"Smart collection does not exist.",
|
|
cancellationToken),
|
|
CollectionType.TelevisionShow => Exists(
|
|
dbContext.Shows,
|
|
mediaItemId,
|
|
"Show does not exist.",
|
|
cancellationToken),
|
|
CollectionType.TelevisionSeason => Exists(
|
|
dbContext.Seasons,
|
|
mediaItemId,
|
|
"Season does not exist.",
|
|
cancellationToken),
|
|
CollectionType.Artist => Exists(
|
|
dbContext.Artists,
|
|
mediaItemId,
|
|
"Artist does not exist.",
|
|
cancellationToken),
|
|
CollectionType.Movie => Exists(
|
|
dbContext.Movies,
|
|
mediaItemId,
|
|
"Movie does not exist.",
|
|
cancellationToken),
|
|
CollectionType.Episode => Exists(
|
|
dbContext.Episodes,
|
|
mediaItemId,
|
|
"Episode does not exist.",
|
|
cancellationToken),
|
|
CollectionType.MusicVideo => Exists(
|
|
dbContext.MusicVideos,
|
|
mediaItemId,
|
|
"Music video does not exist.",
|
|
cancellationToken),
|
|
CollectionType.OtherVideo => Exists(
|
|
dbContext.OtherVideos,
|
|
mediaItemId,
|
|
"Other video does not exist.",
|
|
cancellationToken),
|
|
CollectionType.Song => Exists(
|
|
dbContext.Songs,
|
|
mediaItemId,
|
|
"Song does not exist.",
|
|
cancellationToken),
|
|
CollectionType.Image => Exists(
|
|
dbContext.Images,
|
|
mediaItemId,
|
|
"Image does not exist.",
|
|
cancellationToken),
|
|
CollectionType.RemoteStream => Exists(
|
|
dbContext.RemoteStreams,
|
|
mediaItemId,
|
|
"Remote stream does not exist.",
|
|
cancellationToken),
|
|
_ => Task.FromResult(
|
|
Fail<BaseError, Unit>($"Unsupported collection type '{collectionType}' for a rerun collection."))
|
|
};
|
|
|
|
private static async Task<Validation<BaseError, Unit>> Exists<T>(
|
|
DbSet<T> set,
|
|
int? id,
|
|
string errorMessage,
|
|
CancellationToken cancellationToken)
|
|
where T : class
|
|
{
|
|
// pure existence check — no tracking, nothing materialized into the save-path context
|
|
if (id is null ||
|
|
!await set.AnyAsync(e => EF.Property<int>(e, "Id") == id.Value, cancellationToken))
|
|
{
|
|
return Fail<BaseError, Unit>(errorMessage);
|
|
}
|
|
|
|
return Success<BaseError, Unit>(Unit.Default);
|
|
}
|
|
}
|