first commit. added CI, Docker compose, blank client and server. used unit tests, they pass
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
// Package config loads the server configuration from environment
|
||||
// variables with sane defaults for local development.
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Config holds all runtime configuration for the API server.
|
||||
type Config struct {
|
||||
// HTTPAddr is the listen address, e.g. ":8080".
|
||||
HTTPAddr string
|
||||
// StaticDir points to the built frontend (React/Vite dist).
|
||||
// Empty disables static file serving.
|
||||
StaticDir string
|
||||
// Environment is "development", "staging" or "production".
|
||||
Environment string
|
||||
// AllowedOrigins is a comma-separated list of CORS origins
|
||||
// allowed to call the API from a browser.
|
||||
AllowedOrigins []string
|
||||
}
|
||||
|
||||
// Load reads configuration from the environment, falling back to
|
||||
// development-friendly defaults. Unknown values are validated.
|
||||
func Load() (*Config, error) {
|
||||
cfg := &Config{
|
||||
HTTPAddr: fromEnv("MESSENGER_HTTP_ADDR", ":8080"),
|
||||
StaticDir: os.Getenv("MESSENGER_STATIC_DIR"),
|
||||
Environment: fromEnv("MESSENGER_ENV", "development"),
|
||||
}
|
||||
|
||||
for _, origin := range strings.Split(fromEnv("MESSENGER_ALLOWED_ORIGINS", ""), ",") {
|
||||
if origin = strings.TrimSpace(origin); origin != "" {
|
||||
cfg.AllowedOrigins = append(cfg.AllowedOrigins, origin)
|
||||
}
|
||||
}
|
||||
|
||||
switch cfg.Environment {
|
||||
case "development", "staging", "production":
|
||||
default:
|
||||
return nil, fmt.Errorf("MESSENGER_ENV must be one of development|staging|production, got %q", cfg.Environment)
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// fromEnv returns the value of the environment variable name, or
|
||||
// fallback if the variable is unset or empty.
|
||||
func fromEnv(name, fallback string) string {
|
||||
if v := os.Getenv(name); v != "" {
|
||||
return v
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadDefaults(t *testing.T) {
|
||||
t.Setenv("MESSENGER_HTTP_ADDR", "")
|
||||
t.Setenv("MESSENGER_ENV", "")
|
||||
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Load() error = %v", err)
|
||||
}
|
||||
if cfg.HTTPAddr != ":8080" {
|
||||
t.Errorf("HTTPAddr = %q, want %q", cfg.HTTPAddr, ":8080")
|
||||
}
|
||||
if cfg.Environment != "development" {
|
||||
t.Errorf("Environment = %q, want development", cfg.Environment)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadFromEnv(t *testing.T) {
|
||||
t.Setenv("MESSENGER_HTTP_ADDR", ":9000")
|
||||
t.Setenv("MESSENGER_ENV", "production")
|
||||
t.Setenv("MESSENGER_ALLOWED_ORIGINS", "https://a.example, https://b.example")
|
||||
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Load() error = %v", err)
|
||||
}
|
||||
if cfg.HTTPAddr != ":9000" {
|
||||
t.Errorf("HTTPAddr = %q, want :9000", cfg.HTTPAddr)
|
||||
}
|
||||
if cfg.Environment != "production" {
|
||||
t.Errorf("Environment = %q, want production", cfg.Environment)
|
||||
}
|
||||
if len(cfg.AllowedOrigins) != 2 || cfg.AllowedOrigins[1] != "https://b.example" {
|
||||
t.Errorf("AllowedOrigins = %v, want [https://a.example https://b.example]", cfg.AllowedOrigins)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadInvalidEnv(t *testing.T) {
|
||||
t.Setenv("MESSENGER_ENV", "nope")
|
||||
|
||||
if _, err := Load(); err == nil {
|
||||
t.Fatal("Load() error = nil, want validation error")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Package logger provides a structured JSON logger built on log/slog.
|
||||
// In development a human-readable text handler is used.
|
||||
package logger
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
)
|
||||
|
||||
// New returns a *slog.Logger configured for the current environment.
|
||||
func New() *slog.Logger {
|
||||
level := slog.LevelDebug
|
||||
|
||||
handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
||||
Level: level,
|
||||
})
|
||||
|
||||
return slog.New(handler)
|
||||
}
|
||||
Reference in New Issue
Block a user