feat: local storage logic & endpoints
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
"""Domain entities and value objects — pure, framework-free."""
|
||||
|
||||
from app.domain.entities.storage import ObjectStat
|
||||
from app.domain.entities.track import Artist, Track
|
||||
from app.domain.entities.user import Credentials, User
|
||||
|
||||
__all__ = ["Credentials", "User"]
|
||||
__all__ = ["Artist", "Credentials", "ObjectStat", "Track", "User"]
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
"""Value objects for file storage."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class ObjectStat:
|
||||
size: int
|
||||
content_type: str | None
|
||||
@@ -0,0 +1,29 @@
|
||||
"""Track and Artist domain entities."""
|
||||
|
||||
import datetime as dt
|
||||
import uuid
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class Artist:
|
||||
id: uuid.UUID
|
||||
name: str
|
||||
created_at: dt.datetime
|
||||
updated_at: dt.datetime
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class Track:
|
||||
id: uuid.UUID
|
||||
title: str
|
||||
artist_id: uuid.UUID
|
||||
file_path: str
|
||||
file_format: str
|
||||
file_size: int
|
||||
source: str
|
||||
source_id: str
|
||||
duration_seconds: int | None
|
||||
metadata_status: str
|
||||
created_at: dt.datetime
|
||||
updated_at: dt.datetime
|
||||
@@ -61,3 +61,19 @@ class DependencyUnavailableError(DomainError):
|
||||
"""
|
||||
|
||||
code = "dependency_unavailable"
|
||||
|
||||
|
||||
class StorageError(DomainError):
|
||||
"""File storage operation failed."""
|
||||
|
||||
code = "storage_error"
|
||||
|
||||
|
||||
class RangeNotSatisfiableError(DomainError):
|
||||
"""Requested byte range cannot be satisfied."""
|
||||
|
||||
code = "range_not_satisfiable"
|
||||
|
||||
def __init__(self, total_size: int) -> None:
|
||||
super().__init__("Requested range is not satisfiable.")
|
||||
self.total_size = total_size
|
||||
|
||||
+40
-1
@@ -7,9 +7,13 @@ are bound to these ports at the composition root (``app.api.deps``).
|
||||
|
||||
import datetime as dt
|
||||
import uuid
|
||||
from collections.abc import AsyncIterator
|
||||
from contextlib import AbstractAsyncContextManager
|
||||
from pathlib import Path
|
||||
from typing import Protocol
|
||||
|
||||
from app.domain.entities import Credentials, User
|
||||
from app.domain.entities import Credentials, ObjectStat, User
|
||||
from app.domain.entities.track import Artist, Track
|
||||
from app.domain.tokens import IssuedToken, TokenClaims, TokenType
|
||||
|
||||
|
||||
@@ -56,3 +60,38 @@ class TokenService(Protocol):
|
||||
"""Verify signature + expiry and return claims. Raises
|
||||
:class:`~app.domain.errors.AuthenticationError` on any failure."""
|
||||
...
|
||||
|
||||
|
||||
class FileStorage(Protocol):
|
||||
async def save_file(self, key: str, src_path: Path) -> int: ...
|
||||
async def open_range(
|
||||
self, key: str, start: int, end: int | None
|
||||
) -> tuple[AsyncIterator[bytes], int]: ...
|
||||
async def stat(self, key: str) -> ObjectStat: ...
|
||||
async def exists(self, key: str) -> bool: ...
|
||||
async def delete(self, key: str) -> None: ...
|
||||
def as_local_path(self, key: str) -> AbstractAsyncContextManager[Path]: ...
|
||||
|
||||
|
||||
class ArtistRepository(Protocol):
|
||||
async def get_or_create(self, name: str) -> Artist: ...
|
||||
|
||||
|
||||
class TrackRepository(Protocol):
|
||||
async def get_by_id(self, track_id: uuid.UUID) -> Track | None: ...
|
||||
async def get_by_source(self, source: str, source_id: str) -> Track | None: ...
|
||||
async def add(
|
||||
self,
|
||||
*,
|
||||
id: uuid.UUID,
|
||||
title: str,
|
||||
artist_id: uuid.UUID,
|
||||
file_path: str,
|
||||
file_format: str,
|
||||
file_size: int,
|
||||
source: str,
|
||||
source_id: str,
|
||||
metadata_status: str,
|
||||
added_by: uuid.UUID | None,
|
||||
) -> Track: ...
|
||||
async def delete(self, track_id: uuid.UUID) -> None: ...
|
||||
|
||||
Reference in New Issue
Block a user