Documentation
¶
Overview ¶
Package api wires the HTTP surface for Rivolt. It assembles routes, middleware, and handler dependencies into a single chi Mux.
Index ¶
Constants ¶
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 RunIdPDeleteRetrier ¶ added in v0.32.0
func RunIdPDeleteRetrier(ctx context.Context, d *sql.DB, ac idp.UserProvider, log *slog.Logger, interval time.Duration)
RunIdPDeleteRetrier retries recorded IdP deletion failures every interval until ctx ends, with per-row exponential backoff (see db.ClaimDueIdPDeleteFailures). Safe to run on every replica: rows are claimed with SKIP LOCKED. No-op without a database or an enabled IdP.
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
// AdminDisabled mirrors $RIVOLT_ADMIN_DISABLED: the public site
// serves no admin surface. /admin* and /api/admin* answer 404 (even
// for an admin session), /api/auth/me reports an admin as "user" so
// the SPA hides the link, and impersonation is off. Off by default;
// for the split to admin.rivolt.dev (scratchpad admin-split.md).
AdminDisabled bool
// AdminOnly is RIVOLT_ADMIN_ONLY: the admin deployment. Every request
// must carry a valid Cloudflare Access JWT (Access; nil denies all),
// and only the admin surface exists under /api.
AdminOnly bool
// AdminHealth feeds GET /api/admin/health-summary (nil: every section reads unavailable).
AdminHealth *adminhealth.Service
// PlanLegs caches live planner calls and caps them per user (nil: neither).
PlanLegs *planleg.Cache
Access *cfaccess.Verifier
// AdminDebugProbes mirrors $RIVOLT_ADMIN_DEBUG_PROBES: mounts the raw
// Rivian probes under /api/admin (trips/plan/raw, trips/plan/diag,
// gql/type, gql/enum, gql/raw), which send arbitrary queries to Rivian
// as the admin. Off by default; an operator turns it on for a session.
AdminDebugProbes 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
// Changelog serves the release notes (GET /api/changelog and
// /changelog.atom); nil leaves both unmounted.
Changelog *changelog.Source
// 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
// StatementSigner signs the one-click unsubscribe links of the
// monthly statement email (nil disables the unsubscribe routes).
StatementSigner *statement.Signer
// WeeklySigner signs the weekly recap's unsubscribe links (a scope of its
// own, so they never verify as statement links). nil disables the recap.
WeeklySigner *statement.Signer
// BaseURL is the install's public origin (e.g. https://rivolt.dev),
// sourced from $RIVOLT_PUBLIC_BASE_URL, else $RIVOLT_BASE_URL (the
// admin deployment sets the former: its own host is behind Cloudflare
// Access, so links it emails must point at the public site). Used
// for user-facing links in emails: the magic-link signup URL
// (admin approval and auto-approve) and the monthly statement's
// links. Safe to leave empty in tests — the signup 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.
type InactivePurge ¶ added in v0.36.8
type InactivePurge struct {
// contains filtered or unexported fields
}
InactivePurge is the daily job.
func NewInactivePurge ¶ added in v0.36.8
func NewInactivePurge(pool *sql.DB, flag func() flags.InactivePurgeState, mailer *email.Client, users idp.UserProvider, baseURL string, logger *slog.Logger) *InactivePurge
NewInactivePurge wires the job over the pool. flag reads the live inactive_account_purge flag; mailer may be nil (nothing is warned or deleted then: a deletion is never allowed without a sent warning).
func (*InactivePurge) Run ¶ added in v0.36.8
func (j *InactivePurge) Run(ctx context.Context)
Run sweeps once a day until ctx ends, starting after a delay so a restart does not race boot, with jitter so replicas don't tick together. Every send and delete is claim- or lock-gated, so it is safe on every replica.
func (*InactivePurge) RunOnce ¶ added in v0.36.8
func (j *InactivePurge) RunOnce(ctx context.Context) PurgeResult
RunOnce is one pass.
Source Files
¶
- account_delete.go
- admin.go
- admin_audit.go
- admin_disabled.go
- admin_health.go
- admin_notify.go
- admin_only.go
- ai_budget_mw.go
- api.go
- auth_mw.go
- bodylimit.go
- changelog.go
- chargers_along.go
- charges.go
- data.go
- demo.go
- demo_cache.go
- demo_clientip.go
- demo_trips.go
- drain.go
- drive_efficiency.go
- drive_weather.go
- drives.go
- engagement_settings.go
- events.go
- export.go
- far_future.go
- flags.go
- funnel.go
- health.go
- hydra.go
- inactive_purge.go
- insights.go
- iplimit.go
- lease.go
- ledger.go
- ledger_place.go
- ledger_trips.go
- live.go
- now.go
- onboarding_email.go
- overnight_candidates.go
- plan_legs.go
- plan_validate.go
- planner.go
- push.go
- receipts_optin.go
- rivian.go
- rivian_actions.go
- seo.go
- settings_ai.go
- settings_charging.go
- signup.go
- signup_requests.go
- spa.go
- statement_email.go
- terms.go
- trip_multiday.go
- trip_plan_actual.go
- trip_resolve.go
- trip_saved_active.go
- trip_saved_multiday.go
- tripplan.go
- ttlcache.go
- vehicle_mw.go
- vehicle_position.go
- vehicles.go
- weekly_recap.go