mirror of
https://github.com/hanzoai/bootnode.git
synced 2026-10-25 02:31:30 +00:00
Lays down the Go skeleton parallel to api/ (FastAPI). Foundation: - config: env-driven Settings - db: pgx pool + idempotent schema migrations + typed row structs - iam: HS256 JWT Verifier + bearer-auth middleware - api: chi router with /healthz + /readyz + versioned /v1 mount Routes ported: - /auth: login + signup + refresh (bcrypt + JWT) - /chains: registry + status - /projects: full CRUD scoped by org - /wallets: list + create scoped by project+org Stubs (route wired, handler returns 501): - /api-keys, /webhooks, /rpc, /tokens, /nfts cmd/bootnode/main.go is the production entrypoint with graceful shutdown (SIGINT/SIGTERM) and slog JSON logging. Dockerfile builds a distroless image with CGO disabled. Remaining surface to port (in order of priority): rpc upstream dispatcher, api-keys CRUD + auth, tokens + nfts read-only, webhooks delivery worker, fleet + node + cluster routes, bundler + gas, billing. The Python service in api/ keeps running until parity ships.
90 lines
2.6 KiB
Go
90 lines
2.6 KiB
Go
// Package api — HTTP router wiring.
|
|
package api
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
"github.com/go-chi/cors"
|
|
|
|
"github.com/hanzoai/bootnode/internal/config"
|
|
"github.com/hanzoai/bootnode/internal/db"
|
|
"github.com/hanzoai/bootnode/internal/iam"
|
|
)
|
|
|
|
// Deps bundles the runtime collaborators every route handler may need.
|
|
// Routers receive Deps at registration time; per-request injection
|
|
// flows through r.Context() (Claims, request id).
|
|
type Deps struct {
|
|
Pool *db.Pool
|
|
IAM iam.Verifier
|
|
Config *config.Settings
|
|
}
|
|
|
|
// New builds the root chi router with middleware + every mounted route
|
|
// package. Mounting order is stable; new route packages append at the
|
|
// bottom of the section they belong to.
|
|
func New(deps Deps) http.Handler {
|
|
r := chi.NewRouter()
|
|
|
|
r.Use(middleware.RequestID)
|
|
r.Use(middleware.RealIP)
|
|
r.Use(middleware.Recoverer)
|
|
r.Use(middleware.Timeout(60_000_000_000)) // 60s
|
|
r.Use(cors.Handler(cors.Options{
|
|
AllowedOrigins: allowedOrigins(deps.Config),
|
|
AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
|
|
AllowedHeaders: []string{"Authorization", "Content-Type", "X-Api-Key", "X-Request-Id"},
|
|
ExposedHeaders: []string{"X-Request-Id"},
|
|
AllowCredentials: true,
|
|
MaxAge: 300,
|
|
}))
|
|
|
|
r.Get("/healthz", healthz)
|
|
r.Get("/readyz", readyz(deps))
|
|
|
|
r.Route(deps.Config.APIPrefix, func(v1 chi.Router) {
|
|
// Public auth routes.
|
|
v1.Mount("/auth", AuthRouter(deps))
|
|
|
|
// Authenticated routes.
|
|
v1.Group(func(authed chi.Router) {
|
|
authed.Use(iam.Middleware(deps.IAM))
|
|
authed.Mount("/chains", ChainsRouter(deps))
|
|
authed.Mount("/projects", ProjectsRouter(deps))
|
|
authed.Mount("/api-keys", APIKeysRouter(deps))
|
|
authed.Mount("/wallets", WalletsRouter(deps))
|
|
authed.Mount("/webhooks", WebhooksRouter(deps))
|
|
authed.Mount("/rpc", RPCRouter(deps))
|
|
authed.Mount("/tokens", TokensRouter(deps))
|
|
authed.Mount("/nfts", NFTsRouter(deps))
|
|
})
|
|
})
|
|
|
|
return r
|
|
}
|
|
|
|
func allowedOrigins(cfg *config.Settings) []string {
|
|
out := []string{cfg.FrontendURL}
|
|
out = append(out, cfg.AllowedOrigins...)
|
|
return out
|
|
}
|
|
|
|
func healthz(w http.ResponseWriter, _ *http.Request) {
|
|
_, _ = w.Write([]byte(`{"status":"ok"}`))
|
|
}
|
|
|
|
func readyz(deps Deps) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 2_000_000_000) // 2s
|
|
defer cancel()
|
|
if err := deps.Pool.Ping(ctx); err != nil {
|
|
http.Error(w, `{"status":"degraded","error":"db unreachable"}`, http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
_, _ = w.Write([]byte(`{"status":"ready"}`))
|
|
}
|
|
}
|