feat: routes

This commit is contained in:
2026-06-06 13:00:01 +03:00
parent 83abcd02dd
commit 87b48e941e
22 changed files with 525 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
"""Subsonic-compatible API layer mounted at /rest."""
from fastapi import APIRouter
from app.api.rest.annotation import router as annotation_router
from app.api.rest.browsing import router as browsing_router
from app.api.rest.media import router as media_router
from app.api.rest.playlists import router as playlists_router
from app.api.rest.search import router as search_router
from app.api.rest.system import router as system_router
subsonic_router = APIRouter(tags=["subsonic"])
subsonic_router.include_router(system_router)
subsonic_router.include_router(browsing_router)
subsonic_router.include_router(search_router)
subsonic_router.include_router(playlists_router)
subsonic_router.include_router(media_router)
subsonic_router.include_router(annotation_router)
__all__ = ["subsonic_router"]
+23
View File
@@ -0,0 +1,23 @@
"""Subsonic annotation endpoints: star, rating, scrobble."""
from typing import Any
from fastapi import APIRouter
router = APIRouter()
@router.get("/star")
async def star() -> Any: ...
@router.get("/unstar")
async def unstar() -> Any: ...
@router.get("/setRating")
async def set_rating() -> Any: ...
@router.get("/scrobble")
async def scrobble() -> Any: ...
+47
View File
@@ -0,0 +1,47 @@
"""Subsonic browsing endpoints."""
from typing import Any
from fastapi import APIRouter
router = APIRouter()
@router.get("/getMusicFolders")
async def get_music_folders() -> Any: ...
@router.get("/getIndexes")
async def get_indexes() -> Any: ...
@router.get("/getMusicDirectory")
async def get_music_directory() -> Any: ...
@router.get("/getArtists")
async def get_artists() -> Any: ...
@router.get("/getArtist")
async def get_artist() -> Any: ...
@router.get("/getAlbum")
async def get_album() -> Any: ...
@router.get("/getAlbumList")
async def get_album_list() -> Any: ...
@router.get("/getAlbumList2")
async def get_album_list2() -> Any: ...
@router.get("/getSong")
async def get_song() -> Any: ...
@router.get("/getGenres")
async def get_genres() -> Any: ...
+19
View File
@@ -0,0 +1,19 @@
"""Subsonic media endpoints: stream, download, cover art."""
from typing import Any
from fastapi import APIRouter
router = APIRouter()
@router.get("/stream")
async def stream() -> Any: ...
@router.get("/download")
async def download() -> Any: ...
@router.get("/getCoverArt")
async def get_cover_art() -> Any: ...
+27
View File
@@ -0,0 +1,27 @@
"""Subsonic playlist endpoints."""
from typing import Any
from fastapi import APIRouter
router = APIRouter()
@router.get("/getPlaylists")
async def get_playlists() -> Any: ...
@router.get("/getPlaylist")
async def get_playlist() -> Any: ...
@router.get("/createPlaylist")
async def create_playlist() -> Any: ...
@router.get("/updatePlaylist")
async def update_playlist() -> Any: ...
@router.get("/deletePlaylist")
async def delete_playlist() -> Any: ...
+11
View File
@@ -0,0 +1,11 @@
"""Subsonic search endpoints."""
from typing import Any
from fastapi import APIRouter
router = APIRouter()
@router.get("/search3")
async def search3() -> Any: ...
+15
View File
@@ -0,0 +1,15 @@
"""Subsonic system endpoints: ping and license."""
from typing import Any
from fastapi import APIRouter
router = APIRouter()
@router.get("/ping")
async def ping() -> Any: ...
@router.get("/getLicense")
async def get_license() -> Any: ...
+24
View File
@@ -0,0 +1,24 @@
"""Album endpoints."""
import uuid
from typing import Any
from fastapi import APIRouter
router = APIRouter(prefix="/albums", tags=["albums"])
@router.get("")
async def list_albums() -> Any: ...
@router.get("/{album_id}")
async def get_album(album_id: uuid.UUID) -> Any: ...
@router.get("/{album_id}/tracks")
async def get_album_tracks(album_id: uuid.UUID) -> Any: ...
@router.get("/{album_id}/cover")
async def get_album_cover(album_id: uuid.UUID) -> Any: ...
+28
View File
@@ -0,0 +1,28 @@
"""Artist endpoints."""
import uuid
from typing import Any
from fastapi import APIRouter
router = APIRouter(prefix="/artists", tags=["artists"])
@router.get("")
async def list_artists() -> Any: ...
@router.get("/{artist_id}")
async def get_artist(artist_id: uuid.UUID) -> Any: ...
@router.get("/{artist_id}/albums")
async def get_artist_albums(artist_id: uuid.UUID) -> Any: ...
@router.get("/{artist_id}/tracks")
async def get_artist_tracks(artist_id: uuid.UUID) -> Any: ...
@router.get("/{artist_id}/similar")
async def get_similar_artists(artist_id: uuid.UUID) -> Any: ...
+36
View File
@@ -0,0 +1,36 @@
"""Download job endpoints. Heavy work is dispatched to arq workers."""
import uuid
from typing import Any
from fastapi import APIRouter
router = APIRouter(prefix="/downloads", tags=["downloads"])
@router.get("")
async def list_downloads() -> Any: ...
@router.post("")
async def create_download() -> Any: ...
@router.get("/{job_id}")
async def get_download(job_id: uuid.UUID) -> Any: ...
@router.delete("/{job_id}")
async def cancel_download(job_id: uuid.UUID) -> Any: ...
@router.post("/{job_id}/retry")
async def retry_download(job_id: uuid.UUID) -> Any: ...
@router.post("/pause")
async def pause_downloads() -> Any: ...
@router.post("/resume")
async def resume_downloads() -> Any: ...
+15
View File
@@ -0,0 +1,15 @@
"""Playback history endpoints."""
from typing import Any
from fastapi import APIRouter
router = APIRouter(prefix="/history", tags=["history"])
@router.get("")
async def get_history() -> Any: ...
@router.post("")
async def record_history() -> Any: ...
+19
View File
@@ -0,0 +1,19 @@
"""Like endpoints. Likes are an append-only event-log — never updated in place."""
from typing import Any
from fastapi import APIRouter
router = APIRouter(prefix="/likes", tags=["likes"])
@router.get("")
async def get_likes() -> Any: ...
@router.post("")
async def add_like() -> Any: ...
@router.get("/state")
async def get_likes_state() -> Any: ...
+48
View File
@@ -0,0 +1,48 @@
"""Playlist endpoints."""
import uuid
from typing import Any
from fastapi import APIRouter
router = APIRouter(prefix="/playlists", tags=["playlists"])
@router.get("")
async def list_playlists() -> Any: ...
@router.post("")
async def create_playlist() -> Any: ...
@router.get("/{playlist_id}")
async def get_playlist(playlist_id: uuid.UUID) -> Any: ...
@router.patch("/{playlist_id}")
async def update_playlist(playlist_id: uuid.UUID) -> Any: ...
@router.delete("/{playlist_id}")
async def delete_playlist(playlist_id: uuid.UUID) -> Any: ...
@router.get("/{playlist_id}/tracks")
async def get_playlist_tracks(playlist_id: uuid.UUID) -> Any: ...
@router.post("/{playlist_id}/tracks")
async def add_playlist_tracks(playlist_id: uuid.UUID) -> Any: ...
@router.delete("/{playlist_id}/tracks/{track_id}")
async def remove_playlist_track(playlist_id: uuid.UUID, track_id: uuid.UUID) -> Any: ...
@router.put("/{playlist_id}/tracks/reorder")
async def reorder_playlist_tracks(playlist_id: uuid.UUID) -> Any: ...
@router.get("/{playlist_id}/cover")
async def get_playlist_cover(playlist_id: uuid.UUID) -> Any: ...
+15
View File
@@ -0,0 +1,15 @@
"""Radio / continuous-mix endpoints. Degrades gracefully when ML service is down."""
from typing import Any
from fastapi import APIRouter
router = APIRouter(prefix="/radio", tags=["radio"])
@router.post("")
async def start_radio() -> Any: ...
@router.post("/next")
async def next_radio_track() -> Any: ...
+15
View File
@@ -0,0 +1,15 @@
"""Search endpoints: global and library-scoped."""
from typing import Any
from fastapi import APIRouter
router = APIRouter(prefix="/search", tags=["search"])
@router.get("")
async def search() -> Any: ...
@router.get("/library")
async def search_library() -> Any: ...
+19
View File
@@ -0,0 +1,19 @@
"""External source endpoints (yt-dlp etc.)."""
from typing import Any
from fastapi import APIRouter
router = APIRouter(prefix="/sources", tags=["sources"])
@router.get("")
async def list_sources() -> Any: ...
@router.get("/{source}/search")
async def search_source(source: str) -> Any: ...
@router.get("/{source}/health")
async def source_health(source: str) -> Any: ...
+27
View File
@@ -0,0 +1,27 @@
"""Storage analysis and cleanup endpoints."""
from typing import Any
from fastapi import APIRouter
router = APIRouter(prefix="/storage", tags=["storage"])
@router.get("")
async def get_storage_stats() -> Any: ...
@router.get("/duplicates")
async def get_duplicates() -> Any: ...
@router.get("/broken")
async def get_broken_files() -> Any: ...
@router.get("/missing-metadata")
async def get_missing_metadata() -> Any: ...
@router.post("/cleanup")
async def run_cleanup() -> Any: ...
+20
View File
@@ -0,0 +1,20 @@
"""Audio streaming endpoints: direct stream and HLS."""
import uuid
from typing import Any
from fastapi import APIRouter
router = APIRouter(prefix="/stream", tags=["streaming"])
@router.get("/{track_id}")
async def stream_track(track_id: uuid.UUID) -> Any: ...
@router.get("/{track_id}/hls/playlist.m3u8")
async def hls_playlist(track_id: uuid.UUID) -> Any: ...
@router.get("/{track_id}/hls/{segment}")
async def hls_segment(track_id: uuid.UUID, segment: str) -> Any: ...
+15
View File
@@ -0,0 +1,15 @@
"""Client sync endpoints (offline-first event log)."""
from typing import Any
from fastapi import APIRouter
router = APIRouter(prefix="/sync", tags=["sync"])
@router.get("/changes")
async def get_changes() -> Any: ...
@router.post("/push")
async def push_changes() -> Any: ...
+48
View File
@@ -0,0 +1,48 @@
"""Track endpoints (library CRUD, similarity, optimization, cover, metadata, streaming)."""
import uuid
from typing import Any
from fastapi import APIRouter
router = APIRouter(prefix="/tracks", tags=["tracks"])
@router.get("")
async def list_tracks() -> Any: ...
@router.get("/{track_id}")
async def get_track(track_id: uuid.UUID) -> Any: ...
@router.patch("/{track_id}")
async def update_track(track_id: uuid.UUID) -> Any: ...
@router.delete("/{track_id}")
async def delete_track(track_id: uuid.UUID) -> Any: ...
@router.get("/{track_id}/similar")
async def get_similar_tracks(track_id: uuid.UUID) -> Any: ...
@router.post("/{track_id}/optimize")
async def optimize_track(track_id: uuid.UUID) -> Any: ...
@router.get("/{track_id}/cover")
async def get_track_cover(track_id: uuid.UUID) -> Any: ...
@router.post("/{track_id}/metadata/enrich")
async def enrich_metadata(track_id: uuid.UUID) -> Any: ...
@router.get("/{track_id}/metadata/matches")
async def get_metadata_matches(track_id: uuid.UUID) -> Any: ...
@router.put("/{track_id}/metadata")
async def set_metadata(track_id: uuid.UUID) -> Any: ...
+11
View File
@@ -0,0 +1,11 @@
"""Local file upload endpoint."""
from typing import Any
from fastapi import APIRouter
router = APIRouter(prefix="/upload", tags=["upload"])
@router.post("")
async def upload_file() -> Any: ...
+23
View File
@@ -0,0 +1,23 @@
"""User settings endpoints, including scrobbling configuration."""
from typing import Any
from fastapi import APIRouter
router = APIRouter(prefix="/settings", tags=["settings"])
@router.get("")
async def get_settings() -> Any: ...
@router.patch("")
async def update_settings() -> Any: ...
@router.get("/scrobbling")
async def get_scrobbling_settings() -> Any: ...
@router.put("/scrobbling")
async def set_scrobbling_settings() -> Any: ...