messaging-center/internal/core/config.go

161 lines
3.6 KiB
Go

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
}