RustyCMS: file-based headless CMS — API, Admin UI (content, types, assets), Docker/Caddy, image transform; only demo type and demo content in version control

Made-with: Cursor
This commit is contained in:
Peter Meier
2026-03-12 14:21:49 +01:00
parent aad93d145f
commit 7795a238e1
278 changed files with 15551 additions and 4072 deletions

118
CLAUDE.md Normal file
View File

@@ -0,0 +1,118 @@
# RustyCMS AI Context
> **Für AI-Tools**: Diese Datei (CLAUDE.md) wird von Claude Code gelesen.
> Cursor liest `.cursor/rules/project.mdc`.
> Beide Dateien haben identischen Inhalt — beim Ändern bitte **beide** aktualisieren.
---
## Projektstruktur
```
rustycms/
├── src/ # Rust API
│ ├── api/
│ │ ├── handlers.rs # AppState + alle Content-Handler
│ │ ├── response.rs # format_references, expand/collapse_asset_urls
│ │ ├── assets.rs # Asset-Endpoints (Upload, Serve, Delete, Folders)
│ │ └── routes.rs # Axum-Router
│ ├── schema/
│ │ └── validator.rs # normalize_reference_arrays (single + array refs)
│ └── store/ # FileStore / SqliteStore
├── types/ # Schema-Definitionen (*.json5)
├── content/ # Inhalte als JSON5-Dateien
│ └── assets/ # Bild-Assets (mit Unterordnern)
└── admin-ui/ # Next.js Admin UI (Port 3001)
├── src/components/ # React-Komponenten
├── src/lib/api.ts # API-Client
└── messages/ # i18n (en.json, de.json)
```
## Dev starten
```bash
./dev.sh # API (Port 3000) + Admin UI (Port 3001)
cd admin-ui && npm run dev # nur Admin UI
cargo run # nur API
```
## Konfiguration
Alle Optionen per Env-Variable (`.env.example` als Vorlage) oder CLI-Flag:
| Env-Variable | CLI-Flag | Default | Zweck |
|---|---|---|---|
| `RUSTYCMS_TYPES_DIR` | `--types-dir` | `./types` | Schema-Definitionen |
| `RUSTYCMS_CONTENT_DIR` | `--content-dir` | `./content` | Content-Dateien |
| `RUSTYCMS_BASE_URL` | — | `http://host:port` | Öffentliche API-URL (für Asset-URLs) |
| `RUSTYCMS_API_KEY` | — | unset | Auth für POST/PUT/DELETE |
| `RUSTYCMS_LOCALES` | — | unset | z.B. `de,en` (erstes = Default) |
| `RUSTYCMS_STORE` | — | `file` | `file` oder `sqlite` |
| `RUSTYCMS_CORS_ORIGIN` | — | `*` | Erlaubte CORS-Origin |
| `RUSTYCMS_CACHE_TTL_SECS` | — | `60` | Response-Cache TTL (0 = aus) |
## Asset-URL-Strategie
Assets werden immer mit **relativem Pfad** auf Disk gespeichert:
```
src: "/api/assets/ordner/datei.jpg"
```
Die API expandiert beim Ausliefern automatisch abhängig vom Kontext:
- **GET mit `_resolve`** (Frontend-Consumer): relativ → `https://api.example.com/api/assets/...`
- **GET ohne `_resolve`** (Admin UI bearbeitet): Pfad bleibt relativ
- **POST/PUT** (Speichern): absolute URLs werden vor dem Schreiben kollabiert
Implementierung: `src/api/response.rs``expand_asset_urls()` / `collapse_asset_urls()`
## Collection-Hierarchie (wichtig!)
| Collection | Zweck | Pflichtfelder |
|---|---|---|
| `img` | Rohes Bild-Asset | `src` (relativer Pfad), `description` |
| `image` | Layout-Komponente | `name`, `img` (Referenz auf `img`-Collection) |
**Regel:** `postImage` → referenziert `img` direkt. `row1Content` / `row2Content` etc. → erwarten `image`-Einträge, **nicht** `img`.
Workflow beim Hinzufügen eines Bildes zu einer Content-Row:
1. `img`-Eintrag erstellen (`src: "/api/assets/..."`)
2. `image`-Eintrag erstellen (referenziert den `img`-Eintrag)
3. `image`-Slug in `rowXContent` des Posts eintragen
## Referenz-Handling
### ReferenceOrInlineField (Admin UI)
- `value` ist String → Reference-Modus (Slug-Picker)
- `value` ist Objekt ohne `_slug` → Inline-Modus (Felder direkt eingeben)
- `value` ist Objekt mit `_slug` → aufgelöste Referenz vom API → wird automatisch zu Reference-Modus normalisiert
Normalisierung: `admin-ui/src/components/ReferenceOrInlineField.tsx` via `normalizedValue` memo.
### Normalisierung beim Schreiben (Rust)
`validator::normalize_reference_arrays()` konvertiert vor dem Speichern:
- Einzelne `reference`/`referenceOrInline`-Felder: `{_slug: "foo", ...}``"foo"`
- Array-Items: gleiche Normalisierung per Element
- Aufgerufen in `create_entry` und `update_entry`
## Admin UI Konventionen
- **i18n**: next-intl, Cookie-basiert. Neue Keys immer in **beiden** Dateien eintragen: `admin-ui/messages/en.json` + `messages/de.json`
- **Tailwind v4**: `@plugin "tailwindcss-animate"` (nicht `@import`)
- **Kein Dark Mode**: `@media (prefers-color-scheme: dark)` wurde entfernt, keine `dark:`-Klassen
- **Scroll**: `html, body { height: 100%; overflow: hidden }` — Sidebar scrollt unabhängig von der Seite
- **Neue Komponente**: immer prüfen ob Übersetzungs-Namespace in beiden Message-Dateien vorhanden
## Axum Routing
Literale Routen haben Vorrang vor Wildcard-Routen — Reihenfolge egal, Axum löst korrekt auf:
```rust
.route("/api/assets/folders", get(...).post(...)) // matcht vor:
.route("/api/assets/*path", get(...).delete(...))
```
## Rust-spezifisch
- `clap` braucht Feature `"env"` für Env-Var-Support bei CLI-Args
- `AppState` in `src/api/handlers.rs` — alle neuen geteilten Ressourcen hier hinzufügen
- Hot-Reload: Änderungen in `types/` werden automatisch geladen (kein Neustart nötig)
- Cache wird bei Schreib-Operationen invalidiert (`cache.invalidate_collection()`)