| from typing import List, Optional | |
| from fastapi import HTTPException | |
| from ..lib.arxiv import ( | |
| ArxivClient, | |
| generate_first_page_thumbnail, | |
| ) | |
| from ..schema.arxiv import ArxivEntry, ThumbnailResponse | |
| from ..core.logger import SingletonLogger | |
| from ..errors import ArxivRateLimitError, ArxivAPIError | |
| logger = SingletonLogger().get_logger() | |
| client = ArxivClient() | |
| async def search_arxiv( | |
| search_query: str, | |
| start: int = 0, | |
| max_results: int = 10, | |
| sort_by: Optional[str] = None, | |
| sort_order: Optional[str] = None, | |
| ) -> List[ArxivEntry]: | |
| try: | |
| results = client.search( | |
| search_query=search_query, | |
| start=start, | |
| max_results=max_results, | |
| sort_by=sort_by, | |
| sort_order=sort_order, | |
| ) | |
| return [ArxivEntry.model_validate(r) for r in results] | |
| except ArxivRateLimitError as e: | |
| logger.warning(f"Arxiv rate limit hit: {str(e)}") | |
| headers = {"Retry-After": str(e.retry_after)} if e.retry_after else {} | |
| raise HTTPException( | |
| status_code=429, | |
| detail=e.message, | |
| headers=headers, | |
| ) | |
| except ArxivAPIError as e: | |
| logger.error(f"Arxiv API error: {str(e)}") | |
| raise HTTPException( | |
| status_code=e.status_code or 502, | |
| detail=e.message, | |
| ) | |
| except Exception as e: | |
| logger.error(f"Arxiv search failed: {str(e)}") | |
| raise HTTPException(status_code=500, detail="Failed to search arXiv") | |
| async def feed_topics( | |
| topics: List[str], | |
| start: int = 0, | |
| max_results: int = 10, | |
| sort_by: Optional[str] = None, | |
| sort_order: Optional[str] = None, | |
| ) -> List[ArxivEntry]: | |
| try: | |
| results = client.feed_by_topics( | |
| topics=topics, | |
| start=start, | |
| max_results=max_results, | |
| sort_by=sort_by, | |
| sort_order=sort_order, | |
| ) | |
| return [ArxivEntry.model_validate(r) for r in results] | |
| except ArxivRateLimitError as e: | |
| logger.warning(f"Arxiv rate limit hit: {str(e)}") | |
| headers = {"Retry-After": str(e.retry_after)} if e.retry_after else {} | |
| raise HTTPException( | |
| status_code=429, | |
| detail=e.message, | |
| headers=headers, | |
| ) | |
| except ArxivAPIError as e: | |
| logger.error(f"Arxiv API error: {str(e)}") | |
| raise HTTPException( | |
| status_code=e.status_code or 502, | |
| detail=e.message, | |
| ) | |
| except Exception as e: | |
| logger.error(f"Arxiv topic feed failed: {str(e)}") | |
| raise HTTPException(status_code=500, detail="Failed to fetch arXiv feed") | |
| async def feed_topic_string( | |
| topics_csv: str, | |
| start: int = 0, | |
| max_results: int = 10, | |
| sort_by: Optional[str] = None, | |
| sort_order: Optional[str] = None, | |
| ) -> List[ArxivEntry]: | |
| try: | |
| results = client.feed_by_topic_string( | |
| topics_csv=topics_csv, | |
| start=start, | |
| max_results=max_results, | |
| sort_by=sort_by, | |
| sort_order=sort_order, | |
| ) | |
| return [ArxivEntry.model_validate(r) for r in results] | |
| except ArxivRateLimitError as e: | |
| logger.warning(f"Arxiv rate limit hit: {str(e)}") | |
| headers = {"Retry-After": str(e.retry_after)} if e.retry_after else {} | |
| raise HTTPException( | |
| status_code=429, | |
| detail=e.message, | |
| headers=headers, | |
| ) | |
| except ArxivAPIError as e: | |
| logger.error(f"Arxiv API error: {str(e)}") | |
| raise HTTPException( | |
| status_code=e.status_code or 502, | |
| detail=e.message, | |
| ) | |
| except Exception as e: | |
| logger.error(f"Arxiv topic-string feed failed: {str(e)}") | |
| raise HTTPException(status_code=500, detail="Failed to fetch arXiv feed") | |
| async def create_pdf_thumbnail( | |
| user_id: int, | |
| pdf_url: str, | |
| target_width: int = 1024, | |
| folder: str = "thumbnails", | |
| ) -> ThumbnailResponse: | |
| try: | |
| url = await generate_first_page_thumbnail( | |
| pdf_url=pdf_url, | |
| user_id=user_id, | |
| target_width=target_width, | |
| folder=folder, | |
| ) | |
| if not url: | |
| raise HTTPException(status_code=400, detail="Unable to generate thumbnail") | |
| return ThumbnailResponse(thumbnail_url=url) | |
| except HTTPException: | |
| raise | |
| except Exception as e: | |
| logger.error(f"Thumbnail generation failed: {str(e)}") | |
| raise HTTPException(status_code=500, detail="Failed to generate thumbnail") | |