119 lines
5.0 KiB
Markdown
119 lines
5.0 KiB
Markdown
# 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()`)
|