api

package
v0.27.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 14, 2026 License: MIT Imports: 55 Imported by: 0

Documentation

Overview

Package api wires the HTTP surface for Rivolt. It assembles routes, middleware, and handler dependencies into a single chi Mux.

Index

Constants

View Source
const ImpersonateHeader = "X-Rivolt-Impersonate"

ImpersonateHeader is the header an admin sets to view the app exactly as another user sees it — read-only, for support and debugging (e.g. confirming what a user's vehicle card actually shows them). Trusted ONLY when the caller already carries role=admin via the real cookie/OIDC session established earlier in the chain; see impersonationMW.

Variables

This section is empty.

Functions

func New

func New(d Deps) http.Handler

New builds the root mux with all routes mounted.

Types

type Deps

type Deps struct {
	Rivian rivian.Client
	// Accounts hands out per-user *rivian.LiveClient instances. The
	// /api/settings/rivian sign-in surface resolves the request user
	// to its private client via Accounts.For(uid), so concurrent
	// Login/Restore from different sessions can no longer corrupt
	// each other's tokens. nil when the stub client is in use
	// (nothing to sign into).
	Accounts rivian.AccountRegistry
	// Monitors hands out per-user *rivian.StateMonitor instances
	// that own the websocket subscription + recorder for that user's
	// vehicles. nil in mock/stub modes (no recorder needed there).
	Monitors    *rivian.MonitorRegistry
	PushService *push.Service
	// Per-user data-plane factories. The router resolves req.user →
	// uid → factory.For(uid) once per request, so handlers always
	// see stores scoped to the caller. nil-safe: handlers gate on
	// the resolved *Store being non-nil before reading.
	Drives   *drives.Factory
	Charges  *charges.Factory
	Samples  *samples.Factory
	Settings *settings.Factory
	Push     *push.Factory
	Trips    *trips.Factory
	// PackHealth is the per-vehicle derived-pack-capacity store.
	// Not a factory because samples are scoped by vehicle_id, not
	// user_id; the handler authorizes via the vehicleScoped
	// ownership middleware before reading. Nil-safe: the endpoint
	// returns 503 when unset.
	PackHealth *packhealth.Store
	// SettingsMgr exposes install-wide AI provider config (keys,
	// default models). Install-wide because the deployer pays the
	// LLM bill for every user. May be nil; the admin track
	// repopulates it from app_settings and gates the handlers
	// behind requireAdmin.
	SettingsMgr *settings.Manager
	// Auth, when non-nil, gates /api/* behind a session cookie
	// or trusted-proxy header. Whether unauthenticated requests
	// are 401'd is governed by AuthEnforced below; with Auth
	// non-nil but AuthEnforced false, sessions still resolve into
	// request context but the API stays open (legacy single-tenant
	// UX so docker-compose upgrades don't lock anyone out).
	Auth *auth.Service
	// AuthEnforced flips on the requireUser middleware for /api/*.
	// True when at least one real issuer is configured (OIDC,
	// trusted-proxy, or the debug bypass).
	AuthEnforced bool
	// ImpersonationDisabled mirrors $RIVOLT_IMPERSONATION_DISABLED.
	// true makes the impersonation middleware ignore
	// X-Rivolt-Impersonate outright (regardless of caller role) and
	// is surfaced via /api/config so the SPA hides the admin
	// "View as" control instead of offering a dead button.
	ImpersonationDisabled bool
	// OIDC, when non-nil, mounts /api/auth/oidc/* — the third
	// auth issuer alongside static creds and trusted-proxy
	// header. nil disables the social-login button row in the
	// SPA but doesn't affect any other code path.
	OIDC *oidc.Service
	// Hydra, when non-nil along with Kratos, mounts the custom
	// login + consent handlers under /api/auth/hydra. This makes
	// Rivolt the bring-your-own-UI for an Ory Hydra OAuth2 / OIDC
	// provider — downstream apps (ArgoCD, Grafana, etc.) federate
	// against Hydra and Hydra calls back into us for the user-
	// facing login prompt.
	Hydra  *hydra.Client
	Kratos *kratos.Client
	// HydraRememberFor controls how long Hydra remembers the
	// user's login + consent before forcing a fresh prompt. Zero
	// defers to Hydra's login_session lifespan. Sourced from the
	// RIVOLT_OIDC_HYDRA_REMEMBER_FOR env var in main.go.
	HydraRememberFor time.Duration
	WebFS            fs.FS
	Version          string
	// DB is the shared Postgres pool. Used by request middleware
	// that needs to answer "does this session user own this
	// vehicle?" without round-tripping through a per-user store.
	// Safe to be nil in legacy code paths that predate the
	// ownership middleware; ownership enforcement is only wired
	// when DB is non-nil.
	DB *sql.DB
	// Logger is the structured logger used by middleware for
	// infrastructure-class warnings (DB errors on ownership check,
	// etc.). nil is fine; events are dropped.
	Logger *slog.Logger
	// Flags is the operational-flag store (kill switch, future
	// pause_digest / pause_push rows). When nil the /api/admin/*
	// routes return 503 but the server still boots — the flag
	// surface is non-critical to rendering the app.
	Flags *flags.Store
	// AIBudget enforces the per-user daily cap on LLM-backed
	// endpoints (trip advice, drive efficiency). nil disables the
	// gate — the cost backstop fails open so no-DB and test paths
	// keep working.
	AIBudget *aibudget.Store
	// Secrets is the envelope-encrypted per-user blob store
	// (see internal/crypto, internal/secrets). Holds the
	// sealed rivian.Session and, later, AI provider keys and
	// per-user VAPID private keys. nil is tolerated so tests
	// and the mock/stub client don't have to stand up a
	// sealer; the Rivian sign-in surface becomes read-only
	// when it's absent.
	Secrets *secrets.Store
	// Metrics is the Prometheus instrumentation surface. When non-nil
	// the router wires per-handler latency + count tracking and
	// mounts /metrics. nil disables both — useful for tests that
	// don't want the global default registry pollution.
	Metrics *metrics.Metrics
	// Users, when non-nil, provisions user identities in the
	// configured IdP on POST /api/admin/users and DELETE.
	// nil (or a disabled provider) skips IdP provisioning —
	// the rivolt DB row is still created, callers must create
	// the OIDC identity out-of-band. See internal/idp.
	Users idp.UserProvider
	// SignupRequests, when non-nil, enables the public-facing
	// "request beta access" form at POST /api/signup/request and
	// the admin review surface at /api/admin/signup-requests/*.
	// Approve mints a magic-link signup token on the request row
	// and (if Email is wired) emails the requester. POST /api/signup
	// redeems that token to create the user.
	SignupRequests *signuprequests.Store
	// Email, when non-nil, sends transactional mail (currently just
	// signup approvals) via the Resend HTTP API. nil disables the
	// approval-email send; the admin still gets the code in the
	// approval response so it can be forwarded manually.
	Email *email.Client
	// BaseURL is the install's public origin (e.g. https://rivolt.dev),
	// sourced from $RIVOLT_BASE_URL. Used today only for composing
	// the magic-link signup URL in approval emails; safe to leave
	// empty in tests — the link falls back to a relative path so
	// copy-paste-into-the-same-tab still works.
	BaseURL string
	// ValhallaProxy, when non-nil, mounts a same-origin reverse
	// proxy at /api/maps/valhalla/* that forwards to a self-hosted
	// Valhalla. /api/config advertises whether the proxy is
	// mounted so the SPA picks the right base URL at boot. nil
	// leaves the route unmounted; the SPA renders raw GPS chords
	// without snapping.
	ValhallaProxy http.Handler
	// TilesProxy, when non-nil, mounts a same-origin reverse
	// proxy at /api/maps/tiles/* that forwards to a self-hosted
	// PMTiles file server (nginx serving the .pmtiles bundle
	// with byte-range support). nil leaves the route unmounted;
	// the SPA falls back to CARTO's public dark raster basemap.
	TilesProxy http.Handler
	// ChargersArchive, when non-nil, exposes a server-side
	// chargers-along-corridor query at /api/maps/chargers-along.
	// Backed by an in-memory copy of chargers.pmtiles so the SPA
	// can replace its per-tile HTTP fan-out with one POST.
	ChargersArchive *chargers.Archive
	// Photon is the self-hosted geocoder client. Empty BaseURL on
	// the client disables; /api/geocode falls through to
	// Open-Meteo's city-level service.
	Photon *geocoding.PhotonClient
	// WeatherClient is the shared Open-Meteo HTTP client used by
	// trip-planner weather adjustment. Distinct from the per-callsite
	// weather.NewClient() instances used elsewhere because the
	// planner reuses this in tight loops with MemCache. nil disables
	// weather adjustment (the plan response just omits the field).
	WeatherClient *weather.Client
	// WeatherCache memoises FetchHour results across overlapping plan
	// requests. Keyed on coarsened (lat, lon, hour). nil disables
	// caching; FetchHourCached falls through to the client.
	WeatherCache *weather.MemCache
}

Deps is the bag of dependencies the API router needs. Keep this small; avoid accumulating a "dependency soup" pattern.

All per-user state (data-plane stores, Rivian client, monitor) is reached through factories/registries keyed by the request user's uid — there is no singleton "current user" anywhere in the API surface.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL