Combine track search with live YouTube Music results

GET /tracks now merges the local catalog page with a live YouTube Music
search (deduplicated against tracks already provisioned locally) whenever a
name filter is given, appending the YouTube hits after the local matches and
carrying a continuation token for further pages. Each item is tagged with its
source via the new TrackSearchItemResponse/TracksSearchResponse shapes,
wrapping the existing TrackApplicationResponse or a raw YouTubeSongResult so
neither type had to change.

GET /youtube/search is removed along with its now-unused handler; the
web-player's explore page, its /api/tracks proxy, and the YouTube filler
helper are updated to call the combined endpoint instead of hitting the two
separately.
This commit is contained in:
2026-09-10 09:55:58 +02:00
parent c418844178
commit 92a06a3c15
15 changed files with 266 additions and 240 deletions
+24 -62
View File
@@ -323,7 +323,7 @@ export interface paths {
path?: never;
cookie?: never;
};
/** Get Paginated Tracks. */
/** Get Paginated Tracks, Combined With Live YouTube Music Results When A Name Filter Is Given. */
get: operations["GetTracks"];
put?: never;
/** Create A New Track. */
@@ -488,23 +488,6 @@ export interface paths {
patch?: never;
trace?: never;
};
"/youtube/search": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Search Songs On YouTube Music. */
get: operations["SearchYouTubeTracks"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/youtube/albums": {
parameters: {
query?: never;
@@ -759,6 +742,11 @@ export interface components {
/** Format: date-time */
updatedAt: string;
};
TrackSearchItemResponse: {
source: components["schemas"]["TrackSource"];
track: components["schemas"]["TrackApplicationResponse"] | null;
youTubeSong: components["schemas"]["YouTubeSongResult"] | null;
};
/** @enum {unknown} */
TrackSource: "Local" | "YouTube";
TrackStreamResponse: {
@@ -784,6 +772,22 @@ export interface components {
/** Format: int32 */
expiresInSeconds: number | string;
};
TracksSearchResponse: {
items: components["schemas"]["TrackSearchItemResponse"][];
/** Format: int32 */
pageNumber: number | string;
/** Format: int32 */
pageSize: number | string;
/** Format: int32 */
pageCount: number | string;
/** Format: int32 */
totalItemCount: number | string;
hasPreviousPage: boolean;
hasNextLocalPage: boolean;
nextYoutubeContinuationToken: null | string;
youtubeUnavailable: boolean;
hasNextPage: boolean;
};
UpdateAlbumRequest: {
newTitle: string;
newDescription: null | string;
@@ -843,10 +847,6 @@ export interface components {
id: null | string;
name: string;
};
YouTubeSearchResult: {
items: components["schemas"]["YouTubeSongResult"][];
continuationToken: string;
};
YouTubeSongResult: {
videoId: string;
title: string;
@@ -1749,6 +1749,7 @@ export interface operations {
name?: string;
pageNumber?: number | string;
pageSize?: number | string;
youtubeContinuationToken?: string;
};
header?: never;
path?: never;
@@ -1762,7 +1763,7 @@ export interface operations {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["PaginatedResponseOfTrackApplicationResponse"];
"application/json": components["schemas"]["TracksSearchResponse"];
};
};
};
@@ -2158,45 +2159,6 @@ export interface operations {
};
};
};
SearchYouTubeTracks: {
parameters: {
query: {
query: string;
continuation?: string;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["YouTubeSearchResult"];
};
};
/** @description Bad Request */
400: {
headers: {
[name: string]: unknown;
};
content: {
"application/problem+json": components["schemas"]["HttpValidationProblemDetails"];
};
};
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
SearchYouTubeAlbums: {
parameters: {
query: {