# archivdms GoBD-konformes Dokumentenmanagementsystem (DMS) für den DACH-Raum. Eigenständiges Produkt, getrennt von archivmail (E-Mail-Archivierung) — spätere Mail-Anbindung nur optional über archivmails REST-API, kein gemeinsamer Code. Konzeption basiert auf Recherche zu Paperless-ngx und ecoDMS (siehe `dms-featureliste-prompt.md`). Grundgerüst (Auth/Tenant/Audit/Mailer-Architektur) aus archivmail portiert und auf ein dokumentzentriertes Modell umgebaut. ## Stack - Backend: Go (`net/http`, PostgreSQL via pgx/v5, JWT-Auth) - Frontend: Next.js + shadcn/ui - Multi-Tenancy: applikationsseitig (`tenant_id`-Filter je Query, kein Postgres-RLS) - Suche: Manticore Search (BM25) — Backend-Endpunkt `GET /api/documents/search` und Frontend (globale Suchleiste in der TopBar, Ergebnisseite `/search` mit Tag-/Dokumenttyp-Filter und Pagination) integriert; Hybrid/Vektor geplant ## CI-Pipeline (FDN-07) `.gitea/workflows/ci.yml` definiert eine Gitea-Actions-Pipeline (Syntax kompatibel zu GitHub Actions) mit zwei Jobs: - **backend-lint-test**: `go vet ./...`, `go test ./... -cover` gegen eine pro Lauf frisch gestartete Postgres-Service-Container-Instanz, danach `make build` als reproduzierbares Artefakt. - **frontend-lint-test-build**: `npm ci`/`npm install`-Fallback, ESLint (`npm run lint`), `tsc --noEmit`, `make build-web` (`next build`) als Artefakt. Läuft bei jedem Push/PR; ein fehlschlagender Schritt bricht den jeweiligen Job ab (kein `continue-on-error`), das blockiert den Merge sobald in Gitea die Branch-Protection-Regel "Require status checks to pass" auf diese Jobs gesetzt ist. **Wichtig:** archivdms hat aktuell noch keinen Gitea-Remote (nur lokales `git init`), die Pipeline greift daher erst, sobald das Repo zu einer Gitea-Instanz mit aktivierten Actions und registriertem Runner gepusht wird. ## Betrieb: Logging, Metriken & Fehler-Tracking (FDN-08) Jede HTTP-Anfrage läuft durch eine Basis-Middleware-Kette in `internal/api/observability.go` (`requestID -> metrics -> recover -> ServeMux`, gebaut in `api.New`): - **Korrelations-ID:** pro Anfrage wird eine Request-ID erzeugt oder ein mitgelieferter `X-Request-ID`-Header übernommen (nur alphanumerisch/`-_.`, max. 64 Zeichen — sonst verworfen, Schutz gegen Log-Injection). Die ID steht im `context.Context` und im Response-Header `X-Request-ID`. Handler loggen über `s.reqLog(r.Context())` bzw. `s.reqLog(ctx)`; jede Zeile bekommt dadurch automatisch `request_id=...` — auch in der asynchronen Verarbeitung (`ProcessDocumentJob`, `ReprocessDocument`), soweit der Request-Context durchgereicht wird. - **Panic-Recovery:** `net/http` hat kein zentrales Recovery. Die `recoverMiddleware` fängt jedes Panic ab, loggt es mit Korrelations-ID und gekürztem Stacktrace, zählt `archivdms_panics_total` hoch und antwortet mit einem sauberen HTTP 500 statt einer abgebrochenen Verbindung. - **Keine Geheimnisse in Logs:** geloggt werden nur Methode, normalisierter Pfad, Status, Dauer und Client-IP. Query-Strings, Header und Bodies werden nie ausgegeben (dort stehen Signed-URL-Signaturen, Share-Tokens, Bearer-Keys, Passwörter). `normalizeRoute` ersetzt IDs durch `{id}` und das Segment hinter `/share/` immer durch `{token}`. ### `GET /metrics` Prometheus-Textformat, ohne Fremdabhängigkeit (`internal/api/metrics_handlers.go`, reines `fmt.Fprintf`). Bewusst **ohne Login** (ein Scraper hat keine Session), dafür **IP-beschränkt**: Loopback ist immer erlaubt, weitere Scraper werden über den Config-Key `api.metrics_allowed_ips` (Liste aus IPs oder CIDR-Bereichen, Default leer = nur localhost) freigeschaltet; alles andere bekommt 403. Der Endpunkt gehört nicht ins öffentliche Reverse-Proxy-Mapping. Ausgegeben werden: `archivdms_http_requests_total{method,route,status}` (Fehlerrate = Anteil `status=~"5.."`), `archivdms_http_request_duration_seconds_{bucket,sum,count}` (Latenz-Histogramm), `archivdms_http_requests_in_flight`, `archivdms_goroutines`, `archivdms_uptime_seconds`, `archivdms_panics_total`, `archivdms_build_info{version}` sowie die Queue-Länge `archivdms_processing_jobs{status}` (aus `processing_jobs`, aggregiert über alle Mandanten — es werden keine mandantenbezogenen Daten ausgegeben). Die Label-Kardinalität ist auf 500 Serien gedeckelt, danach landet alles unter `route="/other"`. ## Frontend: Login-Pflicht & UI-Struktur Alle Seiten außer `/login` sind hinter einer Session-Cookie-Prüfung (`middleware.ts`, Cookie `archivdms_session`). Ohne gültiges Cookie wird sofort (ohne sichtbares Flackern) zu `/login` umgeleitet; die vollständige JWT-Prüfung bleibt Aufgabe des Go-Backends bei jedem echten API-Call. Eingeloggte Bereiche laufen unter der Routegruppe `src/app/(app)/` mit gemeinsamer App-Shell (Sidebar, TopBar, Cmd+K-Befehlspalette, Dark Mode). `/documents` und `/reminders` sind Server Components mit serverseitigem Datenfetch (`src/lib/session.ts` reicht das Session-Cookie manuell an die Go-API weiter) statt der bisherigen "use client" + `useEffect`-Ladeschleife. Die Dokumenten-Detail-/Vorschauseite `/documents/{id}` (Server Component `src/app/(app)/documents/[id]/page.tsx` + Client-Island `DocumentPreview`) zeigt die Datei-Vorschau in einem `