package core import ( "os" "strings" "time" "gopkg.in/yaml.v3" ) // Config represents the application configuration. type Config struct { Server ServerConfig `yaml:"server"` Database DatabaseConfig `yaml:"database"` Storage StorageConfig `yaml:"storage"` OAuth OAuthConfig `yaml:"oauth"` Transcription TranscriptionConfig `yaml:"transcription"` Webhooks []WebhookConfig `yaml:"webhooks"` Channels ChannelsConfig `yaml:"channels"` } type ServerConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` } type DatabaseConfig struct { Path string `yaml:"path"` } type StorageConfig struct { Path string `yaml:"path"` } type OAuthConfig struct { Issuer string `yaml:"issuer"` SigningKey string `yaml:"signing_key"` AccessTokenTTL time.Duration `yaml:"access_token_ttl"` Clients []OAuthClient `yaml:"clients"` } type OAuthClient struct { ID string `yaml:"id"` Secret string `yaml:"secret"` Scopes []string `yaml:"scopes"` } type TranscriptionConfig struct { Provider string `yaml:"provider"` APIKey string `yaml:"api_key"` Model string `yaml:"model"` } type WebhookConfig struct { URL string `yaml:"url"` Secret string `yaml:"secret"` Events []string `yaml:"events"` } type ChannelsConfig struct { Email *EmailChannelConfig `yaml:"email"` WhatsApp *WhatsAppChannelConfig `yaml:"whatsapp"` Signal *SignalChannelConfig `yaml:"signal"` } type EmailChannelConfig struct { Enabled bool `yaml:"enabled"` IMAP IMAPConfig `yaml:"imap"` SMTP SMTPConfig `yaml:"smtp"` } type IMAPConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` Username string `yaml:"username"` Password string `yaml:"password"` TLS string `yaml:"tls"` } type SMTPConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` Username string `yaml:"username"` Password string `yaml:"password"` TLS string `yaml:"tls"` } type WhatsAppChannelConfig struct { Enabled bool `yaml:"enabled"` DataDir string `yaml:"data_dir"` } type SignalChannelConfig struct { Enabled bool `yaml:"enabled"` APIURL string `yaml:"api_url"` Account string `yaml:"account"` } // LoadConfig reads and parses the configuration file. func LoadConfig(path string) (*Config, error) { data, err := os.ReadFile(path) if err != nil { return nil, err } // Expand environment variables with default value support (${VAR:-default}) expanded := os.Expand(string(data), func(key string) string { // Handle ${VAR:-default} syntax if idx := strings.Index(key, ":-"); idx != -1 { envKey := key[:idx] defaultVal := key[idx+2:] if val := os.Getenv(envKey); val != "" { return val } return defaultVal } return os.Getenv(key) }) var cfg Config if err := yaml.Unmarshal([]byte(expanded), &cfg); err != nil { return nil, err } // Set defaults if cfg.Server.Host == "" { cfg.Server.Host = "0.0.0.0" } if cfg.Server.Port == 0 { cfg.Server.Port = 8040 } if cfg.Database.Path == "" { cfg.Database.Path = "./mc.db" } if cfg.Storage.Path == "" { cfg.Storage.Path = "./attachments" } if cfg.OAuth.AccessTokenTTL == 0 { cfg.OAuth.AccessTokenTTL = time.Hour } return &cfg, nil } // HasScope checks if a client has a specific scope. func (c *OAuthClient) HasScope(scope string) bool { for _, s := range c.Scopes { if s == scope || s == "admin" { return true } // Check wildcard (e.g., "messages:*" matches "messages:read") if strings.HasSuffix(s, ":*") { prefix := strings.TrimSuffix(s, "*") if strings.HasPrefix(scope, prefix) { return true } } } return false }