feat: Add filtering, searching, and sorting to extraction retrieval endpoints
This commit is contained in:
@@ -88,6 +88,34 @@ async def get_extraction(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/")
|
@router.get("/")
|
||||||
|
async def get_all_extractions(
|
||||||
|
extraction_service: Annotated[ExtractionService, Depends(get_extraction_service)],
|
||||||
|
search: Annotated[str | None, Query(description="Search in title, URL, or service")] = None,
|
||||||
|
sort_by: Annotated[str, Query(description="Sort by field")] = "created_at",
|
||||||
|
sort_order: Annotated[str, Query(description="Sort order (asc/desc)")] = "desc",
|
||||||
|
status_filter: Annotated[str | None, Query(description="Filter by status")] = None,
|
||||||
|
) -> dict[str, list[ExtractionInfo]]:
|
||||||
|
"""Get all extractions with optional filtering, search, and sorting."""
|
||||||
|
try:
|
||||||
|
extractions = await extraction_service.get_all_extractions(
|
||||||
|
search=search,
|
||||||
|
sort_by=sort_by,
|
||||||
|
sort_order=sort_order,
|
||||||
|
status_filter=status_filter,
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail=f"Failed to get extractions: {e!s}",
|
||||||
|
) from e
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"extractions": extractions,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/user")
|
||||||
async def get_user_extractions(
|
async def get_user_extractions(
|
||||||
current_user: Annotated[User, Depends(get_current_active_user_flexible)],
|
current_user: Annotated[User, Depends(get_current_active_user_flexible)],
|
||||||
extraction_service: Annotated[ExtractionService, Depends(get_extraction_service)],
|
extraction_service: Annotated[ExtractionService, Depends(get_extraction_service)],
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from sqlmodel import select
|
|||||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
|
|
||||||
from app.models.extraction import Extraction
|
from app.models.extraction import Extraction
|
||||||
|
from app.models.user import User
|
||||||
from app.repositories.base import BaseRepository
|
from app.repositories.base import BaseRepository
|
||||||
|
|
||||||
|
|
||||||
@@ -38,10 +39,11 @@ class ExtractionRepository(BaseRepository[Extraction]):
|
|||||||
)
|
)
|
||||||
return list(result.all())
|
return list(result.all())
|
||||||
|
|
||||||
async def get_pending_extractions(self) -> list[Extraction]:
|
async def get_pending_extractions(self) -> list[tuple[Extraction, User]]:
|
||||||
"""Get all pending extractions."""
|
"""Get all pending extractions."""
|
||||||
result = await self.session.exec(
|
result = await self.session.exec(
|
||||||
select(Extraction)
|
select(Extraction, User)
|
||||||
|
.join(User, Extraction.user_id == User.id)
|
||||||
.where(Extraction.status == "pending")
|
.where(Extraction.status == "pending")
|
||||||
.order_by(Extraction.created_at),
|
.order_by(Extraction.created_at),
|
||||||
)
|
)
|
||||||
@@ -63,9 +65,13 @@ class ExtractionRepository(BaseRepository[Extraction]):
|
|||||||
sort_by: str = "created_at",
|
sort_by: str = "created_at",
|
||||||
sort_order: str = "desc",
|
sort_order: str = "desc",
|
||||||
status_filter: str | None = None,
|
status_filter: str | None = None,
|
||||||
) -> list[Extraction]:
|
) -> list[tuple[Extraction, User]]:
|
||||||
"""Get extractions for a user with filtering, search, and sorting."""
|
"""Get extractions for a user with filtering, search, and sorting."""
|
||||||
query = select(Extraction).where(Extraction.user_id == user_id)
|
query = (
|
||||||
|
select(Extraction, User)
|
||||||
|
.join(User, Extraction.user_id == User.id)
|
||||||
|
.where(Extraction.user_id == user_id)
|
||||||
|
)
|
||||||
|
|
||||||
# Apply search filter
|
# Apply search filter
|
||||||
if search:
|
if search:
|
||||||
@@ -75,7 +81,42 @@ class ExtractionRepository(BaseRepository[Extraction]):
|
|||||||
Extraction.title.ilike(search_pattern),
|
Extraction.title.ilike(search_pattern),
|
||||||
Extraction.url.ilike(search_pattern),
|
Extraction.url.ilike(search_pattern),
|
||||||
Extraction.service.ilike(search_pattern),
|
Extraction.service.ilike(search_pattern),
|
||||||
)
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Apply status filter
|
||||||
|
if status_filter:
|
||||||
|
query = query.where(Extraction.status == status_filter)
|
||||||
|
|
||||||
|
# Apply sorting
|
||||||
|
sort_column = getattr(Extraction, sort_by, Extraction.created_at)
|
||||||
|
if sort_order.lower() == "asc":
|
||||||
|
query = query.order_by(asc(sort_column))
|
||||||
|
else:
|
||||||
|
query = query.order_by(desc(sort_column))
|
||||||
|
|
||||||
|
result = await self.session.exec(query)
|
||||||
|
return list(result.all())
|
||||||
|
|
||||||
|
async def get_all_extractions_filtered(
|
||||||
|
self,
|
||||||
|
search: str | None = None,
|
||||||
|
sort_by: str = "created_at",
|
||||||
|
sort_order: str = "desc",
|
||||||
|
status_filter: str | None = None,
|
||||||
|
) -> list[tuple[Extraction, User]]:
|
||||||
|
"""Get all extractions with filtering, search, and sorting."""
|
||||||
|
query = select(Extraction, User).join(User, Extraction.user_id == User.id)
|
||||||
|
|
||||||
|
# Apply search filter
|
||||||
|
if search:
|
||||||
|
search_pattern = f"%{search}%"
|
||||||
|
query = query.where(
|
||||||
|
or_(
|
||||||
|
Extraction.title.ilike(search_pattern),
|
||||||
|
Extraction.url.ilike(search_pattern),
|
||||||
|
Extraction.service.ilike(search_pattern),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Apply status filter
|
# Apply status filter
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from app.core.logging import get_logger
|
|||||||
from app.models.sound import Sound
|
from app.models.sound import Sound
|
||||||
from app.repositories.extraction import ExtractionRepository
|
from app.repositories.extraction import ExtractionRepository
|
||||||
from app.repositories.sound import SoundRepository
|
from app.repositories.sound import SoundRepository
|
||||||
|
from app.repositories.user import UserRepository
|
||||||
from app.services.playlist import PlaylistService
|
from app.services.playlist import PlaylistService
|
||||||
from app.services.sound_normalizer import SoundNormalizerService
|
from app.services.sound_normalizer import SoundNormalizerService
|
||||||
from app.utils.audio import get_audio_duration, get_file_hash, get_file_size
|
from app.utils.audio import get_audio_duration, get_file_hash, get_file_size
|
||||||
@@ -32,6 +33,7 @@ class ExtractionInfo(TypedDict):
|
|||||||
error: str | None
|
error: str | None
|
||||||
sound_id: int | None
|
sound_id: int | None
|
||||||
user_id: int
|
user_id: int
|
||||||
|
user_name: str | None
|
||||||
created_at: str
|
created_at: str
|
||||||
updated_at: str
|
updated_at: str
|
||||||
|
|
||||||
@@ -44,6 +46,7 @@ class ExtractionService:
|
|||||||
self.session = session
|
self.session = session
|
||||||
self.extraction_repo = ExtractionRepository(session)
|
self.extraction_repo = ExtractionRepository(session)
|
||||||
self.sound_repo = SoundRepository(session)
|
self.sound_repo = SoundRepository(session)
|
||||||
|
self.user_repo = UserRepository(session)
|
||||||
self.playlist_service = PlaylistService(session)
|
self.playlist_service = PlaylistService(session)
|
||||||
|
|
||||||
# Ensure required directories exist
|
# Ensure required directories exist
|
||||||
@@ -66,6 +69,12 @@ class ExtractionService:
|
|||||||
logger.info("Creating extraction for URL: %s (user: %d)", url, user_id)
|
logger.info("Creating extraction for URL: %s (user: %d)", url, user_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Get user information
|
||||||
|
user = await self.user_repo.get_by_id(user_id)
|
||||||
|
if not user:
|
||||||
|
msg = f"User {user_id} not found"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
# Create the extraction record without service detection for fast response
|
# Create the extraction record without service detection for fast response
|
||||||
extraction_data = {
|
extraction_data = {
|
||||||
"url": url,
|
"url": url,
|
||||||
@@ -92,6 +101,7 @@ class ExtractionService:
|
|||||||
"error": extraction.error,
|
"error": extraction.error,
|
||||||
"sound_id": extraction.sound_id,
|
"sound_id": extraction.sound_id,
|
||||||
"user_id": extraction.user_id,
|
"user_id": extraction.user_id,
|
||||||
|
"user_name": user.name,
|
||||||
"created_at": extraction.created_at.isoformat(),
|
"created_at": extraction.created_at.isoformat(),
|
||||||
"updated_at": extraction.updated_at.isoformat(),
|
"updated_at": extraction.updated_at.isoformat(),
|
||||||
}
|
}
|
||||||
@@ -554,7 +564,7 @@ class ExtractionService:
|
|||||||
status_filter: str | None = None,
|
status_filter: str | None = None,
|
||||||
) -> list[ExtractionInfo]:
|
) -> list[ExtractionInfo]:
|
||||||
"""Get all extractions for a user with filtering, search, and sorting."""
|
"""Get all extractions for a user with filtering, search, and sorting."""
|
||||||
extractions = await self.extraction_repo.get_user_extractions_filtered(
|
extraction_user_tuples = await self.extraction_repo.get_user_extractions_filtered(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
search=search,
|
search=search,
|
||||||
sort_by=sort_by,
|
sort_by=sort_by,
|
||||||
@@ -574,15 +584,27 @@ class ExtractionService:
|
|||||||
"error": extraction.error,
|
"error": extraction.error,
|
||||||
"sound_id": extraction.sound_id,
|
"sound_id": extraction.sound_id,
|
||||||
"user_id": extraction.user_id,
|
"user_id": extraction.user_id,
|
||||||
|
"user_name": user.name,
|
||||||
"created_at": extraction.created_at.isoformat(),
|
"created_at": extraction.created_at.isoformat(),
|
||||||
"updated_at": extraction.updated_at.isoformat(),
|
"updated_at": extraction.updated_at.isoformat(),
|
||||||
}
|
}
|
||||||
for extraction in extractions
|
for extraction, user in extraction_user_tuples
|
||||||
]
|
]
|
||||||
|
|
||||||
async def get_pending_extractions(self) -> list[ExtractionInfo]:
|
async def get_all_extractions(
|
||||||
"""Get all pending extractions."""
|
self,
|
||||||
extractions = await self.extraction_repo.get_pending_extractions()
|
search: str | None = None,
|
||||||
|
sort_by: str = "created_at",
|
||||||
|
sort_order: str = "desc",
|
||||||
|
status_filter: str | None = None,
|
||||||
|
) -> list[ExtractionInfo]:
|
||||||
|
"""Get all extractions with filtering, search, and sorting."""
|
||||||
|
extraction_user_tuples = await self.extraction_repo.get_all_extractions_filtered(
|
||||||
|
search=search,
|
||||||
|
sort_by=sort_by,
|
||||||
|
sort_order=sort_order,
|
||||||
|
status_filter=status_filter,
|
||||||
|
)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -596,8 +618,32 @@ class ExtractionService:
|
|||||||
"error": extraction.error,
|
"error": extraction.error,
|
||||||
"sound_id": extraction.sound_id,
|
"sound_id": extraction.sound_id,
|
||||||
"user_id": extraction.user_id,
|
"user_id": extraction.user_id,
|
||||||
|
"user_name": user.name,
|
||||||
"created_at": extraction.created_at.isoformat(),
|
"created_at": extraction.created_at.isoformat(),
|
||||||
"updated_at": extraction.updated_at.isoformat(),
|
"updated_at": extraction.updated_at.isoformat(),
|
||||||
}
|
}
|
||||||
for extraction in extractions
|
for extraction, user in extraction_user_tuples
|
||||||
|
]
|
||||||
|
|
||||||
|
async def get_pending_extractions(self) -> list[ExtractionInfo]:
|
||||||
|
"""Get all pending extractions."""
|
||||||
|
extraction_user_tuples = await self.extraction_repo.get_pending_extractions()
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"id": extraction.id
|
||||||
|
or 0, # Should never be None for existing extraction
|
||||||
|
"url": extraction.url,
|
||||||
|
"service": extraction.service,
|
||||||
|
"service_id": extraction.service_id,
|
||||||
|
"title": extraction.title,
|
||||||
|
"status": extraction.status,
|
||||||
|
"error": extraction.error,
|
||||||
|
"sound_id": extraction.sound_id,
|
||||||
|
"user_id": extraction.user_id,
|
||||||
|
"user_name": user.name,
|
||||||
|
"created_at": extraction.created_at.isoformat(),
|
||||||
|
"updated_at": extraction.updated_at.isoformat(),
|
||||||
|
}
|
||||||
|
for extraction, user in extraction_user_tuples
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user