Design Prot Merge #1
@@ -1,7 +1,17 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
using Musify.Domain.Entities;
|
using Musify.Domain.Entities;
|
||||||
|
|
||||||
namespace Musify.Application.Albums.Responses
|
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(
|
public record AlbumApplicationResponse(
|
||||||
Guid Id,
|
Guid Id,
|
||||||
string Title,
|
string Title,
|
||||||
@@ -14,9 +24,7 @@ namespace Musify.Application.Albums.Responses
|
|||||||
string? LargeImageKeyName,
|
string? LargeImageKeyName,
|
||||||
DateTime CreatedAt,
|
DateTime CreatedAt,
|
||||||
DateTime UpdatedAt,
|
DateTime UpdatedAt,
|
||||||
IReadOnlyList<Guid> CoverTrackIds,
|
IReadOnlyList<Guid> CoverTrackIds)
|
||||||
string? YouTubeAlbumId = null,
|
|
||||||
string? ThumbnailUrl = null)
|
|
||||||
{
|
{
|
||||||
public const int CoverTrackCount = 4;
|
public const int CoverTrackCount = 4;
|
||||||
|
|
||||||
@@ -45,22 +53,12 @@ namespace Musify.Application.Albums.Responses
|
|||||||
int trackCount,
|
int trackCount,
|
||||||
IReadOnlyList<Guid>? coverTrackIds = null)
|
IReadOnlyList<Guid>? coverTrackIds = null)
|
||||||
{
|
{
|
||||||
var externalAlbum = album as ExternalAlbum;
|
return album switch
|
||||||
return new AlbumApplicationResponse(
|
{
|
||||||
album.Id,
|
ExternalAlbum externalAlbum => ExternalAlbumApplicationResponse.FromEntity(externalAlbum, trackCount, coverTrackIds),
|
||||||
album.Title,
|
UserAlbum userAlbum => FromEntity(userAlbum, trackCount, coverTrackIds),
|
||||||
album.Description,
|
_ => throw new NotSupportedException($"Unsupported album entity type '{album.GetType().Name}'.")
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace Musify.Application.Albums.Responses
|
|||||||
YouTubeAlbumResult? YouTubeAlbum)
|
YouTubeAlbumResult? YouTubeAlbum)
|
||||||
{
|
{
|
||||||
public static AlbumSearchItemResponse FromAlbum(AlbumApplicationResponse album) =>
|
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) =>
|
public static AlbumSearchItemResponse FromYouTubeAlbum(YouTubeAlbumResult album) =>
|
||||||
new(TrackSource.YouTube, null, album);
|
new(TrackSource.YouTube, null, album);
|
||||||
|
|||||||
@@ -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<Guid> 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<Guid>? 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -155,7 +155,9 @@
|
|||||||
></div>
|
></div>
|
||||||
<div class="animate-enter relative flex flex-col gap-2 sm:gap-2.5">
|
<div class="animate-enter relative flex flex-col gap-2 sm:gap-2.5">
|
||||||
<p class="text-sm tracking-[0.14em] text-fg-2 uppercase sm:text-base">{data.greeting}</p>
|
<p class="text-sm tracking-[0.14em] text-fg-2 uppercase sm:text-base">{data.greeting}</p>
|
||||||
<h1 class="font-display text-3xl leading-[1.05] font-semibold tracking-tight text-fg sm:text-6xl">
|
<h1
|
||||||
|
class="font-display text-3xl leading-[1.05] font-semibold tracking-tight text-fg sm:text-6xl"
|
||||||
|
>
|
||||||
Tu música. Sin límites.
|
Tu música. Sin límites.
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
@@ -214,15 +216,15 @@
|
|||||||
<Rail>
|
<Rail>
|
||||||
{#each albums as album, i (album.id)}
|
{#each albums as album, i (album.id)}
|
||||||
<a
|
<a
|
||||||
href={album.youTubeAlbumId
|
href={album.source === 'YouTube'
|
||||||
? `/albums/external/youtube/${album.youTubeAlbumId}`
|
? `/albums/external/youtube/${album.externalId}`
|
||||||
: `/albums/${album.id}`}
|
: `/albums/${album.id}`}
|
||||||
class="group/card animate-enter block w-42 shrink-0 rounded-art focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-bg focus-visible:outline-none"
|
class="group/card animate-enter block w-42 shrink-0 rounded-art focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-bg focus-visible:outline-none"
|
||||||
style="animation-delay:{Math.min(i, 10) * 45}ms"
|
style="animation-delay:{Math.min(i, 10) * 45}ms"
|
||||||
oncontextmenu={(e) => openAlbumMenu(e, album.id)}
|
oncontextmenu={(e) => openAlbumMenu(e, album.id)}
|
||||||
>
|
>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
{#if album.thumbnailUrl}
|
{#if album.source === 'YouTube' && album.thumbnailUrl}
|
||||||
<Cover
|
<Cover
|
||||||
trackId={album.id}
|
trackId={album.id}
|
||||||
src={album.thumbnailUrl}
|
src={album.thumbnailUrl}
|
||||||
|
|||||||
Reference in New Issue
Block a user