Files
cairnquire/apps/server/internal/config/config.go
2026-06-01 10:53:28 -04:00

131 lines
3.2 KiB
Go

package config
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strconv"
)
type Config struct {
Server ServerConfig `json:"server"`
Database DatabaseConfig `json:"database"`
Content ContentConfig `json:"content"`
Auth AuthConfig `json:"auth"`
Email EmailConfig `json:"email"`
LogLevel string `json:"logLevel"`
DevMode bool `json:"devMode"`
}
type ServerConfig struct {
Addr string `json:"addr"`
}
type DatabaseConfig struct {
Path string `json:"path"`
PrimaryURL string `json:"primaryUrl"`
AuthToken string `json:"authToken"`
}
type ContentConfig struct {
SourceDir string `json:"sourceDir"`
StoreDir string `json:"storeDir"`
}
type AuthConfig struct {
PublicOrigin string `json:"publicOrigin"`
}
type EmailConfig struct {
SMTPHost string `json:"smtpHost"`
SMTPPort int `json:"smtpPort"`
From string `json:"from"`
}
func Default() Config {
return Config{
Server: ServerConfig{
Addr: ":8080",
},
Database: DatabaseConfig{
Path: filepath.Join("..", "..", "data", "db.sqlite"),
},
Content: ContentConfig{
SourceDir: filepath.Join("..", "..", "content"),
StoreDir: filepath.Join("..", "..", "data", "files"),
},
Auth: AuthConfig{
PublicOrigin: "http://localhost:8080",
},
Email: EmailConfig{
SMTPHost: "localhost",
SMTPPort: 1025,
From: "notifications@cairnquire.local",
},
LogLevel: "INFO",
}
}
func Load() (Config, error) {
cfg := Default()
if configPath := os.Getenv("CAIRNQUIRE_CONFIG"); configPath != "" {
if err := loadFile(configPath, &cfg); err != nil {
return Config{}, err
}
}
overrideString(&cfg.Server.Addr, "CAIRNQUIRE_SERVER_ADDR")
if port := os.Getenv("PORT"); port != "" {
cfg.Server.Addr = ":" + port
}
overrideString(&cfg.Database.Path, "CAIRNQUIRE_DATABASE_PATH")
overrideString(&cfg.Database.PrimaryURL, "CAIRNQUIRE_DATABASE_PRIMARY_URL")
overrideString(&cfg.Database.AuthToken, "CAIRNQUIRE_DATABASE_AUTH_TOKEN")
overrideString(&cfg.Content.SourceDir, "CAIRNQUIRE_CONTENT_SOURCE_DIR")
overrideString(&cfg.Content.StoreDir, "CAIRNQUIRE_CONTENT_STORE_DIR")
overrideString(&cfg.Auth.PublicOrigin, "CAIRNQUIRE_PUBLIC_ORIGIN")
overrideString(&cfg.Email.SMTPHost, "CAIRNQUIRE_EMAIL_SMTP_HOST")
if port := os.Getenv("CAIRNQUIRE_EMAIL_SMTP_PORT"); port != "" {
if p, err := strconv.Atoi(port); err == nil {
cfg.Email.SMTPPort = p
}
}
overrideString(&cfg.Email.From, "CAIRNQUIRE_EMAIL_FROM")
overrideString(&cfg.LogLevel, "CAIRNQUIRE_LOG_LEVEL")
if devMode := os.Getenv("CAIRNQUIRE_DEV_MODE"); devMode != "" {
parsed, err := strconv.ParseBool(devMode)
if err != nil {
return Config{}, fmt.Errorf("parse CAIRNQUIRE_DEV_MODE: %w", err)
}
cfg.DevMode = parsed
}
if cfg.Server.Addr == "" {
return Config{}, fmt.Errorf("server.addr must not be empty")
}
return cfg, nil
}
func loadFile(path string, cfg *Config) error {
file, err := os.Open(path)
if err != nil {
return fmt.Errorf("open config file %q: %w", path, err)
}
defer file.Close()
if err := json.NewDecoder(file).Decode(cfg); err != nil {
return fmt.Errorf("decode config file %q: %w", path, err)
}
return nil
}
func overrideString(target *string, key string) {
if value := os.Getenv(key); value != "" {
*target = value
}
}