feat(lyrics): LRCLIB provider + cached lyrics endpoints (§6.7)
GET /tracks/{id}/lyrics (get-or-fetch, caches found/not_found with a 7-day
miss TTL) and POST /tracks/{id}/lyrics/refetch (force). Hexagonal wiring:
LyricsProvider/LyricsRepository ports, LrclibHttpClient adapter (keyless,
degrades to not_found on error), SqlAlchemyLyricsRepository (upsert on the
existing lyrics table), LyricsService, LyricsOut schema, deps.py factory.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.application.auth_service import AuthService
|
||||
from app.application.download_service import DownloadService
|
||||
from app.application.lyrics_service import LyricsService
|
||||
from app.application.metadata_service import MetadataEnrichmentService
|
||||
from app.application.remote_library_service import RemoteLibraryService
|
||||
from app.application.streaming_service import StreamingService
|
||||
@@ -38,6 +39,7 @@ from app.infrastructure.db.repositories import (
|
||||
SqlAlchemyDownloadJobRepository,
|
||||
SqlAlchemyHistoryRepository,
|
||||
SqlAlchemyLikeRepository,
|
||||
SqlAlchemyLyricsRepository,
|
||||
SqlAlchemyPlaylistRepository,
|
||||
SqlAlchemyRefreshTokenRepository,
|
||||
SqlAlchemyTrackRepository,
|
||||
@@ -46,6 +48,7 @@ from app.infrastructure.db.repositories import (
|
||||
)
|
||||
from app.infrastructure.metadata.acoustid import AcoustIdHttpClient
|
||||
from app.infrastructure.metadata.fingerprint import FpcalcFingerprinter
|
||||
from app.infrastructure.metadata.lrclib import LrclibHttpClient
|
||||
from app.infrastructure.metadata.tags import MutagenTagReader
|
||||
from app.infrastructure.sources.registry import SourceRegistry, build_source_registry
|
||||
from app.infrastructure.storage.provider import get_file_storage
|
||||
@@ -175,6 +178,19 @@ def get_metadata_service(session: SessionDep, storage: FileStorageDep) -> Metada
|
||||
)
|
||||
|
||||
|
||||
def get_lyrics_service(session: SessionDep) -> LyricsService:
|
||||
"""Wires the LRCLIB lyrics provider + cache repo (plan §6.7). LRCLIB is
|
||||
keyless, so this is always available; failures degrade to ``not_found``."""
|
||||
settings = get_settings()
|
||||
return LyricsService(
|
||||
lyrics=SqlAlchemyLyricsRepository(session),
|
||||
tracks=SqlAlchemyTrackRepository(session),
|
||||
artists=SqlAlchemyArtistRepository(session),
|
||||
albums=SqlAlchemyAlbumRepository(session),
|
||||
provider=LrclibHttpClient(user_agent=settings.musicbrainz_user_agent),
|
||||
)
|
||||
|
||||
|
||||
def get_download_service(session: SessionDep, storage: FileStorageDep) -> DownloadService:
|
||||
return DownloadService(
|
||||
jobs=SqlAlchemyDownloadJobRepository(session),
|
||||
@@ -198,6 +214,7 @@ def get_remote_library_service(session: SessionDep) -> RemoteLibraryService:
|
||||
UploadServiceDep = Annotated[UploadService, Depends(get_upload_service)]
|
||||
StreamingServiceDep = Annotated[StreamingService, Depends(get_streaming_service)]
|
||||
MetadataServiceDep = Annotated[MetadataEnrichmentService, Depends(get_metadata_service)]
|
||||
LyricsServiceDep = Annotated[LyricsService, Depends(get_lyrics_service)]
|
||||
DownloadServiceDep = Annotated[DownloadService, Depends(get_download_service)]
|
||||
RemoteLibraryServiceDep = Annotated[RemoteLibraryService, Depends(get_remote_library_service)]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user