package config import ( "fmt" "os" "time" "gopkg.in/yaml.v3" ) // Config is the top-level configuration structure. type Config struct { Server ServerConfig `yaml:"server"` Auth AuthConfig `yaml:"auth"` DCMTK DCMTKConfig `yaml:"dcmtk"` PACS PACSConfig `yaml:"pacs"` OurAE OurAEConfig `yaml:"our_ae"` PatientAPI PatientAPIConfig `yaml:"patient_api"` CDPublisher CDPublisherConfig `yaml:"cd_publisher"` ISO ISOConfig `yaml:"iso"` } type ServerConfig struct { Port int `yaml:"port"` ReadTimeout time.Duration `yaml:"read_timeout"` WriteTimeout time.Duration `yaml:"write_timeout"` } type AuthConfig struct { Enabled bool `yaml:"enabled"` APIKey string `yaml:"api_key"` } type DCMTKConfig struct { Storescp string `yaml:"storescp"` Movescu string `yaml:"movescu"` Storescu string `yaml:"storescu"` } type PACSConfig struct { AETitle string `yaml:"ae_title"` Host string `yaml:"host"` Port int `yaml:"port"` } type OurAEConfig struct { AETitle string `yaml:"ae_title"` BasePort int `yaml:"base_port"` PortRange int `yaml:"port_range"` } type PatientAPIConfig struct { BaseURL string `yaml:"base_url"` Endpoint string `yaml:"endpoint"` AuthType string `yaml:"auth_type"` AuthHeader string `yaml:"auth_header"` AuthToken string `yaml:"auth_token"` Timeout time.Duration `yaml:"timeout"` Retry int `yaml:"retry"` RetryBackoff time.Duration `yaml:"retry_backoff"` } type CDPublisherConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` } type ISOConfig struct { MicrodicomPath string `yaml:"microdicom_path"` TempDir string `yaml:"temp_dir"` } // Load reads and parses the config file from the given path. // If path is empty, it reads from the MKISO_CONFIG env var, falling back // to ./config.yaml. func Load(path string) (*Config, error) { if path == "" { path = os.Getenv("MKISO_CONFIG") } if path == "" { path = "./config.yaml" } data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("read config file %q: %w", path, err) } var cfg Config if err := yaml.Unmarshal(data, &cfg); err != nil { return nil, fmt.Errorf("parse config file %q: %w", path, err) } if err := cfg.validate(); err != nil { return nil, fmt.Errorf("validate config: %w", err) } return &cfg, nil } func (c *Config) validate() error { if c.Server.Port == 0 { c.Server.Port = 8080 } if c.Server.ReadTimeout == 0 { c.Server.ReadTimeout = 300 * time.Second } if c.Server.WriteTimeout == 0 { c.Server.WriteTimeout = 600 * time.Second } if c.DCMTK.Storescp == "" { return fmt.Errorf("dcmtk.storescp is required") } if c.DCMTK.Movescu == "" { return fmt.Errorf("dcmtk.movescu is required") } if c.DCMTK.Storescu == "" { return fmt.Errorf("dcmtk.storescu is required") } if c.PACS.Host == "" { c.PACS.Host = "localhost" } if c.PACS.Port == 0 { c.PACS.Port = 11112 } if c.PACS.AETitle == "" { c.PACS.AETitle = "ABPACS" } if c.OurAE.AETitle == "" { c.OurAE.AETitle = "CDRECORD" } if c.OurAE.BasePort == 0 { c.OurAE.BasePort = 10104 } if c.OurAE.PortRange == 0 { c.OurAE.PortRange = 100 } if c.PatientAPI.BaseURL == "" { c.PatientAPI.BaseURL = "http://localhost:8090" } if c.PatientAPI.Endpoint == "" { c.PatientAPI.Endpoint = "/patient/by-accession" } if c.PatientAPI.Timeout == 0 { c.PatientAPI.Timeout = 10 * time.Second } if c.PatientAPI.Retry == 0 { c.PatientAPI.Retry = 3 } if c.PatientAPI.RetryBackoff == 0 { c.PatientAPI.RetryBackoff = 500 * time.Millisecond } if c.CDPublisher.Host == "" { c.CDPublisher.Host = "172.16.0.120" } if c.CDPublisher.Port == 0 { c.CDPublisher.Port = 104 } if c.ISO.MicrodicomPath == "" { c.ISO.MicrodicomPath = "/var/www/html/microdicom" } if c.ISO.TempDir == "" { c.ISO.TempDir = "/tmp" } return nil }