Fix query and API bugs: 403 for non-owners, page bounds, lifecycle filters, cover fallback, race-safe inserts, async album delete
This commit is contained in:
Vendored
+65
-2
@@ -1396,6 +1396,13 @@ export interface operations {
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Forbidden */
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Not Found */
|
||||
404: {
|
||||
headers: {
|
||||
@@ -1430,6 +1437,13 @@ export interface operations {
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Forbidden */
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Not Found */
|
||||
404: {
|
||||
headers: {
|
||||
@@ -1465,6 +1479,13 @@ export interface operations {
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Forbidden */
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Not Found */
|
||||
404: {
|
||||
headers: {
|
||||
@@ -1507,6 +1528,13 @@ export interface operations {
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Forbidden */
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Not Found */
|
||||
404: {
|
||||
headers: {
|
||||
@@ -1825,6 +1853,13 @@ export interface operations {
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Forbidden */
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Not Found */
|
||||
404: {
|
||||
headers: {
|
||||
@@ -1957,6 +1992,13 @@ export interface operations {
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Forbidden */
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Not Found */
|
||||
404: {
|
||||
headers: {
|
||||
@@ -1991,6 +2033,13 @@ export interface operations {
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Forbidden */
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Not Found */
|
||||
404: {
|
||||
headers: {
|
||||
@@ -2073,6 +2122,13 @@ export interface operations {
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Forbidden */
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Not Found */
|
||||
404: {
|
||||
headers: {
|
||||
@@ -2353,6 +2409,13 @@ export interface operations {
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Forbidden */
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Not Found */
|
||||
404: {
|
||||
headers: {
|
||||
@@ -2561,8 +2624,8 @@ export interface operations {
|
||||
"application/json": components["schemas"]["TrackApplicationResponse"];
|
||||
};
|
||||
};
|
||||
/** @description Not Found */
|
||||
404: {
|
||||
/** @description No Content */
|
||||
204: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ export const SEARCH_DEBOUNCE_MS = 450;
|
||||
export const SEARCH_MIN_LENGTH = 2;
|
||||
export const INFINITE_SCROLL_ROOT_MARGIN = '300px';
|
||||
export const ALBUMS_PAGE_SIZE = 50;
|
||||
export const ALBUM_TRACKS_PAGE_SIZE = 200;
|
||||
export const ALBUM_TRACKS_PAGE_SIZE = 100;
|
||||
export const ALBUM_EARLIEST_YEAR = 1877;
|
||||
export const LIBRARY_PICKER_PAGE_SIZE = 50;
|
||||
export const EXPLORE_ALBUMS_PAGE_SIZE = 10;
|
||||
@@ -15,7 +15,8 @@ export const HOME_CONTINUE_LIMIT = 10;
|
||||
export const HOME_SPOTLIGHT_TRACKS_LIMIT = 5;
|
||||
export const HOME_POPULAR_MAX = 8;
|
||||
export const PLAYLIST_PICKER_PAGE_SIZE = 50;
|
||||
export const LIKED_TRACKS_PAGE_SIZE = 500;
|
||||
export const LIKED_TRACKS_LIMIT = 500;
|
||||
export const PLAYLIST_TRACKS_LIMIT = 500;
|
||||
export const EXPLORE_USERS_LIMIT = 12;
|
||||
export const FOLLOW_LIST_PAGE_SIZE = 30;
|
||||
export const MENU_WIDTH = 240;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
unwrapOrError
|
||||
} from '$lib/server/api';
|
||||
import { uploadPresignedImage } from '$lib/server/upload';
|
||||
import { fetchAllPages } from '$lib/server/pagination';
|
||||
import { PLAYLIST_TRACKS_LIMIT } from '$lib/config';
|
||||
|
||||
const MAX_NAME = 100;
|
||||
|
||||
@@ -21,11 +23,15 @@ export const load: PageServerLoad = async ({ locals, url, fetch }) => {
|
||||
|
||||
const tracksByPlaylist = await Promise.all(
|
||||
data.items.map((playlist) =>
|
||||
api
|
||||
.GET('/playlists/{playlistId}/tracks', {
|
||||
params: { path: { playlistId: playlist.id }, query: { pageNumber: 1, pageSize: 500 } }
|
||||
})
|
||||
.then((res) => res.data?.items ?? [])
|
||||
fetchAllPages(
|
||||
async (pageNumber, pageSize) =>
|
||||
(
|
||||
await api.GET('/playlists/{playlistId}/tracks', {
|
||||
params: { path: { playlistId: playlist.id }, query: { pageNumber, pageSize } }
|
||||
})
|
||||
).data,
|
||||
PLAYLIST_TRACKS_LIMIT
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
unwrapOrFail
|
||||
} from '$lib/server/api';
|
||||
import { uploadPresignedImage } from '$lib/server/upload';
|
||||
import { fetchAllPages } from '$lib/server/pagination';
|
||||
import { PLAYLIST_TRACKS_LIMIT } from '$lib/config';
|
||||
|
||||
const MAX_NAME = 100;
|
||||
|
||||
@@ -16,15 +18,20 @@ export const load: PageServerLoad = async ({ params, locals, url, fetch, parent
|
||||
optionalUser(locals, url, allowAnonymousListening);
|
||||
const api = createApiClient({ fetch, accessToken: locals.accessToken ?? undefined });
|
||||
|
||||
const [playlistRes, tracksRes] = await Promise.all([
|
||||
const [playlistRes, tracks] = await Promise.all([
|
||||
api.GET('/playlists/{id}', { params: { path: { id: params.id } } }),
|
||||
api.GET('/playlists/{playlistId}/tracks', {
|
||||
params: { path: { playlistId: params.id }, query: { pageNumber: 1, pageSize: 200 } }
|
||||
})
|
||||
fetchAllPages(
|
||||
async (pageNumber, pageSize) =>
|
||||
(
|
||||
await api.GET('/playlists/{playlistId}/tracks', {
|
||||
params: { path: { playlistId: params.id }, query: { pageNumber, pageSize } }
|
||||
})
|
||||
).data,
|
||||
PLAYLIST_TRACKS_LIMIT
|
||||
)
|
||||
]);
|
||||
|
||||
const playlist = unwrapOrError(playlistRes, 'Playlist no encontrada.', 404);
|
||||
const tracks = tracksRes.data?.items ?? [];
|
||||
|
||||
return { playlist, tracks };
|
||||
};
|
||||
|
||||
@@ -3,7 +3,8 @@ import type { LayoutServerLoad } from './$types';
|
||||
import { getAllowAnonymousListening } from '$lib/server/playbackConfig';
|
||||
import { createApiClient } from '$lib/server/api';
|
||||
import { authConfig } from '$lib/server/config';
|
||||
import { LIKED_TRACKS_PAGE_SIZE } from '$lib/config';
|
||||
import { LIKED_TRACKS_LIMIT } from '$lib/config';
|
||||
import { fetchAllPages } from '$lib/server/pagination';
|
||||
|
||||
const PUBLIC_PATHS = new Set(['/auth']);
|
||||
const SIDEBAR_PLAYLISTS_LIMIT = 8;
|
||||
@@ -30,10 +31,11 @@ async function fetchSidebarPlaylists(
|
||||
async function fetchLikedTracks(fetchFn: typeof fetch, accessToken: string | null) {
|
||||
try {
|
||||
const api = createApiClient({ fetch: fetchFn, accessToken: accessToken ?? undefined });
|
||||
const { data } = await api.GET('/likes', {
|
||||
params: { query: { pageNumber: 1, pageSize: LIKED_TRACKS_PAGE_SIZE } }
|
||||
});
|
||||
return data?.items ?? [];
|
||||
return await fetchAllPages(
|
||||
async (pageNumber, pageSize) =>
|
||||
(await api.GET('/likes', { params: { query: { pageNumber, pageSize } } })).data,
|
||||
LIKED_TRACKS_LIMIT
|
||||
);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user