Jak funguje tento blog
Tento blog je server-rendered webová aplikace napsaná v Rustu. Žádný WordPress, žádný statický generátor — čistý Rust server, který renderuje stránky na požádání z PostgreSQL databáze.
Zdrojový kód je dostupný na github.com/xmiksay/blog.
| Co | Jak |
|---|---|
| Backend | Rust, Axum 0.8, Tokio |
| Databáze | PostgreSQL přes SeaORM |
| Šablony | MiniJinja |
| Markdown | pulldown-cmark s custom rozšířeními |
| Auth | Argon2 + Bearer tokeny |
| Frontend | Statické HTML/CSS/JS, Tailwind |
| Nasazení | Docker + docker-compose |
| AI integrace | MCP server pro Claude |
Architektura a tech stack
Tech stack
- Backend: Rust (edition 2024), Axum 0.8, Tokio async runtime
- Databáze: PostgreSQL přes SeaORM 1.x
- Šablony: MiniJinja — Jinja2-like templating v Rustu, soubory v
templates/ - Markdown: pulldown-cmark s vlastními rozšířeními (transclusion
<!--BLOG_PLACEHOLDER_0-->, aj.) - Auth: Argon2 hashování hesel, Bearer token sessions, service tokeny pro MCP
- Frontend: Statické soubory v
static/, Tailwind CSS - Logging:
tracing+tracing-subscribers env filtrem
Struktura projektu
src/
bin/
blog_server.rs # Hlavní web server (port 3000)
blog_migration.rs # Migration CLI (up/down/fresh/status)
blog_cli.rs # Admin CLI (create-user)
entity/ # SeaORM entity modely
user.rs, token.rs, tag.rs, menu.rs, page.rs,
page_revision.rs, image.rs, gallery.rs, gallery_image.rs
migration/ # SeaORM migrace
routes/
public/ # Veřejné routes (stránky, obrázky, galerie)
admin/ # Admin CMS routes (chráněné autentizací)
mod.rs # Sdílené view typy, menu builder
auth.rs # Hashování hesel, token middleware
markdown.rs # Renderování Markdownu s custom rozšířeními
state.rs # AppState (db + template env)
templates/ # MiniJinja HTML šablony
static/ # CSS, JS, obrázky
Jak server funguje
Při spuštění blog_server automaticky proběhnou databázové migrace, načte se template engine a Axum spustí HTTP server na portu 3000.
Catch-all route /{*path} funguje takto:
- Hledá shodu v menu (statický markdown obsah)
- Hledá shodu v pages (dynamický obsah ze stránky v DB)
- Vrátí 404
Revize stránek se ukládají jako diff patche — ne jako plné snapshoty. Šetří místo, zachovává historii.
Datový model
Databáze je PostgreSQL, přístup přes SeaORM. Migrace běží automaticky při startu serveru.
Tabulky
users
| Sloupec | Typ | Popis |
|---|---|---|
id | PK | |
username | unique | Přihlašovací jméno |
password_hash | text | Argon2 hash |
tokens
| Sloupec | Typ | Popis |
|---|---|---|
id | PK | |
nonce | unique | Token hodnota |
user_id | FK → users | Vlastník |
expires_at | timestamp? | NULL = trvalý (service tokeny) |
Tokeny slouží jak pro session (po přihlášení), tak pro service tokeny (MCP integrace).
tags
| Sloupec | Typ | Popis |
|---|---|---|
id | PK | |
name | unique | Název tagu |
description | text | Popis |
menus
| Sloupec | Typ | Popis |
|---|---|---|
id | PK | |
path | unique | URL cesta |
markdown | text | Obsah (renderovaný jako stránka) |
pages
| Sloupec | Typ | Popis |
|---|---|---|
id | PK | |
path | unique | URL cesta (např. about/blog) |
summary | text | Krátký popis |
markdown | text | Obsah stránky |
tag_ids | INT[] | Pole tagů |
private | bool | Skrytá stránka |
created_at/by | Audit | |
modified_at/by | Audit |
page_revisions
| Sloupec | Typ | Popis |
|---|---|---|
id | PK | |
page_id | FK → pages | |
patch | text | Diff oproti předchozí verzi |
created_at/by | Audit |
images a galleries
Obrázky se ukládají jako binární data (bytea) přímo v PostgreSQL — včetně automaticky generovaného náhledu (thumbnail). Galerie jsou uspořádané kolekce obrázků přes image_ids INT[].
Routes — veřejné i admin
Veřejné routes
| Path | Metoda | Popis |
|---|---|---|
/obrazky/{id} | GET | Plný obrázek |
/obrazky/{id}/nahled | GET | Thumbnail |
/{*path} | GET | Catch-all (viz níže) |
Catch-all logika
GET /{*path}
1. match v menus → render menu markdown
2. match v pages → render stránky
3. → 404
Admin routes (chráněno Bearer tokenem)
| Path | Metoda | Popis |
|---|---|---|
/admin/login | GET, POST | Přihlašovací formulář |
/admin/logout | GET | Odhlášení |
/admin | GET | Dashboard |
/admin/stranky | CRUD | Správa stránek |
/admin/menu | CRUD | Správa menu |
/admin/tagy | CRUD | Správa tagů |
/admin/obrazky | CRUD | Správa obrázků |
/admin/galerie | CRUD | Správa galerií |
/admin/tokeny | CRUD | Správa service tokenů (pro MCP) |
Auth flow
POST /admin/login→ ověří heslo Argon2, vytvoří session token, vrátí jako Bearer- Všechny
/admin/*routes čtouAuthorization: Bearer <token>header - Middleware token ověří v DB, přiřadí
user_iddo request extension - Service tokeny (pro MCP) nemají expiraci — spravují se ručně přes
/admin/tokeny
MCP integrace (Claude)
Blog vystavuje MCP server (Model Context Protocol) na /mcp/* routes. To umožňuje Claudovi číst a editovat stránky přímo z konverzace — přesně tak, jak vznikla tato stránka.
Dostupné nástroje
| Nástroj | Kdy použít | Popis |
|---|---|---|
search_pages | Jako první — při průzkumu obsahu | Filtruje stránky podle prefixu cesty a/nebo tagu; vrátí path + summary |
read_page | Před editací, nebo když potřebuji plný obsah | Přečte stránku podle přesné cesty — vrátí metadata + markdown |
edit_page | Vytvoření nebo aktualizace stránky | Mění jen předaná pole; automaticky ukládá diff revizi |
list_tags | Před přiřazením tagů | Vrátí všechny dostupné tagy — jména jsou case-sensitive |
SERVER_INSTRUCTIONS — stránka CLAUDE
Namísto konstanty v kódu jsou instrukce pro Clauda uloženy jako privátní stránka blogu na cestě CLAUDE. Server ji načte a předá jako SERVER_INSTRUCTIONS při každém MCP handshaku.
Výhoda: instrukce lze editovat přes admin rozhraní (nebo přes edit_page) bez recompilu.
Transclusion a read_page
Markdown extenze <!--BLOG_PLACEHOLDER_0--> slouží k renderování — server při výstupu do HTML vloží obsah odkazované stránky inline. Z pohledu Clauda jde o pouhý textový token; obsah odkazované stránky v markdownu není automaticky přítomen.
Pokud tedy Claude narazí na <!--BLOG_PLACEHOLDER_1--> a potřebuje znát její obsah, musí si sám zavolat read_page("some/page").
Auth
MCP routes používají service tokeny — vytváří se přes /admin/tokeny. Token se posílá jako Authorization: Bearer <token>. MCP handler z tokenu odvodí user_id pro audit pole (created_by, modified_by).
Service tokeny nemají defaultně expiraci — životní cyklus spravuje administrátor.
Markdown rozšíření
| Syntaxe | Výsledek |
|---|---|
<!--BLOG_PLACEHOLDER_2--> | Transclusion — vloží obsah jiné stránky (při renderování) |
| `` | Obrázek s odkazem na plnou velikost |
| `` | Mřížka náhledů galerie |
<!--BLOG_PLACEHOLDER_3--> | Statická šachová pozice |
<!--BLOG_PLACEHOLDER_4--> | Přehrávatelná šachová partie |
Implementováno v src/markdown.rs.
Provoz a nasazení
Požadavky
- Rust (edition 2024)
- PostgreSQL
- Docker + Docker Compose (pro kontejnerizované nasazení)
Proměnné prostředí
| Proměnná | Popis | Default |
|---|---|---|
DATABASE_URL | PostgreSQL connection string | postgres://blog:blog@localhost:5432/blog |
RUST_LOG | Log level filtr | blog=debug,tower_http=debug,info |
Spuštění s Docker Compose
# Build release binárek
cargo build --release
# Spuštění
docker compose up --build -d
Aplikace běží na http://localhost:3000.
Vytvoření uživatele
# S Docker Compose
docker compose exec app ./blog_cli create-user <username> <password>
# Bez Dockeru (DATABASE_URL musí být v .env nebo env)
cargo run --bin blog_cli -- create-user <username> <password>
Migrace
Migrace běží automaticky při startu serveru. Ruční správa:
cargo run --bin blog_migration # apply all pending
cargo run --bin blog_migration -- down # rollback last
cargo run --bin blog_migration -- fresh # reset & reapply vše
cargo run --bin blog_migration -- status # zobraz stav
# S Docker Compose
docker compose exec app ./blog_migration
Vývoj lokálně
# Vyžaduje DATABASE_URL v .env
cargo run --bin blog_server
# Ověření kompilace bez spuštění
cargo check
Docker image
cargo build --release
docker build -t blog .
docker run -e DATABASE_URL=... -p 3000:3000 blog
CI/CD
Repozitář obsahuje .github/workflows/ — GitHub Actions pipeline pro automatický build a nasazení.