# admin-api Admin-only HTTP service for ProofedMesh. Currently covers one concern: Verwaltung der Per-Diem-Pauschalen (`per_diem_rates`), die vom `agent-per-diem-calculator` deterministisch angewandt werden (siehe `docs/architecture/v1.1.md` §6.2 und ADR-0003). **Status: Skelett.** Datenbankanbindung (pgx) und Signing-Service-Call sind Stubs mit expliziten Fehlern; HTTP-Handler, HTML-Templates, Modell-Typen und Store-Interface sind vollständig und getestet. Prod-Deployment folgt nach Phase-1-Trust-Core. ## Scope - Admin-Oberfläche `/admin/per-diem-rates` — Liste + Neuanlage + „Retire" (`valid_to` setzen) über HTML + HTMX-ähnlichen simple-POST-Flow. - JSON-API hinter denselben Pfaden für Automatisierung (`Accept: application/json`). - Jede Create/Retire-Operation erzeugt eine **Rate Change Attestation** über den Signing Service — predicateType `https://basicstack.de/attestations/rate_change/v1`. Rückgabe-Hash wird in `per_diem_rates.rate_change_attestation_ref` sowie in `per_diem_rate_changes` persistiert. - Keine Delete-Operation. Historie ist unveränderlich (Schema-Trigger erzwingt das). ## Auth - OIDC via Pocket ID (Authorization-Code-Flow, Session-Cookie). - Autorisierung: Gruppenmitgliedschaft `proofedmesh-admin` wird aus dem ID-Token gelesen und ist Pflicht für alle Endpunkte außer Login. - Hintergrund-Dienste rufen die API nicht — Admin-API ist Human-only. Deshalb kein `client_credentials`-Pfad. ## HTTP-Endpunkte (geplant) | Methode | Pfad | Beschreibung | | ------- | ---------------------------------------- | -------------------------------------------- | | GET | `/admin/per-diem-rates/` | Liste aller Rates (gefiltert nach Country, aktiv/retired) | | GET | `/admin/per-diem-rates/{rate_id}` | Detailansicht einer Rate | | POST | `/admin/per-diem-rates/` | Neue Rate anlegen (JSON oder Form) | | POST | `/admin/per-diem-rates/{rate_id}/retire` | `valid_to` setzen (Rate ablaufen lassen) | Content-Negotiation per `Accept`-Header: `text/html` → Templates, `application/json` → JSON. ## Layout ``` services/admin-api/ ├── README.md — diese Datei ├── go.mod ├── cmd/admin-api/main.go — HTTP-Entry (bind + router + graceful shutdown) ├── internal/ │ ├── model/rate.go — Rate struct, validation │ ├── store/ │ │ ├── store.go — RateStore interface │ │ ├── memory.go — in-memory impl (für Tests + Dev) │ │ ├── memory_test.go │ │ └── postgres.go — pgx impl, derzeit Stub │ ├── httpapi/ │ │ ├── rates.go — Handler │ │ └── rates_test.go — Handler-Tests gegen in-memory store │ └── attest/ │ └── ratechange.go — Signing-Service-Client, derzeit Stub └── web/ └── templates/per_diem_rates.html ``` ## Lokal bauen (sobald Code vollständig) ```bash cd services/admin-api go test ./... # Dev-Modus mit in-memory store, kein Postgres: go run ./cmd/admin-api -store=memory -listen=:8080 # Prod-Modus (sobald postgres.go fertig): go run ./cmd/admin-api -store=postgres \ -db="postgres://trust:…@trust-postgres:5432/trust?sslmode=verify-full" ``` ## Rate Change Attestation (Entwurf) ```json { "_type": "https://in-toto.io/Statement/v1", "subject": [ { "name": "per_diem_rate/PDR-42", "digest": { "sha256": "" } } ], "predicateType": "https://basicstack.de/attestations/rate_change/v1", "predicate": { "change_kind": "create", "rate_id": "PDR-42", "country": "DE", "city": null, "currency": "EUR", "full_day": "28.00", "partial_day_min8h": "14.00", "arrival_day": "14.00", "departure_day": "14.00", "valid_from": "2026-01-01", "valid_to": null, "source_regulation": "BMF-Schreiben …", "performed_by": { "iss": "https://auth.basicstack.de", "sub": "user-admin-42", "session_id": "SES-admin-…" }, "performed_at": "2026-10-25T18:30:00Z" } } ``` Signiert vom Signing Service, Attestations-Hash wandert zurück in die Tabellen `per_diem_rates.rate_change_attestation_ref` und `per_diem_rate_changes.rate_change_attestation_ref`.