""" 책갈피 관리 API 라우터 """ from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, delete, and_ from sqlalchemy.orm import joinedload from typing import List, Optional from datetime import datetime from ...core.database import get_db from ...models.user import User from ...models.document import Document from ...models.bookmark import Bookmark from ..dependencies import get_current_active_user from pydantic import BaseModel class CreateBookmarkRequest(BaseModel): """책갈피 생성 요청""" document_id: str title: str description: Optional[str] = None page_number: Optional[int] = None scroll_position: int = 0 element_id: Optional[str] = None element_selector: Optional[str] = None class UpdateBookmarkRequest(BaseModel): """책갈피 업데이트 요청""" title: Optional[str] = None description: Optional[str] = None page_number: Optional[int] = None scroll_position: Optional[int] = None element_id: Optional[str] = None element_selector: Optional[str] = None class BookmarkResponse(BaseModel): """책갈피 응답""" id: str document_id: str title: str description: Optional[str] page_number: Optional[int] scroll_position: int element_id: Optional[str] element_selector: Optional[str] created_at: datetime updated_at: Optional[datetime] document_title: str class Config: from_attributes = True router = APIRouter() @router.post("/", response_model=BookmarkResponse) async def create_bookmark( bookmark_data: CreateBookmarkRequest, current_user: User = Depends(get_current_active_user), db: AsyncSession = Depends(get_db) ): """책갈피 생성""" # 문서 존재 및 권한 확인 result = await db.execute(select(Document).where(Document.id == bookmark_data.document_id)) document = result.scalar_one_or_none() if not document: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Document not found" ) # 문서 접근 권한 확인 if not document.is_public and document.uploaded_by != current_user.id and not current_user.is_admin: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not enough permissions to access this document" ) # 책갈피 생성 bookmark = Bookmark( user_id=current_user.id, document_id=bookmark_data.document_id, title=bookmark_data.title, description=bookmark_data.description, page_number=bookmark_data.page_number, scroll_position=bookmark_data.scroll_position, element_id=bookmark_data.element_id, element_selector=bookmark_data.element_selector ) db.add(bookmark) await db.commit() await db.refresh(bookmark) # 응답 데이터 생성 response_data = BookmarkResponse.from_orm(bookmark) response_data.document_title = document.title return response_data @router.get("/", response_model=List[BookmarkResponse]) async def list_user_bookmarks( skip: int = 0, limit: int = 50, document_id: Optional[str] = None, current_user: User = Depends(get_current_active_user), db: AsyncSession = Depends(get_db) ): """사용자의 모든 책갈피 조회""" query = ( select(Bookmark) .options(joinedload(Bookmark.document)) .where(Bookmark.user_id == current_user.id) ) if document_id: query = query.where(Bookmark.document_id == document_id) query = query.order_by(Bookmark.created_at.desc()).offset(skip).limit(limit) result = await db.execute(query) bookmarks = result.scalars().all() # 응답 데이터 변환 response_data = [] for bookmark in bookmarks: bookmark_data = BookmarkResponse.from_orm(bookmark) bookmark_data.document_title = bookmark.document.title response_data.append(bookmark_data) return response_data @router.get("/document/{document_id}", response_model=List[BookmarkResponse]) async def get_document_bookmarks( document_id: str, current_user: User = Depends(get_current_active_user), db: AsyncSession = Depends(get_db) ): """특정 문서의 책갈피 목록 조회""" # 문서 존재 및 권한 확인 result = await db.execute(select(Document).where(Document.id == document_id)) document = result.scalar_one_or_none() if not document: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Document not found" ) # 문서 접근 권한 확인 if not document.is_public and document.uploaded_by != current_user.id and not current_user.is_admin: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not enough permissions to access this document" ) # 사용자의 책갈피만 조회 result = await db.execute( select(Bookmark) .options(joinedload(Bookmark.document)) .where( and_( Bookmark.document_id == document_id, Bookmark.user_id == current_user.id ) ) .order_by(Bookmark.page_number, Bookmark.scroll_position) ) bookmarks = result.scalars().all() # 응답 데이터 변환 response_data = [] for bookmark in bookmarks: bookmark_data = BookmarkResponse.from_orm(bookmark) bookmark_data.document_title = bookmark.document.title response_data.append(bookmark_data) return response_data @router.get("/{bookmark_id}", response_model=BookmarkResponse) async def get_bookmark( bookmark_id: str, current_user: User = Depends(get_current_active_user), db: AsyncSession = Depends(get_db) ): """책갈피 상세 조회""" result = await db.execute( select(Bookmark) .options(joinedload(Bookmark.document)) .where(Bookmark.id == bookmark_id) ) bookmark = result.scalar_one_or_none() if not bookmark: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Bookmark not found" ) # 소유자 확인 if bookmark.user_id != current_user.id and not current_user.is_admin: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not enough permissions" ) response_data = BookmarkResponse.from_orm(bookmark) response_data.document_title = bookmark.document.title return response_data @router.put("/{bookmark_id}", response_model=BookmarkResponse) async def update_bookmark( bookmark_id: str, bookmark_data: UpdateBookmarkRequest, current_user: User = Depends(get_current_active_user), db: AsyncSession = Depends(get_db) ): """책갈피 업데이트""" result = await db.execute( select(Bookmark) .options(joinedload(Bookmark.document)) .where(Bookmark.id == bookmark_id) ) bookmark = result.scalar_one_or_none() if not bookmark: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Bookmark not found" ) # 소유자 확인 if bookmark.user_id != current_user.id and not current_user.is_admin: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not enough permissions" ) # 업데이트 if bookmark_data.title is not None: bookmark.title = bookmark_data.title if bookmark_data.description is not None: bookmark.description = bookmark_data.description if bookmark_data.page_number is not None: bookmark.page_number = bookmark_data.page_number if bookmark_data.scroll_position is not None: bookmark.scroll_position = bookmark_data.scroll_position if bookmark_data.element_id is not None: bookmark.element_id = bookmark_data.element_id if bookmark_data.element_selector is not None: bookmark.element_selector = bookmark_data.element_selector await db.commit() await db.refresh(bookmark) response_data = BookmarkResponse.from_orm(bookmark) response_data.document_title = bookmark.document.title return response_data @router.delete("/{bookmark_id}") async def delete_bookmark( bookmark_id: str, current_user: User = Depends(get_current_active_user), db: AsyncSession = Depends(get_db) ): """책갈피 삭제""" result = await db.execute(select(Bookmark).where(Bookmark.id == bookmark_id)) bookmark = result.scalar_one_or_none() if not bookmark: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Bookmark not found" ) # 소유자 확인 if bookmark.user_id != current_user.id and not current_user.is_admin: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not enough permissions" ) # 책갈피 삭제 await db.execute(delete(Bookmark).where(Bookmark.id == bookmark_id)) await db.commit() return {"message": "Bookmark deleted successfully"}