package api import ( "log/slog" "net/http" "path/filepath" "gitea.perlbach24.de/scripte/wireguard-ui-multi/internal/database" "gitea.perlbach24.de/scripte/wireguard-ui-multi/internal/server" ) // API holds shared dependencies for HTTP handlers. type API struct { db *database.DB store *server.Store sessions *SessionStore log *slog.Logger lanIface string uiRoot string } func New(db *database.DB, log *slog.Logger, lanIface, uiRoot string) *API { return &API{ db: db, store: server.NewStore(db), sessions: NewSessionStore(), log: log, lanIface: lanIface, uiRoot: uiRoot, } } func (a *API) templatesDir() string { return filepath.Join(a.uiRoot, "templates") } func (a *API) staticDir() string { return filepath.Join(a.uiRoot, "static") } // Routes builds the full HTTP handler tree (API + UI), using Go 1.22 mux patterns. func (a *API) Routes() http.Handler { mux := http.NewServeMux() // Auth mux.HandleFunc("POST /api/login", a.handleLogin) mux.HandleFunc("POST /api/logout", a.withAuth(a.handleLogout)) // Servers mux.HandleFunc("GET /api/servers", a.withAuth(a.handleListServers)) mux.HandleFunc("POST /api/servers", a.withAuth(a.handleCreateServer)) mux.HandleFunc("GET /api/servers/{id}", a.withAuth(a.handleGetServer)) mux.HandleFunc("PUT /api/servers/{id}", a.withAuth(a.handleUpdateServer)) mux.HandleFunc("DELETE /api/servers/{id}", a.withAuth(a.handleDeleteServer)) mux.HandleFunc("POST /api/servers/{id}/start", a.withAuth(a.handleStartServer)) mux.HandleFunc("POST /api/servers/{id}/stop", a.withAuth(a.handleStopServer)) mux.HandleFunc("POST /api/servers/{id}/reload", a.withAuth(a.handleReloadServer)) mux.HandleFunc("GET /api/servers/{id}/config", a.withAuth(a.handleDownloadServerConfig)) // Peers mux.HandleFunc("GET /api/server/{id}/peers", a.withAuth(a.handleListPeers)) mux.HandleFunc("POST /api/server/{id}/peer", a.withAuth(a.handleCreatePeer)) mux.HandleFunc("DELETE /api/server/{id}/peer/{peerid}", a.withAuth(a.handleDeletePeer)) mux.HandleFunc("GET /api/server/{id}/peer/{peerid}/config", a.withAuth(a.handleDownloadPeerConfig)) mux.HandleFunc("GET /api/server/{id}/peer/{peerid}/qrcode", a.withAuth(a.handlePeerQRCode)) // UI mux.HandleFunc("GET /", a.handleDashboard) mux.HandleFunc("GET /login", a.handleLoginPage) mux.HandleFunc("GET /servers/{id}", a.handleServerPage) mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.Dir(a.staticDir())))) return a.logMiddleware(mux) } func (a *API) logMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { a.log.Info("request", "method", r.Method, "path", r.URL.Path, "remote", r.RemoteAddr) next.ServeHTTP(w, r) }) } // withAuth enforces a valid session and, for mutating requests, a matching CSRF token. func (a *API) withAuth(next func(http.ResponseWriter, *http.Request, *session)) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { sess, err := a.requireAuth(r) if err != nil { http.Error(w, "unauthenticated", http.StatusUnauthorized) return } if !requireCSRF(sess, r) { http.Error(w, "invalid csrf token", http.StatusForbidden) return } next(w, r, sess) } }