Adds a paged collection-items endpoint reusing LibraryBrowseItemResponseModel
so the SPA lists a manual collection's full contents (all media kinds), replacing
the lossy Lucene name-based preview. Confirms POST /items already returns 422 for
bogus ids (guarded by ValidateMediaItems, fb3f2856); adds endpoint-level coverage.
fixes #155
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
57 lines
2.3 KiB
C#
57 lines
2.3 KiB
C#
using ErsatzTV.Application.LibraryBrowse;
|
|
using ErsatzTV.Core;
|
|
using ErsatzTV.Core.Api.LibraryBrowse;
|
|
using ErsatzTV.Core.Errors;
|
|
using ErsatzTV.Infrastructure.Data;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace ErsatzTV.Application.MediaCollections;
|
|
|
|
public class GetCollectionItemsHandler(IDbContextFactory<TvContext> dbContextFactory)
|
|
: IRequestHandler<GetCollectionItems, Either<BaseError, PagedLibraryBrowseItemsResponseModel>>
|
|
{
|
|
public async Task<Either<BaseError, PagedLibraryBrowseItemsResponseModel>> Handle(
|
|
GetCollectionItems request,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
bool exists = await dbContext.Collections
|
|
.AsNoTracking()
|
|
.AnyAsync(c => c.Id == request.Id, cancellationToken);
|
|
if (!exists)
|
|
{
|
|
return new NotFoundError($"Collection {request.Id} does not exist.");
|
|
}
|
|
|
|
// The collection graph is bounded, so load every member id and hydrate them in one shared
|
|
// pass (LibraryBrowseItemMapper), then order + page in-memory. Mixed media kinds are supported
|
|
// because MediaItem ids are globally unique across kinds.
|
|
List<int> mediaItemIds = await dbContext.CollectionItems
|
|
.AsNoTracking()
|
|
.Where(ci => ci.CollectionId == request.Id)
|
|
.Select(ci => ci.MediaItemId)
|
|
.ToListAsync(cancellationToken);
|
|
|
|
List<LibraryBrowseItemResponseModel> all =
|
|
await LibraryBrowseItemMapper.HydrateMediaItemsByIds(dbContext, mediaItemIds, cancellationToken);
|
|
|
|
// Stable title ordering mirrors the library-browse handler (which orders its rows by name),
|
|
// giving the SPA a deterministic, browsable list independent of collection insertion order.
|
|
List<LibraryBrowseItemResponseModel> ordered = all
|
|
.OrderBy(i => i.Title, StringComparer.OrdinalIgnoreCase)
|
|
.ThenBy(i => i.Id)
|
|
.ToList();
|
|
|
|
int pageNum = Math.Max(0, request.PageNum);
|
|
int pageSize = Math.Clamp(request.PageSize, 1, 100);
|
|
|
|
List<LibraryBrowseItemResponseModel> page = ordered
|
|
.Skip(pageNum * pageSize)
|
|
.Take(pageSize)
|
|
.ToList();
|
|
|
|
return new PagedLibraryBrowseItemsResponseModel(ordered.Count, page);
|
|
}
|
|
}
|