feat: normalize release-cardinal fields out of track into a Release entity (Phase 8 §8.0)

This commit is contained in:
daniel-c-harvey
2026-06-11 12:51:21 -04:00
parent 16f356a760
commit f767d288c5
33 changed files with 1032 additions and 297 deletions
+1 -1
View File
@@ -63,7 +63,7 @@
{
try
{
var result = await CmsTrackService.GetAlbumSummariesAsync();
var result = await CmsTrackService.GetReleasesAsync();
_albumCount = result.Success && result.Value is not null ? result.Value.Count : null;
if (!result.Success)
{
@@ -264,7 +264,7 @@
var confirmed = await DialogService.ShowMessageBox(
"Delete track",
$"Permanently delete \"{_track.TrackName}\" by {_track.Artist}? This cannot be undone.",
$"Permanently delete \"{_track.TrackName}\" by {_track.Release?.Artist ?? "Unknown"}? This cannot be undone.",
yesText: "Delete",
cancelText: "Cancel");
@@ -310,14 +310,14 @@
public static TrackEditForm From(TrackDto track) => new()
{
TrackName = track.TrackName,
Artist = track.Artist,
Album = track.Album,
Genre = track.Genre,
ImagePath = track.ImagePath,
ReleaseDate = track.ReleaseDate is { } d
Artist = track.Release?.Artist ?? string.Empty,
Album = track.Release?.Title,
Genre = track.Release?.Genre,
ImagePath = track.Release?.ImagePath,
ReleaseDate = track.Release?.ReleaseDate is { } d
? d.ToDateTime(TimeOnly.MinValue)
: null,
ReleaseType = track.ReleaseType,
ReleaseType = track.Release?.ReleaseType ?? ReleaseType.Single,
TrackNumber = track.TrackNumber
};
}
@@ -52,10 +52,10 @@
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Track Name">@context.TrackName</MudTd>
<MudTd DataLabel="Artist">@context.Artist</MudTd>
<MudTd DataLabel="Album">@(context.Album ?? "—")</MudTd>
<MudTd DataLabel="Genre">@(context.Genre ?? "—")</MudTd>
<MudTd DataLabel="Release Date">@(context.ReleaseDate?.ToString("yyyy-MM-dd") ?? "—")</MudTd>
<MudTd DataLabel="Artist">@(context.Release?.Artist ?? "—")</MudTd>
<MudTd DataLabel="Album">@(context.Release?.Title ?? "—")</MudTd>
<MudTd DataLabel="Genre">@(context.Release?.Genre ?? "—")</MudTd>
<MudTd DataLabel="Release Date">@(context.Release?.ReleaseDate?.ToString("yyyy-MM-dd") ?? "—")</MudTd>
<MudTd DataLabel="Entry Key"><MudText Typo="Typo.caption" Style="font-family: monospace;">@context.EntryKey</MudText></MudTd>
<MudTd DataLabel="File Name"><MudText Typo="Typo.caption" Style="font-family: monospace;">@(context.OriginalFileName ?? "—")</MudText></MudTd>
<MudTd DataLabel="Actions">
@@ -216,7 +216,7 @@
{
var confirmed = await DialogService.ShowMessageBox(
title: "Delete track",
markupMessage: new MarkupString($"Delete <strong>{WebUtility.HtmlEncode(track.TrackName)}</strong> by {WebUtility.HtmlEncode(track.Artist)}? This removes both the metadata row and the underlying audio entry."),
markupMessage: new MarkupString($"Delete <strong>{WebUtility.HtmlEncode(track.TrackName)}</strong> by {WebUtility.HtmlEncode(track.Release?.Artist ?? "Unknown")}? This removes both the metadata row and the underlying audio entry."),
yesText: "Delete",
cancelText: "Cancel");
+13 -13
View File
@@ -450,7 +450,7 @@ public class CmsTrackService : ICmsTrackService
}
}
public async Task<ResultContainer<List<AlbumSummaryDto>>> GetAlbumSummariesAsync(CancellationToken ct = default)
public async Task<ResultContainer<List<ReleaseDto>>> GetReleasesAsync(CancellationToken ct = default)
{
var client = _httpClientFactory.CreateClient(ContentCmsClientName);
@@ -461,36 +461,36 @@ public class CmsTrackService : ICmsTrackService
}
catch (Exception ex)
{
_logger.LogError(ex, "Content API call failed for album summaries");
return ResultContainer<List<AlbumSummaryDto>>.CreateFailResult("Content API is unreachable.");
_logger.LogError(ex, "Content API call failed for releases");
return ResultContainer<List<ReleaseDto>>.CreateFailResult("Content API is unreachable.");
}
using (response)
{
if (!response.IsSuccessStatusCode)
{
_logger.LogError("Content API album summaries failed: {Status}", (int)response.StatusCode);
return ResultContainer<List<AlbumSummaryDto>>.CreateFailResult("Failed to load albums.");
_logger.LogError("Content API releases failed: {Status}", (int)response.StatusCode);
return ResultContainer<List<ReleaseDto>>.CreateFailResult("Failed to load albums.");
}
List<AlbumSummaryDto>? albums;
List<ReleaseDto>? releases;
try
{
albums = await response.Content.ReadFromJsonAsync<List<AlbumSummaryDto>>(ct);
releases = await response.Content.ReadFromJsonAsync<List<ReleaseDto>>(ct);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to deserialize album summaries from Content API response");
return ResultContainer<List<AlbumSummaryDto>>.CreateFailResult("Content API returned an unexpected response.");
_logger.LogError(ex, "Failed to deserialize releases from Content API response");
return ResultContainer<List<ReleaseDto>>.CreateFailResult("Content API returned an unexpected response.");
}
if (albums is null)
if (releases is null)
{
_logger.LogError("Content API returned a null album summaries list");
return ResultContainer<List<AlbumSummaryDto>>.CreateFailResult("Content API returned an empty response.");
_logger.LogError("Content API returned a null releases list");
return ResultContainer<List<ReleaseDto>>.CreateFailResult("Content API returned an empty response.");
}
return ResultContainer<List<AlbumSummaryDto>>.CreatePassResult(albums);
return ResultContainer<List<ReleaseDto>>.CreatePassResult(releases);
}
}
+2 -2
View File
@@ -88,8 +88,8 @@ public interface ICmsTrackService
/// </summary>
Task<Result> GenerateWaveformProfileAsync(string entryKey, CancellationToken ct = default);
/// <summary>Returns all distinct albums with track counts from GET api/track/albums.</summary>
Task<ResultContainer<List<AlbumSummaryDto>>> GetAlbumSummariesAsync(CancellationToken ct = default);
/// <summary>Returns all releases with track counts from GET api/track/albums.</summary>
Task<ResultContainer<List<ReleaseDto>>> GetReleasesAsync(CancellationToken ct = default);
/// <summary>Returns all distinct genres with track counts from GET api/track/genres.</summary>
Task<ResultContainer<List<GenreSummaryDto>>> GetGenreSummariesAsync(CancellationToken ct = default);