Help us shape the future of Go! Take the 2026 Go Developer Survey

settings

package
v0.40.4 Latest Latest
Warning

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

Go to latest
Published: Oct 26, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package settings persists app-level configuration (AI provider keys, charging cost, Rivian session) in the shared Postgres database.

Storage shape: one row per (user_id, namespace) in the user_settings table, with the value column holding a JSONB object of string keys. The public Store API keeps the flat key/value illusion the Manager, charging and rivian helpers were built against — the namespace is derived from the key's first segment ("ai.*" → "ai", "charging.*" → "charging", "rivian.*" → "rivian").

Index

Constants

View Source
const (
	DefaultGasPricePerGallon = 4.0
	DefaultComparisonMPG     = 20.0
)

Defaults for the gas-equivalent comparison chip on the trip planner. $4/gal and 20 MPG approximate a same-class ICE pickup; both are user-editable.

View Source
const (
	HomeSourceCity        = "city"
	HomeSourceAddress     = "address"
	HomeSourceCoordinates = "coordinates"
	HomeSourceCharge      = "charge"
)

The ways a home can be set (HomeLocation.Source), for the Settings card only.

View Source
const (
	ProviderOpenAI    = "openai"
	ProviderAnthropic = "anthropic"
	ProviderGemini    = "gemini"
)

Supported AI providers.

View Source
const (
	DefaultGPSMissingPct float64 = 0.40
	DefaultGPSStaleSec   int     = 300
	DefaultGPSJumpCount  int     = 2
)

Defaults for the GPS accuracy heuristic. Tuned to surface drives with genuinely bad GPS (long tunnels, modem-side dropouts) while ignoring single anomalies and brief parking-garage stalls.

View Source
const (
	DefaultOpenAIModel      = "gpt-4o-mini"
	DefaultAnthropicModel   = "claude-haiku-4-5-20251001"
	DefaultGeminiModel      = "gemini-2.5-flash"
	DefaultOpenAIImageModel = "gpt-image-1"
	DefaultGeminiImageModel = "gemini-2.5-flash-image"
	// Speech-to-text defaults. Whisper still has a single public model;
	// Gemini uses whatever text model you point at /generateContent with
	// audio inline. gemini-2.5-flash is the cheapest multimodal tier.
	DefaultOpenAISpeechModel = "whisper-1"
	DefaultGeminiSpeechModel = "gemini-2.5-flash"
)

Defaults used when nothing is stored and no env override is present.

View Source
const (
	ImageProviderOpenAI = "openai"
	ImageProviderGemini = "gemini"
)

Supported image generation providers. Anthropic isn't in this set because the Messages API has no image-generation surface.

View Source
const (
	SpeechProviderOpenAI = "openai"
	SpeechProviderGemini = "gemini"
)

Supported speech-to-text providers. Same caveat as image: Anthropic has no audio-input API yet.

View Source
const (
	DriveModeEveryday    = "EVERYDAY"
	DriveModeDistance    = "DISTANCE"
	DriveModeSport       = "SPORT"
	DriveModeWinter      = "WINTER"
	DriveModeOffRoadAuto = "OFF_ROAD_AUTO"
)

Valid driveMode values for Rivian's planTrip2 enum. Names cribbed from the home-assistant-rivian integration's DRIVE_MODE_MAP and confirmed against the live gateway. The mobile app's user-facing labels don't match the enum; mapping below.

EVERYDAY               → "All-Purpose"  (default)
DISTANCE               → "Conserve"     (range-extension)
SPORT                  → "Sport"
WINTER                 → "Snow"
OFF_ROAD_AUTO          → "All-Terrain"

DISTANCE produces materially different plans (one fewer stop + shorter route on long trips) so the knob is load-bearing.

View Source
const (
	DistanceMi = "mi"
	DistanceKm = "km"
	TempC      = "c"
	TempF      = "f"
	// TempAuto asks for temperature to follow distance again.
	TempAuto = "auto"

	SourceUser   = "user"
	SourceRegion = "region"
	SourceUnset  = "unset"
	SourceFollow = "follows_distance"
)

Unit values and sources.

View Source
const DefaultHomeCurrency = "USD"

DefaultHomeCurrency is the ISO-4217 code used when the operator hasn't set anything. Picked to match the app's target audience; the UI exposes the field so it can be changed.

View Source
const FavoritesMax = 25

FavoritesMax caps how many favorites we'll persist. Plenty for a human roster, keeps the JSON blob bounded.

View Source
const LabelMaxLen = 80

LabelMaxLen caps the human label so a rogue paste doesn't blow up the UI chip. Address-shape strings often go ~120 chars; we trim hard so chips stay compact.

View Source
const MaxHomePricePerKWh = 5.0

MaxHomePricePerKWh is the dearest home rate a patch accepts, per kilowatt hour.

Variables

View Source
var ErrBadUnit = errors.New("settings: unknown unit")

ErrBadUnit is a unit value outside the lists.

View Source
var ErrHomeLocationInvalid = errors.New("latitude must be between -90 and 90 and longitude between -180 and 180")

ErrHomeLocationInvalid: a latitude or longitude that isn't a finite value in range.

View Source
var ErrNetworkLocationInvalid = errors.New("network location must be a latitude and a longitude in range")

ErrNetworkLocationInvalid: a network row's lat/lon is half given, out of range or 0,0.

View Source
var ErrNoPreviousHome = errors.New("no previous home location")

ErrNoPreviousHome: there's no earlier home to go back to.

View Source
var HomeCurrencies = []string{"USD", "CAD", "EUR", "GBP", "AUD", "NZD", "CHF", "SEK", "NOK", "DKK"}

HomeCurrencies are the currencies a patch accepts for the home rate: the set the cost calculator and the lease calculator already offer.

Functions

func AIConsent added in v0.37.6

func AIConsent(ctx context.Context, s *Store) bool

AIConsent reports whether the user turned AI advice on. Off by default, and off if the setting cannot be read.

func AmbientTempOptIn added in v0.37.8

func AmbientTempOptIn(ctx context.Context, s *Store) bool

AmbientTempOptIn reports whether the user turned the lookup on. Off by default, and off if the setting cannot be read.

func AsOverrides added in v0.18.84

func AsOverrides(networks []ChargingNetwork) []dcfcrates.NetworkOverride

AsOverrides converts a persisted network list into the slug-keyed override slice the tripadvice cost estimator consumes. Custom user-added rows (no Slug) are passed through with their Name so the substring matcher in tripadvice can still hit them.

func GetFlatRate added in v0.38.26

func GetFlatRate(ctx context.Context, s *Store) (bool, error)

GetFlatRate reports whether the user said their rate is flat. False when unset.

func GetOffpeak added in v0.38.10

func GetOffpeak(ctx context.Context, s *Store) (offpeak.Schedule, bool, error)

GetOffpeak returns the saved schedule; ok is false when there is none (or the stored text no longer parses, which reads as none rather than failing a page).

func NormalizeHomeCurrency added in v0.38.21

func NormalizeHomeCurrency(c string) (string, bool)

NormalizeHomeCurrency returns c upper-cased when it is one of HomeCurrencies.

func PatchChargingConfig added in v0.38.21

func PatchChargingConfig(ctx context.Context, s *Store, pricePerKWh *float64, currency *string) error

PatchChargingConfig writes only the home-rate fields that are set (nil leaves the stored value alone); the gas price and comparison MPG are never touched. The caller has validated the values.

func PatchUnits added in v0.39.6

func PatchUnits(ctx context.Context, s *Store, distance, temperature *string) error

PatchUnits records the person's own choice. distance and temperature are each nil to leave alone; temperature may be TempAuto to follow distance again. A distance choice is source "user" and replaces a seeded one.

func ResetChargingNetworks added in v0.18.84

func ResetChargingNetworks(ctx context.Context, s *Store) error

ResetChargingNetworks clears the user's persisted overrides so the next Get returns the seed defaults. Drives the "Reset to defaults" button on the Settings panel.

func RivianReceiptsOptIn added in v0.36.10

func RivianReceiptsOptIn(ctx context.Context, s *Store) bool

RivianReceiptsOptIn reports whether the user opted in. Off by default.

func SeedDistanceUnit added in v0.39.6

func SeedDistanceUnit(ctx context.Context, s *Store, distance string) (bool, error)

SeedDistanceUnit records a client's regional default, once: only if the account has no distance unit yet (the first writer wins, so two clients signing in together agree). It reports whether this call wrote it. A choice the person makes later always replaces it.

func SetAIConsent added in v0.37.6

func SetAIConsent(ctx context.Context, s *Store, on bool) error

SetAIConsent records the choice.

func SetAmbientTempOptIn added in v0.37.8

func SetAmbientTempOptIn(ctx context.Context, s *Store, on bool) error

SetAmbientTempOptIn records the choice.

func SetChargingConfig

func SetChargingConfig(ctx context.Context, s *Store, cfg ChargingConfig) error

SetChargingConfig persists the provided cost settings. Negative prices are rejected by coercing to zero so the UI can't persist a nonsensical value.

func SetChargingNetworks

func SetChargingNetworks(ctx context.Context, s *Store, networks []ChargingNetwork) error

SetChargingNetworks persists the provided price book, overwriting any prior value. Empty names and non-positive prices are dropped silently so a partially filled UI form can't corrupt the list. Pass an empty slice to clear the user's overrides; the next Get will return the defaults again.

func SetFlatRate added in v0.38.26

func SetFlatRate(ctx context.Context, s *Store, on bool) error

SetFlatRate saves the choice; off removes the key (the default).

func SetHomeLocation

func SetHomeLocation(ctx context.Context, s *Store, h HomeLocation) error

SetHomeLocation persists a home location (or clears it with Set=false), keeping the home it replaces as the previous one so the change can be undone.

func SetOffpeak added in v0.38.10

func SetOffpeak(ctx context.Context, s *Store, sch offpeak.Schedule) error

SetOffpeak saves an already validated schedule (offpeak.Normalize); a schedule with no windows clears it.

func SetPlannerFavorites added in v0.18.77

func SetPlannerFavorites(ctx context.Context, s *Store, list []PlannerFavorite) error

SetPlannerFavorites persists the favorites list. Validates each entry's shape (non-zero coords, non-empty label) and caps the total at FavoritesMax — invalid entries are silently dropped to keep the persisted blob clean.

func SetPlannerPrefs

func SetPlannerPrefs(ctx context.Context, s *Store, p PlannerPrefs) error

SetPlannerPrefs persists the defaults. Empty DriveMode clears the stored value; nil HasAdapter clears the stored value.

func SetRivianReceiptsOptIn added in v0.36.10

func SetRivianReceiptsOptIn(ctx context.Context, s *Store, on bool) error

SetRivianReceiptsOptIn records the choice.

func ValidateHomeLocation added in v0.38.21

func ValidateHomeLocation(h HomeLocation) error

ValidateHomeLocation checks a home about to be saved: finite, lat in [-90, 90], lon in [-180, 180], and a known source. A clear (Set=false) is always valid.

func ValidateNetworkLocations added in v0.38.22

func ValidateNetworkLocations(networks []ChargingNetwork) error

ValidateNetworkLocations checks the optional location on each row before a save: both or neither, finite, in range and not 0,0. A built-in network can't carry one (the save drops it).

Types

type AIConfig

type AIConfig struct {
	Provider       string // "", "openai", "anthropic", "gemini"
	OpenAIModel    string
	OpenAIKey      string
	AnthropicModel string
	AnthropicKey   string
	GeminiModel    string
	GeminiKey      string

	// Image generation is a separate pipeline from text analysis and so
	// has its own provider + per-provider model overrides. The API keys
	// are shared with the text providers above.
	ImageProvider    string // "", "openai", "gemini"
	ImageOpenAIModel string
	ImageGeminiModel string

	// Speech-to-text pipeline (voice notes). Same shape as Image.
	SpeechProvider    string // "", "openai", "gemini"
	SpeechOpenAIModel string
	SpeechGeminiModel string

	// RecapWeatherEnabled toggles the optional weather lookup that
	// runs during trip recap generation. Off by default; the feature
	// hits an external HTTP API (Open-Meteo) so it must be opt-in.
	RecapWeatherEnabled bool

	// GPS accuracy heuristic thresholds applied client-side on the
	// drive detail page. See DefaultGPS* for the meaning of each.
	GPSMissingPct float64
	GPSStaleSec   int
	GPSJumpCount  int
}

AIConfig is the raw persisted AI configuration. Keys are never returned from handlers; this struct is the internal representation.

type AIImagePublic

type AIImagePublic struct {
	Provider  string `json:"provider"`            // "", openai|gemini
	Effective string `json:"effective,omitempty"` // what auto-selection picked
	// Per-provider model overrides. Empty string means "use the default".
	OpenAIModel string `json:"openai_model,omitempty"`
	GeminiModel string `json:"gemini_model,omitempty"`
	Ready       bool   `json:"ready"`
}

AIImagePublic is the subset surfaced to the Settings UI. Per-provider key presence is derived from the top-level Providers map, so we only repeat the model choices and the selected provider here.

type AIPublic

type AIPublic struct {
	Provider  string             `json:"provider"` // "", openai|anthropic|gemini
	Effective string             `json:"effective_provider,omitempty"`
	Model     string             `json:"effective_model,omitempty"`
	Providers map[string]ProInfo `json:"providers"`
	Ready     bool               `json:"ready"`

	// Image is the public view of the image-generation configuration.
	Image AIImagePublic `json:"image"`
	// Speech is the public view of the voice-transcription configuration.
	Speech AISpeechPublic `json:"speech"`
}

AIPublic is the redacted view returned to the UI. Keys are reported only as "set" / "not set"; the actual secret never leaves the server.

type AISpeechPublic

type AISpeechPublic struct {
	Provider    string `json:"provider"`
	Effective   string `json:"effective,omitempty"`
	OpenAIModel string `json:"openai_model,omitempty"`
	GeminiModel string `json:"gemini_model,omitempty"`
	Ready       bool   `json:"ready"`
}

AISpeechPublic mirrors AIImagePublic for the speech-to-text pipeline.

type AIUpdate

type AIUpdate struct {
	Provider       *string `json:"provider,omitempty"`
	OpenAIModel    *string `json:"openai_model,omitempty"`
	OpenAIKey      *string `json:"openai_api_key,omitempty"`
	AnthropicModel *string `json:"anthropic_model,omitempty"`
	AnthropicKey   *string `json:"anthropic_api_key,omitempty"`
	GeminiModel    *string `json:"gemini_model,omitempty"`
	GeminiKey      *string `json:"gemini_api_key,omitempty"`

	// Image generation settings.
	ImageProvider    *string `json:"image_provider,omitempty"`
	ImageOpenAIModel *string `json:"image_openai_model,omitempty"`
	ImageGeminiModel *string `json:"image_gemini_model,omitempty"`

	// Speech-to-text settings.
	SpeechProvider    *string `json:"speech_provider,omitempty"`
	SpeechOpenAIModel *string `json:"speech_openai_model,omitempty"`
	SpeechGeminiModel *string `json:"speech_gemini_model,omitempty"`
}

AIUpdate is the JSON body accepted by PUT /api/settings/ai. A nil pointer means "leave this field alone"; an explicit empty string clears a key.

type ChargingConfig

type ChargingConfig struct {
	// HomePricePerKWh is the retail cost in HomeCurrency per kilowatt
	// hour. Zero means "not configured"; in that case estimated cost
	// is not computed.
	HomePricePerKWh float64 `json:"home_price_per_kwh"`
	// HomeCurrency is the ISO-4217 code displayed next to estimated
	// cost. Defaults to USD.
	HomeCurrency string `json:"home_currency"`
	// GasPricePerGallon and ComparisonMPG feed the gas-equivalent chip
	// on the trip planner. Zero on either disables the chip.
	GasPricePerGallon float64 `json:"gas_price_per_gallon"`
	ComparisonMPG     float64 `json:"comparison_mpg"`
}

ChargingConfig is the user-configurable cost-of-energy settings applied locally to estimate the price of sessions Rivian reports as free (home AC, L2 on non-RAN chargers, etc.).

func GetChargingConfig

func GetChargingConfig(ctx context.Context, s *Store) (ChargingConfig, error)

GetChargingConfig returns the stored home-charging cost settings, filling defaults where unset.

type ChargingNetwork

type ChargingNetwork struct {
	Name         string  `json:"name"`
	PricePerKWh  float64 `json:"price_per_kwh"`
	Currency     string  `json:"currency"`
	MemberPrice  float64 `json:"member_price_per_kwh,omitempty"`
	MemberActive bool    `json:"member_active,omitempty"`
	MemberPlan   string  `json:"member_plan,omitempty"`
	Slug         string  `json:"slug,omitempty"`
	// Preferred = "rank this network high in Rivian's planner".
	// Forwarded as networkPreferences[].preference=1 on plan
	// requests. Unset / false sends nothing for that network.
	Preferred bool `json:"preferred,omitempty"`
	// Seeded is computed on read, never stored: the row's rates are still the built-in
	// table's (or blank), so the user has not entered a rate for this network. Its
	// price fields are returned as 0 ("unset"): the table's mid-2025 figures are dated
	// estimates that no network publishes as a price and are not shown or applied as
	// one. A row the user edited has Seeded false.
	Seeded bool `json:"seeded,omitempty"`
	// Lat/Lon are where a network row of the user's own (no built-in Slug) is, when they
	// gave one (#906): both or neither. Private to this user: only their charge map's
	// label and the pricing prefill read it. Never in AsOverrides, the chargers archive,
	// the DCFC Tracker links, demo data or anyone else's price book.
	Lat *float64 `json:"lat,omitempty"`
	Lon *float64 `json:"lon,omitempty"`
}

ChargingNetwork is one entry in the price book. Two use cases share this row:

  1. Manual charge-session pricing on the Charges page (one-click "apply EA rate" for a recorded session). Uses Name + PricePerKWh + Currency.
  2. Trip planner per-stop cost estimation. Adds MemberPrice + MemberActive so the planner can quote both "guest" and "your actual" rates without us tracking memberships behind the scenes. Slug ties a row to the built-in dcfcrates.Networks table for match-pattern lookup (so a charger name like "Electrify America - Pflugerville" still maps to the EA row without the user re-entering patterns).

New fields are all optional with omitempty so the manual-pricing UI keeps deserialising older JSON blobs unchanged.

func DefaultChargingNetworks added in v0.18.84

func DefaultChargingNetworks() []ChargingNetwork

DefaultChargingNetworks returns the seed list derived from dcfcrates.Networks. Same order as the built-in table so the Settings panel renders predictably across reloads.

func GetChargingNetworks

func GetChargingNetworks(ctx context.Context, s *Store) ([]ChargingNetwork, error)

GetChargingNetworks returns the configured price book. When the store has no entry yet, returns the seed defaults from dcfcrates.Networks so the Settings UI always has something to show on first load. The seed result is NOT persisted - the user has to hit Save once before their list diverges from the seed.

type Factory

type Factory struct {
	// contains filtered or unexported fields
}

Factory hands out per-user *Store instances. The settings store is just (db, userID), so a per-user instance is essentially free — caching is purely so callers reuse the same pointer across requests and don't churn the heap on every handler hit.

func NewFactory

func NewFactory(d *sql.DB) *Factory

NewFactory wraps the shared pool.

func (*Factory) For

func (f *Factory) For(userID uuid.UUID) *Store

For returns the *Store for userID, constructing one on first sight.

func (*Factory) RivianReceiptsOptIn added in v0.36.10

func (f *Factory) RivianReceiptsOptIn(ctx context.Context, userID uuid.UUID) bool

RivianReceiptsOptIn is the user's receipts opt-in by id, for callers without a request context (the sync loop).

type GPSPublic

type GPSPublic struct {
	MissingPct float64 `json:"missing_pct"`
	StaleSec   int     `json:"stale_sec"`
	JumpCount  int     `json:"jump_count"`
}

GPSPublic is the redacted view of the GPS accuracy thresholds. Surfaced through /api/config so every page that needs to apply them (drive detail, live panel) reads one source.

type GPSUpdate

type GPSUpdate struct {
	MissingPct *float64 `json:"missing_pct,omitempty"`
	StaleSec   *int     `json:"stale_sec,omitempty"`
	JumpCount  *int     `json:"jump_count,omitempty"`
}

GPSUpdate is the JSON body accepted by PUT /api/admin/settings/gps.

type HomeLocation

type HomeLocation struct {
	Set       bool    `json:"set"`
	Latitude  float64 `json:"latitude"`
	Longitude float64 `json:"longitude"`
	// Label is the short, user-visible name surfaced everywhere
	// the point is referenced (trip planner From: Home, drive
	// timeline "departed Home"). Defaults to "Home" on save when
	// the client doesn't provide one — keeps planner UI compact.
	Label string `json:"label,omitempty"`
	// Address is the full geocoded address kept for context on
	// the Settings card. Never used as the primary label.
	Address string `json:"address,omitempty"`
	// Source is how the home was set: "city", "address", "coordinates" or "charge" ("" before it was recorded).
	Source string `json:"source,omitempty"`
}

HomeLocation is the user's "home" base — used by the trip planner to offer a one-click Origin / Destination preset, and (eventually) by the recap path to recognise round trips that end at home.

Set is the boolean the UI uses to decide whether to render the Home preset button at all. Latitude/Longitude are the resolved coords; Label is the human-readable name (city + state) so the UI doesn't need to reverse-geocode every time.

func GetHomeLocation

func GetHomeLocation(ctx context.Context, s *Store) (HomeLocation, error)

GetHomeLocation reads the saved home location. Returns Set=false when no location has been configured (zero-value HomeLocation).

func PreviousHomeLocation added in v0.38.21

func PreviousHomeLocation(ctx context.Context, s *Store) (HomeLocation, error)

PreviousHomeLocation is the home the latest change replaced (Set=false when there is none).

func RestorePreviousHomeLocation added in v0.38.21

func RestorePreviousHomeLocation(ctx context.Context, s *Store) (HomeLocation, error)

RestorePreviousHomeLocation swaps the current home and the previous one (so restoring twice returns to where it started). ErrNoPreviousHome when there's nothing to restore.

type KV

type KV interface {
	GetAll(ctx context.Context) (map[string]string, error)
	Set(ctx context.Context, key, value string) error
	Delete(ctx context.Context, key string) error
}

KV is the minimal key/value backing store the Manager needs. Both the per-user *Store in this package and the install-wide *appsettings.Store satisfy it. The Manager itself doesn't care which it talks to — but in practice Rivolt wires it to appsettings (AI keys are an install concern: the operator pays the LLM bill).

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager owns the current AI configuration and the corresponding Analyzer. It is concurrency-safe; handlers call Analyzer() on every request.

func NewManager

func NewManager(ctx context.Context, store KV, envSeed AIConfig) (*Manager, error)

NewManager loads any persisted config and builds the initial Analyzer. envSeed is merged in for values that are not yet stored, so first-boot behaviour matches the previous env-only configuration.

func (*Manager) Analyzer

func (m *Manager) Analyzer() *ai.Analyzer

Analyzer returns the currently configured analyzer, or nil if AI is disabled (no key for the selected provider).

func (*Manager) GPSPublic

func (m *Manager) GPSPublic() GPSPublic

GPSPublic returns the current GPS accuracy thresholds.

func (*Manager) GeminiCreds

func (m *Manager) GeminiCreds() (apiKey, model string)

GeminiCreds returns the currently stored Gemini API key and model. The caller must treat the key as a secret and never log it. Used by the profile-image generation endpoint which doesn't go through Analyzer.

func (*Manager) ImageCreds

func (m *Manager) ImageCreds() (provider, apiKey, model string)

ImageCreds returns the provider, API key, and model to use for image generation. Provider is one of "openai" or "gemini", or the empty string when no image-capable provider has a key configured. Respects the user's explicit ImageProvider setting and falls back to auto-selection otherwise.

func (*Manager) ListModels

func (m *Manager) ListModels(ctx context.Context, provider string) ([]string, error)

ListModels fetches the catalogue of usable models for a provider, using the stored API key. The caller is expected to have already saved a key; we deliberately don't accept an ad-hoc key here so the UI has one place (PUT /api/settings/ai) that persists secrets.

func (*Manager) OpenAICreds

func (m *Manager) OpenAICreds() (apiKey, model string)

OpenAICreds returns the stored OpenAI API key and text model. Used by non-Analyzer endpoints (e.g. Whisper transcription) that need OpenAI auth directly.

func (*Manager) Provider

func (m *Manager) Provider() ai.Provider

Provider returns the active ai.Provider, or nil when AI is disabled. Features outside of shot analysis (coach, comparator, ask, digest, profile-name) call this directly so they can wrap the provider in their own task-specific helper.

func (*Manager) Public

func (m *Manager) Public() AIPublic

Public returns the redacted settings DTO.

func (*Manager) RecapPublic

func (m *Manager) RecapPublic() RecapPublic

RecapPublic returns the redacted recap settings DTO.

func (*Manager) RecapWeatherEnabled

func (m *Manager) RecapWeatherEnabled() bool

RecapWeatherEnabled returns the current value of the recap weather enrichment toggle. Cheap; safe to call per request.

func (*Manager) SpeechCreds

func (m *Manager) SpeechCreds() (provider, apiKey, model string)

SpeechCreds returns the provider, API key, and model to use for voice transcription. Same contract as ImageCreds: empty provider means "no speech-capable provider has a key configured".

func (*Manager) Update

func (m *Manager) Update(ctx context.Context, patch AIUpdate) (AIPublic, error)

Update applies the patch, persists it, and rebuilds the analyzer. Returns the new public view (or an error if the patch is invalid).

func (*Manager) UpdateGPS

func (m *Manager) UpdateGPS(ctx context.Context, patch GPSUpdate) (GPSPublic, error)

UpdateGPS applies the patch and persists. Values are clamped to non-negative ranges so a malformed payload can't silently disable the heuristic by way of a negative threshold.

func (*Manager) UpdateRecap

func (m *Manager) UpdateRecap(ctx context.Context, patch RecapUpdate) (RecapPublic, error)

UpdateRecap applies the patch and persists. Doesn't touch the AI analyzer because recap settings are orthogonal to provider config.

func (*Manager) VisionCreds

func (m *Manager) VisionCreds() (provider, apiKey, model string)

VisionCreds returns the provider, API key and model to use for image UNDERSTANDING (vision input, e.g. scanning a coffee bag). This is distinct from ImageCreds, which generates images. Vision follows the main chat provider so the bag gets read by whichever LLM you've chosen to analyse your shots. Supported providers: "openai" and "gemini". Anthropic Claude supports vision but isn't wired here yet; if the user's chat provider is Anthropic we fall back to whichever of OpenAI/Gemini has a key. Returns empty provider when no vision- capable key is set.

type NetworkPref added in v0.20.0

type NetworkPref struct {
	NetworkID  string
	Preference int
}

NetworkPref is one entry in a Rivian networkPreferences[] array. Defined here (not in rivian/) because settings is imported by rivian; the api layer maps these to rivian.NetworkPreference.

func NetworkPreferenceList added in v0.20.0

func NetworkPreferenceList(networks []ChargingNetwork) []NetworkPref

NetworkPreferenceList returns Rivian networkId entries for the networks the user marked Preferred. Empty when the user picked none — caller then omits networkPreferences entirely.

Wire shape mirrors the Rivian Android app (3.6 + 3.12 confirmed via apktool): NetworkPreferencesInput has a single `networkId` field; no `preference` value is sent. The app submits ONLY the preferred IDs; networks omitted from the list are not treated as "deprioritise" but as "no opinion." Preference=1 is included on the Go-side struct so the rivian client still has a value, but the GraphQL serializer is what determines whether it goes on the wire.

Networks span multiple operator IDs (RAN = RAN+RWN; FLO = FLOC+FLOU); all share the user's per-slug toggle and we emit one row per ID.

type PlannerFavorite added in v0.18.77

type PlannerFavorite struct {
	ID        string  `json:"id"`
	Label     string  `json:"label"`
	Latitude  float64 `json:"latitude"`
	Longitude float64 `json:"longitude"`
}

PlannerFavorite is one saved place — coordinates plus a short user-visible name. ID is a UUID minted client-side so deletes and renames don't need a separate lookup.

func GetPlannerFavorites added in v0.18.77

func GetPlannerFavorites(ctx context.Context, s *Store) ([]PlannerFavorite, error)

GetPlannerFavorites returns the user's saved favorites. Returns nil (not an error) when nothing has been saved or the stored blob fails to parse — a corrupt blob is treated the same as "not configured" so a single bad write doesn't permanently break the planner page.

type PlannerPrefs

type PlannerPrefs struct {
	// DriveMode is one of EVERYDAY / DISTANCE / SPORT / WINTER /
	// OFF_ROAD_AUTO. Empty string means "no default — let Rivian
	// pick" (the gateway's default is EVERYDAY, so empty and
	// EVERYDAY produce the same plan).
	DriveMode string `json:"drive_mode"`
	// HasAdapter — does the vehicle have the Tesla NACS adapter?
	// Pointer so absent/false/true are all distinguishable on the
	// wire; default is unset.
	HasAdapter *bool `json:"has_adapter,omitempty"`
}

PlannerPrefs are the user's saved trip-planner defaults. The SPA pre-fills the per-trip form from these so the user doesn't have to pick the same drive mode every time.

func GetPlannerPrefs

func GetPlannerPrefs(ctx context.Context, s *Store) (PlannerPrefs, error)

GetPlannerPrefs returns the saved planner defaults. Returns the zero-value PlannerPrefs when nothing has been configured.

type ProInfo

type ProInfo struct {
	Model  string `json:"model"`
	HasKey bool   `json:"has_key"`
}

ProInfo is the per-provider card content for the settings UI.

type RecapPublic

type RecapPublic struct {
	WeatherEnabled bool `json:"weather_enabled"`
}

RecapPublic is the redacted view of the install-wide recap config returned to the UI. Lives in its own DTO (and on its own endpoint) because the recap toggle isn't an AI configuration knob — it's a data-egress switch (Open-Meteo, see internal/weather) that happens to be consumed by an AI feature. Surfacing it under "AI" sent the wrong signal about what disabling AI does/doesn't hide.

type RecapUpdate

type RecapUpdate struct {
	WeatherEnabled *bool `json:"weather_enabled,omitempty"`
}

RecapUpdate is the JSON body accepted by PUT /api/settings/recap. Same nil-pointer-means-untouched contract as AIUpdate.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store is a thin key/value wrapper around a Postgres connection pool scoped to a single user.

func OpenStore

func OpenStore(db *sql.DB, userID uuid.UUID) (*Store, error)

OpenStore binds a pooled connection to a user_id. The pool is owned by the caller; Store does not close it.

func (*Store) Close

func (s *Store) Close() error

Close is a no-op; the pool lifecycle is handled by main.

func (*Store) Delete

func (s *Store) Delete(ctx context.Context, key string) error

Delete removes a key. No-op if absent.

func (*Store) GetAll

func (s *Store) GetAll(ctx context.Context) (map[string]string, error)

GetAll returns every stored key/value pair across every namespace, flattened. Keys keep their "ai.openai.model" / "charging.home_currency" shape so callers that were built against the old sqlite KV table don't need to know about namespaces.

func (*Store) Set

func (s *Store) Set(ctx context.Context, key, value string) error

Set writes a single value. Empty string is a valid value ("cleared by operator"); callers that want to delete should use Delete instead. Writes are atomic at the (user_id, namespace) row level.

func (*Store) SetMany added in v0.38.21

func (s *Store) SetMany(ctx context.Context, kv map[string]string) error

SetMany writes several values of one namespace in a single statement, so the row changes all at once or not at all and a concurrent writer of the same row sees either none or all of them (Set one by one let two writers interleave their keys). Every key must belong to the same namespace.

type Units added in v0.39.6

type Units struct {
	DistanceUnit      string `json:"distance_unit"`
	TemperatureUnit   string `json:"temperature_unit"`
	DistanceSource    string `json:"distance_source"`
	TemperatureSource string `json:"temperature_source"`
}

Units is the resolved setting: always concrete values, with where each came from.

func GetUnits added in v0.39.6

func GetUnits(ctx context.Context, s *Store) (Units, error)

GetUnits resolves the account's units. An account with nothing stored reads mi and °F with source "unset" (the value is a placeholder: a client seeds it from its own region, see SeedDistanceUnit). A nil store, or one that cannot be read, reads the same.

Jump to

Keyboard shortcuts

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