Merge branch 'backend-refactor-rev' into audit-merge-three

Adapt the refactored web player to the new backend API (id path params,
bulk track adds, flat list items, mix kind) and port AddAlbumToPlayList
to the backend conventions.
This commit is contained in:
2026-09-24 17:30:40 +02:00
444 changed files with 14432 additions and 9275 deletions
@@ -10,15 +10,12 @@ export const addTrackAction = authedAction(async ({ api, form, params }) => {
return fail(400, { message: 'Faltan datos de la canción.' });
}
const { error, response } = await api.POST('/playlists/{playlistId}/tracks', {
params: { path: { playlistId } },
body: { trackId }
const { error } = await api.POST('/playlists/{id}/tracks', {
params: { path: { id: playlistId } },
body: { trackIds: [trackId] }
});
if (error) {
if (response?.status === 409) {
return fail(409, { message: 'La canción ya está en esa playlist.', trackId });
}
return fail(502, { message: 'No se pudo añadir la canción.', trackId });
}
@@ -33,8 +30,8 @@ export const addAlbumToPlaylistAction = authedAction(async ({ api, form, params
return fail(400, { message: 'Faltan datos del álbum.' });
}
const { data, error } = await api.POST('/playlists/{playlistId}/albums/{albumId}', {
params: { path: { playlistId, albumId } }
const { data, error } = await api.POST('/playlists/{id}/albums/{albumId}', {
params: { path: { id: playlistId, albumId } }
});
if (error || !data) {
+2 -2
View File
@@ -56,11 +56,11 @@ describe('toMix', () => {
it('maps items into tracks', () => {
const mix = toMix({
id: 'm1',
title: 'Mix',
subtitle: null,
kind: 'Daily',
itemCount: 1,
items: [{ trackId: 't1', title: 'Song', artist: 'A', durationSeconds: '60', listensCount: 3 }]
});
expect(mix.title).toBe('Tu mezcla diaria');
expect(mix.tracks).toEqual([
{
id: 't1',
+12 -2
View File
@@ -46,11 +46,21 @@ export function toMixTrack(item: MixItemDto): Track {
};
}
const MIX_TEXTS: Record<MixDto['kind'], { title: string; subtitle: string }> = {
Discovery: {
title: 'Descubrimiento',
subtitle: 'Canciones de tu biblioteca que todavía no has escuchado.'
},
Daily: {
title: 'Tu mezcla diaria',
subtitle: 'Lo que más escuchas, con alguna sorpresa de tu biblioteca.'
}
};
export function toMix(dto: MixDto): Mix {
return {
id: dto.id,
title: dto.title,
subtitle: dto.subtitle,
...MIX_TEXTS[dto.kind],
tracks: dto.items.map(toMixTrack)
};
}
+22
View File
@@ -0,0 +1,22 @@
/** Largest page size the API accepts (`PageRequest.MaxPageSize` in the backend). */
export const API_MAX_PAGE_SIZE = 100;
type Page<T> = { items: T[]; hasNextPage: boolean };
/** Loads consecutive pages until the list ends or `maxItems` items were collected. */
export async function fetchAllPages<T>(
loadPage: (pageNumber: number, pageSize: number) => Promise<Page<T> | undefined>,
maxItems: number
): Promise<T[]> {
const items: T[] = [];
for (let pageNumber = 1; items.length < maxItems; pageNumber++) {
const page = await loadPage(pageNumber, API_MAX_PAGE_SIZE);
if (!page) break;
items.push(...page.items);
if (!page.hasNextPage) break;
}
return items.slice(0, maxItems);
}