refactor(api): literal-union the closed-value string fields (R7 S3a·1)
Video.status, Playlist.kind/source (+ PlaylistDetail), DownloadJob.job_kind
were bare `string`; type them as named unions verified against the backend
(VALID_STATES={new,watched,hidden}; kind user|watch_later; source local|
youtube; job_kind download|edit). VideoStatus then propagates through the
optimistic-override map, onState, and the VideoCard/VirtualFeed/PlayerModal
props — tsc now rejects a status typo (e.g. a stale "in_progress", which is a
derived filter, never a stored status) at every call site.
This commit is contained in:
+16
-6
@@ -47,6 +47,11 @@ export interface Tag {
|
||||
channel_count: number;
|
||||
}
|
||||
|
||||
// Per-user watch status stored server-side (VALID_STATES in routes/feed.py). "in_progress" and
|
||||
// "unwatched" are NOT statuses — they're derived feed filters (status "new" + a resume position),
|
||||
// so they never appear on the wire.
|
||||
export type VideoStatus = "new" | "watched" | "hidden";
|
||||
|
||||
export interface Video {
|
||||
id: string;
|
||||
title: string | null;
|
||||
@@ -60,7 +65,7 @@ export interface Video {
|
||||
view_count: number | null;
|
||||
is_short: boolean;
|
||||
live_status: string;
|
||||
status: string;
|
||||
status: VideoStatus;
|
||||
position_seconds: number;
|
||||
saved: boolean; // is the video in the user's built-in Watch later playlist
|
||||
watch_url: string;
|
||||
@@ -124,11 +129,14 @@ export interface FeedResponse {
|
||||
source?: "scrape" | "api";
|
||||
}
|
||||
|
||||
export type PlaylistKind = "user" | "watch_later";
|
||||
export type PlaylistSource = "local" | "youtube";
|
||||
|
||||
export interface Playlist {
|
||||
id: number;
|
||||
name: string;
|
||||
kind: string; // "user" | "watch_later"
|
||||
source: string; // "local" | "youtube"
|
||||
kind: PlaylistKind;
|
||||
source: PlaylistSource;
|
||||
yt_playlist_id: string | null;
|
||||
dirty: boolean; // linked local playlist has edits not yet pushed to YouTube
|
||||
item_count: number;
|
||||
@@ -140,8 +148,8 @@ export interface Playlist {
|
||||
export interface PlaylistDetail {
|
||||
id: number;
|
||||
name: string;
|
||||
kind: string;
|
||||
source: string;
|
||||
kind: PlaylistKind;
|
||||
source: PlaylistSource;
|
||||
yt_playlist_id: string | null;
|
||||
dirty: boolean;
|
||||
items: Video[];
|
||||
@@ -1012,6 +1020,8 @@ interface DownloadAsset {
|
||||
}
|
||||
|
||||
export type DownloadStatus = "queued" | "running" | "paused" | "done" | "error" | "canceled";
|
||||
// "download" (default) or "edit" (a per-user trim/crop derivative cut from another job).
|
||||
export type DownloadJobKind = "download" | "edit";
|
||||
|
||||
// Phase-2 editor recipe. A single `trim` (one output file) or a `segments` cut-list joined into
|
||||
// one file. `accurate` re-encodes for a frame-accurate cut (a crop always re-encodes).
|
||||
@@ -1051,7 +1061,7 @@ export interface DownloadJob {
|
||||
display_uploader: string | null; // user override of the channel name
|
||||
display_uploader_url: string | null; // user override of the channel link
|
||||
extra_links: string[]; // extra reference URLs (beyond source_url)
|
||||
job_kind?: string; // "download" (default) | "edit"
|
||||
job_kind?: DownloadJobKind; // "download" (default) | "edit" (a per-user trim/crop derivative)
|
||||
source_job_id?: number | null; // parent download an edit was cut from
|
||||
edit_spec?: EditSpec | null;
|
||||
can_download: boolean;
|
||||
|
||||
Reference in New Issue
Block a user