from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from ..database import get_db from ..dependencies import get_current_user from ..models import Annotation, PDF, User from ..schemas import AnnotationIn, AnnotationOut router = APIRouter(prefix="/annotations", tags=["annotations"]) def _verify_pdf_ownership(db: Session, pdf_id: int, user_id: int) -> PDF: pdf = db.get(PDF, pdf_id) if not pdf or pdf.user_id != user_id: raise HTTPException(status_code=404, detail="PDF not found.") return pdf def _any_pdf_or_404(db: Session, pdf_id: int) -> PDF: """Return PDF regardless of owner — any authenticated user may read annotations.""" pdf = db.get(PDF, pdf_id) if not pdf: raise HTTPException(status_code=404, detail="PDF not found.") return pdf # ── GET /api/annotations/{pdf_id}/{page_number} ─────────────────────────────── @router.get("/{pdf_id}/{page_number}", response_model=AnnotationOut) def get_annotation( pdf_id: int, page_number: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db), ): _any_pdf_or_404(db, pdf_id) ann = ( db.query(Annotation) .filter( Annotation.pdf_id == pdf_id, Annotation.user_id == current_user.id, Annotation.page_number == page_number, ) .first() ) # Return empty canvas if no annotation exists yet — not an error if ann is None: return AnnotationOut( id=0, pdf_id=pdf_id, page_number=page_number, canvas_data={}, updated_at=datetime.now(timezone.utc), ) return ann # ── GET /api/annotations/{pdf_id}/{page_number}/all ─────────────────────────── # Returns a merged canvas_data whose "objects" array contains annotations from # ALL users for the given page. Any authenticated user may call this. @router.get("/{pdf_id}/{page_number}/all", response_model=AnnotationOut) def get_all_annotations( pdf_id: int, page_number: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db), ): _any_pdf_or_404(db, pdf_id) rows = ( db.query(Annotation) .filter( Annotation.pdf_id == pdf_id, Annotation.page_number == page_number, ) .all() ) if not rows: return AnnotationOut( id=0, pdf_id=pdf_id, page_number=page_number, canvas_data={}, updated_at=datetime.now(timezone.utc), ) # Merge all objects arrays; use canvas metadata (background etc.) from latest record latest = max(rows, key=lambda r: r.updated_at) base: dict = dict(latest.canvas_data) if latest.canvas_data else {} merged_objects: list = [] for row in rows: for obj in (row.canvas_data or {}).get("objects", []): # Tag each object with its owner so the frontend can avoid re-saving # other users' objects under the current user's record (prevents duplicates). obj_copy = dict(obj) obj_copy["_owner_id"] = row.user_id merged_objects.append(obj_copy) base["objects"] = merged_objects return AnnotationOut( id=latest.id, pdf_id=pdf_id, page_number=page_number, canvas_data=base, updated_at=latest.updated_at, ) # ── PUT /api/annotations/{pdf_id}/{page_number} ─────────────────────────────── @router.put("/{pdf_id}/{page_number}", response_model=AnnotationOut) def upsert_annotation( pdf_id: int, page_number: int, body: AnnotationIn, current_user: User = Depends(get_current_user), db: Session = Depends(get_db), ): _any_pdf_or_404(db, pdf_id) ann = ( db.query(Annotation) .filter( Annotation.pdf_id == pdf_id, Annotation.user_id == current_user.id, Annotation.page_number == page_number, ) .first() ) if ann is None: ann = Annotation( pdf_id=pdf_id, user_id=current_user.id, page_number=page_number, canvas_data=body.canvas_data, ) db.add(ann) else: ann.canvas_data = body.canvas_data ann.updated_at = datetime.now(timezone.utc) db.commit() db.refresh(ann) return ann