package http import ( "context" "net/http" "time" "github.com/go-chi/chi/v5" "github.com/noderunners/nip05api/internal/audit" "github.com/noderunners/nip05api/internal/config" "github.com/noderunners/nip05api/internal/db" "github.com/noderunners/nip05api/internal/dm" "github.com/noderunners/nip05api/internal/http/docs" "github.com/noderunners/nip05api/internal/http/handlers" "github.com/noderunners/nip05api/internal/http/middleware" "github.com/noderunners/nip05api/internal/invoice" "github.com/noderunners/nip05api/internal/user" "github.com/noderunners/nip05api/internal/webhook" ) type Deps struct { Cfg *config.Config DB *db.DB Users *user.Service Invoices *invoice.Service DMs *dm.Service Hooks *webhook.Service Audit *audit.Logger Version string } func NewServer(d Deps) *http.Server { r := chi.NewRouter() r.Use(middleware.Recoverer) r.Use(middleware.RealIP) r.Use(middleware.Logging) r.Use(middleware.CORS(d.Cfg)) r.Use(middleware.BodyLimit(1 << 20)) // 1 MiB max request body r.Use(middleware.RateLimit(d.Cfg.RateLimitPerMin)) health := &handlers.Health{DB: d.DB, Version: d.Version} nostrJSON := &handlers.NostrJSON{Users: d.Users, Relays: d.Cfg.Nostr.Relays} pricing := &handlers.Pricing{ YearlySats: d.Cfg.Lightning.PriceYearlySats, LifetimeSats: d.Cfg.Lightning.PriceLifetimeSats, LightningEnabled: d.Cfg.Lightning.Enabled, } users := &handlers.Users{Users: d.Users, GraceDays: d.Cfg.Expiry.GraceDays} usernames := &handlers.Usernames{Users: d.Users} invoices := &handlers.Invoices{Service: d.Invoices, LightningEnabled: d.Cfg.Lightning.Enabled} adminUsers := &handlers.AdminUsers{ Users: d.Users, DMs: d.DMs, Hooks: d.Hooks, Audit: d.Audit, Domain: d.Cfg.Domain, Frontend: d.Cfg.FrontendURL, } adminExtend := &handlers.AdminExtend{ Users: d.Users, DMs: d.DMs, Hooks: d.Hooks, Audit: d.Audit, Domain: d.Cfg.Domain, Frontend: d.Cfg.FrontendURL, } r.Get("/healthz", health.Handle) r.Get("/.well-known/nostr.json", nostrJSON.Handle) r.Get("/openapi.json", docs.ServeJSON) r.Get("/docs", docs.ServeUI) r.Get("/docs/", docs.ServeUI) r.Route("/v1", func(r chi.Router) { r.Get("/pricing", pricing.Handle) r.Get("/users/{pubkey}", users.Get) r.Get("/usernames/{name}/available", usernames.Available) if d.Invoices != nil { r.Post("/invoices", invoices.Create) r.Get("/invoices/{payment_hash}", invoices.Get) } r.Route("/admin", func(r chi.Router) { r.Use(middleware.AdminAuth(d.Cfg.AdminAPIKey)) r.Post("/users", adminUsers.Add) r.Get("/users", adminUsers.List) r.Put("/users/{pubkey}", adminUsers.Update) r.Delete("/users/{pubkey}", adminUsers.Delete) r.Post("/users/{pubkey}/extend", adminExtend.Handle) }) }) return &http.Server{ Addr: d.Cfg.Addr(), Handler: r, ReadHeaderTimeout: 10 * time.Second, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 60 * time.Second, } } func Shutdown(ctx context.Context, srv *http.Server) error { shutdownCtx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() return srv.Shutdown(shutdownCtx) }