From 5e6353d150c0a8ed15ff036978aad795d5ba4418 Mon Sep 17 00:00:00 2001 From: gilesb Date: Thu, 8 Jan 2026 15:10:45 +0000 Subject: [PATCH] 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 --- server.py | 139 +++++++++++++++++++++++++----------------------------- 1 file changed, 64 insertions(+), 75 deletions(-) diff --git a/server.py b/server.py index c949a3e..65bc4c1 100644 --- a/server.py +++ b/server.py @@ -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", + '

Login to see recipes.

', + 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 = ''' +

Recipes (0)

+

No recipes yet. Upload a recipe YAML file to get started.

+ ''' + 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''' + +
+
+
+ {recipe.name} + v{recipe.version} +
+ {inputs_str} +
+
+ {recipe.description or "No description"} +
+
+ {recipe.recipe_id[:24]}... +
+
+
+ ''') + + content = f''' +

Recipes ({total})

+
+ {''.join(html_parts)} +
+ ''' + + 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", - '

Login to see recipes.

', - 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 = ''' -

Recipes (0)

-

No recipes yet. Upload a recipe YAML file to get started.

- ''' - 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''' - -
-
-
- {recipe.name} - v{recipe.version} -
- {inputs_str} -
-
- {recipe.description or "No description"} -
-
- {recipe.recipe_id[:24]}... -
-
-
- ''') - - content = f''' -

Recipes ({total})

-
- {''.join(html_parts)} -
- ''' - - 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."""