Remove the leftover file content layer and align the docs
This commit is contained in:
1 parent
3fd3796d73
commit
47d9841532
21 files changed
+154
-327
No files matched your search
+42
-22
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
Reference in new issue
Block a user