diff --git a/CLAUDE.md b/CLAUDE.md index 570d8d8..be11c9e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 `/` + 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//`. +- Ö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`). diff --git a/Directory.Packages.props b/Directory.Packages.props index 3dbf02a..35b56c0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -9,7 +9,6 @@ - diff --git a/Elternbeirat.Web/Content/events.yml b/Elternbeirat.Web/Content/events.yml deleted file mode 100644 index 5d8f418..0000000 --- a/Elternbeirat.Web/Content/events.yml +++ /dev/null @@ -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 diff --git a/Elternbeirat.Web/Content/pages/datenschutz.md b/Elternbeirat.Web/Content/pages/datenschutz.md deleted file mode 100644 index 813951e..0000000 --- a/Elternbeirat.Web/Content/pages/datenschutz.md +++ /dev/null @@ -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.* diff --git a/Elternbeirat.Web/Content/pages/downloads.md b/Elternbeirat.Web/Content/pages/downloads.md deleted file mode 100644 index d4ed098..0000000 --- a/Elternbeirat.Web/Content/pages/downloads.md +++ /dev/null @@ -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)* diff --git a/Elternbeirat.Web/Content/pages/faq-elternarbeit.md b/Elternbeirat.Web/Content/pages/faq-elternarbeit.md deleted file mode 100644 index e26735b..0000000 --- a/Elternbeirat.Web/Content/pages/faq-elternarbeit.md +++ /dev/null @@ -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). diff --git a/Elternbeirat.Web/Content/pages/faq-elterneuro.md b/Elternbeirat.Web/Content/pages/faq-elterneuro.md deleted file mode 100644 index 3465b3d..0000000 --- a/Elternbeirat.Web/Content/pages/faq-elterneuro.md +++ /dev/null @@ -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). diff --git a/Elternbeirat.Web/Content/pages/faq-mensa.md b/Elternbeirat.Web/Content/pages/faq-mensa.md deleted file mode 100644 index 3d5f91c..0000000 --- a/Elternbeirat.Web/Content/pages/faq-mensa.md +++ /dev/null @@ -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.* diff --git a/Elternbeirat.Web/Content/pages/faq-schliessfach.md b/Elternbeirat.Web/Content/pages/faq-schliessfach.md deleted file mode 100644 index 7ab707d..0000000 --- a/Elternbeirat.Web/Content/pages/faq-schliessfach.md +++ /dev/null @@ -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.* diff --git a/Elternbeirat.Web/Content/pages/faq.md b/Elternbeirat.Web/Content/pages/faq.md deleted file mode 100644 index c88fabf..0000000 --- a/Elternbeirat.Web/Content/pages/faq.md +++ /dev/null @@ -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.* diff --git a/Elternbeirat.Web/Content/pages/foerderverein.md b/Elternbeirat.Web/Content/pages/foerderverein.md deleted file mode 100644 index ecb6bfe..0000000 --- a/Elternbeirat.Web/Content/pages/foerderverein.md +++ /dev/null @@ -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.* diff --git a/Elternbeirat.Web/Content/pages/impressum.md b/Elternbeirat.Web/Content/pages/impressum.md deleted file mode 100644 index 3de2511..0000000 --- a/Elternbeirat.Web/Content/pages/impressum.md +++ /dev/null @@ -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.* diff --git a/Elternbeirat.Web/Content/pages/kontakt.md b/Elternbeirat.Web/Content/pages/kontakt.md deleted file mode 100644 index 5e2ccfb..0000000 --- a/Elternbeirat.Web/Content/pages/kontakt.md +++ /dev/null @@ -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.* diff --git a/Elternbeirat.Web/Content/pages/vorstandsteam.md b/Elternbeirat.Web/Content/pages/vorstandsteam.md deleted file mode 100644 index df13a9d..0000000 --- a/Elternbeirat.Web/Content/pages/vorstandsteam.md +++ /dev/null @@ -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.* diff --git a/Elternbeirat.Web/Content/posts/neue-sporthalle-eroeffnet.md b/Elternbeirat.Web/Content/posts/neue-sporthalle-eroeffnet.md deleted file mode 100644 index f59270e..0000000 --- a/Elternbeirat.Web/Content/posts/neue-sporthalle-eroeffnet.md +++ /dev/null @@ -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.* diff --git a/Elternbeirat.Web/Content/posts/neuer-vorstand-gewaehlt.md b/Elternbeirat.Web/Content/posts/neuer-vorstand-gewaehlt.md deleted file mode 100644 index 8cb545c..0000000 --- a/Elternbeirat.Web/Content/posts/neuer-vorstand-gewaehlt.md +++ /dev/null @@ -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).* diff --git a/Elternbeirat.Web/Elternbeirat.Web.csproj b/Elternbeirat.Web/Elternbeirat.Web.csproj index 70c00f9..a245ec5 100644 --- a/Elternbeirat.Web/Elternbeirat.Web.csproj +++ b/Elternbeirat.Web/Elternbeirat.Web.csproj @@ -19,7 +19,6 @@ - - - - - diff --git a/docs/architektur.md b/docs/architektur.md index a6f481c..f16b13a 100644 --- a/docs/architektur.md +++ b/docs/architektur.md @@ -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 diff --git a/docs/entwicklung.md b/docs/entwicklung.md index ac2eab2..f4783db 100644 --- a/docs/entwicklung.md +++ b/docs/entwicklung.md @@ -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 , 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(collection, sort, ct)` already does the `public=true` + filter, the sort and the deserialization, so a new type is a one-liner: + `public Task> GetMinutesAsync(CancellationToken ct = default)` + `=> GetRecordsAsync("minutes", "-date", ct);` +3. **Feature component.** Add a component under `Features//` 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. diff --git a/docs/recht.md b/docs/recht.md index bc0efac..784f5fe 100644 --- a/docs/recht.md +++ b/docs/recht.md @@ -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) diff --git a/docs/redaktion.md b/docs/redaktion.md index 655c8f9..13d09f3 100644 --- a/docs/redaktion.md +++ b/docs/redaktion.md @@ -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 `/` +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.