Documentation
¶
Overview ¶
Package app is the maping-server composition root. It wires the storage writer, ingest guardrails, control plane, auth layer, and dashboard from the environment and runs the HTTP server until a shutdown signal.
The wiring lives here rather than in package main so the control-plane- dependent decisions — whether the dashboard is auth-gated, the self-serve key-admin adapter, and the CSRF/session-key plumbing — are unit-testable against fakes without a live Postgres. main is a thin shell over Run.
Index ¶
- func Run(log *slog.Logger, opts ...Option) error
- type App
- type BackgroundJob
- type InviteInfo
- type JobContext
- type LimitProvider
- type LimitProviderFactory
- type Limits
- type LoginInterceptor
- type LoginInterceptorFactory
- type MemberAdmin
- type MemberAdminFactory
- type MemberInfo
- type Option
- func WithAccountLink(href string) Option
- func WithBackgroundJob(j BackgroundJob) Option
- func WithDocHeader(html template.HTML) Option
- func WithDocSections(sections ...docs.Section) Option
- func WithExtraMigrations(fsys fs.FS, dir string) Option
- func WithLimitProvider(factory LimitProviderFactory) Option
- func WithLoginInterceptor(factory LoginInterceptorFactory) Option
- func WithMemberAdmin(factory MemberAdminFactory) Option
- func WithPublicHome(home http.HandlerFunc) Option
- func WithRoutes(r RouteRegistrar) Option
- type PostAuthContext
- type RouteContext
- type RouteRegistrar
- type UsageStats
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type App ¶
type App struct {
// contains filtered or unexported fields
}
App is the built composition root: the fully-assembled HTTP mux plus the resources Serve runs and shutdown releases. build performs every wiring decision (infra construction + assembleMux); Serve runs the listener until a signal and drains gracefully. The split lets a test assert the wiring (via assembleMux) with fakes, without binding a socket or a live ClickHouse.
func (*App) Serve ¶
Serve runs the HTTP listener until SIGINT/SIGTERM, then drains gracefully: mark not-ready, stop accepting, drain the ingest->ClickHouse batcher (final flush with bounded retry) before releasing the control-plane pool so the last buffered summaries are not lost on a deploy restart, and cancel background jobs.
type BackgroundJob ¶
type BackgroundJob func(JobContext)
BackgroundJob is a long-running task started after boot and stopped at shutdown via JobContext.Ctx.
type InviteInfo ¶
type InviteInfo = web.InviteInfo
InviteInfo is a listed pending invite rendered in the team panel.
type JobContext ¶
JobContext is what a WithBackgroundJob task receives. Ctx is cancelled at shutdown, so a well-behaved job runs until Ctx.Done(). Pool is nil in dev mode.
type LimitProvider ¶
type LimitProvider = guardrail.LimitProvider
LimitProvider is the per-tenant limits source the ingest guardrails resolve through. A composing build implements it to layer its own behavior (e.g. an account lifecycle) over the core plan budget.
type LimitProviderFactory ¶
type LimitProviderFactory func(base LimitProvider, pool *pgxpool.Pool) LimitProvider
LimitProviderFactory decorates the core LimitProvider with the injecting build's own behavior — e.g. an account lifecycle (suspend/trial) the composing build reads from its own schema. base is the plain plan-budget provider; pool is the control-plane pool (never nil when this factory runs, since limits require a control plane). It returns the provider the ingest guardrails resolve through. It names the public LimitProvider alias so a composing module (which cannot import server/internal/guardrail) can implement it.
type Limits ¶
Limits is a tenant's resolved guardrail budget (rate, burst, cardinality, payload, retention).
func DefaultLimits ¶
func DefaultLimits() Limits
DefaultLimits is the free-tier budget a provider falls back to for an unknown plan. Re-exported so a composing provider can return it without importing the internal guardrail package.
type LoginInterceptor ¶
type LoginInterceptor = auth.LoginInterceptor
LoginInterceptor is the post-authentication hook the OIDC callback consults before the default first-login path. A composing build implements it to bind an identity resolved out of band (e.g. an accepted invite) and finish login.
type LoginInterceptorFactory ¶
type LoginInterceptorFactory func(pool *pgxpool.Pool) LoginInterceptor
LoginInterceptorFactory builds the composed post-auth hook once the control- plane pool exists (the invite store the hook drives needs it). pool is never nil when this runs, since the hook is only constructed with a control plane.
type MemberAdmin ¶
type MemberAdmin = web.MemberAdmin
MemberAdmin is the self-serve team surface (members + invites) the Setup page renders. A composing build supplies one to expose the team panel.
type MemberAdminFactory ¶
type MemberAdminFactory func(pool *pgxpool.Pool) MemberAdmin
MemberAdminFactory builds the team-panel admin once the control-plane pool exists. pool is never nil when this runs.
type MemberInfo ¶
type MemberInfo = web.MemberInfo
MemberInfo is a listed org member rendered in the team panel.
type Option ¶
type Option func(*options)
Option configures Run.
func WithAccountLink ¶
WithAccountLink turns the dashboard sidebar's user-identity block into a link to href — a composing build points it at the account page it owns (e.g. "/account"). Public default: empty, so the block stays a non-interactive display element. The composing build should only set it when it actually mounts that route (e.g. gating on the control plane) so the link never points at a 404.
func WithBackgroundJob ¶
func WithBackgroundJob(j BackgroundJob) Option
WithBackgroundJob registers a task launched after boot in its own goroutine and cancelled at shutdown. Multiple jobs each get their own goroutine.
func WithDocHeader ¶ added in v0.8.0
WithDocHeader injects a full site header rendered above every /doc page, so the documentation wears the same chrome as the rest of the site (the same logo, nav, and calls to action) instead of a detached bar. html is trusted markup the composing build produced from its own templates — typically its marketing header with absolute links. The community build sets none, and the shell falls back to a minimal home brand. Setting it more than once keeps the last header.
func WithDocSections ¶
WithDocSections adds entries to the shared /doc table of contents (the left rail on every documentation page). A composing build registers its own doc topics here so they appear alongside the core product pages; it mounts the pages themselves via WithRoutes, rendering each through RouteContext.RenderDoc. Public default: none, so the community build shows only the core product sections.
func WithExtraMigrations ¶
WithExtraMigrations registers an additional control-plane migration source applied, in lexical filename order, AFTER the embedded core migrations. A composing build passes its own schema (its own plan rows and tables) here so the public core never carries it. Multiple sources apply in registration order. Files must be additive and idempotent, exactly like the core migrations. It has no effect in static dev mode (no control plane to migrate).
func WithLimitProvider ¶
func WithLimitProvider(factory LimitProviderFactory) Option
WithLimitProvider decorates the core LimitProvider that drives the ingest guardrails (rate, payload, cardinality). The composing build passes its own provider to layer its own limit policy on top; the public default (no option) resolves the plain plan budget. It has no effect in static dev mode, where there is no control plane to resolve limits.
func WithLoginInterceptor ¶
func WithLoginInterceptor(factory LoginInterceptorFactory) Option
WithLoginInterceptor wires a post-authentication hook (e.g. an invitation accept flow) the OIDC callback consults before the default first-login path. The factory receives the control-plane pool the hook's store needs. Public default: none (plain login). No effect in static dev mode (no control plane).
func WithMemberAdmin ¶
func WithMemberAdmin(factory MemberAdminFactory) Option
WithMemberAdmin wires the self-serve team panel (members + invites) the Setup page renders. The factory receives the control-plane pool the admin's store needs. Public default: nil, so the dashboard hides the team panel. No effect in static dev mode (no control plane).
func WithPublicHome ¶
func WithPublicHome(home http.HandlerFunc) Option
WithPublicHome wires the anonymous landing page served at "/". When set, an unauthenticated visitor to "/" gets this handler while signed-in users and every dashboard sub-path fall through to the gated dashboard; a composing build registers any companion routes via WithRoutes. Public default: nil, so anonymous "/" redirects to /login (the self-host/OSS surface serves no landing page).
func WithRoutes ¶
func WithRoutes(r RouteRegistrar) Option
WithRoutes registers a RouteRegistrar mounted after the core routes. Multiple registrars run in registration order.
type PostAuthContext ¶
type PostAuthContext = auth.PostAuthContext
PostAuthContext is the capability a LoginInterceptor receives to start a dashboard session for the member it resolved.
type RouteContext ¶
type RouteContext struct {
Mux *http.ServeMux
Pool *pgxpool.Pool
Log *slog.Logger
// Gate wraps a handler in the dashboard auth middleware (session-cookie
// verification, redirect/401 on failure) so an extension can mount its own
// authenticated routes with the same gate the dashboard uses. It is nil when
// auth is off (no control plane) — a registrar that needs auth must check and
// mount nothing.
Gate func(http.Handler) http.Handler
// SessionOrg reads the caller's verified org id from a request that has passed
// through Gate. It is the ONLY sanctioned way for an extension to learn the
// caller's org (never the request body), keeping the session context key
// private to auth. Nil when auth is off.
SessionOrg func(*http.Request) (orgID string, ok bool)
// SessionMemberID reads the caller's verified member id from a request that has
// passed through Gate. Like SessionOrg it reads the private auth session context
// key an extension cannot reach directly, so an extension can resolve the caller's
// own member row (e.g. their email) without trusting request input. Nil when auth
// is off.
SessionMemberID func(*http.Request) (memberID string, ok bool)
// RenderShell writes a full dashboard page (sidebar + top bar chrome) wrapping
// the given content, so an extension's own page looks native to the dashboard
// instead of a detached page. content is trusted HTML the caller produced from a
// template; title sets the top-bar heading and browser tab. Always non-nil.
RenderShell func(w http.ResponseWriter, r *http.Request, title string, content template.HTML)
// RenderDoc writes a full public documentation page (the /doc dark shell with the
// shared table of contents in the left rail) wrapping the given body, so an
// extension's own doc page is indistinguishable from a core one. body is trusted
// HTML — a composing build produces it from its embedded Markdown via
// docs.MarkdownToHTML. Unlike RenderShell the page is public (no auth chrome), so
// this is the seam for extension /doc/* pages. Always non-nil.
RenderDoc func(w http.ResponseWriter, r *http.Request, title string, body template.HTML)
// Usage returns the operator-facing volumetry for one tenant (liveness,
// cardinality, 30-day requests, estimated disk) so an operator console can render
// a per-account usage page without importing the internal storage layer. It is a
// cross-tenant capability guarded by the composing build's own operator check —
// the seam does no authorization. Always non-nil (ClickHouse is the data plane).
Usage func(ctx context.Context, tenantID string) (UsageStats, error)
// LastIngestByTenant returns the most recent ingest time per tenant in one scan,
// so an operator account list can flag live vs churned accounts without an N+1.
// Like Usage it is unauthorized here and gated by the composing build. Always
// non-nil.
LastIngestByTenant func(ctx context.Context) (map[string]time.Time, error)
}
RouteContext is what a WithRoutes registrar receives to mount extra routes. Pool is the control-plane pool, nil in static dev mode (no MAPING_POSTGRES_DSN).
type RouteRegistrar ¶
type RouteRegistrar func(RouteContext)
RouteRegistrar mounts additional routes on the server mux. It runs after the core routes, so its patterns must not collide with the core surfaces (/healthz, /, the /login and /auth routes, and the ingest path).
type UsageStats ¶ added in v0.11.0
type UsageStats struct {
FirstIngest time.Time
LastIngest time.Time
Endpoints uint64
Series uint64
Services uint64
Instances uint64
Requests30d uint64
DiskBytes uint64
}
UsageStats is the operator-facing volumetry for one tenant, handed to a composing build through RouteContext.Usage. It mirrors storage.TenantUsage but lives in the public seam so an extension (which cannot import server/internal/storage) can name it. Series counts distinct (method, route_template, status_class) — the same key the cardinality guardrail meters — so a caller can render "series vs cap" honestly. A never-ingested tenant yields the zero value (zero times, zero counts).