Skip to content

feat(server): add federation search page to discover and save recipes #469

Description

@dubadub

Summary

Add a "Discover" page to the CookCLI web UI that searches the Cooklang Federation over its HTTP API, previews a result, and saves the .cook file into the user's local recipe collection.

Today the server's search box only covers recipes already on disk. The federation is where recipes from other people's published feeds are indexed, and there is currently no path from "someone published a recipe" to "it's in my collection" other than copy-paste.

Federation API used

Public instance: https://recipes.cooklang.org (see cooklang/federation).

  • GET /api/search?q=<query>&locale=<lang> — Tantivy query syntax: bare terms, field-scoped (title:, tags:, ingredients:, difficulty:, servings:, total_time:), boolean AND/OR, negation -tags:dessert, ranges total_time:[0 TO 30], quoted multi-word values.
  • GET /api/recipes/:id — recipe details, including locale and locale_source.
  • GET /api/recipes/:id/download — the raw .cook file.

Proposed UI

  1. GET /discover — search form plus results list. Query and locale round-trip through URL params (/discover?q=...&locale=...) so results are linkable and back/forward work. Empty state explains what the federation is; no-results and API-error states are distinct.
  2. Result card — title, summary, tags, time/servings/difficulty badges, source feed, and a link to the recipe on recipes.cooklang.org. Reuse the existing .recipe-card / .metadata-pill / badge component classes.
  3. GET /discover/:id — preview a single federation recipe rendered with the normal recipe view (ingredients, steps, metadata), with a "Save to my recipes" form.
  4. POST /discover/:id/save — fetches /api/recipes/:id/download, writes it under base_path at a user-supplied (sanitised) filename, redirects to the local recipe page. Reuse the path sanitisation and CSRF same-origin check already in create_recipe (src/server/ui.rs); refuse to overwrite an existing file and surface that as a form error.

Nav entry sits next to Shopping List / Pantry, hidden in static_mode (no writes, and outbound calls don't belong in a statically exported site).

Configuration

  • --federation-url flag on cook server (and COOK_FEDERATION_URL env), defaulting to https://recipes.cooklang.org, so self-hosted federation instances work.
  • --no-federation (or the URL set empty) hides the page entirely, for offline/air-gapped use.
  • Reasonable client timeout on federation calls; a slow or down federation must degrade to an error banner, never hang a request.

Details / edge cases

  • All federation-supplied text is untrusted: escape it, and validate that the downloaded body parses as Cooklang before writing it to disk.
  • Cap the response size accepted from /download (the federation itself caps recipes at 1 MB).
  • Locale filter defaults to the UI language, with an "any language" option.
  • New user-facing strings go through the existing Fluent setup — add keys to locales/en-US/ (new discover.ftl), leaving other locales to fall back.

Out of scope

  • A cook search --federation CLI command (worth a separate ticket).
  • Publishing local recipes to the federation as a feed.
  • Adding federation recipes straight to the shopping list without saving.

Implementation notes

  • Routes: src/server/ui.rs (ui() router).
  • Templates: new templates/discover.html + templates/discover_recipe.html, data structs in src/server/templates.rs.
  • Nav: templates/base.html, gated the same way as shopping list / pantry.
  • HTTP client: reqwest is already in the dependency tree via cooklang-import.

Acceptance criteria

  • /discover?q=pasta returns federation results rendered in the CookCLI UI.
  • Field-scoped and boolean queries (tags:italian AND total_time:[0 TO 30]) reach the API unmangled.
  • A result can be previewed and saved to the local collection, then opens as a normal local recipe.
  • Federation unreachable → clear error banner, page still renders, no panic and no hung request.
  • Page and nav entry are absent in static_mode and when federation is disabled.
  • cargo fmt, cargo clippy, cargo test clean.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions