eeaad62b10
- internal/postgres: pgx-over-database/sql pool (otelsql), embedded goose
migrations into schema 'backend', committed go-jet code + cmd/jetgen tool.
- internal/account: durable accounts + unified telegram/email identities
(UUIDv7 keys), find-or-create provisioning with unique-conflict handling.
- internal/session: opaque 256-bit tokens stored as a SHA-256 hash, revoke-only
(no TTL); write-through cache gating /readyz; store + service.
- internal/telemetry: OTel tracer/meter providers (none/stdout) + request-timing
middleware; internal/config gains Postgres + OTel env loading.
- internal/server: /api/v1 {public,user,internal,admin} skeleton + X-User-ID
middleware; /readyz checks DB ping + cache; main wires
telemetry -> db+migrate -> warm cache -> server.
- Tests: unit + integration (build tag 'integration', testcontainers
postgres:17) for migrations, accounts, sessions, readyz; new integration.yaml.
- Docs: ARCHITECTURE, TESTING, PLAN refinements, root + backend READMEs.
Session/account REST handlers deferred to Stage 6 (gateway); OTLP + dashboards
to Stage 11.
124 lines
3.6 KiB
Go
124 lines
3.6 KiB
Go
// Package config loads and validates the backend's runtime configuration from
|
|
// the process environment.
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"time"
|
|
|
|
"scrabble/backend/internal/postgres"
|
|
"scrabble/backend/internal/telemetry"
|
|
)
|
|
|
|
// Config holds the backend's runtime configuration.
|
|
type Config struct {
|
|
// HTTPAddr is the listen address of the HTTP listener (host:port).
|
|
HTTPAddr string
|
|
// LogLevel is the zap log level: "debug", "info", "warn" or "error".
|
|
LogLevel string
|
|
// Postgres configures the primary database pool.
|
|
Postgres postgres.Config
|
|
// Telemetry configures the OpenTelemetry providers.
|
|
Telemetry telemetry.Config
|
|
}
|
|
|
|
// Defaults applied when the corresponding environment variable is unset.
|
|
const (
|
|
defaultHTTPAddr = ":8080"
|
|
defaultLogLevel = "info"
|
|
)
|
|
|
|
// Load reads the configuration from the environment, applies defaults for unset
|
|
// variables, and validates the result.
|
|
func Load() (Config, error) {
|
|
pg := postgres.DefaultConfig()
|
|
pg.DSN = os.Getenv("BACKEND_POSTGRES_DSN")
|
|
var err error
|
|
if pg.MaxOpenConns, err = envInt("BACKEND_POSTGRES_MAX_OPEN_CONNS", pg.MaxOpenConns); err != nil {
|
|
return Config{}, err
|
|
}
|
|
if pg.MaxIdleConns, err = envInt("BACKEND_POSTGRES_MAX_IDLE_CONNS", pg.MaxIdleConns); err != nil {
|
|
return Config{}, err
|
|
}
|
|
if pg.ConnMaxLifetime, err = envDuration("BACKEND_POSTGRES_CONN_MAX_LIFETIME", pg.ConnMaxLifetime); err != nil {
|
|
return Config{}, err
|
|
}
|
|
if pg.OperationTimeout, err = envDuration("BACKEND_POSTGRES_OPERATION_TIMEOUT", pg.OperationTimeout); err != nil {
|
|
return Config{}, err
|
|
}
|
|
|
|
tel := telemetry.DefaultConfig()
|
|
tel.ServiceName = envOr("BACKEND_SERVICE_NAME", tel.ServiceName)
|
|
tel.TracesExporter = envOr("BACKEND_OTEL_TRACES_EXPORTER", tel.TracesExporter)
|
|
tel.MetricsExporter = envOr("BACKEND_OTEL_METRICS_EXPORTER", tel.MetricsExporter)
|
|
|
|
c := Config{
|
|
HTTPAddr: envOr("BACKEND_HTTP_ADDR", defaultHTTPAddr),
|
|
LogLevel: envOr("BACKEND_LOG_LEVEL", defaultLogLevel),
|
|
Postgres: pg,
|
|
Telemetry: tel,
|
|
}
|
|
if err := c.validate(); err != nil {
|
|
return Config{}, err
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
// validate reports whether the configuration values are acceptable.
|
|
func (c Config) validate() error {
|
|
switch c.LogLevel {
|
|
case "debug", "info", "warn", "error":
|
|
default:
|
|
return fmt.Errorf("config: invalid BACKEND_LOG_LEVEL %q", c.LogLevel)
|
|
}
|
|
if c.HTTPAddr == "" {
|
|
return fmt.Errorf("config: BACKEND_HTTP_ADDR must not be empty")
|
|
}
|
|
if err := c.Postgres.Validate(); err != nil {
|
|
return fmt.Errorf("config: %w (set BACKEND_POSTGRES_DSN)", err)
|
|
}
|
|
if err := c.Telemetry.Validate(); err != nil {
|
|
return fmt.Errorf("config: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// envOr returns the value of the environment variable named key, or fallback
|
|
// when the variable is unset or empty.
|
|
func envOr(key, fallback string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
// envInt parses the environment variable named key as an int, returning
|
|
// fallback when it is unset and an error when it is set but malformed.
|
|
func envInt(key string, fallback int) (int, error) {
|
|
v := os.Getenv(key)
|
|
if v == "" {
|
|
return fallback, nil
|
|
}
|
|
n, err := strconv.Atoi(v)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("config: %s: %w", key, err)
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
// envDuration parses the environment variable named key as a Go duration,
|
|
// returning fallback when it is unset and an error when it is set but malformed.
|
|
func envDuration(key string, fallback time.Duration) (time.Duration, error) {
|
|
v := os.Getenv(key)
|
|
if v == "" {
|
|
return fallback, nil
|
|
}
|
|
d, err := time.ParseDuration(v)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("config: %s: %w", key, err)
|
|
}
|
|
return d, nil
|
|
}
|