Files
LMS/backend/app/routers/annotations.py
T

153 lines
4.5 KiB
Python

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