From 5b2a0e4d46966acbdffeab860859b7daeae5a4a8 Mon Sep 17 00:00:00 2001 From: XShine555 Date: Thu, 24 Sep 2026 05:09:37 +0200 Subject: [PATCH] web-player: normalize tracks, albums and playlists into single domain types at the server boundary --- .../lib/components/player/PlayerBar.svelte | 6 +- .../src/lib/components/player/Queue.svelte | 2 +- .../lib/components/player/TrackInfo.svelte | 27 ++-- .../lib/components/ui/media/TrackList.svelte | 29 ++-- web-player/src/lib/config.ts | 1 - web-player/src/lib/data/albums.ts | 19 +-- web-player/src/lib/data/mixes.ts | 19 --- web-player/src/lib/data/recentlyPlayed.ts | 23 +-- web-player/src/lib/player/liked.svelte.ts | 48 +----- web-player/src/lib/player/player.svelte.ts | 153 ++++-------------- web-player/src/lib/server/mappers.test.ts | 139 ++++++++++++++++ web-player/src/lib/server/mappers.ts | 94 +++++++++++ web-player/src/lib/state/menus.svelte.ts | 9 +- web-player/src/lib/types.ts | 53 ++++++ web-player/src/lib/utils/format.ts | 6 +- .../src/routes/(app)/albums/+page.server.ts | 3 +- .../src/routes/(app)/albums/+page.svelte | 5 +- .../routes/(app)/albums/[id]/+page.server.ts | 9 +- .../src/routes/(app)/albums/[id]/+page.svelte | 15 +- .../src/routes/(app)/explore/+page.server.ts | 7 +- .../src/routes/(app)/explore/+page.svelte | 64 +++----- .../src/routes/(app)/library/+page.server.ts | 5 +- .../src/routes/(app)/library/+page.svelte | 9 +- .../src/routes/(app)/liked/+page.svelte | 4 +- .../routes/(app)/mixes/[id]/+page.server.ts | 3 +- .../src/routes/(app)/mixes/[id]/+page.svelte | 44 ++--- .../routes/(app)/playlists/+page.server.ts | 18 +-- .../src/routes/(app)/playlists/+page.svelte | 4 +- .../(app)/playlists/[id]/+page.server.ts | 5 +- .../routes/(app)/playlists/[id]/+page.svelte | 9 +- .../routes/(app)/user/[id]/+page.server.ts | 16 +- .../src/routes/(app)/user/[id]/+page.svelte | 4 +- .../[id]/[list=followList]/+page.server.ts | 5 +- .../user/[id]/[list=followList]/+page.svelte | 6 +- web-player/src/routes/+layout.server.ts | 7 +- web-player/src/routes/+page.server.ts | 30 ++-- web-player/src/routes/+page.svelte | 57 +++---- .../routes/api/albums/[id]/tracks/+server.ts | 3 +- .../api/playlists/[id]/tracks/+server.ts | 3 +- web-player/src/routes/api/tracks/+server.ts | 7 +- .../users/[id]/[list=followList]/+server.ts | 8 +- 41 files changed, 526 insertions(+), 452 deletions(-) delete mode 100644 web-player/src/lib/data/mixes.ts create mode 100644 web-player/src/lib/server/mappers.test.ts create mode 100644 web-player/src/lib/server/mappers.ts diff --git a/web-player/src/lib/components/player/PlayerBar.svelte b/web-player/src/lib/components/player/PlayerBar.svelte index 879d43b..1082bb8 100644 --- a/web-player/src/lib/components/player/PlayerBar.svelte +++ b/web-player/src/lib/components/player/PlayerBar.svelte @@ -12,7 +12,7 @@ import ListMusic from '@lucide/svelte/icons/list-music'; function seek(value: number) { - if (player.current.duration > 0) player.seekFraction(value / player.current.duration); + if (player.duration > 0) player.seekFraction(value / player.duration); } @@ -39,13 +39,13 @@ - {fmtTime(player.current.duration)} + {fmtTime(player.duration)} diff --git a/web-player/src/lib/components/player/Queue.svelte b/web-player/src/lib/components/player/Queue.svelte index 9b7b2fa..5be19bd 100644 --- a/web-player/src/lib/components/player/Queue.svelte +++ b/web-player/src/lib/components/player/Queue.svelte @@ -53,7 +53,7 @@ - {#if player.current.id} + {#if player.current}

Reproduciendo

- {#if player.current.id} + {#if current} {:else} @@ -41,14 +34,14 @@ {/if}
- {#if player.current.explicit} + {#if current?.explicit} {/if} - {player.current.title} + {current?.title ?? ''}
diff --git a/web-player/src/lib/components/ui/media/TrackList.svelte b/web-player/src/lib/components/ui/media/TrackList.svelte index 11e5461..5b88784 100644 --- a/web-player/src/lib/components/ui/media/TrackList.svelte +++ b/web-player/src/lib/components/ui/media/TrackList.svelte @@ -1,18 +1,9 @@ @@ -55,10 +55,7 @@ eyebrow="Álbum" title={album.title} description={album.description ?? undefined} - meta={albumMeta( - album.releaseYear === null ? undefined : Number(album.releaseYear), - tracks.length - )} + meta={albumMeta(album.releaseYear, tracks.length)} > {#snippet cover()} { usersPromise ]); - const tracks = unwrapOrError(tracksRes, 'No se pudieron cargar las canciones.'); + const tracks = toPage(unwrapOrError(tracksRes, 'No se pudieron cargar las canciones.'), (item) => + toTrack(item.track) + ); return { query, genre, genres, tracks, - albums: (albumsRes?.data?.items ?? []).map((item) => item.album), + albums: (albumsRes?.data?.items ?? []).map((item) => toAlbum(item.album)), albumsTotal: Number(albumsRes?.data?.totalItemCount ?? 0), users: usersRes?.data?.items ?? [], usersTotal: Number(usersRes?.data?.totalItemCount ?? 0), diff --git a/web-player/src/routes/(app)/explore/+page.svelte b/web-player/src/routes/(app)/explore/+page.svelte index d3fb114..6d91b07 100644 --- a/web-player/src/routes/(app)/explore/+page.svelte +++ b/web-player/src/routes/(app)/explore/+page.svelte @@ -1,7 +1,8 @@ @@ -46,7 +45,7 @@ {#if items.length > 0} ({ ...track, addedAt: track.likedAt }))); + const listTracks = $derived(tracks.map((track) => ({ ...track, date: track.likedAt }))); const isCurrentQueue = $derived(isQueueCurrent(tracks)); function playFrom(index: number) { @@ -55,7 +55,7 @@ columns={['added', 'plays']} onPlay={playFrom} rowAction={{ - onclick: (track) => liked.toggle({ ...track, artist: track.artist ?? undefined }), + onclick: (track) => liked.toggle(track), icon: X, label: 'Quitar de Me gusta' }} diff --git a/web-player/src/routes/(app)/mixes/[id]/+page.server.ts b/web-player/src/routes/(app)/mixes/[id]/+page.server.ts index 51ffa46..59bb8b2 100644 --- a/web-player/src/routes/(app)/mixes/[id]/+page.server.ts +++ b/web-player/src/routes/(app)/mixes/[id]/+page.server.ts @@ -1,5 +1,6 @@ import type { PageServerLoad, Actions } from './$types'; import { createApiClient, requireUser, unwrapOrError } from '$lib/server/api'; +import { toMix } from '$lib/server/mappers'; import { addTrackAction } from '$lib/server/playlistActions'; export const load: PageServerLoad = async ({ params, locals, url, fetch }) => { @@ -8,7 +9,7 @@ export const load: PageServerLoad = async ({ params, locals, url, fetch }) => { const mixRes = await api.GET('/mixes/{id}', { params: { path: { id: params.id } } }); - return { mix: unwrapOrError(mixRes, 'Mezcla no encontrada.', 404) }; + return { mix: toMix(unwrapOrError(mixRes, 'Mezcla no encontrada.', 404)) }; }; export const actions: Actions = { diff --git a/web-player/src/routes/(app)/mixes/[id]/+page.svelte b/web-player/src/routes/(app)/mixes/[id]/+page.svelte index 92ee433..10b573e 100644 --- a/web-player/src/routes/(app)/mixes/[id]/+page.svelte +++ b/web-player/src/routes/(app)/mixes/[id]/+page.svelte @@ -1,12 +1,6 @@ @@ -60,18 +40,18 @@ eyebrow="Mezcla" title={mix.title} description={mix.subtitle ?? undefined} - meta="{plural(items.length, 'canción', 'canciones')} · {fmtTime(totalSeconds)}" + meta="{plural(tracks.length, 'canción', 'canciones')} · {fmtTime(totalSeconds)}" > {#snippet cover()} item.trackId)} + trackIds={tracks.map((track) => track.id)} size="hero" alt={mix.title} class="shrink-0" /> {/snippet} {#snippet actions()} -
- {#each items as track, i (track.id)} + {#each previewItems as track, i (track.id)} playSpotlightTrack(previewItems, i)} - active={player.current.id === track.id} + active={player.currentId === track.id} size="sm" title={track.title} subtitle={track.artist} subtitleHref={track.ownerUserId} - explicit={track.isExplicit} + explicit={track.explicit} > {#snippet leading()} {fmtTime(Number(track.duration))}{fmtTime(track.duration)} {/snippet} @@ -250,7 +231,7 @@ href="/mixes/{mix.id}" title={mix.title} subtitle={mix.subtitle} - trackIds={mix.items.map((item) => item.trackId)} + trackIds={mix.tracks.map((track) => track.id)} onPlay={(event) => playMix(mix, event)} index={i} /> @@ -314,7 +295,7 @@ ]} playlistAction={{ action: '?/addTrack', - fields: { trackId: String(trackMenu.state.track.id) }, + fields: { trackId: trackMenu.state.track.id }, label: 'Añadir a una playlist' }} playlists={data.userPlaylists} diff --git a/web-player/src/routes/api/albums/[id]/tracks/+server.ts b/web-player/src/routes/api/albums/[id]/tracks/+server.ts index e356c9c..8df1853 100644 --- a/web-player/src/routes/api/albums/[id]/tracks/+server.ts +++ b/web-player/src/routes/api/albums/[id]/tracks/+server.ts @@ -1,6 +1,7 @@ import type { RequestHandler } from './$types'; import { json } from '@sveltejs/kit'; import { createApiClient, unwrapOrError } from '$lib/server/api'; +import { toPage, toTrack } from '$lib/server/mappers'; import { ALBUM_TRACKS_PAGE_SIZE } from '$lib/config'; export const GET: RequestHandler = async ({ params, locals, fetch }) => { @@ -12,5 +13,5 @@ export const GET: RequestHandler = async ({ params, locals, fetch }) => { } }); - return json(unwrapOrError(result, 'No se pudo cargar el álbum.')); + return json(toPage(unwrapOrError(result, 'No se pudo cargar el álbum.'), toTrack)); }; diff --git a/web-player/src/routes/api/playlists/[id]/tracks/+server.ts b/web-player/src/routes/api/playlists/[id]/tracks/+server.ts index 3f8618d..29b103d 100644 --- a/web-player/src/routes/api/playlists/[id]/tracks/+server.ts +++ b/web-player/src/routes/api/playlists/[id]/tracks/+server.ts @@ -1,6 +1,7 @@ import type { RequestHandler } from './$types'; import { json } from '@sveltejs/kit'; import { createApiClient, unwrapOrError } from '$lib/server/api'; +import { toPage, toTrack } from '$lib/server/mappers'; import { PLAYLIST_TRACKS_PAGE_SIZE } from '$lib/config'; export const GET: RequestHandler = async ({ params, locals, fetch }) => { @@ -12,5 +13,5 @@ export const GET: RequestHandler = async ({ params, locals, fetch }) => { } }); - return json(unwrapOrError(result, 'No se pudo cargar la playlist.')); + return json(toPage(unwrapOrError(result, 'No se pudo cargar la playlist.'), toTrack)); }; diff --git a/web-player/src/routes/api/tracks/+server.ts b/web-player/src/routes/api/tracks/+server.ts index 786796b..6cd7c1d 100644 --- a/web-player/src/routes/api/tracks/+server.ts +++ b/web-player/src/routes/api/tracks/+server.ts @@ -1,6 +1,7 @@ import type { RequestHandler } from './$types'; import { json } from '@sveltejs/kit'; import { createApiClient, unwrapOrError } from '$lib/server/api'; +import { toPage, toTrack } from '$lib/server/mappers'; import { EXPLORE_PAGE_SIZE, MAX_PAGE_SIZE } from '$lib/config'; export const GET: RequestHandler = async ({ url, locals, fetch }) => { @@ -17,5 +18,9 @@ export const GET: RequestHandler = async ({ url, locals, fetch }) => { params: { query: { name, genre, pageNumber, pageSize } } }); - return json(unwrapOrError(result, 'No se pudieron cargar las canciones.')); + return json( + toPage(unwrapOrError(result, 'No se pudieron cargar las canciones.'), (item) => + toTrack(item.track) + ) + ); }; diff --git a/web-player/src/routes/api/users/[id]/[list=followList]/+server.ts b/web-player/src/routes/api/users/[id]/[list=followList]/+server.ts index b90b4a8..6ccd838 100644 --- a/web-player/src/routes/api/users/[id]/[list=followList]/+server.ts +++ b/web-player/src/routes/api/users/[id]/[list=followList]/+server.ts @@ -1,6 +1,7 @@ import type { RequestHandler } from './$types'; import { json } from '@sveltejs/kit'; import { createApiClient, unwrapOrError } from '$lib/server/api'; +import { toPage } from '$lib/server/mappers'; import { fetchFollowList } from '$lib/server/follows'; import { FOLLOW_LIST_PAGE_SIZE } from '$lib/config'; @@ -15,5 +16,10 @@ export const GET: RequestHandler = async ({ params, url, locals, fetch }) => { FOLLOW_LIST_PAGE_SIZE ); - return json(unwrapOrError(result, 'No se pudo cargar la lista.', result.response?.status)); + return json( + toPage( + unwrapOrError(result, 'No se pudo cargar la lista.', result.response?.status), + (user) => user + ) + ); };