Fix /recipes redirect loop by merging duplicate routes

Removed duplicate /recipes route that was causing infinite redirect.
Now single route handles both HTML and JSON responses directly.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gilesb
2026-01-08 15:10:45 +00:00
parent 0c69c9c136
commit 5e6353d150

139
server.py
View File

@@ -1055,19 +1055,76 @@ async def list_recipes_api(request: Request, page: int = 1, limit: int = 20):
current_user = get_user_from_cookie(request)
all_recipes = list_all_recipes()
total = len(all_recipes)
# Pagination
if wants_html(request):
# HTML response
if not current_user:
return HTMLResponse(render_page(
"Recipes",
'<p class="text-gray-400 py-8 text-center"><a href="/login" class="text-blue-400 hover:text-blue-300">Login</a> to see recipes.</p>',
None,
active_tab="recipes"
))
# Filter to user's recipes
actor_id = f"@{current_user}@{L2_DOMAIN}"
user_recipes = [c for c in all_recipes if c.uploader in (current_user, actor_id)]
total = len(user_recipes)
if not user_recipes:
content = '''
<h2 class="text-xl font-semibold text-white mb-6">Recipes (0)</h2>
<p class="text-gray-400 py-8 text-center">No recipes yet. Upload a recipe YAML file to get started.</p>
'''
return HTMLResponse(render_page("Recipes", content, current_user, active_tab="recipes"))
html_parts = []
for recipe in user_recipes:
var_count = len(recipe.variable_inputs)
fixed_count = len(recipe.fixed_inputs)
input_info = []
if var_count:
input_info.append(f"{var_count} variable")
if fixed_count:
input_info.append(f"{fixed_count} fixed")
inputs_str = ", ".join(input_info) if input_info else "no inputs"
html_parts.append(f'''
<a href="/recipe/{recipe.recipe_id}" class="block">
<div class="bg-dark-700 rounded-lg p-4 hover:bg-dark-600 transition-colors">
<div class="flex flex-wrap items-center justify-between gap-3 mb-3">
<div class="flex items-center gap-3">
<span class="px-3 py-1 bg-purple-600 text-white text-sm font-medium rounded-full">{recipe.name}</span>
<span class="text-gray-400 text-xs">v{recipe.version}</span>
</div>
<span class="text-xs text-gray-400">{inputs_str}</span>
</div>
<div class="text-sm text-gray-400 mb-2">
{recipe.description or "No description"}
</div>
<div class="text-xs text-gray-500 font-mono truncate">
{recipe.recipe_id[:24]}...
</div>
</div>
</a>
''')
content = f'''
<h2 class="text-xl font-semibold text-white mb-6">Recipes ({total})</h2>
<div class="space-y-4">
{''.join(html_parts)}
</div>
'''
return HTMLResponse(render_page("Recipes", content, current_user, active_tab="recipes"))
# JSON response for APIs
total = len(all_recipes)
start = (page - 1) * limit
end = start + limit
recipes_page = all_recipes[start:end]
has_more = end < total
if wants_html(request):
# HTML response - redirect to /recipes page with proper UI
return RedirectResponse(f"/recipes?page={page}")
# JSON response for APIs
return {
"recipes": [c.model_dump() for c in recipes_page],
"pagination": {
@@ -1222,74 +1279,6 @@ def build_dag_from_recipe(yaml_config: dict, user_inputs: dict[str, str], recipe
# ============ Recipe UI Pages ============
@app.get("/recipes", response_class=HTMLResponse)
async def recipes_page(request: Request, page: int = 1):
"""Recipes list page (HTML)."""
current_user = get_user_from_cookie(request)
if not current_user:
return HTMLResponse(render_page(
"Recipes",
'<p class="text-gray-400 py-8 text-center"><a href="/login" class="text-blue-400 hover:text-blue-300">Login</a> to see recipes.</p>',
None,
active_tab="recipes"
))
all_recipes = list_all_recipes()
# Filter to user's configs
actor_id = f"@{current_user}@{L2_DOMAIN}"
user_recipes = [c for c in all_recipes if c.uploader in (current_user, actor_id)]
total = len(user_recipes)
if not user_recipes:
content = '''
<h2 class="text-xl font-semibold text-white mb-6">Recipes (0)</h2>
<p class="text-gray-400 py-8 text-center">No recipes yet. Upload a recipe YAML file to get started.</p>
'''
return HTMLResponse(render_page("Recipes", content, current_user, active_tab="recipes"))
html_parts = []
for recipe in user_recipes:
var_count = len(recipe.variable_inputs)
fixed_count = len(recipe.fixed_inputs)
input_info = []
if var_count:
input_info.append(f"{var_count} variable")
if fixed_count:
input_info.append(f"{fixed_count} fixed")
inputs_str = ", ".join(input_info) if input_info else "no inputs"
html_parts.append(f'''
<a href="/recipe/{recipe.recipe_id}" class="block">
<div class="bg-dark-700 rounded-lg p-4 hover:bg-dark-600 transition-colors">
<div class="flex flex-wrap items-center justify-between gap-3 mb-3">
<div class="flex items-center gap-3">
<span class="px-3 py-1 bg-purple-600 text-white text-sm font-medium rounded-full">{recipe.name}</span>
<span class="text-gray-400 text-xs">v{recipe.version}</span>
</div>
<span class="text-xs text-gray-400">{inputs_str}</span>
</div>
<div class="text-sm text-gray-400 mb-2">
{recipe.description or "No description"}
</div>
<div class="text-xs text-gray-500 font-mono truncate">
{recipe.recipe_id[:24]}...
</div>
</div>
</a>
''')
content = f'''
<h2 class="text-xl font-semibold text-white mb-6">Recipes ({total})</h2>
<div class="space-y-4">
{''.join(html_parts)}
</div>
'''
return HTMLResponse(render_page("Recipes", content, current_user, active_tab="recipes"))
@app.get("/recipe/{recipe_id}", response_class=HTMLResponse)
async def recipe_detail_page(recipe_id: str, request: Request):
"""Recipe detail page with run form."""