This commit is contained in:
Andre
2026-03-29 20:52:20 +02:00
commit e832c4e7ce
29 changed files with 3730 additions and 0 deletions
View File
+85
View File
@@ -0,0 +1,85 @@
"""GET /api/extract/stream – SSE endpoint."""
from __future__ import annotations
import json
import logging
from typing import AsyncGenerator
from fastapi import APIRouter
from fastapi.responses import StreamingResponse
from app.extractors.base import is_video_url
from app.extractors.website import extract_website
from app.extractors.video import extract_video
from app.models import Recipe, StatusEvent, ErrorEvent, ExtractResponse
from app.services.nutrition import enrich_nutrition
from app.services.tandoor_service import recipe_to_tandoor
from app.services.history_service import save_recipe as save_to_history
log = logging.getLogger(__name__)
router = APIRouter()
def _sse(event: str, data: str) -> str:
return f"event: {event}\ndata: {data}\n\n"
async def _stream(url: str) -> AsyncGenerator[str, None]:
try:
recipe: Recipe | None = None
pipeline = extract_video(url) if is_video_url(url) else extract_website(url)
async for item in pipeline:
if isinstance(item, StatusEvent):
yield _sse("status", item.model_dump_json())
elif isinstance(item, Recipe):
recipe = item
if recipe is None:
raise RuntimeError("Kein Rezept extrahiert.")
# Nutrition enrichment (may yield status events in future; here inline)
yield _sse("status", StatusEvent(
stage="nutrition", message="Berechne Nährwerte...", progress=90
).model_dump_json())
recipe = await enrich_nutrition(recipe)
tandoor_json = recipe_to_tandoor(recipe)
base = ExtractResponse(recipe=recipe, tandoor_json=tandoor_json)
history_id = None
try:
history_id = save_to_history(base.model_dump())
except Exception as db_err:
log.warning("History save failed: %s", db_err)
response = ExtractResponse(recipe=recipe, tandoor_json=tandoor_json, history_id=history_id)
yield _sse("result", response.model_dump_json())
except Exception as e:
log.exception("Extraction error for %s", url)
yield _sse("error", ErrorEvent(message=str(e)).model_dump_json())
@router.get("/extract/stream")
async def extract_stream(url: str):
if not url or not url.startswith(("http://", "https://")):
async def bad():
yield _sse("error", ErrorEvent(message="Ungültige URL.").model_dump_json())
return StreamingResponse(
bad(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no",
"Connection": "keep-alive",
},
)
return StreamingResponse(
_stream(url),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no",
"Connection": "keep-alive",
},
)
+22
View File
@@ -0,0 +1,22 @@
"""GET /api/history, DELETE /api/history/{id}"""
from fastapi import APIRouter, HTTPException
from app.services.history_service import get_all, delete_recipe
router = APIRouter()
@router.get("/history")
async def get_history():
try:
return get_all()
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/history/{history_id}")
async def delete_history_entry(history_id: int):
try:
delete_recipe(history_id)
return {"status": "ok"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
+21
View File
@@ -0,0 +1,21 @@
"""POST /api/import – import recipe to Tandoor."""
from fastapi import APIRouter, HTTPException
from app.models import ExtractResponse
from app.services.tandoor_service import import_to_tandoor
from app.services.history_service import mark_imported
router = APIRouter()
@router.post("/import")
async def import_recipe(data: ExtractResponse):
try:
result = await import_to_tandoor(data.recipe)
tandoor_recipe_id = result.get("id")
if data.history_id:
mark_imported(data.history_id, tandoor_recipe_id)
return {"status": "ok", "recipe_id": tandoor_recipe_id}
except RuntimeError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Import fehlgeschlagen: {e}")