Files
siftlode/frontend/src/lib/api/playlists.ts
T
peter 8ce1e0230a refactor(api): extract 8 content domains into api/*.ts slices (R7.5 S1)
Move feed, channels, sync, playlists, tags, notifications, saved-views and
quota out of the lib/api.ts god object into per-domain slice modules, each
owning its own types + method slice and spread into the façade. The `api`
object shape and all 54 call sites are unchanged; account/auth/setup/admin
stay inline until S2.

Reunites the interleaved tags read/CRUD that motivated the epic.
2026-07-28 22:41:34 +02:00

92 lines
3.4 KiB
TypeScript

// Playlists: local + YouTube-linked, item add/remove/reorder, the built-in Watch later shortcut,
// and the push-to-YouTube plan/apply flow. Only touches core's req.
import { req } from "./core";
import type { Video } from "./feed";
// Not exported: consumers compare `playlist.kind`/`.source` against string literals, they never
// name these types — keeping them local satisfies the no-unused-exports gate (knip).
type PlaylistKind = "user" | "watch_later";
type PlaylistSource = "local" | "youtube";
export interface Playlist {
id: number;
name: string;
kind: PlaylistKind;
source: PlaylistSource;
yt_playlist_id: string | null;
dirty: boolean; // linked local playlist has edits not yet pushed to YouTube
item_count: number;
total_duration_seconds: number;
cover_thumbnail: string | null;
has_video?: boolean; // only set when listed with ?contains=<video_id>
}
export interface PlaylistDetail {
id: number;
name: string;
kind: PlaylistKind;
source: PlaylistSource;
yt_playlist_id: string | null;
dirty: boolean;
items: Video[];
}
export interface PushPlan {
action: "create" | "update";
to_insert: number;
to_delete: number;
to_reorder: number;
yt_extra: number; // items on YouTube that the push would remove (divergence)
units_estimate: number;
remaining_today: number;
affordable: boolean;
}
export interface PushResult {
created: number;
inserted: number;
deleted: number;
reordered: number;
failures: string[];
yt_playlist_id: string | null;
}
export const playlistsApi = {
playlists: (containsVideoId?: string): Promise<Playlist[]> =>
req(`/api/playlists${containsVideoId ? `?contains=${containsVideoId}` : ""}`),
playlist: (id: number): Promise<PlaylistDetail> => req(`/api/playlists/${id}`),
createPlaylist: (name: string): Promise<Playlist> =>
req("/api/playlists", { method: "POST", body: JSON.stringify({ name }) }),
renamePlaylist: (id: number, name: string): Promise<Playlist> =>
req(`/api/playlists/${id}`, { method: "PATCH", body: JSON.stringify({ name }) }),
deletePlaylist: (id: number, onYoutube = false) =>
req(`/api/playlists/${id}${onYoutube ? "?on_youtube=true" : ""}`, { method: "DELETE" }),
addToPlaylist: (id: number, videoId: string) =>
req(`/api/playlists/${id}/items`, {
method: "POST",
body: JSON.stringify({ video_id: videoId }),
}),
removeFromPlaylist: (id: number, videoId: string) =>
req(`/api/playlists/${id}/items/${videoId}`, { method: "DELETE" }),
reorderPlaylist: (id: number, videoIds: string[]) =>
req(`/api/playlists/${id}/order`, {
method: "PUT",
body: JSON.stringify({ video_ids: videoIds }),
}),
// Bookmark shortcut for the built-in Watch later playlist.
watchLaterAdd: (videoId: string) =>
req("/api/playlists/watch-later", {
method: "POST",
body: JSON.stringify({ video_id: videoId }),
}),
watchLaterRemove: (videoId: string) =>
req(`/api/playlists/watch-later/${videoId}`, { method: "DELETE" }),
syncYoutubePlaylists: (): Promise<{ synced: number; reason?: string }> =>
req("/api/playlists/sync-youtube", { method: "POST" }),
revertPlaylist: (id: number): Promise<{ items: number }> =>
req(`/api/playlists/${id}/revert-youtube`, { method: "POST" }),
playlistPushPlan: (id: number): Promise<PushPlan> => req(`/api/playlists/${id}/push-plan`),
pushPlaylist: (id: number): Promise<PushResult> =>
req(`/api/playlists/${id}/push`, { method: "POST" }),
};