feat(storage): library + disk statistics endpoint (§A6)
Implement `GET /api/v1/storage`, replacing the stub. Returns aggregate library facts (track/artist/album counts, total footprint, playtime, per-format / per-source / metadata-status breakdowns, top genres) plus the real capacity of the backing volume. - domain: `LibraryStats`, `FormatBreakdown`, `DiskUsage` value objects - ports: `FileStorage.disk_usage()` (local = shutil.disk_usage walking up to the nearest existing ancestor; S3 returns None — no fixed disk) - repo: `TrackRepository.library_stats()` (single set of GROUP BYs) - tests: storage stats API (auth, empty library, upload counting) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+10
-2
@@ -17,7 +17,9 @@ from app.domain.entities import (
|
||||
AudioTags,
|
||||
CoverArt,
|
||||
Credentials,
|
||||
DiskUsage,
|
||||
Fingerprint,
|
||||
LibraryStats,
|
||||
Like,
|
||||
ObjectStat,
|
||||
PlayHistoryEntry,
|
||||
@@ -98,6 +100,10 @@ class FileStorage(Protocol):
|
||||
async def exists(self, key: str) -> bool: ...
|
||||
async def delete(self, key: str) -> None: ...
|
||||
def as_local_path(self, key: str) -> AbstractAsyncContextManager[Path]: ...
|
||||
async def disk_usage(self) -> DiskUsage | None:
|
||||
"""Capacity of the volume backing the store, or ``None`` when the
|
||||
backend has no addressable disk (e.g. an object store)."""
|
||||
...
|
||||
|
||||
|
||||
class ArtistRepository(Protocol):
|
||||
@@ -128,9 +134,11 @@ class TrackRepository(Protocol):
|
||||
added_by: uuid.UUID | None,
|
||||
) -> Track: ...
|
||||
async def delete(self, track_id: uuid.UUID) -> None: ...
|
||||
# genres must come before ``list`` — the method named ``list`` shadows the
|
||||
# builtin in later annotations (same pattern as AlbumRepository below).
|
||||
# genres / library_stats must come before ``list`` — the method named
|
||||
# ``list`` shadows the builtin in later annotations (same pattern as
|
||||
# AlbumRepository below).
|
||||
async def genres(self) -> list[tuple[str, int]]: ...
|
||||
async def library_stats(self) -> LibraryStats: ...
|
||||
async def list(
|
||||
self,
|
||||
*,
|
||||
|
||||
Reference in New Issue
Block a user