"""Review API — AP0.9d.""" from __future__ import annotations from datetime import datetime from typing import Literal, Optional from capabilities import require_capability from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field from services import reviews as review_service from tenant_context import TenantContext router = APIRouter(prefix="/api/reviews", tags=["reviews"]) class ReviewUpdateRequest(BaseModel): title: Optional[str] = Field(default=None, min_length=1, max_length=255) summary: Optional[str] = None status: Optional[Literal["planned", "completed", "skipped"]] = None due_at: Optional[str] = None clear_due_at: bool = False action_id: Optional[str] = None milestone_id: Optional[str] = None clear_action_id: bool = False clear_milestone_id: bool = False def _parse_due_at(value: Optional[str]) -> Optional[datetime]: if value is None: return None try: return datetime.fromisoformat(value.replace("Z", "+00:00")) except ValueError as exc: raise HTTPException(status_code=400, detail="Ungültiges due_at") from exc @router.get("/{review_id}") def get_review( review_id: str, ctx: TenantContext = Depends(require_capability("kairo.review.read")), ): item = review_service.get_review(tenant_id=ctx.tenant_id, review_id=review_id) if not item: raise HTTPException(status_code=404, detail="Review nicht gefunden") return item @router.patch("/{review_id}") def update_review( review_id: str, body: ReviewUpdateRequest, ctx: TenantContext = Depends(require_capability("kairo.review.manage")), ): due_at = _parse_due_at(body.due_at) if body.due_at is not None else None try: item = review_service.update_review( tenant_id=ctx.tenant_id, review_id=review_id, user_id=ctx.user_id, title=body.title, summary=body.summary, status=body.status, due_at=due_at, clear_due_at=body.clear_due_at, action_id=body.action_id, milestone_id=body.milestone_id, clear_action_id=body.clear_action_id, clear_milestone_id=body.clear_milestone_id, ) except ValueError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc if not item: raise HTTPException(status_code=404, detail="Review nicht gefunden") return item @router.delete("/{review_id}", status_code=204) def delete_review( review_id: str, ctx: TenantContext = Depends(require_capability("kairo.review.manage")), ): if not review_service.delete_review( tenant_id=ctx.tenant_id, review_id=review_id, user_id=ctx.user_id ): raise HTTPException(status_code=404, detail="Review nicht gefunden")