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 ca275fe7a8
commit 6e2f3a257a
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));
}
}
}