Backend Phase A: foundation, migrations, Docker setup
- Go backend skeleton: config (caarlos0/env), slog JSON logging, pgxpool store, HTTP server with graceful shutdown. - httpapi: render helpers, Problem errors, middleware chain (requestID / logging / recover / CORS), /healthz and /readyz. - Migrations: full initial schema (users, sessions, lists, list_members, items, op_log SOURCE OF TRUTH, item_names) + golang-migrate runner binary using source/iofs (embedded). - Docker: multi-stage Dockerfile (Go 1.26 -> distroless nonroot), builds both server and migrate binaries. - deploy: docker-compose (caddy + backend + migrate + postgres:16), Caddyfile (auto-HTTPS), .env.example, pg extensions init script. - AGENTS.md: project context + roadmap for AI agents. Verified: image builds, both binaries run in container (smoke test).
This commit is contained in:
commit
2899eb205b
23 changed files with 1372 additions and 0 deletions
70
backend/internal/config/config.go
Normal file
70
backend/internal/config/config.go
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
// Package config holds all runtime configuration for the backend.
|
||||
// Values are parsed from environment variables via struct tags.
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/caarlos0/env/v11"
|
||||
)
|
||||
|
||||
// Config is the single source of runtime configuration.
|
||||
type Config struct {
|
||||
// HTTPAddr is the address the HTTP server listens on, e.g. ":8080".
|
||||
HTTPAddr string `env:"HTTP_ADDR" envDefault:":8080"`
|
||||
|
||||
// AppEnv: "development" or "production".
|
||||
AppEnv string `env:"APP_ENV" envDefault:"development"`
|
||||
|
||||
// LogLevel: debug | info | warn | error.
|
||||
LogLevel string `env:"LOG_LEVEL" envDefault:"info"`
|
||||
|
||||
// PublicBaseURL is the externally reachable URL (scheme + host), no trailing slash.
|
||||
// Used e.g. for OIDC redirect URIs. Example: "https://mitbringsl.example.com".
|
||||
PublicBaseURL string `env:"PUBLIC_BASE_URL" envDefault:"http://localhost:8080"`
|
||||
|
||||
// Database
|
||||
DatabaseURL string `env:"DATABASE_URL,required"`
|
||||
DBMaxOpenConns int32 `env:"DB_MAX_OPEN_CONNS" envDefault:"25"`
|
||||
DBMaxIdleConns int32 `env:"DB_MAX_IDLE_CONNS" envDefault:"5"`
|
||||
DBMaxLifetime time.Duration `env:"DB_MAX_CONN_LIFETIME" envDefault:"30m"`
|
||||
|
||||
// Session token config (opaque tokens issued by this backend after login).
|
||||
SessionTokenBytes int `env:"SESSION_TOKEN_BYTES" envDefault:"32"` // 256-bit
|
||||
SessionTokenTTL time.Duration `env:"SESSION_TOKEN_TTL" envDefault:"720h"` // 30 days
|
||||
SessionCookieName string `env:"SESSION_COOKIE_NAME" envDefault:"mitbringsl_session"`
|
||||
|
||||
// OIDC providers. Both optional; enable per provider.
|
||||
GoogleOIDC GoogleOIDCConfig
|
||||
GenericOIDC GenericOIDCConfig
|
||||
|
||||
// CORS allowed origins (comma-separated). Empty = no CORS headers.
|
||||
CORSAllowedOrigins []string `env:"CORS_ALLOWED_ORIGINS" envSeparator:","`
|
||||
}
|
||||
|
||||
// GoogleOIDCConfig for "Sign in with Google".
|
||||
type GoogleOIDCConfig struct {
|
||||
Enabled bool `env:"OIDC_GOOGLE_ENABLED" envDefault:"false"`
|
||||
ClientID string `env:"OIDC_GOOGLE_CLIENT_ID"` // the audience the verifier accepts
|
||||
Issuer string `env:"OIDC_GOOGLE_ISSUER" envDefault:"https://accounts.google.com"`
|
||||
}
|
||||
|
||||
// GenericOIDCConfig for any standards-compliant OIDC IdP (Keycloak, Authentik, ...).
|
||||
type GenericOIDCConfig struct {
|
||||
Enabled bool `env:"OIDC_GENERIC_ENABLED" envDefault:"false"`
|
||||
Issuer string `env:"OIDC_GENERIC_ISSUER"`
|
||||
ClientID string `env:"OIDC_GENERIC_CLIENT_ID"`
|
||||
}
|
||||
|
||||
// Load reads configuration from environment variables and validates basic invariants.
|
||||
func Load() (*Config, error) {
|
||||
var cfg Config
|
||||
if err := env.Parse(&cfg); err != nil {
|
||||
return nil, fmt.Errorf("parse env: %w", err)
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// IsProduction reports whether the app runs in production mode.
|
||||
func (c *Config) IsProduction() bool { return c.AppEnv == "production" }
|
||||
Loading…
Add table
Add a link
Reference in a new issue