mirror of
https://github.com/Andre-Bzk/Rezept0r.git
synced 2026-11-15 15:43:04 +01:00
Prototyp
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
"""Tandoor REST API client."""
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
|
||||
from app.config import get_settings
|
||||
from app.models import (
|
||||
Ingredient,
|
||||
Recipe,
|
||||
TandoorIngredient,
|
||||
TandoorRecipe,
|
||||
TandoorStep,
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def recipe_to_tandoor(recipe: Recipe) -> TandoorRecipe:
|
||||
"""Convert internal Recipe to Tandoor-compatible import format."""
|
||||
working_time = (recipe.prep_time_minutes or 0) + (recipe.cook_time_minutes or 0)
|
||||
|
||||
# All ingredients go into step[0] (Tandoor convention)
|
||||
tandoor_ingredients = [_to_tandoor_ingredient(ing) for ing in recipe.ingredients]
|
||||
|
||||
# Build steps: first step carries all ingredients, subsequent steps are plain text
|
||||
steps: list[TandoorStep] = []
|
||||
for i, step_text in enumerate(recipe.steps):
|
||||
step = TandoorStep(
|
||||
instruction=step_text,
|
||||
ingredients=tandoor_ingredients if i == 0 else [],
|
||||
time=0,
|
||||
)
|
||||
steps.append(step)
|
||||
|
||||
if not steps:
|
||||
# If no steps, create a placeholder with all ingredients
|
||||
steps = [TandoorStep(instruction="Zubereitung", ingredients=tandoor_ingredients)]
|
||||
|
||||
keywords = [{"label": tag, "name": tag} for tag in recipe.tags]
|
||||
|
||||
servings_text = "Portionen"
|
||||
if recipe.nutrition and recipe.nutrition.kcal_per_serving:
|
||||
kcal = round(recipe.nutrition.kcal_per_serving)
|
||||
servings_text = f"Portionen (ca. {kcal} kcal)"
|
||||
|
||||
return TandoorRecipe(
|
||||
name=recipe.title,
|
||||
description=recipe.description or "",
|
||||
servings=recipe.servings or 1,
|
||||
working_time=working_time,
|
||||
source_url=recipe.source_url,
|
||||
image=recipe.image_url,
|
||||
steps=steps,
|
||||
keywords=keywords,
|
||||
servings_text=servings_text,
|
||||
)
|
||||
|
||||
|
||||
def _to_tandoor_ingredient(ing: Ingredient) -> TandoorIngredient:
|
||||
amount = 0.0
|
||||
if ing.amount:
|
||||
try:
|
||||
from app.services.nutrition import _parse_float
|
||||
val = _parse_float(ing.amount)
|
||||
amount = val if val is not None else 0.0
|
||||
except Exception:
|
||||
amount = 0.0
|
||||
|
||||
unit = {"name": ing.unit} if ing.unit else None
|
||||
|
||||
return TandoorIngredient(
|
||||
food={"name": ing.name},
|
||||
amount=amount,
|
||||
unit=unit,
|
||||
note=ing.note or "",
|
||||
)
|
||||
|
||||
|
||||
async def import_to_tandoor(recipe: Recipe) -> dict:
|
||||
"""Import a recipe to Tandoor via REST API. Returns created recipe data."""
|
||||
settings = get_settings()
|
||||
|
||||
if not settings.tandoor_api_token:
|
||||
raise RuntimeError("TANDOOR_API_TOKEN nicht konfiguriert.")
|
||||
if not settings.tandoor_base_url:
|
||||
raise RuntimeError("TANDOOR_BASE_URL nicht konfiguriert.")
|
||||
|
||||
base_url = settings.tandoor_base_url.rstrip("/")
|
||||
headers = {
|
||||
"Authorization": f"Bearer {settings.tandoor_api_token}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
tandoor_recipe = recipe_to_tandoor(recipe)
|
||||
payload = tandoor_recipe.model_dump(exclude={"image"})
|
||||
|
||||
async with httpx.AsyncClient(timeout=30) as client:
|
||||
# POST recipe
|
||||
resp = await client.post(
|
||||
f"{base_url}/api/recipe/",
|
||||
json=payload,
|
||||
headers=headers,
|
||||
)
|
||||
if resp.status_code not in (200, 201):
|
||||
raise RuntimeError(
|
||||
f"Tandoor API Fehler {resp.status_code}: {resp.text[:300]}"
|
||||
)
|
||||
created = resp.json()
|
||||
recipe_id = created.get("id")
|
||||
|
||||
# Upload image if available
|
||||
if recipe.image_url and recipe_id:
|
||||
await _upload_image(client, base_url, recipe_id, recipe.image_url, settings.tandoor_api_token)
|
||||
|
||||
return created
|
||||
|
||||
|
||||
async def _upload_image(
|
||||
client: httpx.AsyncClient,
|
||||
base_url: str,
|
||||
recipe_id: int,
|
||||
image_url: str,
|
||||
token: str,
|
||||
) -> None:
|
||||
"""Download image from URL and upload to Tandoor."""
|
||||
try:
|
||||
img_resp = await client.get(image_url, follow_redirects=True, timeout=15)
|
||||
img_resp.raise_for_status()
|
||||
image_data = img_resp.content
|
||||
|
||||
# Determine content type
|
||||
content_type = img_resp.headers.get("content-type", "image/jpeg").split(";")[0]
|
||||
ext = {"image/jpeg": "jpg", "image/png": "png", "image/webp": "webp"}.get(content_type, "jpg")
|
||||
|
||||
files = {"image": (f"recipe.{ext}", image_data, content_type)}
|
||||
patch_resp = await client.patch(
|
||||
f"{base_url}/api/recipe/{recipe_id}/",
|
||||
files=files,
|
||||
headers={"Authorization": f"Bearer {token}"}, # NO Content-Type here
|
||||
)
|
||||
if patch_resp.status_code not in (200, 201):
|
||||
log.warning("Image upload failed: %s", patch_resp.text[:200])
|
||||
except Exception as e:
|
||||
log.warning("Could not upload image: %s", e)
|
||||
Reference in New Issue
Block a user