"""Note API endpoints (R-2: polymorphic parent validation).""" from __future__ import annotations from typing import Optional from fastapi import APIRouter, Depends, HTTPException, Query, Response, status from sqlalchemy.ext.asyncio import AsyncSession from app.core.db import get_db from app.core.deps import get_current_user from app.models.note import NoteParentType from app.models.user import User from app.schemas.note import NoteCreate, NoteOut, NoteUpdate from app.services.note_service import ( InvalidParent, create_note as svc_create_note, get_note as svc_get_note, list_notes as svc_list_notes, soft_delete_note as svc_soft_delete_note, update_note as svc_update_note, ) router = APIRouter(prefix="/notes", tags=["notes"]) @router.post( "/", response_model=NoteOut, status_code=status.HTTP_201_CREATED, summary="Create a new note (R-2: validates parent exists)", ) async def create_note( payload: NoteCreate, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> NoteOut: try: note = await svc_create_note( db, payload, org_id=current_user.org_id, author_id=current_user.id ) except InvalidParent as e: raise HTTPException(status_code=404, detail=str(e)) from e return NoteOut.model_validate(note) @router.get( "/", response_model=list[NoteOut], summary="List notes (filterable by parent_type + parent_id)", ) async def list_notes( skip: int = Query(0, ge=0), limit: int = Query(20, ge=1, le=100), parent_type: Optional[NoteParentType] = None, parent_id: Optional[int] = None, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> list[NoteOut]: notes = await svc_list_notes( db, org_id=current_user.org_id, skip=skip, limit=limit, parent_type=parent_type.value if parent_type else None, parent_id=parent_id, ) return [NoteOut.model_validate(n) for n in notes] @router.get( "/{note_id}", response_model=NoteOut, summary="Get one note", ) async def get_note( note_id: int, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> NoteOut: note = await svc_get_note(db, note_id, org_id=current_user.org_id) if note is None: raise HTTPException(status_code=404, detail="Note not found") return NoteOut.model_validate(note) @router.patch( "/{note_id}", response_model=NoteOut, summary="Update a note's body", ) async def update_note( note_id: int, payload: NoteUpdate, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> NoteOut: note = await svc_get_note(db, note_id, org_id=current_user.org_id) if note is None: raise HTTPException(status_code=404, detail="Note not found") updated = await svc_update_note(db, note, payload) return NoteOut.model_validate(updated) @router.delete( "/{note_id}", status_code=status.HTTP_204_NO_CONTENT, response_class=Response, summary="Soft-delete a note", ) async def delete_note( note_id: int, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> Response: note = await svc_get_note(db, note_id, org_id=current_user.org_id) if note is None: raise HTTPException(status_code=404, detail="Note not found") await svc_soft_delete_note(db, note) return Response(status_code=status.HTTP_204_NO_CONTENT) __all__ = ["router"]