package config import ( "os" "paraclub-ai-mailer/internal/logger" "strings" "time" "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" ) type Config struct { IMAP IMAPConfig `yaml:"imap"` AI AIConfig `yaml:"ai"` Context ContextConfig `yaml:"context"` Polling PollingConfig `yaml:"polling"` Logging LoggingConfig `yaml:"logging"` } type IMAPConfig struct { Server string `yaml:"server"` Port int `yaml:"port"` Username string `yaml:"username"` Password string `yaml:"password"` MailboxIn string `yaml:"mailbox_in"` DraftBox string `yaml:"draft_box"` ProcessedBox string `yaml:"processed_box"` UseTLS bool `yaml:"use_tls"` } type AIConfig struct { OpenRouterAPIKey string `yaml:"openrouter_api_key"` Model string `yaml:"model"` Temperature float32 `yaml:"temperature"` MaxTokens int `yaml:"max_tokens"` } type ContextConfig struct { URLs []string `yaml:"urls"` } type PollingConfig struct { Interval time.Duration `yaml:"interval"` } type LoggingConfig struct { Level string `yaml:"level"` FilePath string `yaml:"file_path"` } func readFileContent(path string) (string, error) { content, err := os.ReadFile(path) if err != nil { return "", err } return strings.TrimSpace(string(content)), nil } func Load(path string) (*Config, error) { logger.WithField("path", path).Debug("Loading configuration file") data, err := os.ReadFile(path) if err != nil { logger.WithError(err).Error("Failed to read configuration file") return nil, err } logger.WithField("bytes", len(data)).Debug("Read configuration file") var config Config if err := yaml.Unmarshal(data, &config); err != nil { logger.WithError(err).Error("Failed to parse YAML configuration") return nil, err } // Handle IMAP password from environment or file if strings.HasPrefix(config.IMAP.Password, "${") && strings.HasSuffix(config.IMAP.Password, "}") { envVar := strings.TrimSuffix(strings.TrimPrefix(config.IMAP.Password, "${"), "}") config.IMAP.Password = os.Getenv(envVar) logger.WithField("envVar", envVar).Debug("Resolved IMAP password from environment") } else if strings.HasPrefix(config.IMAP.Password, "file://") { filePath := strings.TrimPrefix(config.IMAP.Password, "file://") password, err := readFileContent(filePath) if err != nil { logger.WithError(err).Error("Failed to read IMAP password from file") return nil, err } config.IMAP.Password = password logger.WithField("path", filePath).Debug("Read IMAP password from file") } // Handle OpenRouter API key from environment or file if strings.HasPrefix(config.AI.OpenRouterAPIKey, "${") && strings.HasSuffix(config.AI.OpenRouterAPIKey, "}") { envVar := strings.TrimSuffix(strings.TrimPrefix(config.AI.OpenRouterAPIKey, "${"), "}") config.AI.OpenRouterAPIKey = os.Getenv(envVar) logger.WithField("envVar", envVar).Debug("Resolved OpenRouter API key from environment") } else if strings.HasPrefix(config.AI.OpenRouterAPIKey, "file://") { filePath := strings.TrimPrefix(config.AI.OpenRouterAPIKey, "file://") apiKey, err := readFileContent(filePath) if err != nil { logger.WithError(err).Error("Failed to read OpenRouter API key from file") return nil, err } config.AI.OpenRouterAPIKey = apiKey logger.WithField("path", filePath).Debug("Read OpenRouter API key from file") } logger.WithFields(logrus.Fields{ "imapServer": config.IMAP.Server, "imapPort": config.IMAP.Port, "imapUsername": config.IMAP.Username, "imapMailboxIn": config.IMAP.MailboxIn, "imapDraftBox": config.IMAP.DraftBox, "imapUseTLS": config.IMAP.UseTLS, "aiModel": config.AI.Model, "aiTemperature": config.AI.Temperature, "aiMaxTokens": config.AI.MaxTokens, "contextUrlCount": len(config.Context.URLs), "pollingInterval": config.Polling.Interval, "loggingLevel": config.Logging.Level, "loggingFilePath": config.Logging.FilePath, }).Debug("Configuration loaded successfully") return &config, nil }