From 78cc2f7248f69fba8b836eb61077f295cc18ca0f Mon Sep 17 00:00:00 2001 From: XShine555 Date: Mon, 20 Jul 2026 21:04:48 +0200 Subject: [PATCH] feat(backend): expose the first cover track ids of playlists and albums --- .../Musify.Application/Albums/GetAlbumById.cs | 13 +++++++++++-- backend/Musify.Application/Albums/GetAlbums.cs | 13 +++++++++++-- .../Albums/GetAlbumsByUserId.cs | 13 +++++++++++-- .../Responses/AlbumApplicationResponse.cs | 13 ++++++++++--- .../Albums/SaveYouTubeAlbum.cs | 13 ++++++++++--- .../Musify.Application/Albums/UpdateAlbum.cs | 13 ++++++++++--- .../PlayLists/GetPlayListById.cs | 16 ++++++++++++---- .../PlayLists/GetPlayLists.cs | 17 +++++++++++++++-- .../PlayLists/GetPlayListsByUserId.cs | 17 +++++++++++++++-- .../Responses/PlayListApplicationResponse.cs | 16 +++++++++++----- .../PlayLists/UpdatePlayList.cs | 10 +++++++++- 11 files changed, 125 insertions(+), 29 deletions(-) diff --git a/backend/Musify.Application/Albums/GetAlbumById.cs b/backend/Musify.Application/Albums/GetAlbumById.cs index ff79572..ccf9e51 100644 --- a/backend/Musify.Application/Albums/GetAlbumById.cs +++ b/backend/Musify.Application/Albums/GetAlbumById.cs @@ -17,13 +17,22 @@ namespace Musify.Application.Albums var album = await database.UserAlbums .AsNoTracking() .Where(a => a.Id == request.Id) - .Select(a => new { Album = a, TrackCount = a.AlbumTracks.Count }) + .Select(a => new + { + Album = a, + TrackCount = a.AlbumTracks.Count, + CoverTrackIds = a.AlbumTracks + .OrderBy(albumTrack => albumTrack.TrackNumber) + .Take(AlbumApplicationResponse.CoverTrackCount) + .Select(albumTrack => albumTrack.TrackId) + .ToList() + }) .SingleOrDefaultAsync(cancellationToken); if (album is null) return Error.NotFound(); - return AlbumApplicationResponse.FromEntity(album.Album, album.TrackCount); + return AlbumApplicationResponse.FromEntity(album.Album, album.TrackCount, album.CoverTrackIds); } } } diff --git a/backend/Musify.Application/Albums/GetAlbums.cs b/backend/Musify.Application/Albums/GetAlbums.cs index 88238f0..0c0b51c 100644 --- a/backend/Musify.Application/Albums/GetAlbums.cs +++ b/backend/Musify.Application/Albums/GetAlbums.cs @@ -31,11 +31,20 @@ namespace Musify.Application.Albums var pagedEntities = await albumsQuery .OrderByDescending(album => album.CreatedAt) - .Select(album => new { Album = album, TrackCount = album.AlbumTracks.Count }) + .Select(album => new + { + Album = album, + TrackCount = album.AlbumTracks.Count, + CoverTrackIds = album.AlbumTracks + .OrderBy(albumTrack => albumTrack.TrackNumber) + .Take(AlbumApplicationResponse.CoverTrackCount) + .Select(albumTrack => albumTrack.TrackId) + .ToList() + }) .ToPagedListAsync(request.PageNumber, request.PageSize, totalCount, cancellationToken); var pagedAlbums = new StaticPagedList( - pagedEntities.Select(entry => AlbumApplicationResponse.FromEntity(entry.Album, entry.TrackCount)), + pagedEntities.Select(entry => AlbumApplicationResponse.FromEntity(entry.Album, entry.TrackCount, entry.CoverTrackIds)), pagedEntities.PageNumber, pagedEntities.PageSize, pagedEntities.TotalItemCount); return PaginatedResponse.FromPagedList(pagedAlbums); diff --git a/backend/Musify.Application/Albums/GetAlbumsByUserId.cs b/backend/Musify.Application/Albums/GetAlbumsByUserId.cs index f28249a..068c996 100644 --- a/backend/Musify.Application/Albums/GetAlbumsByUserId.cs +++ b/backend/Musify.Application/Albums/GetAlbumsByUserId.cs @@ -31,11 +31,20 @@ namespace Musify.Application.Albums var pagedEntities = await albumsQuery .OrderByDescending(album => album.CreatedAt) - .Select(album => new { Album = album, TrackCount = album.AlbumTracks.Count }) + .Select(album => new + { + Album = album, + TrackCount = album.AlbumTracks.Count, + CoverTrackIds = album.AlbumTracks + .OrderBy(albumTrack => albumTrack.TrackNumber) + .Take(AlbumApplicationResponse.CoverTrackCount) + .Select(albumTrack => albumTrack.TrackId) + .ToList() + }) .ToPagedListAsync(request.PageNumber, request.PageSize, totalCount, cancellationToken); var pagedAlbums = new StaticPagedList( - pagedEntities.Select(entry => AlbumApplicationResponse.FromEntity(entry.Album, entry.TrackCount)), + pagedEntities.Select(entry => AlbumApplicationResponse.FromEntity(entry.Album, entry.TrackCount, entry.CoverTrackIds)), pagedEntities.PageNumber, pagedEntities.PageSize, pagedEntities.TotalItemCount); return PaginatedResponse.FromPagedList(pagedAlbums); diff --git a/backend/Musify.Application/Albums/Responses/AlbumApplicationResponse.cs b/backend/Musify.Application/Albums/Responses/AlbumApplicationResponse.cs index d47fb71..efc4281 100644 --- a/backend/Musify.Application/Albums/Responses/AlbumApplicationResponse.cs +++ b/backend/Musify.Application/Albums/Responses/AlbumApplicationResponse.cs @@ -13,9 +13,15 @@ namespace Musify.Application.Albums.Responses string? MediumImageKeyName, string? LargeImageKeyName, DateTime CreatedAt, - DateTime UpdatedAt) + DateTime UpdatedAt, + IReadOnlyList CoverTrackIds) { - public static AlbumApplicationResponse FromEntity(UserAlbum album, int trackCount) + public const int CoverTrackCount = 4; + + public static AlbumApplicationResponse FromEntity( + UserAlbum album, + int trackCount, + IReadOnlyList? coverTrackIds = null) { return new AlbumApplicationResponse( album.Id, @@ -28,7 +34,8 @@ namespace Musify.Application.Albums.Responses album.Pictures?.MediumName, album.Pictures?.LargeName, album.CreatedAt, - album.UpdatedAt); + album.UpdatedAt, + coverTrackIds ?? []); } } } diff --git a/backend/Musify.Application/Albums/SaveYouTubeAlbum.cs b/backend/Musify.Application/Albums/SaveYouTubeAlbum.cs index 49d1e89..2e74a0a 100644 --- a/backend/Musify.Application/Albums/SaveYouTubeAlbum.cs +++ b/backend/Musify.Application/Albums/SaveYouTubeAlbum.cs @@ -89,14 +89,21 @@ namespace Musify.Application.Albums return Error.Failure(description: $"Failed to save album {request.AlbumId}"); } - var trackCount = await database.AlbumHasTracks + var albumTracks = database.AlbumHasTracks .AsNoTracking() - .CountAsync(albumTrack => albumTrack.AlbumId == album.Id, cancellationToken); + .Where(albumTrack => albumTrack.AlbumId == album.Id); + + var trackCount = await albumTracks.CountAsync(cancellationToken); + var coverTrackIds = await albumTracks + .OrderBy(albumTrack => albumTrack.TrackNumber) + .Take(AlbumApplicationResponse.CoverTrackCount) + .Select(albumTrack => albumTrack.TrackId) + .ToListAsync(cancellationToken); logger.LogInformation("Saved YouTube album {AlbumId} as {AlbumGuid} with {TrackCount} tracks", request.AlbumId, album.Id, trackCount); - return AlbumApplicationResponse.FromEntity(album, trackCount); + return AlbumApplicationResponse.FromEntity(album, trackCount, coverTrackIds); } private static string Truncate(string value, int maxLength) => diff --git a/backend/Musify.Application/Albums/UpdateAlbum.cs b/backend/Musify.Application/Albums/UpdateAlbum.cs index 961c4b8..fa0181b 100644 --- a/backend/Musify.Application/Albums/UpdateAlbum.cs +++ b/backend/Musify.Application/Albums/UpdateAlbum.cs @@ -53,11 +53,18 @@ namespace Musify.Application.Albums return Error.Failure(description: $"Failed to update album {request.AlbumId}"); } - var trackCount = await database.AlbumHasTracks + var albumTracks = database.AlbumHasTracks .AsNoTracking() - .CountAsync(albumTrack => albumTrack.AlbumId == album.Id, cancellationToken); + .Where(albumTrack => albumTrack.AlbumId == album.Id); - return AlbumApplicationResponse.FromEntity(album, trackCount); + var trackCount = await albumTracks.CountAsync(cancellationToken); + var coverTrackIds = await albumTracks + .OrderBy(albumTrack => albumTrack.TrackNumber) + .Take(AlbumApplicationResponse.CoverTrackCount) + .Select(albumTrack => albumTrack.TrackId) + .ToListAsync(cancellationToken); + + return AlbumApplicationResponse.FromEntity(album, trackCount, coverTrackIds); } } } diff --git a/backend/Musify.Application/PlayLists/GetPlayListById.cs b/backend/Musify.Application/PlayLists/GetPlayListById.cs index 7e658d3..d5afb0f 100644 --- a/backend/Musify.Application/PlayLists/GetPlayListById.cs +++ b/backend/Musify.Application/PlayLists/GetPlayListById.cs @@ -14,16 +14,24 @@ namespace Musify.Application.PlayLists { public async ValueTask> Handle(GetPlayListByIdQuery request, CancellationToken cancellationToken) { - var playList = await database.PlayLists + var entry = await database.PlayLists .AsNoTracking() .Where(p => p.Id == request.Id) - .Select(p => PlayListApplicationResponse.FromEntity(p)) + .Select(p => new + { + PlayList = p, + CoverTrackIds = p.PlayListTracks + .OrderBy(plt => plt.Position) + .Take(PlayListApplicationResponse.CoverTrackCount) + .Select(plt => plt.TrackId) + .ToList() + }) .SingleOrDefaultAsync(cancellationToken); - if (playList is null) + if (entry is null) return Error.NotFound(); - return playList; + return PlayListApplicationResponse.FromEntity(entry.PlayList, entry.CoverTrackIds); } } } diff --git a/backend/Musify.Application/PlayLists/GetPlayLists.cs b/backend/Musify.Application/PlayLists/GetPlayLists.cs index 806dacc..ff1722c 100644 --- a/backend/Musify.Application/PlayLists/GetPlayLists.cs +++ b/backend/Musify.Application/PlayLists/GetPlayLists.cs @@ -2,6 +2,7 @@ using ErrorOr; using Microsoft.EntityFrameworkCore; using Musify.Application.Shared; using Musify.Application.PlayLists.Responses; +using X.PagedList; using X.PagedList.EF; using Mediator; using Musify.Application.Contracts; @@ -18,12 +19,24 @@ namespace Musify.Application.PlayLists { var totalCount = await database.PlayLists.CountAsync(cancellationToken); - var pagedPlayLists = await database.PlayLists + var pagedEntities = await database.PlayLists .AsNoTracking() .OrderBy(p => p.Id) - .Select(p => PlayListApplicationResponse.FromEntity(p)) + .Select(p => new + { + PlayList = p, + CoverTrackIds = p.PlayListTracks + .OrderBy(plt => plt.Position) + .Take(PlayListApplicationResponse.CoverTrackCount) + .Select(plt => plt.TrackId) + .ToList() + }) .ToPagedListAsync(request.PageNumber, request.PageSize, totalCount, cancellationToken); + var pagedPlayLists = new StaticPagedList( + pagedEntities.Select(entry => PlayListApplicationResponse.FromEntity(entry.PlayList, entry.CoverTrackIds)), + pagedEntities.PageNumber, pagedEntities.PageSize, pagedEntities.TotalItemCount); + return PaginatedResponse.FromPagedList(pagedPlayLists); } } diff --git a/backend/Musify.Application/PlayLists/GetPlayListsByUserId.cs b/backend/Musify.Application/PlayLists/GetPlayListsByUserId.cs index d8ee3d8..091985b 100644 --- a/backend/Musify.Application/PlayLists/GetPlayListsByUserId.cs +++ b/backend/Musify.Application/PlayLists/GetPlayListsByUserId.cs @@ -2,6 +2,7 @@ using ErrorOr; using Microsoft.EntityFrameworkCore; using Musify.Application.Shared; using Musify.Application.PlayLists.Responses; +using X.PagedList; using X.PagedList.EF; using Mediator; using Musify.Application.Contracts; @@ -32,11 +33,23 @@ namespace Musify.Application.PlayLists var totalCount = await playListsQuery.CountAsync(cancellationToken); - var pagedPlayLists = await playListsQuery + var pagedEntities = await playListsQuery .OrderBy(p => p.CreatedAt) - .Select(p => PlayListApplicationResponse.FromEntity(p)) + .Select(p => new + { + PlayList = p, + CoverTrackIds = p.PlayListTracks + .OrderBy(plt => plt.Position) + .Take(PlayListApplicationResponse.CoverTrackCount) + .Select(plt => plt.TrackId) + .ToList() + }) .ToPagedListAsync(request.PageNumber, request.PageSize, totalCount, cancellationToken); + var pagedPlayLists = new StaticPagedList( + pagedEntities.Select(entry => PlayListApplicationResponse.FromEntity(entry.PlayList, entry.CoverTrackIds)), + pagedEntities.PageNumber, pagedEntities.PageSize, pagedEntities.TotalItemCount); + return PaginatedResponse.FromPagedList(pagedPlayLists); } } diff --git a/backend/Musify.Application/PlayLists/Responses/PlayListApplicationResponse.cs b/backend/Musify.Application/PlayLists/Responses/PlayListApplicationResponse.cs index 1a3809e..4a1fb93 100644 --- a/backend/Musify.Application/PlayLists/Responses/PlayListApplicationResponse.cs +++ b/backend/Musify.Application/PlayLists/Responses/PlayListApplicationResponse.cs @@ -1,4 +1,4 @@ -using Musify.Domain.Entities; +using Musify.Domain.Entities; namespace Musify.Application.PlayLists.Responses { @@ -10,9 +10,14 @@ namespace Musify.Application.PlayLists.Responses string? MediumImageKeyName, string? LargeImageKeyName, DateTime CreatedAt, - DateTime UpdatedAt) + DateTime UpdatedAt, + IReadOnlyList CoverTrackIds) { - public static PlayListApplicationResponse FromEntity(PlayList playList) + public const int CoverTrackCount = 4; + + public static PlayListApplicationResponse FromEntity( + PlayList playList, + IReadOnlyList? coverTrackIds = null) { return new PlayListApplicationResponse( playList.Id, @@ -22,7 +27,8 @@ namespace Musify.Application.PlayLists.Responses playList.Pictures?.MediumName, playList.Pictures?.LargeName, playList.CreatedAt, - playList.UpdatedAt); + playList.UpdatedAt, + coverTrackIds ?? []); } } -} \ No newline at end of file +} diff --git a/backend/Musify.Application/PlayLists/UpdatePlayList.cs b/backend/Musify.Application/PlayLists/UpdatePlayList.cs index f83954b..a231b99 100644 --- a/backend/Musify.Application/PlayLists/UpdatePlayList.cs +++ b/backend/Musify.Application/PlayLists/UpdatePlayList.cs @@ -93,8 +93,16 @@ namespace Musify.Application.PlayLists return Error.Failure(description: $"Failed to update playlist {playListEntity.Id}"); } + var coverTrackIds = await database.PlayListHasTracks + .AsNoTracking() + .Where(plt => plt.PlayListId == playListEntity.Id) + .OrderBy(plt => plt.Position) + .Take(PlayListApplicationResponse.CoverTrackCount) + .Select(plt => plt.TrackId) + .ToListAsync(cancellationToken); + logger.LogInformation("Updated playlist {PlayListId}", playListEntity.Id); - return PlayListApplicationResponse.FromEntity(playListEntity); + return PlayListApplicationResponse.FromEntity(playListEntity, coverTrackIds); } async Task> PublishUpdatePlayListPictureSourceEventAsync(