diff --git a/backend/Core/Musify.Application/Albums/Responses/AlbumApplicationResponse.cs b/backend/Core/Musify.Application/Albums/Responses/AlbumApplicationResponse.cs index c1e6948..0323ded 100644 --- a/backend/Core/Musify.Application/Albums/Responses/AlbumApplicationResponse.cs +++ b/backend/Core/Musify.Application/Albums/Responses/AlbumApplicationResponse.cs @@ -1,7 +1,17 @@ +using System.Text.Json.Serialization; using Musify.Domain.Entities; namespace Musify.Application.Albums.Responses { + // [JsonPolymorphic]/[JsonDerivedType] are required here, not optional: without them + // System.Text.Json serializes strictly by the declared CLR type at each call site + // (AlbumApplicationResponse, both as a bare return type and nested inside + // AlbumSearchItemResponse.Album), so an ExternalAlbumApplicationResponse instance would + // silently lose ExternalId/ThumbnailUrl on the wire. This also gives every response a + // "source" discriminator field for free, matching AlbumSearchItemResponse.Source. + [JsonPolymorphic(TypeDiscriminatorPropertyName = "source")] + [JsonDerivedType(typeof(AlbumApplicationResponse), typeDiscriminator: "Local")] + [JsonDerivedType(typeof(ExternalAlbumApplicationResponse), typeDiscriminator: "YouTube")] public record AlbumApplicationResponse( Guid Id, string Title, @@ -14,9 +24,7 @@ namespace Musify.Application.Albums.Responses string? LargeImageKeyName, DateTime CreatedAt, DateTime UpdatedAt, - IReadOnlyList CoverTrackIds, - string? YouTubeAlbumId = null, - string? ThumbnailUrl = null) + IReadOnlyList CoverTrackIds) { public const int CoverTrackCount = 4; @@ -45,22 +53,12 @@ namespace Musify.Application.Albums.Responses int trackCount, IReadOnlyList? coverTrackIds = null) { - var externalAlbum = album as ExternalAlbum; - return new AlbumApplicationResponse( - album.Id, - album.Title, - album.Description, - album.ReleaseYear, - album is UserAlbum userAlbum ? userAlbum.OwnerUserId : 0, - trackCount, - album.Pictures?.SmallName, - album.Pictures?.MediumName, - album.Pictures?.LargeName, - album.CreatedAt, - album.UpdatedAt, - coverTrackIds ?? [], - externalAlbum?.ExternalId, - externalAlbum?.ThumbnailUrl); + return album switch + { + ExternalAlbum externalAlbum => ExternalAlbumApplicationResponse.FromEntity(externalAlbum, trackCount, coverTrackIds), + UserAlbum userAlbum => FromEntity(userAlbum, trackCount, coverTrackIds), + _ => throw new NotSupportedException($"Unsupported album entity type '{album.GetType().Name}'.") + }; } } } diff --git a/backend/Core/Musify.Application/Albums/Responses/AlbumSearchItemResponse.cs b/backend/Core/Musify.Application/Albums/Responses/AlbumSearchItemResponse.cs index 5789edd..40acd5a 100644 --- a/backend/Core/Musify.Application/Albums/Responses/AlbumSearchItemResponse.cs +++ b/backend/Core/Musify.Application/Albums/Responses/AlbumSearchItemResponse.cs @@ -14,7 +14,7 @@ namespace Musify.Application.Albums.Responses YouTubeAlbumResult? YouTubeAlbum) { public static AlbumSearchItemResponse FromAlbum(AlbumApplicationResponse album) => - new(album.YouTubeAlbumId is not null ? TrackSource.YouTube : TrackSource.Local, album, null); + new(album is ExternalAlbumApplicationResponse ? TrackSource.YouTube : TrackSource.Local, album, null); public static AlbumSearchItemResponse FromYouTubeAlbum(YouTubeAlbumResult album) => new(TrackSource.YouTube, null, album); diff --git a/backend/Core/Musify.Application/Albums/Responses/ExternalAlbumApplicationResponse.cs b/backend/Core/Musify.Application/Albums/Responses/ExternalAlbumApplicationResponse.cs new file mode 100644 index 0000000..a9f12a3 --- /dev/null +++ b/backend/Core/Musify.Application/Albums/Responses/ExternalAlbumApplicationResponse.cs @@ -0,0 +1,47 @@ +using Musify.Domain.Entities; + +namespace Musify.Application.Albums.Responses +{ + public record ExternalAlbumApplicationResponse( + Guid Id, + string Title, + string? Description, + int? ReleaseYear, + long OwnerUserId, + int TrackCount, + string? SmallImageKeyName, + string? MediumImageKeyName, + string? LargeImageKeyName, + DateTime CreatedAt, + DateTime UpdatedAt, + IReadOnlyList CoverTrackIds, + string ExternalId, + string? ThumbnailUrl) + : AlbumApplicationResponse( + Id, Title, Description, ReleaseYear, OwnerUserId, TrackCount, + SmallImageKeyName, MediumImageKeyName, LargeImageKeyName, + CreatedAt, UpdatedAt, CoverTrackIds) + { + public static ExternalAlbumApplicationResponse FromEntity( + ExternalAlbum album, + int trackCount, + IReadOnlyList? coverTrackIds = null) + { + return new ExternalAlbumApplicationResponse( + album.Id, + album.Title, + album.Description, + album.ReleaseYear, + 0, + trackCount, + album.Pictures?.SmallName, + album.Pictures?.MediumName, + album.Pictures?.LargeName, + album.CreatedAt, + album.UpdatedAt, + coverTrackIds ?? [], + album.ExternalId, + album.ThumbnailUrl); + } + } +} diff --git a/web-player/src/routes/+page.svelte b/web-player/src/routes/+page.svelte index 8b3cbc1..b59f9e9 100644 --- a/web-player/src/routes/+page.svelte +++ b/web-player/src/routes/+page.svelte @@ -155,7 +155,9 @@ >

{data.greeting}

-

+

Tu música. Sin límites.

@@ -214,15 +216,15 @@ {#each albums as album, i (album.id)} openAlbumMenu(e, album.id)} >
- {#if album.thumbnailUrl} + {#if album.source === 'YouTube' && album.thumbnailUrl}