Read all content from PocketBase, per request #30

Merged
Tom merged 7 commits from feature/pocketbase-consume into main 2026-09-23 16:02:46 +02:00
21 changed files with 154 additions and 327 deletions
Showing only changes of commit 47d9841532 - Show all commits

No files matched your search

+39 -24
View File
@@ -2,7 +2,8 @@
Website des Elternbeirats der IGMH. Blazor Web App mit statischem
Server-Side-Rendering, .NET 10. Läuft als Container auf Unraid hinter dem Nginx
Proxy Manager.
Proxy Manager. Die Inhalte liegen in **PocketBase** (zweiter Container); die App
liest sie pro Request über einen typisierten `PocketBaseClient`.
Architekturentscheidungen und deren Begründung: `docs/architektur.md`. Dort
nachlesen, bevor eine davon in Frage gestellt wird.
@@ -11,7 +12,7 @@ nachlesen, bevor eine davon in Frage gestellt wird.
| Thema | Datei |
|---|---|
| Warum SSR statt WASM, warum keine DB (AE-1 bis AE-4) | `docs/architektur.md` |
| Warum SSR statt WASM, warum PocketBase als Datenschicht (AE-1 bis AE-4) | `docs/architektur.md` |
| Stack lokal starten, Tests, compose-Overlays | `docs/entwicklung.md` |
| Unraid, compose, NPM-Proxy-Host, Registry, Rollback | `docs/deployment.md` |
| Inhalte anlegen und ändern (PocketBase-Admin) | `docs/redaktion.md` |
@@ -26,28 +27,36 @@ Sub-Issues als Checkliste im Body, alle am Milestone „Elternbeirat-Website".
## Kommandos
```bash
dotnet run --project Elternbeirat.Web # lokal; Inhalte werden beim Start gelesen (kein Auto-Reload)
dotnet test # Smoke-Tests: jede Route liefert 200
docker compose build && docker compose up -d
# Lokaler Stack (App + PocketBase) über das Dev-Overlay, siehe docs/entwicklung.md:
docker compose -f compose.yaml -f compose.dev.yaml up --build # App :5000, PocketBase-Admin :8090/_/
dotnet test # Smoke-Tests (starten eigene PocketBase) + Unit-Tests
```
Es gibt keine Datenbank, keine Migrationen, kein Seeding. Ein `git clone` ist der
vollständige Stand.
Die App liest ihren Inhalt pro Request aus PocketBase; sie hält selbst keinen
Inhalt und liest nichts beim Start ein. Kein EF Core, keine Migrationen, kein
Seeding im Code. Ein `git clone` ist **nicht** der vollständige Stand — der
Inhalt liegt in PocketBase (`pb_data`), das separat gesichert wird (siehe
`docs/deployment.md`).
## Nicht offensichtlich
- **Inhalte liegen im Image**, nicht in einem Volume. Jede Textänderung braucht
Commit und Rebuild. Das ist Absicht (`docs/architektur.md`, AE-3) — nicht
„vereinfachen".
- **Inhalte liegen in PocketBase** (`pb_data`-Volume), nicht im Image. Eine
Textänderung ist ein Eintrag im PocketBase-Admin und sofort live — kein Commit,
kein Rebuild (`docs/architektur.md`, AE-2/AE-3). Nur die statischen Assets unter
`wwwroot/` (CSS, Fonts, PDFs) sind ins Image gebacken.
- **Kein `UseHttpsRedirection()`, kein `UseHsts()`.** NPM terminiert TLS; beides
erzeugt hinter dem Proxy eine Redirect-Schleife. `UseForwardedHeaders` mit
geleerten `KnownNetworks`/`KnownProxies` ist korrekt so, weil der Container kein
Port-Mapping hat und nur über NPM erreichbar ist.
- **Das chiseled-Runtime-Image hat keine Shell.** `HEALTHCHECK` mit `curl` oder
`sh` schlägt dort fehl.
- Neue Seite = Markdown in `Content/pages/`. Neuer Beitrag = Markdown in
`Content/posts/`. Neuer Termin = Eintrag in `Content/events.yml`. In allen
Fällen wird **kein** `.razor` angefasst.
- Neue Seite / neuer Beitrag / neuer Termin / neue Frage = **Record in der
passenden PocketBase-Collection** (`pages` / `posts` / `events` / `faqs`), nicht
eine Datei und **kein** `.razor`. `pages` mit `slug` ist sofort unter `/<slug>`
erreichbar; `location`+`order` steuern das Menü, `embed` bettet Listen ein
(`docs/redaktion.md`). Eine **neue Art von Liste** (eigene Route/Logik) ist
dagegen Code: neue Collection + `PocketBaseClient`-Methode + Komponente
(`docs/entwicklung.md`).
- Slugs ohne Umlaute (`ueber-uns`, nicht `über-uns`).
## Regeln
@@ -57,22 +66,28 @@ vollständige Stand.
- **Keine externen Ressourcen.** Keine CDN-Skripte, keine Google Fonts, keine
Maps- oder Video-Embeds. Schriften werden selbst ausgeliefert. Grund ist
Datenschutz, nicht Geschmack (`docs/recht.md`).
- **Keine Datenbank, kein EF Core, kein ORM.** Inhalte sind Dateien.
- **Inhalte gehören nach `Content/`**, nicht als Markup in Komponenten.
- Kein CMS, kein Login, keine Benutzerkonten.
- **Kein EF Core, kein ORM, keine Migrationen.** Inhalte kommen aus PocketBase
über den `PocketBaseClient` (REST), nicht aus einer relationalen DB im Code.
- **Inhalte gehören nach PocketBase**, nicht als Markup in Komponenten und nicht
in Dateien im Repo.
- Kein eigenes CMS, kein Login und keine Benutzerkonten **in der Website** —
gepflegt wird ausschließlich im PocketBase-Admin (Redakteure sind Superuser).
- UI-Texte auf Deutsch, ohne Anglizismen. `Termine`, nicht `Events`.
- Neue NuGet-Abhängigkeiten vorher begründen. Bestand: `Markdig`, `YamlDotNet`.
- Neue NuGet-Abhängigkeiten vorher begründen. Genutzt wird nur `Markdig`
(Markdown → HTML). `YamlDotNet` stammt aus der abgelösten Datei-Schicht, wird
nicht mehr verwendet und soll aus dem `csproj` entfernt werden.
## Stil
- Nullable aktiviert, `ImplicitUsings` an, File-scoped Namespaces.
- Services über DI, als Singleton registriert (Inhalte werden beim Start
eingelesen und gecacht).
- Öffentliche Typen und Methoden der `Services` bekommen XML-Doc (auf Englisch,
leicht verständlich), Razor-Markup nicht.
- Datenzugriff über den typisierten `PocketBaseClient` (registriert via
`AddHttpClient`), der pro Request liest — kein Start-Cache, kein Singleton mit
Inhalten. Komponenten liegen feature-basiert unter `Features/<Bereich>/`.
- Öffentliche Typen und Methoden im `PocketBaseClient` und in `Contracts`
bekommen XML-Doc (auf Englisch, leicht verständlich), Razor-Markup nicht.
- **Code durchgängig auf Englisch** — Typen, Member, Variablen, Kommentare,
Skripte und Doku. Das gilt **auch für Domänenbegriffe**: im Code `Event`, nicht
`Termin`; `Post`, nicht `Beitrag`. Deutsch bleibt ausschließlich, was ein
Besucher liest oder ein Redakteur pflegt: UI-Texte, Markdown-Inhalte,
Frontmatter-Schlüssel wie `titel:` sowie Slugs/Dateinamen unter `Content/`
(z. B. `vorstandsteam`, `/termine`).
Besucher liest oder ein Redakteur pflegt: UI-Texte sowie die **Werte** der
PocketBase-Records (z. B. `title: Vorstandsteam`, der Markdown-`body`). Die
Feldnamen und Slugs bleiben dagegen englisch (`title`, `slug`, `/board`).
-1
View File
@@ -9,7 +9,6 @@
<ItemGroup>
<PackageVersion Include="Markdig" Version="0.38.0" />
<PackageVersion Include="YamlDotNet" Version="16.2.1" />
</ItemGroup>
<!-- Test-only packages. -->
-26
View File
@@ -1,26 +0,0 @@
# Calendar entries of the Elternbeirat.
#
# One entry per event. Required keys: title, start.
# start/end:
# date only -> "2026-03-15" (all-day)
# date with time -> "2026-03-15 19:30"
# location and note are optional.
- title: Elternbeiratssitzung
start: "2026-10-08 19:30"
location: Lehrerzimmer
note: Themen bitte vorab per E-Mail einreichen.
- title: Herbstbasar
start: "2026-11-14 10:00"
end: "2026-11-14 16:00"
location: Aula
- title: Weihnachtsferien
start: "2026-12-21"
end: "2027-01-06"
- title: Elternsprechtag
start: "2026-09-05 15:00"
end: "2026-09-05 18:00"
location: Klassenräume
@@ -1,22 +0,0 @@
---
title: Datenschutz
---
# Datenschutzerklärung
*TODO: Rechtlich verbindliche Datenschutzerklärung ergänzen. Erst nach Freigabe
finalisieren – siehe `docs/recht.md`.*
Diese Website wird bewusst ohne externe Ressourcen betrieben: keine CDN-Skripte,
keine Google Fonts, keine Karten- oder Video-Einbettungen. Schriften werden von
unserem eigenen Server ausgeliefert. Dadurch werden beim Besuch keine Daten an
Dritte übertragen.
## Verantwortliche Stelle
*TODO: siehe [Impressum](/impressum).*
## Server-Logdaten
*TODO: beschreiben, welche Zugriffsdaten der Server (bzw. der vorgelagerte
Proxy) protokolliert und wie lange.*
@@ -1,17 +0,0 @@
---
title: Downloads
---
# Downloads
Hier stellen wir Unterlagen zur Elternarbeit als Datei bereit: Protokolle der
Sitzungen, Elterninformationen und die Geschäftsordnung.
## Unterlagen
*TODO: Download-Liste ergänzen. Dateien liegen unter `wwwroot/downloads/` und
werden hier verlinkt, z. B.:*
- *Protokoll der Vollversammlung (PDF)*
- *Geschäftsordnung des Elternbeirats (PDF)*
- *Elterninformation Elternvertreter (PDF)*
@@ -1,20 +0,0 @@
---
title: FAQ Elternarbeit
---
# Elternarbeit
Unterstützung für neu gewählte und erfahrene Elternvertreterinnen und
Elternvertreter.
## Welche Aufgaben habe ich als Elternvertreter?
*TODO: Aufgaben und Rechte der Klassenelternvertretung beschreiben.*
## Wie läuft die Zusammenarbeit mit dem Elternbeirat?
*TODO: Sitzungsrhythmus und Ansprechpartner ergänzen.*
## Wo finde ich Vorlagen und Unterlagen?
Unterlagen und Protokolle finden Sie im Bereich [Downloads](/downloads).
@@ -1,22 +0,0 @@
---
title: FAQ Elterneuro
---
# Elterneuro
Der Elterneuro ist ein freiwilliger Beitrag der Eltern, mit dem der Elternbeirat
Projekte an der Schule unterstützt.
## Wofür wird der Elterneuro verwendet?
*TODO: konkrete Beispiele für die Verwendung ergänzen.*
## Wie hoch ist der Beitrag?
Der Elterneuro ist freiwillig.
*TODO: übliche Beitragshöhe und Zahlungsweg ergänzen.*
## An wen kann ich mich bei Fragen wenden?
Bei Fragen erreichen Sie uns über die [Kontaktseite](/kontakt).
@@ -1,21 +0,0 @@
---
title: FAQ Mensa
---
# Mensa
Alles rund um das Mittagessen an der IGMH: Anmeldung, Guthaben und Fristen.
## Wie melde ich mein Kind an?
Die Essensbestellung läuft über das System i-NET Menue.
*TODO: Ablauf der Erstanmeldung und Zugangsdaten beschreiben.*
## Wie lade ich Guthaben auf?
*TODO: Aufladeweg und Zahlungsarten ergänzen.*
## Bis wann kann ich bestellen oder stornieren?
*TODO: Fristen für Bestellung und Stornierung ergänzen.*
@@ -1,21 +0,0 @@
---
title: FAQ Schließfach
---
# Schließfächer
Informationen zur Anmietung eines Schließfachs an der IGMH.
## Welche Größen gibt es und was kosten sie?
*TODO: Verfügbare Größen und Preise ergänzen.*
## Wie miete ich ein Schließfach an?
Die Verwaltung läuft über das Serviceportal von AstraDirect.
*TODO: Ablauf der Anmietung und Link zum Portal ergänzen.*
## Wie tausche oder kündige ich mein Fach?
*TODO: Vorgehen für Fachtausch und Kündigung ergänzen.*
-15
View File
@@ -1,15 +0,0 @@
---
title: FAQ
---
# Häufige Fragen
Hier finden Eltern Antworten auf wiederkehrende Fragen rund um den Schulalltag.
Die Themen sind nach Bereichen aufgeteilt:
- [Mensa](/faq-mensa) – Anmeldung, Guthaben, Fristen
- [Schließfächer](/faq-schliessfach) – Größen, Preise, Verwaltung
- [Elterneuro](/faq-elterneuro) – freiwilliger Beitrag und Verwendung
- [Elternarbeit](/faq-elternarbeit) – Tipps für Elternvertreterinnen und Elternvertreter
*TODO: Reihenfolge und weitere Themen ergänzen, sobald die Unterseiten stehen.*
@@ -1,19 +0,0 @@
---
title: Förderverein
---
# Förderverein „Freunde der IGMH"
Der Förderverein „Freunde der IGMH" unterstützt die Schule bei Anschaffungen und
Projekten, die aus dem regulären Budget nicht finanziert werden können. Mitglieder
sind Eltern, Lehrkräfte, Ehemalige und Förderer der Schule.
## Was der Verein fördert
- Ausstattung für Unterricht und Arbeitsgemeinschaften
- Musische, sportliche und kulturelle Projekte
- Anschaffungen, die allen Schülerinnen und Schülern zugutekommen
## Mitglied werden
*TODO: Beitrittsformular bzw. Ansprechpartner und Beitragshöhe ergänzen.*
@@ -1,24 +0,0 @@
---
title: Impressum
---
# Impressum
*TODO: Rechtlich verbindliches Impressum ergänzen. Erst nach Freigabe mit echten
Daten füllen – siehe `docs/recht.md`.*
## Angaben gemäß § 5 DDG
*TODO: Name und Anschrift des Diensteanbieters (Elternbeirat / Schule).*
## Vertreten durch
*TODO: gesetzlicher Vertreter.*
## Kontakt
*TODO: E-Mail-Adresse (siehe [Kontakt](/kontakt)).*
## Verantwortlich i. S. d. § 18 Abs. 2 MStV
*TODO: Name und Anschrift der verantwortlichen Person.*
-16
View File
@@ -1,16 +0,0 @@
---
title: Kontakt
---
# Kontakt
Sie erreichen den Elternbeirat der IGMH per E-Mail:
[TODO-adresse@example.org](mailto:TODO-adresse@example.org)
*TODO: Echte Kontakt-E-Mail-Adresse eintragen.*
## Anschrift
Elternbeirat der IGMH
*TODO: Anschrift der Schule ergänzen.*
@@ -1,18 +0,0 @@
---
title: Vorstandsteam
---
# Das Vorstandsteam
Der Elternbeirat der IGMH wird von einem ehrenamtlichen Vorstand geleitet.
Er vertritt die Elternschaft gegenüber Schule und Schulträger und koordiniert
die Arbeit der Klassenelternvertreter.
## Aufgaben des Vorstands
- Vertretung der Eltern in der Schulkonferenz
- Zusammenarbeit mit Schulleitung und Kollegium
- Organisation der Vollversammlungen und Sitzungen
- Ansprechpartner für Fragen rund um Mensa, Schließfächer und Förderverein
*Die namentliche Vorstellung der Vorstandsmitglieder folgt.*
@@ -1,11 +0,0 @@
---
title: Neue Sporthalle feierlich eröffnet
date: 2025-10-05
---
# Neue Sporthalle feierlich eröffnet
Die neue Sporthalle der IGMH ist eröffnet. Schülerinnen, Schüler und Lehrkräfte
haben damit deutlich mehr Platz für Sportunterricht und Arbeitsgemeinschaften.
*TODO: Bericht und Fotos ergänzen.*
@@ -1,12 +0,0 @@
---
title: Neuer Vorstand des Elternbeirats gewählt
date: 2025-02-26
---
# Neuer Vorstand des Elternbeirats gewählt
Bei der Vollversammlung hat der Elternbeirat der IGMH einen neuen Vorstand
gewählt. Das Team bedankt sich für das entgegengebrachte Vertrauen und freut
sich auf die gemeinsame Arbeit im neuen Schuljahr.
*TODO: Namen und Ämter des neuen Vorstands ergänzen (nach Freigabe).*
-7
View File
@@ -19,7 +19,6 @@
<ItemGroup>
<PackageReference Include="Markdig" />
<PackageReference Include="YamlDotNet" />
</ItemGroup>
<!-- The typed PocketBaseClient the app reads its content from. Referencing
@@ -28,10 +27,4 @@
<ProjectReference Include="..\Elternbeirat.PocketBase\Elternbeirat.PocketBase.csproj" />
</ItemGroup>
<!-- Content ships as files inside the image, not in a volume. Copy it to
the output directory so the container can find it. -->
<ItemGroup>
<Content Include="Content\**\*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>
+42 -22
View File
@@ -2,12 +2,13 @@
Why the site is built the way it is. Read this before questioning one of these
decisions — each records the reasoning, not just the choice. Operational how-to
lives in `docs/deployment.md`; open work lives in `tasks/`.
lives in `docs/deployment.md`; open work lives in the Gitea issues (milestone
„Elternbeirat-Website").
> **Note on scope.** These decisions describe stage 1: a static information site
> with content as files. The move to editor-based content maintenance
> (`tasks/001-redaktion-ohne-entwickler.md`) deliberately revisits AE-2 and AE-3 —
> when that lands, update this file.
> **Note on scope.** The site started as a static information site with content
> as files (stage 1). Editorial maintenance without a developer then replaced
> that file layer with PocketBase, which revised **AE-2** and **AE-3** below —
> each now records both the original decision and why it changed.
## AE-1: Blazor with static server-side rendering, not WebAssembly
@@ -24,30 +25,49 @@ costs around 60 MB of RAM in the container.
(`@rendermode InteractiveServer` on exactly that one component), without changing the
architecture. Never global.
## AE-2: Content as files in the repo, not in a database
## AE-2: Content in PocketBase, read per request (revised)
**Decision:** Page content as Markdown with YAML frontmatter, events as structured
YAML (`Content/events.yml`), documents as PDF under `wwwroot`.
**Decision:** All visible content lives in **PocketBase** (collections `pages`,
`posts`, `events`, `faqs`); the app reads it over the REST API per request via a
typed `PocketBaseClient`. Documents remain PDFs under `wwwroot`.
**Rationale:** No DB backup, no migration schema, no consistency problems between
files and database. Changes are commits and are therefore traceable and reversible.
For a site with ~10 subpages and a few events per year, anything else is overhead.
**Original decision (stage 1):** Content as files in the repo — Markdown with YAML
frontmatter, events as `Content/events.yml`. The rationale then was: no DB backup,
no migration schema, no file/DB drift, and "backup = `git clone`". For a handful of
pages that was the leanest option.
**Consequence:** Text changes require a commit and a redeploy. With an expected five
changes a year that is acceptable — and the reason the editor expansion
(`tasks/001`) is treated as a separate stage.
**Why it changed:** The site needs to be maintainable by the board **without a
developer and without a redeploy**. Files meant every text fix was a commit and a
rebuild. PocketBase keeps the "small, self-hostable, one binary" spirit while
letting an editor change content live in its admin UI. It has **no** migration
schema of the EF/ORM kind — the collections are its data — so the objection that
weighed against "a database" in stage 1 does not apply here.
## AE-3: Content is baked into the image, not mounted as a volume
**Consequence:** Editing is now a task in the PocketBase admin, not a commit (see
`redaktion.md`). The app holds no content of its own; if PocketBase is unreachable a
page degrades to empty rather than failing. "Backup = `git clone`" no longer covers
the content — the backup is now `pb_data` (see AE-3 and `deployment.md`).
**Decision:** `Content/` and `wwwroot/` are part of the image.
## AE-3: Content lives in PocketBase's `pb_data`, not baked into the image (revised)
**Rationale:** Mounted content from the appdata share could be edited directly on the
NAS, but that is exactly when the repo and the live state drift apart — and the
"backup = `git clone`" property from AE-2 would be worthless.
**Decision:** The editable content lives in PocketBase's `pb_data`, a persistent
volume separate from the app image. Only the static assets under `wwwroot/` (CSS,
fonts, PDFs) are baked into the image.
**Consequence:** No quick fix on the live system. Typos are corrected properly via a
commit. Note: the content is read once at startup and cached — there is **no**
`FileSystemWatcher` (a change needs a restart / `dotnet watch`).
**Original decision (stage 1):** `Content/` and `wwwroot/` were both baked into the
image, deliberately **not** a mounted volume, so the live state could never drift
from the repo and "backup = `git clone`" held.
**Why it changed:** Editing content live (AE-2) is only possible if that content
survives a redeploy — so it must be a volume, not part of the image. The drift
concern is answered differently now: the content simply has one home (PocketBase),
and the repo no longer claims to hold it. The app image stays stateless and can be
rebuilt and redeployed at any time without touching the data.
**Consequence:** The backup is a copy of `pb_data` (plus a restore test), not a
`git clone` (set up in issue #9, see `deployment.md`). The app reads content per
request over HTTP, so there is nothing cached at startup and no `FileSystemWatcher`
— an editorial change is live immediately, no restart needed.
## AE-4: TLS and certificates exclusively in the Nginx Proxy Manager
+29
View File
@@ -100,3 +100,32 @@ The app reads its content from PocketBase over the compose network
inside the compose network, independent of the container name). Editing content
means editing records in the PocketBase admin UI at
<http://localhost:8090/_/>, not editing files. See `redaktion.md`.
---
## Adding a new content type
Editors can add **records** to the existing collections and add plain text
`pages`, but a **new kind of list** — its own collection rendered on its own
route with its own sorting or grouping — is a development task, not editorial.
It takes three steps, all small because the plumbing is shared:
1. **Collection + DTO.** Create the collection in the PocketBase admin (give it a
`public` bool and open List/View rules, like the others), then add a matching
record type in `Elternbeirat.Contracts` with `[JsonPropertyName(...)]` on each
field — mirror `Page`/`Post`.
2. **Client method.** Add one method to `PocketBaseClient`. The shared
`GetRecordsAsync<T>(collection, sort, ct)` already does the `public=true`
filter, the sort and the deserialization, so a new type is a one-liner:
`public Task<IReadOnlyList<Minutes>> GetMinutesAsync(CancellationToken ct = default)`
`=> GetRecordsAsync<Minutes>("minutes", "-date", ct);`
3. **Feature component.** Add a component under `Features/<Name>/` with its own
`@page "/…"` route that injects `PocketBaseClient`, calls the new method and
renders the result. Literal routes win over the `/{Slug}` catch-all, so pick a
slug that no `pages` record needs. Follow an existing list (`PostList`,
`EventList`, `FaqList`) for the load-in-`OnInitializedAsync`, log-and-degrade
pattern, and add a route smoke test plus a fixture seed.
Text pages need none of this — they are all served generically by
`ContentPage` (`@page "/{Slug}"`), which is why a new `pages` record is live
without a rebuild.
+4 -3
View File
@@ -1,9 +1,10 @@
# Legal — imprint, privacy, photos
Not legal advice — but the points where school sites regularly get flagged. The
actual imprint and privacy texts live in `Content/pages/impressum.md` and
`Content/pages/datenschutz.md`; filling them in with released, real data is tracked
in `tasks/003-echte-inhalte-vor-go-live.md`.
actual imprint and privacy texts are `pages` records in PocketBase (slugs
`imprint` and `privacy`), edited in the admin UI — see `redaktion.md`. Filling
them in with released, real data before go-live is tracked as a Gitea issue
(milestone „Elternbeirat-Website").
## Imprint (§ 5 DDG)
+40 -6
View File
@@ -5,11 +5,9 @@ Wie die sichtbaren Inhalte der Website gepflegt werden. Der Inhalt liegt in
über die Weboberfläche bearbeitet. Keine Dateien, kein Commit, kein Rebuild: eine
Änderung im Admin ist sofort live.
> **Übergang (Stand #8).** Der Inhalt ist bereits vollständig in PocketBase; die
> Blazor-App wird gerade darauf umgestellt, ihn von dort zu lesen (Issue #8).
> Solange das läuft, kann die ausgelieferte Seite noch aus den alten Dateien
> unter `Content/` stammen. Sobald #8 durch ist, ist PocketBase die einzige
> Quelle und dieser Abschnitt der einzige Pflegeweg.
> **Stand.** Die Blazor-App liest ihren gesamten Inhalt aus PocketBase (Issue #8);
> die alte Datei-Schicht unter `Content/` gibt es nicht mehr. PocketBase ist damit
> die einzige Quelle und dieser Abschnitt der einzige Pflegeweg.
> **Sprachkonvention.** Feldnamen und Slugs sind **englisch** (`title`, `slug`,
> `board`, `posts`). Der Text, den ein Besucher liest, bleibt **deutsch** — also
@@ -49,16 +47,52 @@ Jede Collection hat als **letztes Feld `public`** (ja/nein). Nur Records mit
`public = true` erscheinen auf der Website — so lässt sich ein Entwurf anlegen,
ohne dass er schon sichtbar ist.
### Was redaktionell geht — und was Code braucht
Redaktion arbeitet mit **Daten**, nicht mit Struktur. Konkret:
| Was du willst | Geht redaktionell? | Wie |
|---|---|---|
| Beitrag / Termin / Frage hinzufügen | **Ja** | Neuer Record in `posts` / `events` / `faqs` — erscheint sofort in der jeweiligen Liste |
| Neue Textseite | **Ja** | Neuer Record in `pages` (siehe unten) |
| Eine Seite mit einer Liste anteasern | **Ja** | Feld `embed` der Seite (`posts`/`events`/`faqs`) |
| **Neue Art von Liste** (eigene Seite mit eigener Sortierung/Gruppierung und eigener Adresse, z. B. „Protokolle") | **Nein** | Braucht Entwicklung: neue Collection **plus** Code |
Das heißt: **Bestehende Listen lassen sich beliebig erweitern**, aber eine *neue*
Listenart entsteht nicht durch Anlegen einer Collection allein. Die drei Listen
(`posts`, `events`, `faqs`) sind fest verdrahtet, weil jede eine eigene
Darstellung hat — Datum und Sortierung, Kalender/`.ics`, Themen-Gruppierung. Eine
weitere solche Ansicht anzulegen ist ein **Entwickler-Task**: eine neue Collection
in PocketBase, eine Lesemethode im `PocketBaseClient` und eine eigene Komponente
mit ihrer Route (siehe `docs/entwicklung.md`). Das ist Absicht: Inhalte sind
Daten, Struktur und Darstellung sind Code.
---
## Seiten (`pages`)
Eine Seite hat `title` (Überschrift für Menschen, Umlaute erlaubt), `slug` (die
URL, klein und **ohne Umlaute**: `board`, nicht `über-uns`) und `body` (der
Text, als Editor-Feld).
Text, als **Markdown**).
**Eine neue Seite anlegen** — Record in `pages`, dann ist die Adresse `/<slug>`
sofort live, ohne Code und ohne Rebuild:
1. `title` setzen (Pflicht).
2. `slug` setzen (Pflicht), englisch und ohne Umlaute — z. B. `slug = schulweg`
ergibt `/schulweg`.
3. `body` als Markdown schreiben (optional; darf leer bleiben).
4. **`public` anhaken.** Ohne Haken ist die Seite ein Entwurf und erscheint auf
der Website als „nicht gefunden" (404) — das ist der häufigste Stolperstein.
5. Optional `location` (`header`/`footer`) **und** `order` setzen, damit die Seite
ins Menü kommt; optional `embed` für eingebettete Blöcke (siehe unten).
- **Slug bleibt englisch und ohne Umlaute.** `imprint`, `privacy`, `contact`,
`board`, `patrons`. Der `title` darf deutsch mit Umlauten sein (`Förderverein`).
- **Reservierte Slugs.** `posts`, `events`, `faqs` und `/` (die Startseite `home`)
gehören den festen Listen-/Sonderansichten. Eine `pages`-Seite mit einem dieser
Slugs wird von der jeweiligen Ansicht verdeckt und **nicht** angezeigt; in der
Produktion dienen solche Records nur als Menü-Platzhalter.
- **Menü:** Ob eine Seite ins Menü kommt, steht an der Seite selbst — die Felder
`location` (`header` oder `footer`) und `order` (Reihenfolge, ab 1). Die App
baut Kopf- und Fußnavigation daraus; nichts wird im Markup angefasst.