// Package web ist die HTTP-Schicht: Routing, Templates, Handler. // Bewusst html/template + htmx, kein Frontend-Build (siehe CLAUDE.md, // Stack). Persistenz (Submission/Finding/Evidence speichern) ist noch // nicht angebunden — das ist der nächste Schritt (Verdrahtung über // internal/store). Dieser Server führt Pre-Publish-Prüfungen aus // (extrahieren + bewerten) und zeigt das Ergebnis an, ohne es // aufzubewahren. package web import ( "context" "embed" "fmt" "html/template" "net/http" "github.com/netcell-it/deklarix/internal/evidence" "github.com/netcell-it/deklarix/internal/extract" "github.com/netcell-it/deklarix/internal/rules" "github.com/netcell-it/deklarix/internal/store" ) //go:embed templates/*.html var templatesFS embed.FS //go:embed static/* var staticFS embed.FS // Extractor ist die Schnittstelle, die der Server für Stufe 1 braucht. // *extract.Client erfüllt sie; Tests injizieren einen Fake statt echte // Claude-API-Aufrufe zu machen. type Extractor interface { Extract(ctx context.Context, in extract.Input) (extract.Result, error) ModelVersion() string } // Store ist die Schnittstelle, die der Server zur Persistenz braucht — // bewusst schmal (nur was diese Handler tatsächlich nutzen), nicht der // komplette *store.Store. *store.Store erfüllt sie; Tests injizieren // einen Fake statt eine echte Datenbank zu brauchen. type Store interface { CreateSubmission(ctx context.Context, platform, postType, caption string) (store.Submission, error) GetSubmission(ctx context.Context, id string) (store.Submission, error) SetSubmissionStatus(ctx context.Context, id, status string) error CreateExtraction(ctx context.Context, submissionID string, payload []byte, modelVersion, promptVersion string) (store.Extraction, error) GetLatestExtraction(ctx context.Context, submissionID string) (store.Extraction, error) CreateFinding(ctx context.Context, submissionID string, extractionID *string, ruleID string, ruleVersion int, severity, title, fix string, sources []string) (store.Finding, error) ListCurrentFindings(ctx context.Context, submissionID string) ([]store.Finding, error) CreateEvidencePackage(ctx context.Context, submissionID, dossierPath, sha256Hex string, timestampToken []byte) (store.EvidencePackage, error) GetLatestEvidencePackage(ctx context.Context, submissionID string) (store.EvidencePackage, error) } // Server bündelt Routing und Abhängigkeiten der Web-Schicht. type Server struct { mux *http.ServeMux extractor Extractor ruleSet []rules.Rule store Store timestamper evidence.Timestamper dossierDir string templates *template.Template } // NewServer erstellt den Server. ruleSet kommt von rules.Load und wird // einmal beim Start geladen, nicht pro Request. dossierDir ist das // Verzeichnis, in das erzeugte Nachweis-Dossiers geschrieben werden. func NewServer(extractor Extractor, ruleSet []rules.Rule, st Store, timestamper evidence.Timestamper, dossierDir string) (*Server, error) { tmpl, err := template.ParseFS(templatesFS, "templates/*.html") if err != nil { return nil, fmt.Errorf("web: templates parsen: %w", err) } s := &Server{ extractor: extractor, ruleSet: ruleSet, store: st, timestamper: timestamper, dossierDir: dossierDir, templates: tmpl, } mux := http.NewServeMux() mux.HandleFunc("GET /health", s.handleHealth) mux.HandleFunc("GET /{$}", s.handleIndex) mux.HandleFunc("POST /pruefen", s.handleCheck) mux.HandleFunc("POST /veroeffentlichen", s.handleArchive) mux.HandleFunc("GET /dossier/{id}", s.handleDossierDownload) mux.Handle("GET /static/", http.FileServerFS(staticFS)) s.mux = mux return s, nil } // ServeHTTP macht Server zu einem http.Handler. func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.mux.ServeHTTP(w, r) }