cooklang / cooklang/cookcli

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

Open
#469 0 comments 0 reactions 0 assignees View on GitHub
area: server feature request
Dominant language
Rust
Stars
1.4k
Forks
109
Avg merge
1d 22h
Merged PRs (30d)
19

Description

## Summary

Add a "Discover" page to the CookCLI web UI that searches the [Cooklang Federation](https://recipes.cooklang.org) 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=&locale=` — 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.

Contributor guide

Open the contributing guide

Research direction

Start in src/server/ui.rs, especially the ui() router and existing create_recipe flow, then inspect src/server/templates.rs, templates/base.html, and the proposed discover templates. Trace the existing Fluent setup and run the relevant cargo tests before adding coverage. Done means the acceptance criteria pass, including disabled/static modes, error handling, safe saves, and cargo fmt, cargo clippy, and cargo test.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
full-stack
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.