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

userprefs

package
v0.21.0 Latest Latest
Warning

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

Go to latest
Published: Oct 29, 2026 License: AGPL-3.0 Imports: 9 Imported by: 0

Documentation

Overview

Package userprefs stores how one person reads the console: how a number is written, which instant a timestamp means, which words the translated screens use (ADR-0459), and whether the console is drawn light or dark (ADR-0507).

It is per user rather than per browser because of the time zone. A language is how somebody reads at one desk, and ADR-0267 kept it in the browser for that reason; a time zone is where somebody *is*, and a person who answers tasks on a laptop and checks a stuck instance on a tablet has one of those and two of these. A preference they must set again per device is one they will have set wrong somewhere, and a timestamp read in the wrong zone is an operational error rather than an ugly one.

The server stores and validates; it does not format. Formatting happens in the browser, which is why validation here is strict: an unknown zone stored would come back as an exception from `Intl.DateTimeFormat` and break every timestamp on the screen instead of this one field.

Index

Constants

This section is empty.

Variables

View Source
var Appearances = []string{"light", "dark", "system", "cockpit"}

Appearances are how the console may be drawn (ADR-0507): light, dark, following the operating system's scheme, or the cockpit skin (ADR-0509), a playful dark variant nobody gets without choosing it. "" is the unset value and draws today's light console; "light" is the same picture chosen on purpose, which matters once the default changes. The list is the stylesheet's — app.css answers exactly these.

View Source
var Languages = []string{"de", "en"}

Languages are the locales the console's catalogue actually holds (ADR-0267). A language without a catalogue would render every translated screen as its own message keys, so the list is the catalogue's and is checked, not trusted. The api package replaces it at start-up with the message files the embedded console carries (ADR-0497); this default is what the package's own tests and a console-less build see.

View Source
var Regions = []string{"international", "ch", "de", "us"}

Regions are the number and date formats on offer. It is a fixed list rather than a locale picker, and that is the decision: each entry's output is a table in the console's own formatter (api/web/numfmt.js), so the preview on the settings screen promises exactly what the badge will show. A locale handed to `toLocaleString` would render whatever the browser's data says this year, which is neither previewable nor testable.

"international" is the default and is the unset value, not a fifth row: the console formats that way today, so an instance where nobody opens the screen keeps the behaviour it has.

Functions

This section is empty.

Types

type Prefs

type Prefs struct {
	// UserID is the authenticated principal's id. It is the store's key and is
	// never read from a request body.
	UserID string `json:"userId"`
	// Region is how a number and a date are written. One of Regions, or "".
	Region string `json:"region"`
	// TimeZone is which instant a timestamp means: an IANA name, or "" for the
	// device's own zone.
	TimeZone string `json:"timeZone"`
	// Language is which words the translated screens use. One of Languages, or "".
	Language string `json:"language"`
	// Appearance is whether the console is drawn light or dark. One of
	// Appearances, or "". It is here rather than in the browser for the reason
	// the time zone is: it belongs to the person, and `system` already covers a
	// person who wants each device to follow its own scheme.
	Appearance string `json:"appearance"`
	// UpdatedAt is when this record was last written, in unix nanos. It is here so
	// a support question ("since when has this been set?") has an answer.
	UpdatedAt int64 `json:"updatedAt,omitempty"`
}

Prefs is one person's answer to the four questions. Each field's zero value is "not chosen", which is why none of them is a pointer and why "international" and "" both mean the default: a person who clears the screen gets the console they had before it existed.

func (Prefs) Validate

func (p Prefs) Validate() error

Validate reports why a record may not be stored, or nil.

Every refusal names the field, because this validates a form somebody filled in and "invalid request" would send them looking at all four.

type Service

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

Service serves one person's display preferences: GET and PUT on /api/v1/me/preferences, and nothing else. There is no listing and no admin view — a preference is read by the person it belongs to, and by nobody else.

func New

func New(loop *runloop.Loop, store *Store) *Service

New builds the service over the store, serializing every access on the loop: a sidecar store does no locking of its own, and two tabs saving at once is the ordinary case for a setting a person edits on one screen.

func (*Service) HandleGet

func (s *Service) HandleGet(w http.ResponseWriter, r *http.Request)

HandleGet answers this person's preferences.

Nothing stored is not a miss: it is the state every account starts in, and the answer is the unset record, which is the console's behaviour before anybody opened the screen.

func (*Service) HandleSave

func (s *Service) HandleSave(w http.ResponseWriter, r *http.Request)

HandleSave rewrites this person's preferences and answers the stored record.

The whole record is written rather than patched: the screen edits its fields together and sends all of them, so a field left out is a field cleared — which is how "back to the defaults" works without a second endpoint. The top bar's light/dark switch sends the whole record too, with only the appearance changed.

type Store

type Store struct {
	*sidecar.Store[Prefs]
}

Store is the durable home of the display preferences: one JSON file per user id, with the atomic-write discipline every design-time store shares. It is owned by the run loop, like the stores it sits beside, and does no locking of its own.

func NewStore

func NewStore(dir string) (*Store, error)

NewStore opens (creating if needed) the preferences directory.

The key is the user id, and the filename scheme is the default: user ids come from whichever directory signs people in — an email address, an OIDC subject, a UUID — and hex-encoding is what makes any of those a safe, unique, deterministic filename. The encoding also guards the key on the way in, so an id carrying a slash cannot address a file outside the store.

Jump to

Keyboard shortcuts

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