refactor(backend): read artist credit from TrackArtists join

Drop the now-redundant Track.Artist string column now that every
track (local and YouTube) links its credited artists through
TrackArtist. TrackApplicationResponse formats the display string by
joining TrackArtists ordered by position, and every read query that
projects a track now includes TrackArtists/Artist so the join isn't
empty at response time.
This commit is contained in:
2026-07-17 17:57:22 +02:00
parent 4a802e44a3
commit 64e1f3903e
11 changed files with 873 additions and 6 deletions
@@ -65,7 +65,6 @@ namespace Musify.Application.Tracks
{
Title = request.Title,
NormalizedTitle = request.Title.ToUpperInvariant(),
Artist = user.Name,
OwnerUserId = user.Id,
OriginalPictureName = pictureIntent.ObjectName,
OriginalAudioName = audioIntent.ObjectName,
@@ -24,6 +24,8 @@ namespace Musify.Application.Tracks
.ToListAsync(cancellationToken);
var tracksById = await database.Tracks
.Include(t => t.TrackArtists)
.ThenInclude(trackArtist => trackArtist.Artist)
.Where(t => recentTrackIds.Contains(t.Id))
.Select(t => new { t.Id, Track = t, ListensCount = t.ListeningHistories.Count } )
.ToDictionaryAsync(t => t.Id, cancellationToken);
@@ -15,6 +15,8 @@ namespace Musify.Application.Tracks
public async ValueTask<ErrorOr<TrackApplicationResponse>> Handle(GetTrackByIdQuery request, CancellationToken cancellationToken)
{
var entity = await database.Tracks.AsNoTracking()
.Include(t => t.TrackArtists)
.ThenInclude(trackArtist => trackArtist.Artist)
.Where(t => t.Id == request.TrackId)
.Select(t => new { Track = t, ListensCount = t.ListeningHistories.Count })
.SingleOrDefaultAsync(cancellationToken);
@@ -20,6 +20,8 @@ namespace Musify.Application.Tracks
var tracksQuery = database.UserHasTracks
.AsNoTracking()
.Include(ut => ut.Track)
.ThenInclude(track => track.TrackArtists)
.ThenInclude(trackArtist => trackArtist.Artist)
.AsQueryable();
if (!string.IsNullOrEmpty(request.Name))
@@ -27,6 +27,8 @@ namespace Musify.Application.Tracks
var tracksQuery = database.UserHasTracks
.AsNoTracking()
.Include(ut => ut.Track)
.ThenInclude(track => track.TrackArtists)
.ThenInclude(trackArtist => trackArtist.Artist)
.Where(p => p.UserId == request.UserId);
if (!string.IsNullOrEmpty(request.Name))
@@ -1,3 +1,4 @@
using System.Linq;
using Musify.Domain.Entities;
using Musify.Domain.ValueObjects;
@@ -20,7 +21,7 @@ namespace Musify.Application.Tracks.Responses
new(
track.Id,
track.Title,
track.Artist,
FormatArtist(track),
track.Source,
track.ExternalId,
track.AudioTranscodeProcessingStatus,
@@ -29,5 +30,15 @@ namespace Musify.Application.Tracks.Responses
track.OwnerUserId,
track.CreatedAt,
track.UpdatedAt);
private static string? FormatArtist(Track track)
{
if (track.TrackArtists.Count == 0)
return null;
return string.Join(", ", track.TrackArtists
.OrderBy(trackArtist => trackArtist.Position)
.Select(trackArtist => trackArtist.Artist.Name));
}
}
}