Bisher lief keine Migration automatisch: dem Runtime-Image fehlten Prisma-CLI, prisma/migrations/ und prisma.config.ts (nur dist+generated wurden kopiert, prisma war devDependency). Jetzt: - prisma von devDependencies zu dependencies verschoben (wird fuer migrate:deploy im --omit=dev Runtime-Image gebraucht) - neues npm-Script migrate:deploy (prisma migrate deploy) - Dockerfile kopiert prisma/, prisma.config.ts zusaetzlich ins Runtime-Image - deploy/k8s/backend/migrate-job.yaml: Job, der dasselbe Backend-Image mit ueberschriebenem command nutzt statt eines eigenen Migrations-Images, laeuft einmalig vor dem Rollout (Reihenfolge/kubectl-Befehle als Kommentar in der Datei) Verifiziert: tsc, 253/253 Unit-Tests, Build clean, `npx prisma --version` laedt prisma.config.ts korrekt.
117 lines
11 KiB
Markdown
117 lines
11 KiB
Markdown
# ApiCore – Architektur
|
||
|
||
Basiert auf [`docs/lastenheft.md`](docs/lastenheft.md). Diese Datei beschreibt, WIE die fachlichen Anforderungen technisch umgesetzt werden.
|
||
|
||
## 1. Backend – Modulschnitt (NestJS)
|
||
|
||
Ein NestJS-Modul pro fachlichem Bereich, Reihenfolge entspricht Baureihenfolge in TASKS.md:
|
||
|
||
| Modul | Zweck | Abhängig von |
|
||
|---|---|---|
|
||
| `AuthModule` | Login, Refresh, Passwort-Reset, JWT-Strategien | – |
|
||
| `UsersModule` | Benutzerverwaltung, Rollen | Auth |
|
||
| `AuditModule` | Audit-Log-Grundstruktur (Service `AuditLogService`, wird von anderen Modulen injiziert) | Users |
|
||
| `ApiariesModule` | Bienenstände | Auth |
|
||
| `ColoniesModule` | Völker, Standortwechsel-Historie, Ableger/Vereinigung | Apiaries |
|
||
| `QueensModule` | Königinnen, Volk-Historie | Colonies |
|
||
| `InspectionsModule` | Durchsichten | Colonies |
|
||
| `TasksModule` | Aufgaben, polymorph verknüpft | – |
|
||
| `VarroaModule` | Varroamessungen | Colonies |
|
||
| `TreatmentsModule` | Behandlungen | Colonies |
|
||
| `FeedingsModule` | Fütterungen | Colonies |
|
||
| `HarvestsModule` | Ernten | Apiaries, Colonies |
|
||
| `ExtractionsModule` | Schleuderungen | Harvests |
|
||
| `HoneyBatchesModule` | Honigchargen, Honigmessungen | Extractions |
|
||
| `StorageModule` | Lagerbehälter, einfache Lagerverwaltung (StockItem/StockMovement) | HoneyBatches |
|
||
| `DashboardModule` | Aggregations-Endpunkte | alle o.g. |
|
||
| `SearchModule` | globale Suche, Filter-Bausteine | alle o.g. |
|
||
| `ExportModule` | CSV/JSON/PDF-Export | alle o.g. |
|
||
|
||
Jedes Modul: `*.controller.ts`, `*.service.ts`, `dto/`, `*.module.ts`, `*.spec.ts` (Unit), `test/*.e2e-spec.ts` (Integration). Rollenprüfung über `@Roles()`-Decorator + `RolesGuard`, angewendet pro Endpunkt – niemals nur UI-seitig.
|
||
|
||
Alle Routen unter `/api/v1/...`, versioniert über NestJS `URI Versioning`. OpenAPI-Doku automatisch über `@nestjs/swagger`, erreichbar unter `/api/v1/docs`.
|
||
|
||
## 2. Frontend – Seitenstruktur (Next.js App Router)
|
||
|
||
```
|
||
app/
|
||
├── (auth)/login/
|
||
├── (app)/ geschütztes Layout mit Bottom-Nav (mobil) / Sidebar (Desktop)
|
||
│ ├── dashboard/
|
||
│ ├── apiaries/[id]/
|
||
│ ├── colonies/[id]/
|
||
│ ├── queens/[id]/
|
||
│ ├── inspections/new/
|
||
│ ├── tasks/
|
||
│ ├── varroa/new/
|
||
│ ├── treatments/new/
|
||
│ ├── feedings/new/
|
||
│ ├── harvests/new/
|
||
│ ├── honey-batches/[id]/
|
||
│ ├── storage/
|
||
│ ├── search/
|
||
│ └── settings/users/ nur Administrator
|
||
```
|
||
|
||
Zentrale Schnellaktion "+ Neu" (Abschnitt 22): permanent sichtbarer Floating-Button in der mobilen Bottom-Nav, öffnet Auswahl-Sheet (Durchsicht, Fütterung, Behandlung, Varroamessung, Aufgabe, Ernte, Volk, Königin) → direkt zum jeweiligen Schnellerfassungs-Formular, mobil möglichst 1-2 Taps bis zum ersten Eingabefeld.
|
||
|
||
Datenzugriff ausschließlich über TanStack Query gegen die Backend-API (kein direkter DB-Zugriff, kein Next.js-eigenes Datenbank-Layer) – erzwingt API-first auch praktisch.
|
||
|
||
## 3. Auth-Flow
|
||
|
||
1. Login (`POST /api/v1/auth/login`) mit E-Mail/Passwort → Access-Token (kurzlebig, ~15 min, JWT) + Refresh-Token (langlebig, ~7 Tage, in DB als Hash gespeichert, rotierend).
|
||
2. Frontend hält Access-Token im Memory/React-Context, Refresh-Token als httpOnly-Cookie.
|
||
3. Bei 401 automatischer Refresh über `POST /api/v1/auth/refresh`, danach Retry.
|
||
4. Jeder geschützte Endpunkt: `JwtAuthGuard` (Authentifizierung) + `RolesGuard` (Autorisierung nach Rolle aus Abschnitt 3).
|
||
5. API-Client-Rolle nutzt dieselben JWT-Endpunkte mit client-credentials-artigem Service-Account (kein OAuth2-Server im MVP, siehe Annahmen).
|
||
|
||
## 4. Datenfluss / Rückverfolgbarkeit
|
||
|
||
Kernkette (Abschnitt 16, 29) relational über Fremdschlüssel, nicht über Textfelder:
|
||
|
||
```
|
||
Apiary → Colony → (Inspection | Treatment | Feeding | VarroaMeasurement)
|
||
Apiary/Colony → Harvest → Extraction → HoneyBatch → StorageContainer
|
||
```
|
||
|
||
`HoneyBatch` referenziert `Extraction`, `Extraction` referenziert n `Harvest`s (m:n über `ExtractionHarvest`), `Harvest` referenziert n `Colony`s (m:n über `HarvestColony`) + eine `Apiary`. Damit ist "von welchem Volk stammt Charge X" eine reine Join-Abfrage, kein Freitext.
|
||
|
||
Historie statt Löschen: alle fachlichen Haupttabellen haben `status`/`deactivatedAt` statt Hard-Delete. Verschiebungen (Volk↔Stand) und Königinnenwechsel werden als eigene Historientabellen geführt (`ColonyApiaryHistory`, `QueenColonyHistory`), nicht durch Überschreiben des aktuellen Zustands.
|
||
|
||
## 5. Technische Leitentscheidungen (bestätigt, keine Abweichung)
|
||
|
||
- Validierung: **class-validator/class-transformer** (nicht Zod) – konsistent mit NestJS-Idiomatik und `@nestjs/swagger`-Decorators, ein Schema-Ort statt zwei.
|
||
- Ein gemeinsames Git-Repo im `ApiCore/`-Root für Backend + Frontend (Monorepo-artig, aber zwei unabhängige `package.json`, kein Turborepo/Nx-Overhead im MVP).
|
||
- Package-Manager: npm (in beiden Projekten, keine Vermischung mit pnpm/yarn).
|
||
- Backend-Tests: Jest (NestJS-Standard). Frontend-Tests: Vitest + React Testing Library.
|
||
|
||
## 6. Annahmen & offene Fragen
|
||
|
||
Fachliche Annahmen, getroffen weil das Lastenheft an der Stelle keine eindeutige Vorgabe macht. Bei Bedarf mit dem Fachbereich klären, aktuell aber Grundlage der Umsetzung:
|
||
|
||
1. **HoneyMeasurement als MVP-Entität**: Abschnitt 26 MUSS-Liste nennt Honigmessungen nicht explizit, Abschnitt 4.17 aber als eigenen Unterpunkt und Abschnitt 30 verlangt Wassergehalt schnell abrufbar. Da sie klein und für die Chargen-Kette relevant ist, wird sie als Teil des `HoneyBatchesModule` im MVP mitgebaut statt auf v1.x verschoben.
|
||
2. **Treatment auf Einzelvolk statt "Völkergruppe"**: Abschnitt 4.11 erlaubt "Volk oder Völkergruppe". MVP modelliert `Treatment` pro Volk (`colonyId` Pflichtfeld); eine Gruppenbehandlung wird im MVP als mehrere Treatment-Datensätze mit gleicher `batchNumber`/Notiz erfasst statt eigener Gruppen-Entität. Spätere Migration kann eine `TreatmentGroup`-Entität nachziehen, falls das in der Praxis zu unhandlich ist.
|
||
3. **Lagerverwaltung MVP-Umfang**: "grundlegende Lagerverwaltung" (Abschnitt 26 Punkt 12) wird als generisches `StockItem` + `StockMovement` (Kategorie Honig/Verbrauchsmaterial/Imkereimaterial) umgesetzt. Das detaillierte Inventar mit Wartungsdaten (Abschnitt 4.22) bleibt v1.x/"SOLL", wie im Lastenheft vorgesehen.
|
||
4. **API-Client-Rolle im MVP**: kein vollwertiger OAuth2/Client-Credentials-Server. Ein API-Client ist ein `User` mit Rolle `API_CLIENT` und einem langlebigen Service-Refresh-Token; vollwertiges Scopes-/Client-Management ist SOLL/KANN.
|
||
5. **Backup-fähige Struktur**: kein eigenes Backup-Tooling im Code. Umgesetzt als: reproduzierbare Prisma-Migrationen (kein manuelles Schema-Drift), dokumentierte `pg_dump`/PITR-Strategie in diesem Dokument (Betrieb obliegt dem bestehenden Cluster/DBA), `/health`-Endpoint für Betriebsüberwachung.
|
||
6. **Deviations vom Leitentscheidungs-Rahmen**: bisher keine. Wird ergänzt, falls während der Umsetzung ein konkreter Grund für eine Abweichung von NestJS/Prisma/Next.js/Tailwind/shadcn/TanStack Query auftritt.
|
||
7. **Dokumentenspeicher als lokales Volume (Modul 29)**: `DocumentsModule` speichert Uploads unterhalb von `DOCUMENTS_STORAGE_PATH` auf dem lokalen Dateisystem des Backend-Pods, keine Objektspeicher-Anbindung (S3-kompatibel o.ä.) im MVP/v1.x. Der Pfad ist über die ConfigMap austauschbar, wie in Abschnitt 27 des Lastenhefts vorgesehen. Wichtig für den Betrieb: das aktuelle `deploy/k8s`-Deployment läuft mit `replicas: 2` ohne geteiltes Volume – für konsistente Uploads über beide Pods hinweg muss vor Produktivbetrieb mit Dokumenten-Feature ein `ReadWriteMany`-PVC (z.B. NFS- oder EFS-basiert) gemountet und `DOCUMENTS_STORAGE_PATH` darauf gesetzt werden, sonst sieht nur der Pod, der die Datei entgegengenommen hat, sie auch beim Download wieder.
|
||
|
||
## 7. Kubernetes
|
||
|
||
`deploy/k8s/` enthält je Projekt: `deployment.yaml`, `service.yaml`, `ingress.yaml`, sowie `configmap.example.yaml` und `secret.example.yaml` (Platzhalter, keine echten Werte). Backend exponiert `GET /health` (liveness) und `GET /health/ready` (readiness, prüft DB-Verbindung). Beide Projekte je ein schlankes Multi-Stage-Dockerfile (Build-Stage mit vollem `node_modules`, Runtime-Stage mit `npm ci --omit=dev` bzw. Next.js `output: 'standalone'`).
|
||
|
||
`deploy/k8s/postgres/` enthält das PostgreSQL-`StatefulSet` (ein Replica, `volumeClaimTemplates` für persistente Daten, `postgres:18-alpine`), den zugehörigen headless `Service` (`apicore-postgres`, DNS-Name fürs Backend) sowie `secret.example.yaml` für `POSTGRES_USER`/`POSTGRES_PASSWORD`/`POSTGRES_DB`. `deploy/k8s/backend/secret.example.yaml`s `DATABASE_URL` zeigt auf diesen Service.
|
||
|
||
## 8. Backup & Restore
|
||
|
||
**Schema-Migrationen (Reproduzierbarkeit):** Ausschließlich `prisma/migrations/` als Quelle der Wahrheit. Lokal/Entwicklung: `npx prisma migrate dev`. Gegen jede Produktions- oder Staging-Datenbank ausschließlich `npx prisma migrate deploy` (idempotent, wendet nur fehlende Migrationen an, kein Schema-Drift). `prisma db push` wird an keiner Stelle im Code, in Skripten oder im Dockerfile verwendet und ist für Produktions-/Staging-Umgebungen untersagt. `migrate deploy` läuft als eigener Schritt vor dem Rollout, nicht im `CMD` des Runtime-Images, damit mehrere Pod-Replicas nicht gleichzeitig Migrationen ausführen: `deploy/k8s/backend/migrate-job.yaml` ist ein Kubernetes-`Job`, der dasselbe Backend-Image mit überschriebenem `command` (`npm run migrate:deploy`) nutzt – kein separates Migrations-Image. Reihenfolge und `kubectl`-Befehle stehen als Kommentar in der Datei.
|
||
|
||
**Datensicherung:** Seit der Entscheidung, PostgreSQL selbst im Cluster zu betreiben (siehe CLAUDE.md), liegt die Backup-Verantwortung bei diesem Projekt, nicht mehr bei einem externen Cluster-/DBA-Betrieb. Aktuell **noch nicht umgesetzt** (offener Punkt, siehe TASKS.md) – empfohlene Strategie:
|
||
|
||
- Tägliches logisches Backup via `pg_dump -Fc` (custom format, komprimiert, für `pg_restore` geeignet), z. B. als Kubernetes-`CronJob` gegen den `apicore-postgres`-Service, Ablage auf einem separaten PVC oder Objektspeicher außerhalb des Postgres-eigenen Volumes.
|
||
- Kontinuierliches WAL-Archiving/PITR (Point-in-Time Recovery), falls die Storage-Class/das Backup-Tooling das unterstützt, für Wiederherstellung auf einen beliebigen Zeitpunkt zwischen den täglichen Dumps.
|
||
- Restore-Test: `pg_restore` eines Dumps in eine isolierte Datenbank, danach `npx prisma migrate deploy` zur Kontrolle, dass der Migrationsstand konsistent ist (`prisma migrate status`).
|
||
|
||
**Restore-Ablauf (Kurzfassung):** 1) Zielinstanz/-datenbank bereitstellen. 2) `pg_restore` (oder WAL-Replay bis zum Zielzeitpunkt bei PITR). 3) `npx prisma migrate status` prüfen. 4) `npx prisma migrate deploy` falls seit dem Dump neue Migrationen hinzugekommen sind. 5) `GET /health/ready` gegen die wiederhergestellte Instanz aufrufen, um DB-Erreichbarkeit zu bestätigen, bevor Verkehr umgeschaltet wird.
|