"""Project API — AP1.5.""" from __future__ import annotations from datetime import date from typing import Literal, Optional from capabilities import require_capability from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field from services import projects as project_service from tenant_context import TenantContext initiative_router = APIRouter(prefix="/api/initiatives", tags=["projects"]) items_router = APIRouter(prefix="/api/projects", tags=["projects"]) class ProjectCreateRequest(BaseModel): title: str = Field(min_length=1, max_length=255) description: str = "" status: Literal["active", "paused", "completed", "archived"] = "active" roadmap_item_id: Optional[str] = None sort_order: int = 0 target_date: Optional[date] = None class ProjectUpdateRequest(BaseModel): title: Optional[str] = Field(default=None, min_length=1, max_length=255) description: Optional[str] = None status: Optional[Literal["active", "paused", "completed", "archived"]] = None roadmap_item_id: Optional[str] = None clear_roadmap_item: bool = False sort_order: Optional[int] = None target_date: Optional[date] = None clear_target_date: bool = False @initiative_router.get("/{initiative_id}/projects") def list_initiative_projects( initiative_id: str, ctx: TenantContext = Depends(require_capability("kairo.project.read")), ): try: return project_service.list_projects_for_initiative( tenant_id=ctx.tenant_id, initiative_id=initiative_id ) except ValueError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc @initiative_router.post("/{initiative_id}/projects", status_code=201) def create_initiative_project( initiative_id: str, body: ProjectCreateRequest, ctx: TenantContext = Depends(require_capability("kairo.project.manage")), ): try: return project_service.create_project( tenant_id=ctx.tenant_id, initiative_id=initiative_id, user_id=ctx.user_id, title=body.title, description=body.description, status=body.status, roadmap_item_id=body.roadmap_item_id, sort_order=body.sort_order, target_date=body.target_date, ) except ValueError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc @items_router.get("/{project_id}") def get_project( project_id: str, ctx: TenantContext = Depends(require_capability("kairo.project.read")), ): item = project_service.get_project(tenant_id=ctx.tenant_id, project_id=project_id) if not item: raise HTTPException(status_code=404, detail="Projekt nicht gefunden") return item @items_router.patch("/{project_id}") def update_project( project_id: str, body: ProjectUpdateRequest, ctx: TenantContext = Depends(require_capability("kairo.project.manage")), ): try: item = project_service.update_project( tenant_id=ctx.tenant_id, project_id=project_id, user_id=ctx.user_id, title=body.title, description=body.description, status=body.status, roadmap_item_id=body.roadmap_item_id, clear_roadmap_item=body.clear_roadmap_item, sort_order=body.sort_order, target_date=body.target_date, clear_target_date=body.clear_target_date, ) except ValueError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc if not item: raise HTTPException(status_code=404, detail="Projekt nicht gefunden") return item @items_router.delete("/{project_id}", status_code=204) def delete_project( project_id: str, ctx: TenantContext = Depends(require_capability("kairo.project.manage")), ): if not project_service.delete_project( tenant_id=ctx.tenant_id, project_id=project_id, user_id=ctx.user_id ): raise HTTPException(status_code=404, detail="Projekt nicht gefunden")