Bug 1 (500 on watermark/graphics save): Replace/Add handlers projected the freshly-built entity graph, whose ProgramScheduleItemWatermark / -GraphicsElement join rows carry only foreign-key ids — the Watermark/GraphicsElement navs are null, and Mapper.ProjectToViewModel dereferences them unguarded, throwing an NRE that the controller surfaced as a 500 on PUT/POST. Both handlers now reload the persisted item(s) through the read-side include chain before projecting. Extracted that chain into ProgramScheduleItemQueryExtensions.IncludeScheduleItemDetails() so GET, Replace and Add share one source of truth. Masking: PersistItems returned a lazy LanguageExt Map, and the existing round-trip test only checked .IsRight — never enumerating it, so the deferred NRE never fired. The new ScheduleItemWriteProjectionTests force enumeration (as the controller's .ToList()/serialization does) and seed watermark/graphics via a separate context so the handler's fresh factory context has nothing pre-tracked. Bug 2 (server side): GetProgramScheduleItemsHandler now .OrderBy(i => i.Index) — it previously returned id order, which is not index order. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
49 lines
2.3 KiB
C#
49 lines
2.3 KiB
C#
using ErsatzTV.Core.Domain;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace ErsatzTV.Application.ProgramSchedules;
|
|
|
|
internal static class ProgramScheduleItemQueryExtensions
|
|
{
|
|
/// <summary>
|
|
/// The single source of truth for the navigation graph a <see cref="ProgramScheduleItem" /> needs before
|
|
/// it can be projected via <see cref="Mapper.ProjectToViewModel" />. The mapper dereferences the
|
|
/// <c>Watermark</c> / <c>GraphicsElement</c> navs of each join row without a null guard, so any handler
|
|
/// that projects freshly-persisted items MUST reload through this include chain (see #229 — the write
|
|
/// path used to project the tracked-but-unloaded graph and threw a NullReferenceException, surfacing as a
|
|
/// 500 on PUT/POST whenever an item carried a watermark or graphics element).
|
|
/// </summary>
|
|
public static IQueryable<ProgramScheduleItem> IncludeScheduleItemDetails(this IQueryable<ProgramScheduleItem> query) =>
|
|
query
|
|
.Include(i => i.Collection)
|
|
.Include(i => i.MultiCollection)
|
|
.Include(i => i.SmartCollection)
|
|
.Include(i => i.RerunCollection)
|
|
.Include(i => i.Playlist)
|
|
.Include(i => i.MediaItem)
|
|
.ThenInclude(i => (i as Movie).MovieMetadata)
|
|
.ThenInclude(mm => mm.Artwork)
|
|
.Include(i => i.MediaItem)
|
|
.ThenInclude(i => (i as Season).SeasonMetadata)
|
|
.ThenInclude(sm => sm.Artwork)
|
|
.Include(i => i.MediaItem)
|
|
.ThenInclude(i => (i as Season).Show)
|
|
.ThenInclude(s => s.ShowMetadata)
|
|
.ThenInclude(sm => sm.Artwork)
|
|
.Include(i => i.MediaItem)
|
|
.ThenInclude(i => (i as Show).ShowMetadata)
|
|
.ThenInclude(sm => sm.Artwork)
|
|
.Include(i => i.MediaItem)
|
|
.ThenInclude(i => (i as Artist).ArtistMetadata)
|
|
.ThenInclude(am => am.Artwork)
|
|
.Include(i => i.PreRollFiller)
|
|
.Include(i => i.MidRollFiller)
|
|
.Include(i => i.PostRollFiller)
|
|
.Include(i => i.TailFiller)
|
|
.Include(i => i.FallbackFiller)
|
|
.Include(i => i.ProgramScheduleItemWatermarks)
|
|
.ThenInclude(i => i.Watermark)
|
|
.Include(i => i.ProgramScheduleItemGraphicsElements)
|
|
.ThenInclude(i => i.GraphicsElement);
|
|
}
|