diff --git a/app/api/v1/extractions.py b/app/api/v1/extractions.py index ee19415..3e991a1 100644 --- a/app/api/v1/extractions.py +++ b/app/api/v1/extractions.py @@ -88,6 +88,34 @@ async def get_extraction( @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( current_user: Annotated[User, Depends(get_current_active_user_flexible)], extraction_service: Annotated[ExtractionService, Depends(get_extraction_service)], diff --git a/app/repositories/extraction.py b/app/repositories/extraction.py index ff4167e..f6b4d4a 100644 --- a/app/repositories/extraction.py +++ b/app/repositories/extraction.py @@ -5,6 +5,7 @@ from sqlmodel import select from sqlmodel.ext.asyncio.session import AsyncSession from app.models.extraction import Extraction +from app.models.user import User from app.repositories.base import BaseRepository @@ -38,10 +39,11 @@ class ExtractionRepository(BaseRepository[Extraction]): ) 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.""" result = await self.session.exec( - select(Extraction) + select(Extraction, User) + .join(User, Extraction.user_id == User.id) .where(Extraction.status == "pending") .order_by(Extraction.created_at), ) @@ -63,9 +65,13 @@ class ExtractionRepository(BaseRepository[Extraction]): sort_by: str = "created_at", sort_order: str = "desc", status_filter: str | None = None, - ) -> list[Extraction]: + ) -> list[tuple[Extraction, User]]: """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 if search: @@ -75,7 +81,42 @@ class ExtractionRepository(BaseRepository[Extraction]): Extraction.title.ilike(search_pattern), Extraction.url.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 diff --git a/app/services/extraction.py b/app/services/extraction.py index 98003ba..457bd51 100644 --- a/app/services/extraction.py +++ b/app/services/extraction.py @@ -13,6 +13,7 @@ from app.core.logging import get_logger from app.models.sound import Sound from app.repositories.extraction import ExtractionRepository from app.repositories.sound import SoundRepository +from app.repositories.user import UserRepository from app.services.playlist import PlaylistService from app.services.sound_normalizer import SoundNormalizerService from app.utils.audio import get_audio_duration, get_file_hash, get_file_size @@ -32,6 +33,7 @@ class ExtractionInfo(TypedDict): error: str | None sound_id: int | None user_id: int + user_name: str | None created_at: str updated_at: str @@ -44,6 +46,7 @@ class ExtractionService: self.session = session self.extraction_repo = ExtractionRepository(session) self.sound_repo = SoundRepository(session) + self.user_repo = UserRepository(session) self.playlist_service = PlaylistService(session) # Ensure required directories exist @@ -66,6 +69,12 @@ class ExtractionService: logger.info("Creating extraction for URL: %s (user: %d)", url, user_id) 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 extraction_data = { "url": url, @@ -92,6 +101,7 @@ class ExtractionService: "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(), } @@ -554,7 +564,7 @@ class ExtractionService: status_filter: str | None = None, ) -> list[ExtractionInfo]: """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, search=search, sort_by=sort_by, @@ -574,15 +584,27 @@ class ExtractionService: "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 in extractions + for extraction, user in extraction_user_tuples ] - async def get_pending_extractions(self) -> list[ExtractionInfo]: - """Get all pending extractions.""" - extractions = await self.extraction_repo.get_pending_extractions() + async def get_all_extractions( + self, + 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 [ { @@ -596,8 +618,32 @@ class ExtractionService: "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 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 ]