diff options
Diffstat (limited to 'internal/api/router.go')
| -rw-r--r-- | internal/api/router.go | 80 |
1 files changed, 80 insertions, 0 deletions
diff --git a/internal/api/router.go b/internal/api/router.go new file mode 100644 index 0000000..1cb9ff6 --- /dev/null +++ b/internal/api/router.go @@ -0,0 +1,80 @@ +package api + +import ( + "net/http" + + "vpnem/internal/rules" +) + +func NewRouter(store *rules.Store) http.Handler { + h := NewHandler(store) + mux := http.NewServeMux() + + mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"status":"ok"}`)) + }) + mux.HandleFunc("/vpnui", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + http.Redirect(w, r, "/vpnui/", http.StatusTemporaryRedirect) + }) + mux.HandleFunc("/vpnui/", methodHandler(http.MethodGet, h.VPNUI)) + mux.HandleFunc("/api/v1/servers", methodHandler(http.MethodGet, h.Servers)) + mux.HandleFunc("/api/v2/catalog", methodHandler(http.MethodGet, h.CatalogV2)) + mux.HandleFunc("/api/v1/routing-policy", methodHandler(http.MethodGet, h.RoutingPolicy)) + mux.HandleFunc("/api/v1/subscribe", methodHandler(http.MethodGet, h.Subscribe)) + mux.HandleFunc("/api/v1/ruleset/manifest", methodHandler(http.MethodGet, h.RuleSetManifest)) + mux.HandleFunc("/api/v1/version", methodHandler(http.MethodGet, h.Version)) + mux.HandleFunc("/api/v1/control/nodes", func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + h.ControlNodes(w, r) + case http.MethodPost: + h.UpsertControlNode(w, r) + default: + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + } + }) + mux.HandleFunc("/api/v1/control/preflight", methodHandler(http.MethodPost, h.QuickPreflightControlNode)) + mux.HandleFunc("/api/v1/control/quick-provision", methodHandler(http.MethodPost, h.QuickProvisionControlNode)) + mux.HandleFunc("/api/v1/control/nodes/", h.ControlNodeAction) + mux.HandleFunc("/api/v1/control/catalog/publish", methodHandler(http.MethodPost, h.PublishControlCatalog)) + + // Static file serving for .srs and .txt rule files + rulesFS := http.StripPrefix("/rules/", http.FileServer(http.Dir(store.RulesDir()))) + mux.Handle("/rules/", rulesFS) + + // Static file serving for client releases + releasesFS := http.StripPrefix("/releases/", http.FileServer(http.Dir(store.ReleasesDir()))) + mux.Handle("/releases/", releasesFS) + + // Client error log endpoint (obscure URL, no auth needed — just writes to file) + mux.HandleFunc("/logs2026vpnem/errors", methodHandler(http.MethodPost, h.ClientLog)) + + // Web viewer for client logs (admin-protected via env var) + mux.HandleFunc("/client-logs", methodHandler(http.MethodGet, h.ClientLogsViewer)) + + // Client connection report and recommendation (RealIP middleware auto-detects client IP) + mux.HandleFunc("/api/v1/connect", RealIP(h.ClientConnect)) + mux.HandleFunc("/api/v1/disconnect", RealIP(h.ClientDisconnect)) + mux.HandleFunc("/api/v1/recommend", RealIP(h.Recommend)) + + return mux +} + +func methodHandler(method string, next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if r.Method != method { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + next(w, r) + } +} |
