web-player: normalize tracks, albums and playlists into single domain types at the server boundary

This commit is contained in:
2026-09-24 05:09:37 +02:00
parent 37fc229bf6
commit 5b2a0e4d46
41 changed files with 526 additions and 452 deletions
@@ -1,5 +1,6 @@
import type { PageServerLoad, Actions } from './$types';
import { createApiClient, optionalUser, unwrapOrError } from '$lib/server/api';
import { toPlaylistSummary } from '$lib/server/mappers';
import { followUserAction, unfollowUserAction } from '$lib/server/followActions';
const PROFILE_PLAYLISTS_PAGE_SIZE = 50;
@@ -32,14 +33,19 @@ export const load: PageServerLoad = async ({ params, locals, url, fetch, parent
)
);
const playlists = playlistItems.map((playlist, i) => ({
...playlist,
trackCount: tracksByPlaylist[i]
}));
const playlists = playlistItems.map((playlist, i) =>
toPlaylistSummary(playlist, tracksByPlaylist[i])
);
const isOwnProfile = viewer !== null && viewer.sub === params.id;
return { profile, playlists, isOwnProfile, isAnonymous: viewer === null };
return {
profile,
followersCount: Number(profile.followersCount),
playlists,
isOwnProfile,
isAnonymous: viewer === null
};
};
export const actions: Actions = {
@@ -26,7 +26,7 @@
{/snippet}
{#snippet followStats()}
{@const followers = plural(Number(profile.followersCount), 'seguidor', 'seguidores')}
{@const followers = plural(data.followersCount, 'seguidor', 'seguidores')}
{#if data.isOwnProfile || profile.canViewFollowers}
<a href="/user/{profile.id}/followers" class="transition-colors hover:text-fg">{followers}</a>
{:else}
@@ -60,7 +60,7 @@
<MediaCard
href="/playlists/{playlist.id}"
title={playlist.name}
subtitle={playlistMeta(Number(playlist.trackCount))}
subtitle={playlistMeta(playlist.trackCount)}
trackIds={playlist.coverTrackIds}
src="/api/playlists/{playlist.id}/cover?size=large&v={encodeURIComponent(
playlist.updatedAt
@@ -1,5 +1,6 @@
import type { PageServerLoad } from './$types';
import { createApiClient, optionalUser, 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';
@@ -19,7 +20,9 @@ export const load: PageServerLoad = async ({ params, locals, url, fetch, parent
return {
list: params.list,
profile,
users: forbidden ? null : unwrapOrError(listRes, 'No se pudo cargar la lista.'),
users: forbidden
? null
: toPage(unwrapOrError(listRes, 'No se pudo cargar la lista.'), (item) => item),
forbidden,
viewerId: viewer?.sub ?? null
};
@@ -20,13 +20,13 @@
$effect(() => {
items = data.users?.items ?? [];
page = Number(data.users?.pageNumber ?? 1);
page = data.users?.pageNumber ?? 1;
hasMore = data.users?.hasNextPage ?? false;
});
const isFollowers = $derived(data.list === 'followers');
const title = $derived(isFollowers ? 'Seguidores' : 'Siguiendo');
const total = $derived(Number(data.users?.totalItemCount ?? 0));
const total = $derived(data.users?.totalItemCount ?? 0);
async function loadMore() {
if (loadingMore) return;
@@ -36,7 +36,7 @@
if (!res.ok) throw new Error(String(res.status));
const next = (await res.json()) as NonNullable<typeof data.users>;
items = appendUnique(items, next.items, (u) => u.id);
page = Number(next.pageNumber);
page = next.pageNumber;
hasMore = next.hasNextPage;
} catch {
hasMore = false;