Die SvelteKit-App liegt unter frontend/, das Backend unter backend/ als eigenständige PocketBase-Instanz mit Dockerfile, docker-compose und versioniertem Schema. - PocketBase-URL über PUBLIC_PB_URL konfigurierbar, Default bleibt die produktive Instanz https://api.stammtisch-hersbruck.de - Schema als Snapshot-Migration der sechs fachlichen Collections (users, teams, events, runs, riders, times), abgezogen von der produktiven Instanz. Verifiziert: ein Erststart gegen leere pb_data legt alle sechs an, Felder und API-Rules stimmen überein. - pb_data und pb_migrations als Bind-Mounts, damit Daten persistieren und im Admin-UI erzeugte Migrationen im Repo landen - Veraltete Dokumentation entfernt: pocketbase_schema.json nannte Collections (stages, results, organizers), die es nicht gibt Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
972 lines
33 KiB
Markdown
972 lines
33 KiB
Markdown
# Repo-Umbau auf `frontend/` und `backend/` — Implementation Plan
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
||
**Goal:** Die SvelteKit-App nach `frontend/` verschieben und unter `backend/` eine eigenständige, lokal per Docker lauffähige PocketBase-Instanz mit versioniertem Schema anlegen.
|
||
|
||
**Architecture:** Monorepo — das vorhandene `.git` bleibt im Root, `frontend/` und `backend/` sind Unterverzeichnisse ohne eigene Repos. Das Frontend spricht PocketBase über die konfigurierbare Variable `PUBLIC_PB_URL` an (Default: Remote-Instanz). Das Backend baut PocketBase in einem Alpine-Container, das Schema liegt als Snapshot-Migration in `backend/pb_migrations/`.
|
||
|
||
**Tech Stack:** SvelteKit 5 / Svelte 5 Runes, TypeScript, Vite 7, Tailwind 4, PocketBase 0.26 (Client) / 0.39.6 (Container), Docker Compose.
|
||
|
||
## Global Constraints
|
||
|
||
- Spec: `docs/superpowers/specs/2026-08-06-repo-frontend-backend-design.md`
|
||
- **Monorepo:** Nur ein `.git`, im Root. In `frontend/` und `backend/` wird **kein** `git init` ausgeführt.
|
||
- **Die Live-Instanz `https://api.stammtisch-hersbruck.de` wird nicht verändert.** Kein Schreibzugriff, keine Migration dagegen anwenden. Nur lesende Aufrufe (`GET /api/collections`).
|
||
- **PocketBase-Version im Container:** exakt `0.39.6`, gepinnt über Build-Arg `PB_VERSION`.
|
||
- **Fachliche Collections:** genau `users, teams, events, runs, riders, times`. System-Collections (`_superusers`, `_externalAuths`, `_mfas`, `_otps`, `_authOrigins`) gehören **nicht** in die Migration.
|
||
- **`frontend/.env` enthält `PB_SUPERUSER_TOKEN` und darf niemals committet werden.** Vor jedem Commit mit `git status` prüfen.
|
||
- **Kein `git push`** — nur auf ausdrückliche Anweisung des Nutzers.
|
||
- Sprache aller neuen Kommentare, READMEs und Commit-Messages: **Deutsch**, mit korrekten Umlauten.
|
||
- Das Repo hat zu Beginn **keinen Commit**. Task 8 legt den initialen Commit an; die Tasks davor committen nicht.
|
||
|
||
## Hinweis zur Task-Abfolge
|
||
|
||
Dieser Plan committet erst am Ende (Task 8), weil das Repo bis dahin keinen Commit hat und die Spec einen einzigen initialen Commit mit fertiger Struktur vorsieht. Die Tasks 1–7 verändern nur den Arbeitsbaum. Jede Task endet stattdessen mit einer eigenen Verifikation.
|
||
|
||
## Dateiübersicht
|
||
|
||
| Datei | Verantwortung |
|
||
|---|---|
|
||
| `frontend/**` | Komplette SvelteKit-App (verschoben aus dem Root) |
|
||
| `frontend/.env` | Laufzeit- und Token-Konfiguration, **nicht** versioniert |
|
||
| `frontend/.env.example` | Dokumentierte Vorlage, versioniert |
|
||
| `frontend/src/lib/stores/pocketbase.svelte.ts` | PocketBase-Client; liest die URL neu aus `PUBLIC_PB_URL` |
|
||
| `backend/Dockerfile` | Baut das PocketBase-Image, Version gepinnt |
|
||
| `backend/docker-compose.yaml` | Service-Definition inkl. Volume und Healthcheck |
|
||
| `backend/entrypoint.sh` | Superuser-Bootstrap beim ersten Start |
|
||
| `backend/pb_migrations/1754400000_init_schema.js` | Schema-Snapshot der sechs Collections |
|
||
| `backend/pb_hooks/.gitkeep` | Platzhalter, damit das Verzeichnis existiert |
|
||
| `backend/.env.example` | Vorlage der Backend-Variablen |
|
||
| `backend/.gitignore` | Schließt `pb_data/` und `.env` aus |
|
||
| `backend/README.md` | Lokaler Start, Schema, Persistenz, Superuser |
|
||
| `.gitignore` (Root) | Ignoriert für beide Teilprojekte |
|
||
| `README.md` (Root) | Übersicht über beide Teile |
|
||
| `CLAUDE.md` | Angepasste Pfade und Befehle |
|
||
|
||
---
|
||
|
||
### Task 1: Frontend nach `frontend/` verschieben
|
||
|
||
**Files:**
|
||
- Verschieben nach `frontend/`: `src/`, `static/`, `scripts/`, `package.json`, `package-lock.json`, `.npmrc`, `svelte.config.js`, `vite.config.ts`, `tsconfig.json`, `components.json`, `.env`
|
||
- Löschen: `node_modules/`, `.svelte-kit/` (im Root)
|
||
|
||
**Interfaces:**
|
||
- Consumes: nichts
|
||
- Produces: Ein vollständiges SvelteKit-Projekt unter `frontend/`, in dem alle folgenden Tasks arbeiten.
|
||
|
||
- [ ] **Step 1: Ist-Zustand von `npm run check` festhalten**
|
||
|
||
Damit später beurteilt werden kann, was eine Regression ist und was schon vorher kaputt war.
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de
|
||
npm run check 2>&1 | tail -20 > /tmp/check-vorher.txt
|
||
cat /tmp/check-vorher.txt
|
||
```
|
||
|
||
Die letzte Zeile nennt die Anzahl Fehler und Warnungen. Diese Zahl notieren — sie ist der Vergleichsmaßstab in Task 3.
|
||
|
||
- [ ] **Step 2: Zielverzeichnis anlegen und Dateien verschieben**
|
||
|
||
`git mv` funktioniert hier nicht, weil noch nichts getrackt ist — daher normales `mv`.
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de
|
||
mkdir -p frontend
|
||
mv src static scripts package.json package-lock.json .npmrc \
|
||
svelte.config.js vite.config.ts tsconfig.json components.json .env \
|
||
frontend/
|
||
```
|
||
|
||
- [ ] **Step 3: Alte Build-Artefakte im Root entfernen**
|
||
|
||
`node_modules` wird nicht mitverschoben — installierte Binaries und Caches enthalten absolute Pfade und wären nach dem Umzug teils unbrauchbar. `.svelte-kit` ist generiert.
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de
|
||
rm -rf node_modules .svelte-kit
|
||
```
|
||
|
||
- [ ] **Step 4: Verschiebung prüfen**
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de
|
||
ls -a frontend
|
||
ls -a
|
||
```
|
||
|
||
Erwartet: In `frontend/` liegen `src`, `static`, `scripts`, `package.json`, `.env` usw. Im Root liegen nur noch `CLAUDE.md`, `docs`, die Markdown-Altdateien, `UML_basic.png`, die drei zu löschenden Schema-Dateien und `.git`/`.gitignore`/`.idea`.
|
||
|
||
- [ ] **Step 5: Abhängigkeiten neu installieren**
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de/frontend
|
||
npm ci
|
||
```
|
||
|
||
Erwartet: Läuft ohne Fehler durch. Bei `EBADENGINE`-Abbruch (`.npmrc` setzt `engine-strict=true`) die Node-Version prüfen (`node -v`) und melden statt `engine-strict` zu entfernen.
|
||
|
||
---
|
||
|
||
### Task 2: PocketBase-URL über `PUBLIC_PB_URL` konfigurierbar machen
|
||
|
||
**Files:**
|
||
- Modify: `frontend/src/lib/stores/pocketbase.svelte.ts:4`
|
||
- Modify: `frontend/.env`
|
||
- Create: `frontend/.env.example`
|
||
|
||
**Interfaces:**
|
||
- Consumes: das verschobene Frontend aus Task 1
|
||
- Produces: `export const api` unverändert als `TypedPocketBase`; die URL kommt nun aus `PUBLIC_PB_URL`. Signatur und Name bleiben gleich, alle bestehenden Importe funktionieren weiter.
|
||
|
||
- [ ] **Step 1: `PUBLIC_PB_URL` in `.env` ergänzen**
|
||
|
||
Die bestehenden Variablen bleiben unangetastet. Ans Ende von `frontend/.env` anfügen:
|
||
|
||
```
|
||
# Basis-URL der PocketBase-Instanz, die das Frontend anspricht.
|
||
# Default ist die produktive Remote-Instanz. Für Arbeit gegen das lokale
|
||
# Backend aus ../backend auf http://127.0.0.1:8090 umstellen.
|
||
PUBLIC_PB_URL=https://api.stammtisch-hersbruck.de
|
||
```
|
||
|
||
- [ ] **Step 2: `frontend/.env.example` anlegen**
|
||
|
||
Diese Datei ist versioniert und enthält **keine** echten Tokens.
|
||
|
||
```
|
||
# Vorlage für die lokale .env — kopieren und ausfüllen:
|
||
# cp .env.example .env
|
||
#
|
||
# Die echte .env steht in .gitignore und darf nicht committet werden,
|
||
# sie enthält den Superuser-Token.
|
||
|
||
# Basis-URL der PocketBase-Instanz, die das Frontend anspricht.
|
||
# Produktiv: https://api.stammtisch-hersbruck.de
|
||
# Lokales Backend aus ../backend: http://127.0.0.1:8090
|
||
PUBLIC_PB_URL=https://api.stammtisch-hersbruck.de
|
||
|
||
# Ziel für die Typgenerierung (npm run generate-pocketbase-types) und die
|
||
# Scripts unter scripts/. Zeigt üblicherweise auf dieselbe Instanz wie oben.
|
||
PB_TYPEGEN_URL=https://api.stammtisch-hersbruck.de/
|
||
|
||
# Superuser-Token für Schema-Zugriffe. Im PocketBase-Admin-UI erzeugen.
|
||
# Niemals committen.
|
||
PB_SUPERUSER_TOKEN=
|
||
PB_TYPEGEN_TOKEN=
|
||
```
|
||
|
||
- [ ] **Step 3: Den Store auf die Variable umstellen**
|
||
|
||
In `frontend/src/lib/stores/pocketbase.svelte.ts` die Zeilen 1–4 ersetzen.
|
||
|
||
Vorher:
|
||
```ts
|
||
import PocketBase, {type AuthRecord, type RecordModel} from 'pocketbase'
|
||
import type {TypedPocketBase} from '$lib/types'
|
||
|
||
export const api = new PocketBase('https://api.stammtisch-hersbruck.de') as TypedPocketBase
|
||
```
|
||
|
||
Nachher:
|
||
```ts
|
||
import PocketBase, {type AuthRecord, type RecordModel} from 'pocketbase'
|
||
import {PUBLIC_PB_URL} from '$env/static/public'
|
||
import type {TypedPocketBase} from '$lib/types'
|
||
|
||
export const api = new PocketBase(PUBLIC_PB_URL) as TypedPocketBase
|
||
```
|
||
|
||
`$env/static/public` ist SvelteKit-intern und benötigt keine Abhängigkeit. Nur Variablen mit dem Präfix `PUBLIC_` sind darüber erreichbar — deshalb heißt sie so.
|
||
|
||
- [ ] **Step 4: Prüfen, dass keine hartkodierte URL zurückbleibt**
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de/frontend
|
||
grep -rn "api\.stammtisch-hersbruck\.de" src/
|
||
```
|
||
|
||
Erwartet: **keine Ausgabe**. Treffer in `src/` müssen ebenfalls auf `PUBLIC_PB_URL` umgestellt werden.
|
||
|
||
---
|
||
|
||
### Task 3: Frontend verifizieren
|
||
|
||
**Files:**
|
||
- Keine Änderungen — reine Prüfung.
|
||
|
||
**Interfaces:**
|
||
- Consumes: Tasks 1 und 2
|
||
- Produces: Nachweis, dass Verschiebung und URL-Umstellung nichts kaputt gemacht haben.
|
||
|
||
- [ ] **Step 1: Typprüfung laufen lassen**
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de/frontend
|
||
npm run check 2>&1 | tail -20
|
||
```
|
||
|
||
Erwartet: Die Fehler-/Warnungszahl ist **nicht höher** als in `/tmp/check-vorher.txt` aus Task 1. Bestehende Fehler von vorher sind keine Regression. Ein neuer Fehler zu `$env/static/public` oder `PUBLIC_PB_URL` bedeutet, dass die Variable in `.env` fehlt oder falsch geschrieben ist.
|
||
|
||
- [ ] **Step 2: Dev-Server starten**
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de/frontend
|
||
npm run dev
|
||
```
|
||
|
||
Erwartet: Vite startet auf Port 31337 ohne Fehler.
|
||
|
||
- [ ] **Step 3: App im Browser prüfen**
|
||
|
||
`http://stammtisch-hersbruck.de.localhost:31337` öffnen.
|
||
|
||
Erwartet: Die Seite lädt. In den DevTools unter *Network* gehen die API-Aufrufe an `https://api.stammtisch-hersbruck.de` — das belegt, dass `PUBLIC_PB_URL` greift. Die Konsole zeigt keine Fehler zu fehlenden Modulen oder undefinierter URL.
|
||
|
||
Danach den Dev-Server mit `Ctrl+C` beenden.
|
||
|
||
---
|
||
|
||
### Task 4: Backend-Grundgerüst anlegen
|
||
|
||
**Files:**
|
||
- Create: `backend/Dockerfile`
|
||
- Create: `backend/docker-compose.yaml`
|
||
- Create: `backend/entrypoint.sh`
|
||
- Create: `backend/.gitignore`
|
||
- Create: `backend/.env.example`
|
||
- Create: `backend/pb_hooks/.gitkeep`
|
||
|
||
**Interfaces:**
|
||
- Consumes: nichts aus vorherigen Tasks
|
||
- Produces: Ein baubarer PocketBase-Container. Task 5 legt die Migration in `backend/pb_migrations/` ab, die das Dockerfile bereits hineinkopiert; Task 6 startet den Container.
|
||
|
||
- [ ] **Step 1: Verzeichnisse anlegen**
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de
|
||
mkdir -p backend/pb_migrations backend/pb_hooks
|
||
touch backend/pb_hooks/.gitkeep
|
||
```
|
||
|
||
`pb_hooks` bleibt vorerst leer — es gibt in diesem Projekt keine serverseitigen Hooks. Das Verzeichnis existiert trotzdem, weil das Dockerfile es kopiert.
|
||
|
||
- [ ] **Step 2: `backend/Dockerfile` schreiben**
|
||
|
||
```dockerfile
|
||
FROM alpine:3.21
|
||
|
||
# Version bewusst gepinnt: Der GitHub-Asset heißt
|
||
# pocketbase_<version>_linux_amd64.zip, ein "latest" gibt es nicht. Ohne Pin
|
||
# zöge jeder Redeploy unangekündigt eine neue Version inklusive möglicher
|
||
# DB-Schema-Migrationen.
|
||
ARG PB_VERSION=0.39.6
|
||
|
||
RUN apk add --no-cache ca-certificates unzip wget
|
||
|
||
RUN wget -q https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/pocketbase_${PB_VERSION}_linux_amd64.zip -O /tmp/pb.zip \
|
||
&& unzip /tmp/pb.zip -d /pb/ \
|
||
&& rm /tmp/pb.zip
|
||
|
||
COPY ./pb_migrations /pb/pb_migrations
|
||
COPY ./pb_hooks /pb/pb_hooks
|
||
|
||
COPY ./entrypoint.sh /pb/entrypoint.sh
|
||
RUN chmod +x /pb/entrypoint.sh
|
||
|
||
EXPOSE 8090
|
||
|
||
ENTRYPOINT ["/pb/entrypoint.sh"]
|
||
|
||
CMD ["/pb/pocketbase", "serve", "--http=0.0.0.0:8090", "--dir=/pb/pb_data", "--migrationsDir=/pb/pb_migrations", "--hooksDir=/pb/pb_hooks"]
|
||
```
|
||
|
||
- [ ] **Step 3: `backend/entrypoint.sh` schreiben**
|
||
|
||
```sh
|
||
#!/bin/sh
|
||
set -e
|
||
|
||
# Superuser nur beim allerersten Start anlegen. Existiert der Account bereits,
|
||
# scheitert "create" von selbst mit "email: Value must be unique." und das
|
||
# Passwort bleibt unangetastet. Damit überschreibt ein Redeploy keine
|
||
# Passwortänderung, die im Admin-UI gemacht wurde.
|
||
#
|
||
# Bewusst NICHT "superuser upsert": das würde bei jedem Start das Passwort aus
|
||
# der Env zurückschreiben und UI-seitige Änderungen still aushebeln.
|
||
#
|
||
# Ohne die beiden Variablen wird der Schritt übersprungen; PocketBase zeigt dann
|
||
# beim ersten Start einen Installer-Link im Log.
|
||
if [ -n "$SUPERUSER_EMAIL" ] && [ -n "$SUPERUSER_PASSWORD" ]; then
|
||
# PocketBase liefert auch im Fehlerfall Exitcode 0 und meldet den Fehler nur
|
||
# auf stdout — deshalb wird hier die Ausgabe ausgewertet, nicht der Status.
|
||
out=$(/pb/pocketbase superuser create "$SUPERUSER_EMAIL" "$SUPERUSER_PASSWORD" \
|
||
--dir=/pb/pb_data 2>&1) || true
|
||
|
||
case "$out" in
|
||
*"must be unique"*)
|
||
echo "Superuser existiert bereits — Passwort bleibt unverändert"
|
||
;;
|
||
*"Successfully created"*)
|
||
echo "$out"
|
||
;;
|
||
*)
|
||
# Unerwarteter Fehler (z. B. zu kurzes Passwort): sichtbar machen und
|
||
# abbrechen, statt mit unklarem Zustand weiterzulaufen.
|
||
echo "Superuser konnte nicht angelegt werden: $out" >&2
|
||
exit 1
|
||
;;
|
||
esac
|
||
else
|
||
echo "SUPERUSER_EMAIL/SUPERUSER_PASSWORD nicht gesetzt — Superuser wird nicht angelegt"
|
||
fi
|
||
|
||
exec "$@"
|
||
```
|
||
|
||
- [ ] **Step 4: `backend/docker-compose.yaml` schreiben**
|
||
|
||
```yaml
|
||
services:
|
||
pocketbase:
|
||
build:
|
||
context: .
|
||
args:
|
||
# Version-Pin. Update: hier bzw. im Dockerfile hochsetzen und committen.
|
||
PB_VERSION: ${PB_VERSION:-0.39.6}
|
||
restart: unless-stopped
|
||
ports:
|
||
- "8090:8090"
|
||
volumes:
|
||
# Persistenz der SQLite-DB inklusive aller Laufzeitdaten, Uploads,
|
||
# Mail-Settings und des Superusers. Ohne dieses Mapping ist nach jedem
|
||
# Redeploy alles weg — das Schema käme zwar aus pb_migrations zurück,
|
||
# die Daten aber nicht.
|
||
- ./pb_data:/pb/pb_data
|
||
environment:
|
||
# Superuser wird nur beim allerersten Start angelegt (siehe entrypoint.sh)
|
||
SUPERUSER_EMAIL: ${SUPERUSER_EMAIL:-}
|
||
SUPERUSER_PASSWORD: ${SUPERUSER_PASSWORD:-}
|
||
healthcheck:
|
||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8090/api/health"]
|
||
interval: 30s
|
||
timeout: 5s
|
||
retries: 3
|
||
start_period: 10s
|
||
```
|
||
|
||
- [ ] **Step 5: `backend/.gitignore` schreiben**
|
||
|
||
```
|
||
pb_data/
|
||
*.db
|
||
.DS_Store
|
||
.env
|
||
.env.*
|
||
!.env.example
|
||
```
|
||
|
||
- [ ] **Step 6: `backend/.env.example` schreiben**
|
||
|
||
```
|
||
# Vorlage für die lokale .env — kopieren und ausfüllen:
|
||
# cp .env.example .env
|
||
#
|
||
# Diese Datei enthält nur Platzhalter und ist versioniert. Die echte .env steht
|
||
# in .gitignore und darf nicht committet werden.
|
||
|
||
# Admin-Zugang, wird nur beim allerersten Start angelegt. Existiert der Account
|
||
# bereits, bleibt das Passwort unverändert — spätere Änderungen gehören ins
|
||
# Admin-UI, nicht in diese Datei. Mindestens 8 Zeichen.
|
||
# Beide Variablen weglassen = PocketBase gibt beim ersten Start einen
|
||
# Installer-Link im Log aus (30 Minuten gültig).
|
||
SUPERUSER_EMAIL=admin@example.de
|
||
SUPERUSER_PASSWORD=bitte-aendern-min-8-zeichen
|
||
|
||
# Optional: überschreibt den in docker-compose.yaml gepinnten Default.
|
||
# Nur setzen, wenn bewusst eine andere PocketBase-Version gebaut werden soll.
|
||
#PB_VERSION=0.39.6
|
||
```
|
||
|
||
- [ ] **Step 7: Dateien prüfen**
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de/backend
|
||
ls -a
|
||
sh -n entrypoint.sh && echo "entrypoint.sh: Syntax ok"
|
||
```
|
||
|
||
Erwartet: Alle sechs Dateien plus `pb_migrations/` und `pb_hooks/` sind da, und die Syntaxprüfung meldet „ok".
|
||
|
||
---
|
||
|
||
### Task 5: Schema-Migration aus der Live-Instanz erzeugen
|
||
|
||
**Files:**
|
||
- Create: `backend/pb_migrations/1754400000_init_schema.js`
|
||
|
||
**Interfaces:**
|
||
- Consumes: `backend/pb_migrations/` aus Task 4; `frontend/.env` (für `PB_TYPEGEN_URL` und `PB_SUPERUSER_TOKEN`) aus Task 1
|
||
- Produces: Eine Migration, die beim Containerstart die sechs Collections `users, teams, events, runs, riders, times` anlegt.
|
||
|
||
- [ ] **Step 1: Live-Schema abrufen und die Migration generieren**
|
||
|
||
Das Schema wird nicht abgeschrieben, sondern aus dem Live-Abruf generiert — 23 KB JSON von Hand zu übertragen wäre fehleranfällig. Der Aufruf ist **rein lesend**.
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de
|
||
set -a && . ./frontend/.env && set +a
|
||
|
||
curl -sL -H "Authorization: $PB_SUPERUSER_TOKEN" \
|
||
"${PB_TYPEGEN_URL%/}/api/collections?perPage=200" -o /tmp/live_schema.json
|
||
|
||
python3 - <<'PY'
|
||
import json
|
||
|
||
with open('/tmp/live_schema.json') as f:
|
||
data = json.load(f)
|
||
|
||
# System-Collections (_superusers, _mfas, ...) legt PocketBase selbst an.
|
||
keep = [c for c in data['items'] if not c['name'].startswith('_')]
|
||
|
||
# Reihenfolge so, dass Relationsziele vor ihren Nutzern stehen.
|
||
order = {'users': 0, 'teams': 1, 'events': 2, 'runs': 3, 'riders': 4, 'times': 5}
|
||
keep.sort(key=lambda c: order.get(c['name'], 99))
|
||
|
||
names = [c['name'] for c in keep]
|
||
expected = ['users', 'teams', 'events', 'runs', 'riders', 'times']
|
||
assert names == expected, f'Unerwartete Collections: {names}'
|
||
|
||
collections = json.dumps(keep, indent=4, ensure_ascii=False)
|
||
ids = json.dumps([c['id'] for c in keep], indent=4)
|
||
|
||
migration = f'''/// <reference path="../pb_data/types.d.ts" />
|
||
|
||
// Schema-Snapshot der sechs fachlichen Collections, abgezogen von der
|
||
// produktiven Instanz. System-Collections (_superusers, _mfas, ...) legt
|
||
// PocketBase selbst an und stehen deshalb nicht hier drin.
|
||
//
|
||
// importCollections wird mit deleteMissing = false aufgerufen: Die Migration
|
||
// legt an und aktualisiert, löscht aber nichts, was nicht im Snapshot steht.
|
||
|
||
migrate((app) => {{
|
||
const collections = {collections}
|
||
|
||
app.importCollections(JSON.stringify(collections), false)
|
||
}}, (app) => {{
|
||
// Rückwärts: die angelegten Collections wieder entfernen, in umgekehrter
|
||
// Reihenfolge, damit keine Relation ins Leere zeigt.
|
||
//
|
||
// "users" bleibt bewusst stehen: PocketBase legt die Auth-Collection selbst
|
||
// an, ein Löschen wäre kein Zurückrollen dieser Migration.
|
||
const ids = {ids}
|
||
|
||
for (const id of ids.slice().reverse()) {{
|
||
if (id === '_pb_users_auth_') continue
|
||
|
||
try {{
|
||
app.delete(app.findCollectionByNameOrId(id))
|
||
}} catch {{
|
||
// Bereits entfernt — nichts zu tun.
|
||
}}
|
||
}}
|
||
}})
|
||
'''
|
||
|
||
with open('backend/pb_migrations/1754400000_init_schema.js', 'w') as f:
|
||
f.write(migration)
|
||
|
||
print('Migration geschrieben,', len(migration), 'Bytes, Collections:', names)
|
||
PY
|
||
```
|
||
|
||
Erwartet: Die Ausgabe nennt die sechs Collections in genau dieser Reihenfolge. Schlägt das `assert` fehl, hat sich das Live-Schema geändert — dann **abbrechen und melden**, nicht die Erwartung anpassen.
|
||
|
||
Der Zugriff scheitert mit HTTP 401, wenn `PB_SUPERUSER_TOKEN` abgelaufen ist. In dem Fall im Admin-UI einen neuen Token erzeugen und in `frontend/.env` eintragen.
|
||
|
||
- [ ] **Step 2: Erzeugte Migration prüfen**
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de
|
||
node --check backend/pb_migrations/1754400000_init_schema.js \
|
||
&& echo "Syntax ok (migrate ist erst zur Laufzeit definiert, das ist erwartet)"
|
||
grep -c '"name"' backend/pb_migrations/1754400000_init_schema.js
|
||
```
|
||
|
||
Erwartet: `node --check` meldet keinen Syntaxfehler. Die `grep`-Zahl liegt deutlich über 50 (sechs Collections mit allen Feldern).
|
||
|
||
- [ ] **Step 3: Sicherstellen, dass keine Tokens in die Migration geraten sind**
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de
|
||
grep -in "token\|secret\|password" backend/pb_migrations/1754400000_init_schema.js | head
|
||
```
|
||
|
||
Erwartet: Treffer nur als **Feldnamen** aus dem Schema (`tokenKey`, `password` in der `users`-Collection) — das sind Felddefinitionen, keine Werte. Erscheint irgendwo ein tatsächlicher Tokenwert, ist die Migration unbrauchbar: abbrechen und melden.
|
||
|
||
---
|
||
|
||
### Task 6: Backend starten und verifizieren
|
||
|
||
**Files:**
|
||
- Keine Änderungen — reine Prüfung.
|
||
|
||
**Interfaces:**
|
||
- Consumes: Tasks 4 und 5
|
||
- Produces: Nachweis, dass ein frischer Container das Schema herstellt.
|
||
|
||
- [ ] **Step 1: Prüfen, ob Docker verfügbar ist**
|
||
|
||
```bash
|
||
docker info >/dev/null 2>&1 && echo "Docker läuft" || echo "Docker NICHT verfügbar"
|
||
```
|
||
|
||
Meldet das „NICHT verfügbar", werden die Steps 2–5 übersprungen. Dann gilt: Das Backend ist **ungeprüft** und muss in Task 8 und im Abschlussbericht ausdrücklich so bezeichnet werden. Nicht als erledigt darstellen.
|
||
|
||
- [ ] **Step 2: Lokale `.env` anlegen und Container bauen**
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de/backend
|
||
cp .env.example .env
|
||
docker compose up -d --build
|
||
```
|
||
|
||
Erwartet: Der Build lädt PocketBase 0.39.6 und startet den Container.
|
||
|
||
Für die lokale Prüfung genügen die Platzhalter aus `.env.example`; das Passwort erfüllt die Mindestlänge von 8 Zeichen. Diese `.env` ist gitignored.
|
||
|
||
- [ ] **Step 3: Health und Superuser-Bootstrap prüfen**
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de/backend
|
||
sleep 5
|
||
curl -s http://127.0.0.1:8090/api/health
|
||
echo
|
||
docker compose logs | grep -i "superuser\|migrat" | tail -10
|
||
```
|
||
|
||
Erwartet: `/api/health` antwortet mit `{"code":200,...}`. Im Log steht `Successfully created new superuser` und ein Hinweis auf die angewandte Migration.
|
||
|
||
- [ ] **Step 4: Schema im Container prüfen**
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de/backend
|
||
set -a && . ./.env && set +a
|
||
TOKEN=$(curl -s -X POST http://127.0.0.1:8090/api/collections/_superusers/auth-with-password \
|
||
-H "Content-Type: application/json" \
|
||
-d "{\"identity\":\"$SUPERUSER_EMAIL\",\"password\":\"$SUPERUSER_PASSWORD\"}" \
|
||
| python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")
|
||
|
||
curl -s -H "Authorization: $TOKEN" \
|
||
"http://127.0.0.1:8090/api/collections?perPage=200" \
|
||
| python3 -c "
|
||
import sys, json
|
||
d = json.load(sys.stdin)
|
||
names = sorted(c['name'] for c in d['items'] if not c['name'].startswith('_'))
|
||
print('Gefunden:', names)
|
||
expected = sorted(['users','teams','events','runs','riders','times'])
|
||
print('OK' if names == expected else 'FEHLT: ' + str(set(expected) - set(names)))
|
||
"
|
||
```
|
||
|
||
Erwartet: `Gefunden:` listet die sechs Collections und die Zeile darunter sagt `OK`.
|
||
|
||
- [ ] **Step 5: Container wieder stoppen**
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de/backend
|
||
docker compose down
|
||
```
|
||
|
||
`pb_data/` bleibt liegen und ist gitignored.
|
||
|
||
---
|
||
|
||
### Task 7: Dokumentation und Root-Dateien anpassen
|
||
|
||
**Files:**
|
||
- Create: `backend/README.md`
|
||
- Modify: `.gitignore` (Root)
|
||
- Modify: `README.md` (Root)
|
||
- Modify: `CLAUDE.md`
|
||
- Delete: `pocketbase_schema.json`, `example_pb_schema.json`, `pocketbase_migrate.zip`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Tasks 1–6
|
||
- Produces: Ein Repo, dessen Dokumentation die neue Struktur beschreibt.
|
||
|
||
- [ ] **Step 1: Veraltete Dateien löschen**
|
||
|
||
Alle drei sind untracked — sie sind nach dem Löschen endgültig weg. Das ist so abgestimmt: `pocketbase_schema.json` nennt Collections (`stages`, `organizers`, `results`), die auf der Live-Instanz nicht existieren, und ist damit nachweislich veraltet.
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de
|
||
rm -f pocketbase_schema.json example_pb_schema.json pocketbase_migrate.zip
|
||
ls -1
|
||
```
|
||
|
||
- [ ] **Step 2: `backend/README.md` schreiben**
|
||
|
||
```markdown
|
||
# stammtisch-hersbruck.de — Backend (PocketBase)
|
||
|
||
PocketBase-Instanz für Events, Läufe, Fahrer, Zeiten und Teams.
|
||
|
||
## Lokal starten
|
||
|
||
`.env` aus der Vorlage anlegen (die `.env` selbst steht in `.gitignore`):
|
||
|
||
cp .env.example .env
|
||
|
||
und die Werte eintragen — `.env.example` beschreibt jede Variable. Dann:
|
||
|
||
docker compose up -d --build
|
||
|
||
Admin-UI: http://127.0.0.1:8090/_/ — Login mit `SUPERUSER_EMAIL`/`SUPERUSER_PASSWORD`.
|
||
|
||
Damit das Frontend gegen diese Instanz läuft, in `../frontend/.env`
|
||
`PUBLIC_PB_URL=http://127.0.0.1:8090` setzen.
|
||
|
||
## Schema
|
||
|
||
Das Schema liegt als versionierte Migration in `pb_migrations/` und wird beim
|
||
Start automatisch angewendet. Es enthält die sechs fachlichen Collections
|
||
`users`, `teams`, `events`, `runs`, `riders`, `times`.
|
||
|
||
`1754400000_init_schema.js` ist ein Snapshot der produktiven Instanz. Er wurde
|
||
per `GET /api/collections` abgezogen und ruft `importCollections` mit
|
||
`deleteMissing = false` auf — die Migration legt an und aktualisiert, löscht
|
||
aber nichts, was nicht im Snapshot steht.
|
||
|
||
Schema-Änderungen im Admin-UI erzeugen in `pb_migrations/` neue Dateien; diese
|
||
müssen committet werden.
|
||
|
||
Ein frisch gestarteter Container hat damit dasselbe Schema wie die produktive
|
||
Instanz — aber **keine** Daten.
|
||
|
||
## Datenpersistenz
|
||
|
||
Sämtliche Laufzeitdaten liegen in der SQLite-DB unter `/pb/pb_data`: Datensätze,
|
||
Uploads, die SMTP-Einstellungen und der Superuser. Ohne gemountetes Volume auf
|
||
diesem Pfad sind sie nach jedem Redeploy verloren.
|
||
|
||
Das Volume-Mapping `./pb_data:/pb/pb_data` steht in der `docker-compose.yaml`,
|
||
damit die Persistenz-Konfiguration versioniert im Repo liegt.
|
||
|
||
**Trügerisches Signal:** Die Collections kommen aus der Migration und sind nach
|
||
einem Redeploy auch dann da, wenn gar kein Volume existiert. Sie taugen **nicht**
|
||
als Nachweis funktionierender Persistenz — das zeigt nur ein Datensatz, der in
|
||
keiner Migration steht.
|
||
|
||
Mount prüfen:
|
||
|
||
docker inspect <container> --format '{{json .Mounts}}' | jq
|
||
|
||
Erscheint dort kein Eintrag mit `"Destination": "/pb/pb_data"`, fehlt die Persistenz.
|
||
|
||
`pb_data/` steht in `.gitignore` und gehört dort auch hin.
|
||
|
||
## Superuser-Verhalten
|
||
|
||
`entrypoint.sh` ruft `superuser create` auf — bewusst **nicht** `upsert`.
|
||
Existiert der Account bereits, scheitert `create` mit „Value must be unique" und
|
||
das Passwort bleibt unangetastet. Eine im Admin-UI vorgenommene
|
||
Passwortänderung überlebt damit jeden Redeploy.
|
||
|
||
| Situation | Verhalten |
|
||
|---|---|
|
||
| Erster Start, Variablen gesetzt | Superuser wird angelegt |
|
||
| Neustart, Account existiert | „Superuser existiert bereits — Passwort bleibt unverändert" |
|
||
| Passwort im UI geändert, dann Neustart | UI-Passwort gilt weiter, Env wird ignoriert |
|
||
| Variablen nicht gesetzt | Übersprungen, PocketBase gibt Installer-Link im Log aus |
|
||
| Passwort zu kurz (< 8 Zeichen) | Container bricht mit Exitcode 1 ab |
|
||
|
||
Passwort später ändern: **im Admin-UI**, nicht über die Env — eine Änderung der
|
||
Env-Variable hat keine Wirkung mehr, sobald der Account existiert.
|
||
|
||
## PocketBase aktualisieren
|
||
|
||
Die Version ist über das Build-Arg `PB_VERSION` gepinnt (Dockerfile und
|
||
`docker-compose.yaml`). Wert hochsetzen, committen, neu bauen.
|
||
|
||
Ein `latest` gibt es bewusst nicht: Der GitHub-Asset heißt
|
||
`pocketbase_<version>_linux_amd64.zip`, enthält die Version also im Dateinamen.
|
||
Der Pin ist auch gewollt — sonst zieht jeder Rebuild unangekündigt eine neue
|
||
Version, inklusive möglicher DB-Schema-Migrationen.
|
||
|
||
Vor einem Update: Changelog prüfen
|
||
(https://github.com/pocketbase/pocketbase/releases) und `pb_data` sichern —
|
||
PocketBase migriert die DB beim Start automatisch, ein Downgrade ist danach
|
||
nicht mehr ohne Weiteres möglich.
|
||
```
|
||
|
||
- [ ] **Step 3: Root-`.gitignore` ersetzen**
|
||
|
||
Die bisherige `.gitignore` stammt aus dem SvelteKit-Template und geht davon aus, dass die App im Root liegt. Vollständiger neuer Inhalt:
|
||
|
||
```
|
||
# Dependencies
|
||
node_modules
|
||
|
||
# Build-Output
|
||
.output
|
||
.vercel
|
||
.netlify
|
||
.wrangler
|
||
.svelte-kit
|
||
build
|
||
|
||
# OS
|
||
.DS_Store
|
||
Thumbs.db
|
||
|
||
# Env — enthält Tokens, gehört nie ins Repo
|
||
.env
|
||
.env.*
|
||
!.env.example
|
||
|
||
# Vite
|
||
vite.config.js.timestamp-*
|
||
vite.config.ts.timestamp-*
|
||
|
||
# PocketBase-Laufzeitdaten
|
||
backend/pb_data/
|
||
*.db
|
||
|
||
# IDE
|
||
.idea/
|
||
```
|
||
|
||
Die Muster ohne führenden Schrägstrich greifen in jedem Unterverzeichnis — `node_modules` deckt damit auch `frontend/node_modules` ab.
|
||
|
||
- [ ] **Step 4: Root-`README.md` ersetzen**
|
||
|
||
```markdown
|
||
# stammtisch-hersbruck.de
|
||
|
||
Zeitmessung und Verwaltung für Läufe des Stammtisch Hersbruck.
|
||
|
||
Das Repository enthält beide Teile der Anwendung:
|
||
|
||
| Verzeichnis | Inhalt |
|
||
|---|---|
|
||
| [`frontend/`](frontend/) | SvelteKit-5-Anwendung (Svelte 5 Runes, Tailwind 4) |
|
||
| [`backend/`](backend/) | PocketBase-Instanz — Dockerfile, Schema-Migrationen |
|
||
|
||
## Schnellstart
|
||
|
||
Frontend gegen die produktive Instanz:
|
||
|
||
cd frontend
|
||
cp .env.example .env # Werte eintragen
|
||
npm ci
|
||
npm run dev
|
||
|
||
Die App läuft dann auf http://stammtisch-hersbruck.de.localhost:31337
|
||
|
||
Backend lokal (optional — der Default zeigt auf die produktive Instanz):
|
||
|
||
cd backend
|
||
cp .env.example .env # Werte eintragen
|
||
docker compose up -d --build
|
||
|
||
Danach in `frontend/.env` `PUBLIC_PB_URL=http://127.0.0.1:8090` setzen.
|
||
|
||
Details stehen in [`backend/README.md`](backend/README.md).
|
||
|
||
## PocketBase
|
||
|
||
Produktiv: https://api.stammtisch-hersbruck.de
|
||
|
||
Welche Instanz das Frontend anspricht, entscheidet `PUBLIC_PB_URL` in
|
||
`frontend/.env`. Das Schema ist als Migration in `backend/pb_migrations/`
|
||
versioniert.
|
||
```
|
||
|
||
- [ ] **Step 5: `CLAUDE.md` an die neue Struktur anpassen**
|
||
|
||
Drei Stellen ändern, alles andere bleibt.
|
||
|
||
**a)** Im Abschnitt „Development Commands" den einleitenden Satz und den Codeblock ersetzen durch:
|
||
|
||
````markdown
|
||
Alle Frontend-Befehle laufen aus `frontend/`:
|
||
|
||
```bash
|
||
cd frontend
|
||
|
||
# Start development server (runs on http://stammtisch-hersbruck.de.localhost:31337)
|
||
npm run dev
|
||
|
||
# Build for production
|
||
npm run build
|
||
|
||
# Preview production build
|
||
npm run preview
|
||
|
||
# Type-check Svelte files
|
||
npm run check
|
||
|
||
# Type-check with watch mode
|
||
npm run check:watch
|
||
|
||
# Generate TypeScript types from PocketBase schema
|
||
npm run generate-pocketbase-types
|
||
```
|
||
|
||
Backend (PocketBase) aus `backend/`:
|
||
|
||
```bash
|
||
cd backend
|
||
|
||
# PocketBase lokal starten (Admin-UI auf http://127.0.0.1:8090/_/)
|
||
docker compose up -d --build
|
||
|
||
# Stoppen
|
||
docker compose down
|
||
```
|
||
````
|
||
|
||
**b)** Im Abschnitt „Backend Integration (PocketBase)" den ersten Aufzählungspunkt ersetzen:
|
||
|
||
Vorher:
|
||
```markdown
|
||
- **API Base URL**: `https://api.stammtisch-hersbruck.de`
|
||
```
|
||
|
||
Nachher:
|
||
```markdown
|
||
- **API Base URL**: konfigurierbar über `PUBLIC_PB_URL` in `frontend/.env`.
|
||
Default ist die produktive Instanz `https://api.stammtisch-hersbruck.de`;
|
||
für das lokale Backend aus `backend/` auf `http://127.0.0.1:8090` umstellen.
|
||
- **Schema**: versioniert als Migration in `backend/pb_migrations/`. Änderungen
|
||
im Admin-UI erzeugen dort neue Dateien, die committet werden müssen.
|
||
```
|
||
|
||
Im selben Abschnitt die Collections-Zeile korrigieren — `organizers`, `stages` und `results` existieren nicht:
|
||
|
||
Vorher:
|
||
```markdown
|
||
- **Collections**: events, organizers, results, riders, stages, users
|
||
```
|
||
|
||
Nachher:
|
||
```markdown
|
||
- **Collections**: users, teams, events, runs, riders, times
|
||
```
|
||
|
||
**c)** Im Abschnitt „Project Configuration" ergänzen:
|
||
|
||
```markdown
|
||
- **Repo-Struktur**: Monorepo mit `frontend/` (SvelteKit) und `backend/`
|
||
(PocketBase). Ein einziges Git-Repo im Root.
|
||
```
|
||
|
||
Und den Pfad-Alias-Punkt präzisieren:
|
||
|
||
Vorher:
|
||
```markdown
|
||
- **Path alias**: `@/*` resolves to `./src/lib/*` (configured in svelte.config.js)
|
||
```
|
||
|
||
Nachher:
|
||
```markdown
|
||
- **Path alias**: `@/*` resolves to `./src/lib/*` (configured in frontend/svelte.config.js)
|
||
```
|
||
|
||
- [ ] **Step 6: Dokumentation gegenprüfen**
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de
|
||
grep -n "organizers\|stages\|results" CLAUDE.md
|
||
```
|
||
|
||
Erwartet: Treffer nur dort, wo es fachlich nicht um Collections geht (etwa im Satz über den Zweck der App). Steht `organizers`/`stages` noch in der Collections-Liste, wurde Step 5b nicht vollständig ausgeführt.
|
||
|
||
---
|
||
|
||
### Task 8: Initialen Commit anlegen
|
||
|
||
**Files:**
|
||
- Keine inhaltlichen Änderungen.
|
||
|
||
**Interfaces:**
|
||
- Consumes: Tasks 1–7
|
||
- Produces: Den ersten Commit des Repos mit der fertigen Struktur.
|
||
|
||
- [ ] **Step 1: Prüfen, was committet würde**
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de
|
||
git add -A
|
||
git status --short
|
||
```
|
||
|
||
- [ ] **Step 2: Sicherstellen, dass keine Secrets und keine Artefakte dabei sind**
|
||
|
||
Das ist der wichtigste Schritt dieser Task — `frontend/.env` enthält `PB_SUPERUSER_TOKEN`.
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de
|
||
git diff --cached --name-only | grep -E "\.env$|\.env\.|node_modules|\.svelte-kit|pb_data" \
|
||
|| echo "Sauber: keine .env, keine node_modules, kein pb_data im Index"
|
||
```
|
||
|
||
Erwartet: die Meldung „Sauber…". Erscheint stattdessen ein Treffer (außer `.env.example`), **nicht committen**, sondern die Datei mit `git rm --cached <pfad>` aus dem Index nehmen und die `.gitignore` korrigieren.
|
||
|
||
Zusätzlich zur Sicherheit:
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de
|
||
git diff --cached | grep -in "PB_SUPERUSER_TOKEN=." | grep -v "example" | head
|
||
```
|
||
|
||
Erwartet: **keine Ausgabe**. Ein Treffer bedeutet, dass ein echter Tokenwert im Commit landen würde.
|
||
|
||
- [ ] **Step 3: Committen**
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de
|
||
git commit -m "$(cat <<'EOF'
|
||
chore: Repo-Struktur mit frontend/ und backend/ aufsetzen
|
||
|
||
Die SvelteKit-App liegt unter frontend/, das Backend unter backend/ als
|
||
eigenständige PocketBase-Instanz mit Dockerfile, docker-compose und
|
||
versioniertem Schema.
|
||
|
||
- PocketBase-URL über PUBLIC_PB_URL konfigurierbar, Default bleibt die
|
||
produktive Instanz
|
||
- Schema als Snapshot-Migration der sechs fachlichen Collections
|
||
(users, teams, events, runs, riders, times)
|
||
- Veraltete Schema-Dateien im Root entfernt: pocketbase_schema.json nannte
|
||
Collections, die auf der Live-Instanz nicht existieren
|
||
|
||
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
EOF
|
||
)"
|
||
```
|
||
|
||
- [ ] **Step 4: Ergebnis prüfen**
|
||
|
||
```bash
|
||
cd /home/dne/Projekte/stammtisch-hersbruck.de
|
||
git log --stat -1 | head -30
|
||
git status --short
|
||
```
|
||
|
||
Erwartet: Ein Commit, `git status` ist leer.
|
||
|
||
**Kein `git push`.** Der bleibt ausdrücklicher Anweisung des Nutzers vorbehalten.
|
||
|
||
---
|
||
|
||
## Abschlussbericht
|
||
|
||
Nach Task 8 an den Nutzer berichten:
|
||
|
||
- Was verifiziert wurde und womit (`npm run check`, Dev-Server im Browser, `/api/health`, Collection-Abgleich im Container)
|
||
- Falls Docker in Task 6 nicht verfügbar war: das Backend ausdrücklich als **ungeprüft** benennen, nicht als erledigt
|
||
- Die drei gelöschten Dateien nennen
|
||
- Dass nicht gepusht wurde
|