✨ feat: enhance validation rules and improve content structure across collections
This commit is contained in:
@@ -429,6 +429,70 @@ If the collection feeds public pages or admin block previews, also verify that t
|
||||
|
||||
---
|
||||
|
||||
## Collection Validators
|
||||
|
||||
Validatoren definieren Sicherheitsregeln und Typ-Constraints, indem sie als `validator`-Key innerhalb der `fields`-Definitionen einer Collection-YAML (`api/collections/*.yml`) konfiguriert werden.
|
||||
|
||||
**Unterschied Client- vs. Serverseitige Validatoren:**
|
||||
|
||||
- **Serverseite (`tibi-server`)**: Validatoren werden zentral im Go-Backend bei jedem Datensatz-Schreibvorgang (`POST` / `PUT`) ausgeführt (nach den `validate`-Hooks). Wenn Daten nicht den Constraints entsprechen, erfolgt ein Abbruch (`400 Bad Request`).
|
||||
- **Clientseite (`tibi-admin-nova`)**: Das CMS-Admin-Interface liest diese Validator-Regeln automatisch über das OpenAPI-Schema ein und wendet sie instant als Client-Side-Validierung in den Formularen an (Rote Markierungen und Check vor dem eigentlichen API-Call). **Validatoren müssen daher nur 1x zentral in der YAML definiert werden.**
|
||||
|
||||
**Häufige Validator-Optionen je Feldtyp:**
|
||||
|
||||
- **Generell**:
|
||||
- `required: true` (Zwingendes Pflichtfeld)
|
||||
- `allowZero: true` (Erlaubt die explizite Eingabe von `""` oder `0`, selbst wenn `required: true` aktiv ist)
|
||||
- `in: ["wert1", "wert2"]` (Nur dieser exakte Pool an primitiven Werten ist erlaubt)
|
||||
- `eval: "$this.length >= 3 && $this.length <= 100"` (Serverseitige Javascript-Evaluation für Custom-Logik)
|
||||
- **Einfache Texte (`string`)**:
|
||||
- `minLength: X` und `maxLength: Y`
|
||||
- `pattern: "^[a-zA-Z0-9]+$"` (Prüft Regex-Match des kompletten Werts)
|
||||
- `format: email` (oder `url`, `uuid`, `slug` für eingebaute Regex-Prüfungen)
|
||||
- **Zahlen (`number`, `float`)**:
|
||||
- `min: X` und `max: Y`
|
||||
- **Datum/Zeit (`date`, `datetime`, `time`)**:
|
||||
- `minDate: "YYYY-MM-DD"` und `maxDate: "YYYY-MM-DD"` (Zulässige Zeitgrenzen)
|
||||
- **Listen/Arrays (`string[]`, `object[]`)**:
|
||||
- `minItems: X` und `maxItems: Y`
|
||||
- **Dateien/Bilder (`file`, `file[]`)**:
|
||||
- `maxFileSize: "50MB"` (und `minFileSize`)
|
||||
- `accept: ["image/png", "image/webp"]` (Erlaubte MIME-Types)
|
||||
- Constraints für Bildabmessungen konfigurierbar via Sub-Objekt:
|
||||
```yaml
|
||||
image:
|
||||
minWidth: 800
|
||||
maxWidth: 2400
|
||||
minHeight: 600
|
||||
maxHeight: 1800
|
||||
```
|
||||
|
||||
**Beispiel für die Einbindung in einer Collection:**
|
||||
|
||||
```yaml
|
||||
fields:
|
||||
- name: internalName
|
||||
type: string
|
||||
validator:
|
||||
required: true
|
||||
maxLength: 100
|
||||
meta:
|
||||
label: { de: "Interner Name", en: "Internal Name" }
|
||||
|
||||
- name: externalLink
|
||||
type: string
|
||||
validator:
|
||||
format: url
|
||||
meta:
|
||||
label: Externe URL
|
||||
|
||||
- name: document
|
||||
type: file
|
||||
validator:
|
||||
maxFileSize: "20MB"
|
||||
accept: ["application/pdf"]
|
||||
```
|
||||
|
||||
## Seed data pattern (Playwright)
|
||||
|
||||
Test seed data uses `_testdata: true` as a hidden marker field. **Real content must NEVER use this flag** — otherwise test teardown will delete it.
|
||||
|
||||
@@ -141,6 +141,52 @@ For `GET /:collection/:id`, the Go server sets `_id` automatically from the URL
|
||||
|
||||
GET read hooks should **not** set their own `_id` filter for `req.param("id")`. Only add authorization filters (e.g. `{ userId: userId }`).
|
||||
|
||||
|
||||
## Interne DB-Lookups in Hooks (Read & Write)
|
||||
|
||||
Innerhalb von goja-Hooks hast du über die `context.db`-API Vollzugriff auf die lokale MongoDB. Dies ist essenziell für komplexe Prüfungen (z. B. "Gehört der angemeldete User wirklich zur ID im Foreign-Key des Objekts?").
|
||||
|
||||
**Wichtige Konzepte für DB-Calls in Hooks:**
|
||||
1. **Keine Automatik-Lookups (`_lookup`) in Hook-Queries:** Der Go-Befehl `context.db.find` liefert nur die flachen Datenbank-Dokumente als Array. Die in der REST-API verfügbare `lookup`-Automatik für Foreign-Keys wird in den internen Backend-Hooks *nicht* angewendet. Du musst die verknüpften Collections ggf. manuell nachladen.
|
||||
2. **Immer Arrays:** `context.db.find` gibt **immer** ein Array zurück, auch wenn du `limit: 1` setzt.
|
||||
3. **Rechte ignorierend:** Die `context.db.*`-Methoden umgehen alle `permissions` der YAML-Rollen. Du lädst als System-Benutzer!
|
||||
|
||||
**Beispiel: Datensatz validieren / verknüpftes Element prüfen**
|
||||
|
||||
```javascript
|
||||
// hooks/my_action/post.before
|
||||
(function() {
|
||||
var userId = context.auth().id;
|
||||
var submittedRefId = context.data.refId;
|
||||
|
||||
// 1. Manuell nachladen
|
||||
var targetList = context.db.find("target_collection", {
|
||||
filter: { id: submittedRefId },
|
||||
limit: 1 // Begrenzen für Performance
|
||||
});
|
||||
|
||||
if (targetList.length === 0) {
|
||||
throw { status: 404, json: { error: "Ziel nicht gefunden" } };
|
||||
}
|
||||
|
||||
var target = targetList[0];
|
||||
|
||||
// 2. Custom Security Check
|
||||
if (target.ownerId !== userId) {
|
||||
throw { status: 403, json: { error: "Keine Berechtigung für dieses Ziel" } };
|
||||
}
|
||||
|
||||
// ... Hook fortsetzen
|
||||
})();
|
||||
```
|
||||
|
||||
**Verfügbare DB-Methoden in `context.db`:**
|
||||
* `context.db.find(collection, { filter: {}, selector: {}, sort: [], limit: 10 })`
|
||||
* `context.db.count(collection, { filter: {} })`
|
||||
* `context.db.create(collection, { field: "value" })`
|
||||
* `context.db.update(collection, "id_string", { field: "new_value" })` (bzw. mit Mongo-Operatoren `$set`, `$inc`, etc.)
|
||||
* `context.db.delete(collection, "id_string")`
|
||||
|
||||
## Current hook surfaces that matter for website projects
|
||||
|
||||
- Collection CRUD hooks under `get`, `post`, `put`, `delete`
|
||||
|
||||
Reference in New Issue
Block a user