package admin import ( "database/sql" "log" "net/http" "time" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/redis/go-redis/v9" "github.com/wangjia/pangolin/server/internal/codes" ) // NewRouter wires the full admin HTTP handler chain: // // IP allowlist → [ /login, /static ] (no session) // IP allowlist → session → [ everything else ] (authenticated) func NewRouter(h *Handlers, sessions *SessionStore, cfg *Config, sec *SecurityLog) http.Handler { ipAllow := func(next http.Handler) http.Handler { return NewIPAllow(cfg.AllowCIDRs, sec, next) } requireSession := func(next http.Handler) http.Handler { return NewSessionMiddleware(sessions, next) } r := chi.NewRouter() r.Use(middleware.Recoverer) r.Use(ipAllow) // Static assets (CSS/JS) — embedded; behind the allowlist but pre-auth. r.Handle("/static/*", http.FileServer(http.FS(assetsFS))) // Pre-auth login routes. r.Get("/login", h.LoginPage) r.Post("/login", h.LoginSubmit) // Authenticated routes. r.Group(func(pr chi.Router) { pr.Use(requireSession) pr.Get("/", h.Dashboard) pr.Post("/logout", h.Logout) pr.Get("/codes", h.CodesPage) pr.Post("/codes", h.CreateBatch) pr.Post("/codes/void", h.VoidBatch) pr.Get("/nodes", h.NodesPage) pr.Post("/nodes/op", h.NodeOp) pr.Get("/audit", h.AuditPage) }) return r } // BuildServices assembles the downstream services. The codes service (#3) is // real; lifecycle (#5) and provisioning (#14) are stubs until those modules // land — the UI greys out their controls (Ready()==false). func BuildServices(db *sql.DB, rdb *redis.Client, failMax int, lockDur time.Duration) Services { codeStore := codes.NewStore(db) codeSvc := codes.NewService(codeStore, rdb, failMax, lockDur) return Services{ Codes: NewCodesAdapter(codeSvc, codeStore), Lifecycle: NewStubLifecycle(), Provision: NewStubProvision(), } } // NewHandler builds the complete admin http.Handler from its dependencies. func NewHandler(cfg *Config, db *sql.DB, rdb *redis.Client, svc Services, logger *log.Logger) (http.Handler, error) { if logger == nil { logger = log.Default() } store := NewDBStore(db) sessions := NewSessionStore(rdb, cfg.SessionTTL) sec := NewSecurityLog(store, logger) auth := NewAuthenticator(store, sessions, rdb, cfg, sec) handlers, err := NewHandlers(cfg, store, sessions, auth, svc, sec, logger) if err != nil { return nil, err } return NewRouter(handlers, sessions, cfg, sec), nil }