export type TrackAvailability = 'server' | 'downloading' | 'error' | 'missing'; /** * Metadata-enrichment state, distinct from file `availability`. `pending` = the * worker hasn't finished (or hasn't started); `enriched` = identity found; * `failed` = no match / a worker error (see `metadataError`); `manual` = user- * edited and never auto-overwritten. */ export type MetadataStatus = 'pending' | 'enriched' | 'failed' | 'manual'; export interface Track { id: string; title: string; artistId: string; artistName: string; albumId: string; albumTitle: string; albumArtUrl?: string; hasCover: boolean; durationMs: number; trackNumber?: number; discNumber?: number; year?: number; genre?: string; availability: TrackAvailability; metadataStatus: MetadataStatus; /** Human-readable reason the last enrichment run set `failed`; else undefined. */ metadataError?: string; fileSize?: number; format?: string; bitrate?: number; liked: boolean; /** Where the track entered the library (e.g. `upload`, `local_folder`). */ source?: string; /** ISO timestamp the track was added to the library. */ createdAt?: string; /** ISO timestamp the last successful enrichment ran; undefined if never. */ enrichedAt?: string; } export interface Album { id: string; title: string; artistId: string; artistName: string; artUrl?: string; year?: number; trackCount: number; totalDurationMs: number; genre?: string; } export interface Artist { id: string; name: string; artUrl?: string; albumCount: number; trackCount: number; } export interface Playlist { id: string; name: string; description?: string; ownerId: string; trackCount: number; totalDurationMs: number; artUrl?: string; isPublic: boolean; createdAt: string; updatedAt: string; } export interface PlaylistTrack extends Track { position: number; addedAt: string; } export interface DownloadJob { id: string; url: string; title?: string; artist?: string; album?: string; status: 'queued' | 'downloading' | 'processing' | 'done' | 'error'; progress: number; errorMessage?: string; trackId?: string; createdAt: string; updatedAt: string; } export interface UploadResponse { track_id: string; title: string; already_exists: boolean; } export interface StorageFormatBreakdown { fileFormat: string; trackCount: number; totalSize: number; } export interface StorageGenreCount { genre: string; trackCount: number; } /** Capacity of the volume backing the media store. Absent for object-store * backends (S3), which have no fixed disk to report. */ export interface StorageDiskUsage { total: number; used: number; free: number; } export interface StorageStats { totalTracks: number; totalArtists: number; totalAlbums: number; /** Sum of every track's recorded file size (the library's footprint). */ totalSize: number; totalDurationSeconds: number; largestTrackSize: number; earliestAdded?: string; latestAdded?: string; byFormat: StorageFormatBreakdown[]; byMetadataStatus: Record; bySource: Record; topGenres: StorageGenreCount[]; disk?: StorageDiskUsage; } export interface User { id: string; username: string; email?: string; role: 'admin' | 'user'; createdAt: string; lastActiveAt?: string; } export interface AuthTokens { accessToken: string; refreshToken: string; // Optional: the backend's TokenResponse carries no TTL — expiry is driven by // 401→refresh, not a client-side clock. Present only if a backend supplies it. expiresIn?: number; } export interface LoginRequest { username: string; password: string; } export interface LoginResponse { user: User; tokens: AuthTokens; } export interface RegisterRequest { username: string; password: string; } export interface PaginatedResponse { items: T[]; total: number; page: number; pageSize: number; hasMore: boolean; } export interface LibraryFilters { search?: string; genre?: string; artistId?: string; albumId?: string; /** Filter by ingest origin, e.g. `upload`, `youtube`, `local`. */ source?: string; liked?: boolean; page?: number; pageSize?: number; sortBy?: 'title' | 'artist' | 'album' | 'year' | 'dateAdded'; sortOrder?: 'asc' | 'desc'; } export interface ApiError { status: number; message: string; code?: string; } /** One AcoustID candidate from `GET /tracks/{id}/metadata/matches` (§A7). */ export interface MetadataMatch { acoustid: string; /** Confidence 0..1. */ score: number; recordingMbid?: string; releaseGroupMbid?: string; title?: string; artist?: string; album?: string; year?: number; } /** Manual edits / an accepted match, sent to `PUT /tracks/{id}/metadata`. */ export interface MetadataEdit { title?: string; artistName?: string; albumTitle?: string; year?: number; genre?: string; trackNumber?: number; }