A federated search system for Cooklang recipes that allows decentralized publishing and centralized discovery through RSS/Atom feeds.
- 🔍 Unified search with powerful query syntax powered by Tantivy
- 📡 RSS/Atom feed crawler with automatic updates
- 🏷️ Advanced filtering by tags, ingredients, time, difficulty, and more
- 🌐 Web UI for browsing and searching recipes
- 💻 CLI tools for searching, downloading, and publishing recipes
- 🔄 Background scheduler for automated feed crawling
- 🛡️ Rate limiting to protect API endpoints from abuse
- 🐳 Docker support for easy deployment
# Clone the repository
git clone <repository-url>
cd federation
# Start the server
docker-compose up -d
# Access the web UI
open http://localhost:3000- Rust 1.75 or later
- SQLite 3
# Clone the repository
git clone <repository-url>
cd federation
# Copy environment variables
cp .env.example .env
# Download Tailwind CSS CLI
./scripts/download-tailwind.sh
# Start development server (with Tailwind watch mode)
./scripts/dev.shThe server will be available at http://localhost:3001
# Build Tailwind CSS once
./tailwindcss -i ./styles/input.css -o ./src/web/static/css/output.css
# Run database migrations
cargo run -- migrate
# Start the server
cargo run -- serveThe federation search supports powerful query syntax powered by Tantivy's QueryParser:
# Search all fields
breakfast
# Search specific field
tags:breakfast
title:pasta
ingredients:tomato
difficulty:easy# Boolean operators
pasta AND tags:italian
breakfast OR brunch
# Exclusion
chocolate -tags:dessert
# Range queries
total_time:[0 TO 30] # 30 minutes or less
servings:[4 TO 8] # Serves 4-8 people
# Complex combinations
chocolate tags:dessert difficulty:easy
pasta AND tags:italian AND total_time:[0 TO 30]Use quotes for multi-word field values:
tags:"quick breakfast"
title:"chocolate chip cookies"title- Recipe titlesummary- Recipe descriptioninstructions- Cooking instructionsingredients- Ingredient listtags- Recipe tagsdifficulty- Difficulty level (easy, medium, hard)servings- Number of servingstotal_time- Total cooking time in minutesfile_path- Source file path (for GitHub recipes)
# Basic search
cargo run -- search "chocolate cookies"
# Field-specific search
cargo run -- search "tags:breakfast"
# Complex query
cargo run -- search "pasta AND tags:italian AND total_time:[0 TO 30]"cargo run -- download 123 --output ./recipes# Generate an Atom feed from .cook files
cargo run -- publish --input ./my-recipes --output feed.xmlGET /health- Health checkGET /ready- Readiness checkGET /api/stats- System statistics
GET /api/search?q=<query>- Search recipes with unified query syntax- Examples:
/api/search?q=breakfast- Basic search/api/search?q=tags:breakfast- Field-specific search/api/search?q=pasta%20AND%20tags:italian- Boolean search/api/search?q=total_time:[0%20TO%2030]- Range search
- Examples:
GET /api/recipes/:id- Get recipe detailsGET /api/recipes/:id/download- Download .cook file
GET /api/feeds- List all feedsPOST /api/feeds- Register a new feedGET /api/feeds/:id- Get feed detailsDELETE /api/feeds/:id- Remove a feed
Environment variables (see .env.example):
| Variable | Description | Default |
|---|---|---|
DATABASE_URL |
Database connection string | sqlite:./data/federation.db |
HOST |
Server host | 0.0.0.0 |
PORT |
Server port | 3000 |
EXTERNAL_URL |
External URL for CLI | http://localhost:3000 |
API_RATE_LIMIT |
API requests per second | 100 |
CRAWLER_INTERVAL |
Seconds between feed updates | 3600 |
MAX_FEED_SIZE |
Maximum feed size in bytes | 5242880 (5MB) |
MAX_RECIPE_SIZE |
Maximum recipe size in bytes | 1048576 (1MB) |
RATE_LIMIT |
Crawler requests per second per domain | 1 |
INDEX_PATH |
Search index directory | ./data/index |
RUST_LOG |
Logging level | info,federation=debug |
To build the project for production:
# Build everything (CSS + Rust binary)
./scripts/build.shThis will:
- Build Tailwind CSS with minification
- Build the Rust binary in release mode
The output will be:
- Binary:
./target/release/federation - Minified CSS:
./src/web/static/css/output.css
To run in production:
# Set environment variables
export DATABASE_URL="sqlite:./data/federation.db"
export PORT=3000
# Run the server
./target/release/federation servecargo testcargo clippy -- -D warningsMigrations are located in the migrations/ directory and are automatically applied on server startup.
- Web Framework: Axum with Tokio async runtime
- Database: SQLite (PostgreSQL compatible)
- Search Engine: Tantivy full-text search
- Feed Parsing: feed-rs for RSS/Atom
- Recipe Parsing: cooklang-rs
- Templates: Askama with Tailwind CSS
- CLI: Clap for command-line interface
- Rate Limiting: tower-governor for API protection
For production use, it's recommended to deploy this service behind a reverse proxy (nginx, Caddy, Traefik, etc.) that:
- Terminates TLS/SSL
- Sets proper
X-Forwarded-Forheaders for accurate rate limiting - Provides additional DDoS protection
- Handles load balancing if running multiple instances
Rate limiting works best when proper IP information is available via reverse proxy headers.
To make your recipes discoverable:
- Create
.cookfiles in a directory - Generate an Atom feed:
cargo run -- publish --input ./recipes --output feed.xml
- Host the feed and .cook files at a public URL
- Add your feed to a federation server:
curl -X POST http://localhost:3000/api/feeds \ -H "Content-Type: application/json" \ -d '{"url": "https://your-site.com/feed.xml"}'
See LICENSE file for details.
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.