Files
cairnquire/apps/server/internal/config/config.go
Tim Bendt 780ff3a02c feat: miller column browser, offline cache, search, breadcrumbs
- Dynamic miller column sizing with flexbox layout
- Root column (160px), middle slices (48px), active column (flex)
- Auto-scroll browser to rightmost column on navigation
- Offline indicator UI and IndexedDB cache integration
- /api/documents endpoint for client-side document caching
- Breadcrumb navigation in document content area
- FTS5 search with SQLite virtual table
- Client-side Mermaid and KaTeX rendering via CDN
- Deep sample content (advanced-topics/raft-deep-dive)
- Border removal (only search button keeps radius)
- Full viewport layout with proper borders between sidebar/content
2026-04-30 11:55:03 -04:00

103 lines
2.4 KiB
Go

package config
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
)
type Config struct {
Server ServerConfig `json:"server"`
Database DatabaseConfig `json:"database"`
Content ContentConfig `json:"content"`
Web WebConfig `json:"web"`
LogLevel string `json:"logLevel"`
}
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 WebConfig struct {
DistDir string `json:"distDir"`
DevViteURL string `json:"devViteUrl"`
}
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"),
},
Web: WebConfig{
DistDir: filepath.Join("..", "web", "dist"),
DevViteURL: os.Getenv("MD_HUB_DEV_VITE_URL"),
},
LogLevel: "INFO",
}
}
func Load() (Config, error) {
cfg := Default()
if configPath := os.Getenv("MD_HUB_CONFIG"); configPath != "" {
if err := loadFile(configPath, &cfg); err != nil {
return Config{}, err
}
}
overrideString(&cfg.Server.Addr, "MD_HUB_SERVER_ADDR")
overrideString(&cfg.Database.Path, "MD_HUB_DATABASE_PATH")
overrideString(&cfg.Database.PrimaryURL, "MD_HUB_DATABASE_PRIMARY_URL")
overrideString(&cfg.Database.AuthToken, "MD_HUB_DATABASE_AUTH_TOKEN")
overrideString(&cfg.Content.SourceDir, "MD_HUB_CONTENT_SOURCE_DIR")
overrideString(&cfg.Content.StoreDir, "MD_HUB_CONTENT_STORE_DIR")
overrideString(&cfg.Web.DistDir, "MD_HUB_WEB_DIST_DIR")
overrideString(&cfg.Web.DevViteURL, "MD_HUB_DEV_VITE_URL")
overrideString(&cfg.LogLevel, "MD_HUB_LOG_LEVEL")
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
}
}