from __future__ import annotations
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query, Response, status
from app.auth import get_current_user_optional
from app.models import (
CreateItemDTO,
LifecycleItem,
LifecycleType,
UpdateItemDTO,
User,
)
from app.store import DatabaseStore, get_store
from app.telemetry import (
record_item_completed,
record_item_created,
record_item_creation_failure,
record_item_deleted,
)
router = APIRouter()
@router.get("", response_model=List[LifecycleItem], status_code=status.HTTP_200_OK)
async def list_items(
groupId: Optional[str] = Query(None, description="Filter by groupId ('null' for personal items)"),
type: Optional[LifecycleType] = Query(None, description="Filter by lifecycle type"),
searchQuery: Optional[str] = Query(None, description="Search query string"),
current_user: User = Depends(get_current_user_optional),
store: DatabaseStore = Depends(get_store),
) -> List[LifecycleItem]:
items = store.get_items(
user=current_user,
group_id=groupId,
lifecycle_type=type,
search_query=searchQuery,
)
return items
@router.post("", response_model=LifecycleItem, status_code=status.HTTP_201_CREATED)
async def create_item(
payload: CreateItemDTO,
current_user: User = Depends(get_current_user_optional),
store: DatabaseStore = Depends(get_store),
) -> LifecycleItem:
l_type = payload.lifecycleType.value if hasattr(payload.lifecycleType, "value") else str(payload.lifecycleType)
if not payload.name or not payload.name.strip():
record_item_creation_failure(l_type)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Item name cannot be empty",
)
try:
item = store.create_item(user=current_user, data=payload)
record_item_created(item.lifecycleType.value)
store.sync_metrics()
return item
except (ValueError, PermissionError) as exc:
record_item_creation_failure(l_type)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(exc),
)
except Exception as exc:
record_item_creation_failure(l_type)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Item creation failed: {exc}",
)
@router.get("/{id}", response_model=LifecycleItem, status_code=status.HTTP_200_OK)
async def get_item(
id: str,
current_user: User = Depends(get_current_user_optional),
store: DatabaseStore = Depends(get_store),
) -> LifecycleItem:
item = store.get_item_by_id(id)
if not item:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Item not found",
)
if not store.is_item_accessible(item, current_user):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not authorized to access this item",
)
return item
@router.patch("/{id}", response_model=LifecycleItem, status_code=status.HTTP_200_OK)
async def update_item(
id: str,
payload: UpdateItemDTO,
current_user: User = Depends(get_current_user_optional),
store: DatabaseStore = Depends(get_store),
) -> LifecycleItem:
item = store.get_item_by_id(id)
if not item:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Item not found",
)
try:
updated = store.update_item(user=current_user, item_id=id, data=payload)
if not updated:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Item not found")
store.sync_metrics()
return updated
except (ValueError, PermissionError) as exc:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc))
@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_item(
id: str,
current_user: User = Depends(get_current_user_optional),
store: DatabaseStore = Depends(get_store),
) -> Response:
item = store.get_item_by_id(id)
if not item:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Item not found",
)
try:
deleted = store.delete_item(user=current_user, item_id=id)
if not deleted:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Item not found")
record_item_deleted(item.lifecycleType.value)
store.sync_metrics()
return Response(status_code=status.HTTP_204_NO_CONTENT)
except PermissionError as exc:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc))
@router.post("/{id}/complete", response_model=LifecycleItem, status_code=status.HTTP_200_OK)
async def toggle_complete(
id: str,
current_user: User = Depends(get_current_user_optional),
store: DatabaseStore = Depends(get_store),
) -> LifecycleItem:
item = store.get_item_by_id(id)
if not item:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Item not found",
)
try:
updated = store.toggle_item_completion(id, user=current_user)
if not updated:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Item not found",
)
status_str = updated.status.value if hasattr(updated.status, "value") else str(updated.status)
if status_str == "completed":
record_item_completed(updated.lifecycleType.value)
store.sync_metrics()
return updated
except PermissionError as exc:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(exc))