47 lines
1.8 KiB
Python
47 lines
1.8 KiB
Python
"""Recipe suggestion endpoints."""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
|
|
from app.cloud_session import CloudUser, get_session
|
|
from app.db.session import get_store
|
|
from app.db.store import Store
|
|
from app.models.schemas.recipe import RecipeRequest, RecipeResult
|
|
from app.services.recipe.recipe_engine import RecipeEngine
|
|
from app.tiers import can_use
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.post("/suggest", response_model=RecipeResult)
|
|
async def suggest_recipes(
|
|
req: RecipeRequest,
|
|
session: CloudUser = Depends(get_session),
|
|
store: Store = Depends(get_store),
|
|
) -> RecipeResult:
|
|
# Inject session-authoritative tier/byok immediately — client-supplied values are ignored.
|
|
req = req.model_copy(update={"tier": session.tier, "has_byok": session.has_byok})
|
|
if req.level == 4 and not req.wildcard_confirmed:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail="Level 4 (Wildcard) requires wildcard_confirmed=true.",
|
|
)
|
|
if req.level in (3, 4) and not can_use("recipe_suggestions", req.tier, req.has_byok):
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail="LLM recipe levels require Paid tier or a configured LLM backend.",
|
|
)
|
|
if req.style_id and not can_use("style_picker", req.tier):
|
|
raise HTTPException(status_code=403, detail="Style picker requires Paid tier.")
|
|
engine = RecipeEngine(store)
|
|
return await asyncio.to_thread(engine.suggest, req)
|
|
|
|
|
|
@router.get("/{recipe_id}")
|
|
async def get_recipe(recipe_id: int, store: Store = Depends(get_store)) -> dict:
|
|
recipe = await asyncio.to_thread(store.get_recipe, recipe_id)
|
|
if not recipe:
|
|
raise HTTPException(status_code=404, detail="Recipe not found.")
|
|
return recipe
|