| |
|
|
| import json |
| import logging |
| from typing import List |
|
|
| from fastapi import APIRouter, Depends, HTTPException, Query, status |
| from sqlalchemy.ext.asyncio import AsyncSession |
| from sqlalchemy import select |
|
|
| from app.api import deps |
| from app.models.user import User |
| from app.models.paper import Paper |
| from app.models.library import LibraryItem |
| from app.schemas.library import ( |
| LibraryCreate, |
| LibraryResponse, |
| LibraryUpdate, |
| ) |
|
|
| logger = logging.getLogger("rm_research.api.library") |
|
|
| router = APIRouter() |
|
|
| |
| |
| |
| @router.post( |
| "/", |
| response_model=LibraryResponse, |
| status_code=status.HTTP_201_CREATED, |
| summary="Save paper to library", |
| ) |
| async def save_paper( |
| item_in: LibraryCreate, |
| db: AsyncSession = Depends(deps.get_db), |
| current_user: User = Depends(deps.get_current_user), |
| ) -> LibraryResponse: |
| """Save a paper to the user's personal research library.""" |
|
|
| |
| paper_result = await db.execute( |
| select(Paper).where(Paper.id == item_in.paper_id) |
| ) |
| paper = paper_result.scalar_one_or_none() |
|
|
| if paper is None: |
| raise HTTPException( |
| status_code=status.HTTP_404_NOT_FOUND, |
| detail="Paper not found.", |
| ) |
|
|
| |
| existing = await db.execute( |
| select(LibraryItem.id) |
| .where(LibraryItem.user_id == current_user.id) |
| .where(LibraryItem.paper_id == item_in.paper_id) |
| ) |
|
|
| if existing.scalar_one_or_none(): |
| raise HTTPException( |
| status_code=status.HTTP_409_CONFLICT, |
| detail="Paper already exists in your library.", |
| ) |
|
|
| |
| library_item = LibraryItem( |
| user_id=current_user.id, |
| paper_id=paper.id, |
| tags=json.dumps(item_in.tags_list) if item_in.tags_list else "[]", |
| notes=item_in.notes, |
| ) |
|
|
| db.add(library_item) |
|
|
| try: |
| await db.commit() |
| await db.refresh(library_item) |
| return library_item |
|
|
| except Exception: |
| await db.rollback() |
| logger.exception( |
| "Failed saving library item | user=%s paper=%s", |
| current_user.id, |
| item_in.paper_id, |
| ) |
| raise HTTPException( |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| detail="Database error while saving paper.", |
| ) |
|
|
| |
| |
| |
| @router.get( |
| "/", |
| response_model=List[LibraryResponse], |
| summary="View saved library", |
| ) |
| async def get_library( |
| limit: int = Query(50, ge=1, le=100), |
| offset: int = Query(0, ge=0), |
| db: AsyncSession = Depends(deps.get_db), |
| current_user: User = Depends(deps.get_current_user), |
| ) -> List[LibraryResponse]: |
| """Retrieve saved papers from the user's library with pagination.""" |
|
|
| result = await db.execute( |
| select(LibraryItem) |
| .where(LibraryItem.user_id == current_user.id) |
| .order_by(LibraryItem.created_at.desc()) |
| .limit(limit) |
| .offset(offset) |
| ) |
|
|
| return result.scalars().all() |
|
|
| |
| |
| |
| @router.patch( |
| "/{library_id}", |
| response_model=LibraryResponse, |
| summary="Update library item", |
| ) |
| async def update_library_item( |
| library_id: int, |
| item_update: LibraryUpdate, |
| db: AsyncSession = Depends(deps.get_db), |
| current_user: User = Depends(deps.get_current_user), |
| ) -> LibraryResponse: |
| """Update notes or tags for a saved paper.""" |
|
|
| result = await db.execute( |
| select(LibraryItem) |
| .where(LibraryItem.id == library_id) |
| .where(LibraryItem.user_id == current_user.id) |
| ) |
|
|
| library_item = result.scalar_one_or_none() |
|
|
| if library_item is None: |
| raise HTTPException( |
| status_code=status.HTTP_404_NOT_FOUND, |
| detail="Library item not found.", |
| ) |
|
|
| if item_update.notes is not None: |
| library_item.notes = item_update.notes |
|
|
| if item_update.tags_list is not None: |
| |
| library_item.tags = json.dumps(item_update.tags_list) |
|
|
| try: |
| await db.commit() |
| await db.refresh(library_item) |
| return library_item |
|
|
| except Exception: |
| await db.rollback() |
| logger.exception("Failed updating library item | id=%s", library_id) |
| raise HTTPException( |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| detail="Database error while updating item.", |
| ) |
|
|
| |
| |
| |
| @router.delete( |
| "/{library_id}", |
| status_code=status.HTTP_204_NO_CONTENT, |
| summary="Remove paper from library", |
| ) |
| async def delete_library_item( |
| library_id: int, |
| db: AsyncSession = Depends(deps.get_db), |
| current_user: User = Depends(deps.get_current_user), |
| ): |
| """Delete a saved paper from the user's library.""" |
|
|
| result = await db.execute( |
| select(LibraryItem) |
| .where(LibraryItem.id == library_id) |
| .where(LibraryItem.user_id == current_user.id) |
| ) |
|
|
| library_item = result.scalar_one_or_none() |
|
|
| if library_item is None: |
| raise HTTPException( |
| status_code=status.HTTP_404_NOT_FOUND, |
| detail="Library item not found.", |
| ) |
|
|
| try: |
| await db.delete(library_item) |
| await db.commit() |
|
|
| except Exception: |
| await db.rollback() |
| logger.exception("Failed deleting library item | id=%s", library_id) |
| raise HTTPException( |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| detail="Database error while deleting item.", |
| ) |
|
|