Normalize API counters in the server mappers instead of route loaders
This commit is contained in:
@@ -1,6 +1,16 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import type { components } from '$lib/api/schema';
|
import type { components } from '$lib/api/schema';
|
||||||
import { toAlbum, toDatedTrack, toLikedTrack, toMix, toPage, toPlaylist, toTrack } from './mappers';
|
import {
|
||||||
|
toAlbum,
|
||||||
|
toCount,
|
||||||
|
toDatedTrack,
|
||||||
|
toLikedTrack,
|
||||||
|
toListeningStats,
|
||||||
|
toMix,
|
||||||
|
toPage,
|
||||||
|
toPlaylist,
|
||||||
|
toTrack
|
||||||
|
} from './mappers';
|
||||||
|
|
||||||
const trackDto: components['schemas']['TrackApplicationResponse'] = {
|
const trackDto: components['schemas']['TrackApplicationResponse'] = {
|
||||||
id: 't1',
|
id: 't1',
|
||||||
@@ -129,3 +139,29 @@ describe('toPage', () => {
|
|||||||
).toEqual({ items: [2, 4], pageNumber: 2, hasNextPage: true, totalItemCount: 10 });
|
).toEqual({ items: [2, 4], pageNumber: 2, hasNextPage: true, totalItemCount: 10 });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('toCount', () => {
|
||||||
|
it('normalizes numbers, numeric strings and missing values', () => {
|
||||||
|
expect(toCount(4)).toBe(4);
|
||||||
|
expect(toCount('12')).toBe(12);
|
||||||
|
expect(toCount(undefined)).toBe(0);
|
||||||
|
expect(toCount(null)).toBe(0);
|
||||||
|
expect(toCount('abc')).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toListeningStats', () => {
|
||||||
|
it('converts string counters to numbers', () => {
|
||||||
|
expect(
|
||||||
|
toListeningStats({ tracksThisWeek: '5', secondsThisWeek: 300, streakDays: '2' })
|
||||||
|
).toEqual({ tracksThisWeek: 5, secondsThisWeek: 300, streakDays: 2 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to zeros without data', () => {
|
||||||
|
expect(toListeningStats(undefined)).toEqual({
|
||||||
|
tracksThisWeek: 0,
|
||||||
|
secondsThisWeek: 0,
|
||||||
|
streakDays: 0
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -82,6 +82,26 @@ export function toPlaylist(dto: PlaylistDto): Playlist {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ListeningStatsDto = components['schemas']['ListeningStatsResponse'];
|
||||||
|
|
||||||
|
export interface ListeningStats {
|
||||||
|
tracksThisWeek: number;
|
||||||
|
secondsThisWeek: number;
|
||||||
|
streakDays: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toCount(value: number | string | null | undefined): number {
|
||||||
|
return Number(value ?? 0) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toListeningStats(dto: ListeningStatsDto | null | undefined): ListeningStats {
|
||||||
|
return {
|
||||||
|
tracksThisWeek: toCount(dto?.tracksThisWeek),
|
||||||
|
secondsThisWeek: toCount(dto?.secondsThisWeek),
|
||||||
|
streakDays: toCount(dto?.streakDays)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function toPage<D, T>(dto: PageDto<D>, map: (item: D) => T): Paged<T> {
|
export function toPage<D, T>(dto: PageDto<D>, map: (item: D) => T): Paged<T> {
|
||||||
return {
|
return {
|
||||||
items: dto.items.map(map),
|
items: dto.items.map(map),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { LayoutServerLoad } from './$types';
|
|||||||
import { getAllowAnonymousListening } from '$lib/server/playbackConfig';
|
import { getAllowAnonymousListening } from '$lib/server/playbackConfig';
|
||||||
import { apiFor } from '$lib/server/api';
|
import { apiFor } from '$lib/server/api';
|
||||||
import { loginRedirect } from '$lib/server/guards';
|
import { loginRedirect } from '$lib/server/guards';
|
||||||
import { toLikedTrack, toPlaylist, toTrack } from '$lib/server/mappers';
|
import { toCount, toLikedTrack, toPlaylist, toTrack } from '$lib/server/mappers';
|
||||||
import { authConfig } from '$lib/server/config';
|
import { authConfig } from '$lib/server/config';
|
||||||
import { LIKED_TRACKS_PAGE_SIZE, PLAYLIST_PICKER_PAGE_SIZE } from '$lib/config';
|
import { LIKED_TRACKS_PAGE_SIZE, PLAYLIST_PICKER_PAGE_SIZE } from '$lib/config';
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ export const load: LayoutServerLoad = async ({ locals, url, fetch }) => {
|
|||||||
})
|
})
|
||||||
.then(({ data }) => ({
|
.then(({ data }) => ({
|
||||||
items: (data?.items ?? []).map(toPlaylist),
|
items: (data?.items ?? []).map(toPlaylist),
|
||||||
total: Number(data?.totalItemCount ?? 0)
|
total: toCount(data?.totalItemCount)
|
||||||
})),
|
})),
|
||||||
{ items: [], total: 0 }
|
{ items: [], total: 0 }
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { redirect } from '@sveltejs/kit';
|
|||||||
import type { PageServerLoad, Actions } from './$types';
|
import type { PageServerLoad, Actions } from './$types';
|
||||||
import { apiFor } from '$lib/server/api';
|
import { apiFor } from '$lib/server/api';
|
||||||
import { requireUser } from '$lib/server/guards';
|
import { requireUser } from '$lib/server/guards';
|
||||||
import { toMix, toPlaylist, toTrack } from '$lib/server/mappers';
|
import { toCount, toListeningStats, toMix, toPlaylist, toTrack } from '$lib/server/mappers';
|
||||||
import { addTrackAction } from '$lib/server/actions/playlist';
|
import { addTrackAction } from '$lib/server/actions/playlist';
|
||||||
import { HOME_MIXES_LIMIT, HOME_SHELF_LIMIT, HOME_SPOTLIGHT_TRACKS_LIMIT } from '$lib/config';
|
import { HOME_MIXES_LIMIT, HOME_SHELF_LIMIT, HOME_SPOTLIGHT_TRACKS_LIMIT } from '$lib/config';
|
||||||
import { pickGreeting } from '$lib/server/greeting';
|
import { pickGreeting } from '$lib/server/greeting';
|
||||||
@@ -24,12 +24,8 @@ export const load: PageServerLoad = async ({ locals, url, fetch }) => {
|
|||||||
});
|
});
|
||||||
const listeningStatsPromise = api
|
const listeningStatsPromise = api
|
||||||
.GET('/users/{id}/listening-stats', { params: { path: { id: user.sub } } })
|
.GET('/users/{id}/listening-stats', { params: { path: { id: user.sub } } })
|
||||||
.then((res) => ({
|
.then((res) => toListeningStats(res.data))
|
||||||
tracksThisWeek: Number(res.data?.tracksThisWeek ?? 0),
|
.catch(() => toListeningStats(null));
|
||||||
secondsThisWeek: Number(res.data?.secondsThisWeek ?? 0),
|
|
||||||
streakDays: Number(res.data?.streakDays ?? 0)
|
|
||||||
}))
|
|
||||||
.catch(() => ({ tracksThisWeek: 0, secondsThisWeek: 0, streakDays: 0 }));
|
|
||||||
|
|
||||||
const [playlists, mixes, recentlyPlayed] = await Promise.all([
|
const [playlists, mixes, recentlyPlayed] = await Promise.all([
|
||||||
playlistsPromise,
|
playlistsPromise,
|
||||||
@@ -49,7 +45,7 @@ export const load: PageServerLoad = async ({ locals, url, fetch }) => {
|
|||||||
})
|
})
|
||||||
.then((res) => ({
|
.then((res) => ({
|
||||||
items: (res.data?.items ?? []).map(toTrack),
|
items: (res.data?.items ?? []).map(toTrack),
|
||||||
totalCount: Number(res.data?.totalItemCount ?? 0)
|
totalCount: toCount(res.data?.totalItemCount)
|
||||||
}))
|
}))
|
||||||
.catch(() => ({ items: [], totalCount: 0 }))
|
.catch(() => ({ items: [], totalCount: 0 }))
|
||||||
: Promise.resolve({ items: [], totalCount: 0 });
|
: Promise.resolve({ items: [], totalCount: 0 });
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { PageServerLoad, Actions } from './$types';
|
import type { PageServerLoad, Actions } from './$types';
|
||||||
import { apiFor, unwrapOrError } from '$lib/server/api';
|
import { apiFor, unwrapOrError } from '$lib/server/api';
|
||||||
import { getGenres } from '$lib/server/genres';
|
import { getGenres } from '$lib/server/genres';
|
||||||
import { toAlbum, toPage, toTrack } from '$lib/server/mappers';
|
import { toAlbum, toCount, toPage, toTrack } from '$lib/server/mappers';
|
||||||
import { addAlbumToPlaylistAction, addTrackAction } from '$lib/server/actions/playlist';
|
import { addAlbumToPlaylistAction, addTrackAction } from '$lib/server/actions/playlist';
|
||||||
import { EXPLORE_ALBUMS_PAGE_SIZE, EXPLORE_PAGE_SIZE, EXPLORE_USERS_LIMIT } from '$lib/config';
|
import { EXPLORE_ALBUMS_PAGE_SIZE, EXPLORE_PAGE_SIZE, EXPLORE_USERS_LIMIT } from '$lib/config';
|
||||||
|
|
||||||
@@ -56,9 +56,9 @@ export const load: PageServerLoad = async ({ url, locals, fetch }) => {
|
|||||||
genres,
|
genres,
|
||||||
tracks,
|
tracks,
|
||||||
albums: (albumsRes?.data?.items ?? []).map((item) => toAlbum(item.album)),
|
albums: (albumsRes?.data?.items ?? []).map((item) => toAlbum(item.album)),
|
||||||
albumsTotal: Number(albumsRes?.data?.totalItemCount ?? 0),
|
albumsTotal: toCount(albumsRes?.data?.totalItemCount),
|
||||||
users: usersRes?.data?.items ?? [],
|
users: usersRes?.data?.items ?? [],
|
||||||
usersTotal: Number(usersRes?.data?.totalItemCount ?? 0),
|
usersTotal: toCount(usersRes?.data?.totalItemCount),
|
||||||
viewerId: user?.sub ?? null
|
viewerId: user?.sub ?? null
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { PageServerLoad, Actions } from './$types';
|
import type { PageServerLoad, Actions } from './$types';
|
||||||
import { apiFor, unwrapOrError } from '$lib/server/api';
|
import { apiFor, unwrapOrError } from '$lib/server/api';
|
||||||
import { optionalUser } from '$lib/server/guards';
|
import { optionalUser } from '$lib/server/guards';
|
||||||
import { toPlaylist } from '$lib/server/mappers';
|
import { toCount, toPlaylist } from '$lib/server/mappers';
|
||||||
import { PROFILE_PLAYLISTS_PAGE_SIZE } from '$lib/config';
|
import { PROFILE_PLAYLISTS_PAGE_SIZE } from '$lib/config';
|
||||||
import { followUserAction, unfollowUserAction } from '$lib/server/actions/follow';
|
import { followUserAction, unfollowUserAction } from '$lib/server/actions/follow';
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ export const load: PageServerLoad = async ({ params, locals, url, fetch, parent
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
profile,
|
profile,
|
||||||
followersCount: Number(profile.followersCount),
|
followersCount: toCount(profile.followersCount),
|
||||||
playlists,
|
playlists,
|
||||||
isOwnProfile,
|
isOwnProfile,
|
||||||
isAnonymous: viewer === null
|
isAnonymous: viewer === null
|
||||||
|
|||||||
Reference in New Issue
Block a user