// 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/extract" "github.com/netcell-it/deklarix/internal/rules" ) //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) (rules.Facts, error) } // Server bündelt Routing und Abhängigkeiten der Web-Schicht. type Server struct { mux *http.ServeMux extractor Extractor ruleSet []rules.Rule templates *template.Template } // NewServer erstellt den Server. ruleSet kommt von rules.Load und wird // einmal beim Start geladen, nicht pro Request. func NewServer(extractor Extractor, ruleSet []rules.Rule) (*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, templates: tmpl} mux := http.NewServeMux() mux.HandleFunc("GET /health", s.handleHealth) mux.HandleFunc("GET /{$}", s.handleIndex) mux.HandleFunc("POST /pruefen", s.handleCheck) 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) }