fix(schedules): reload write-path response with read includes + order GET by index (#229)
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>
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user