fix: include Release nav on all TrackRepository query paths; add unique constraint on release(title, artist)

This commit is contained in:
daniel-c-harvey
2026-06-11 14:48:52 -04:00
parent f767d288c5
commit 70d4a87cd5
6 changed files with 246 additions and 7 deletions
+19 -7
View File
@@ -1,3 +1,4 @@
using Data.Errors;
using Data.Managers;
using DeepDrftData.Repositories;
using DeepDrftModels.DTOs;
@@ -121,13 +122,12 @@ public class TrackManager
}
};
// An all-null filter must produce identical results to no filter, so collapse it to
// null and take the unfiltered base path (preserves backward compatibility).
// Always route through GetPagedFilteredAsync — it handles a null filter by skipping
// all Where predicates, and it always includes Release. This removes the base-class
// GetPagedAsync path, which has no .Include and would return entities with null Release.
var effectiveFilter = filter is null || filter.IsEmpty ? null : filter;
var page = effectiveFilter is null
? await Repository.GetPagedAsync(parameters)
: await Repository.GetPagedFilteredAsync(parameters, effectiveFilter, cancellationToken);
var page = await Repository.GetPagedFilteredAsync(parameters, effectiveFilter, cancellationToken);
var dtoPage = PagedResult<TrackDto>.From(page, page.Items.Select(TrackConverter.Convert));
return ResultContainer<PagedResult<TrackDto>>.CreatePassResult(dtoPage);
@@ -178,8 +178,20 @@ public class TrackManager
entity.Title = title;
entity.Artist = artist;
var added = await Repository.AddReleaseAsync(entity, cancellationToken);
return ResultContainer<ReleaseDto>.CreatePassResult(TrackConverter.Convert(added));
try
{
var added = await Repository.AddReleaseAsync(entity, cancellationToken);
return ResultContainer<ReleaseDto>.CreatePassResult(TrackConverter.Convert(added));
}
catch (ClassifiedDbException ex) when (ex.Error.Category == DbErrorCategory.UniqueViolation)
{
// Concurrent upload inserted the same (title, artist) between our read and write.
// Re-query and return the winning row. Should not return null here since the
// constraint just fired, but re-throw if it does so the caller sees an error.
var race = await Repository.GetReleaseByTitleAndArtistAsync(title, artist, cancellationToken);
if (race is null) throw;
return ResultContainer<ReleaseDto>.CreatePassResult(TrackConverter.Convert(race));
}
}
catch (Exception e)
{