package config import ( "time" "github.com/tech/sendico/pkg/db" "github.com/tech/sendico/pkg/messaging" ) const ( defaultShutdownTimeoutSeconds = 15 defaultMetricsAddress = ":9420" defaultIngestStream = "CALLBACKS" defaultIngestSubject = "callbacks.events" defaultIngestDurable = "callbacks-ingest" defaultIngestBatchSize = 32 defaultIngestFetchTimeoutMS = 2000 defaultIngestIdleSleepMS = 500 defaultTaskCollection = "callback_tasks" defaultInboxCollection = "callback_inbox" defaultEndpointsCollection = "webhook_endpoints" defaultWorkerConcurrency = 8 defaultWorkerPollIntervalMS = 200 defaultLockTTLSeconds = 30 defaultRequestTimeoutMS = 10000 defaultMaxAttempts = 8 defaultMinDelayMS = 1000 defaultMaxDelayMS = 300000 defaultJitterRatio = 0.20 defaultDNSResolveTimeoutMS = 2000 defaultSecretsVaultField = "value" ) // Loader parses callbacks service configuration. type Loader interface { Load(path string) (*Config, error) } // Config is the full callbacks service configuration. type Config struct { Runtime *RuntimeConfig `yaml:"runtime"` Metrics *MetricsConfig `yaml:"metrics"` Database *db.Config `yaml:"database"` Messaging *messaging.Config `yaml:"messaging"` Ingest IngestConfig `yaml:"ingest"` Delivery DeliveryConfig `yaml:"delivery"` Security SecurityConfig `yaml:"security"` Secrets SecretsConfig `yaml:"secrets"` } // RuntimeConfig contains process lifecycle settings. type RuntimeConfig struct { ShutdownTimeoutSeconds int `yaml:"shutdown_timeout_seconds"` } func (c *RuntimeConfig) ShutdownTimeout() time.Duration { if c == nil || c.ShutdownTimeoutSeconds <= 0 { return defaultShutdownTimeoutSeconds * time.Second } return time.Duration(c.ShutdownTimeoutSeconds) * time.Second } // MetricsConfig configures observability endpoints. type MetricsConfig struct { Address string `yaml:"address"` } func (c *MetricsConfig) ListenAddress() string { if c == nil || c.Address == "" { return defaultMetricsAddress } return c.Address } // IngestConfig configures JetStream ingestion. type IngestConfig struct { Stream string `yaml:"stream"` Subject string `yaml:"subject"` Durable string `yaml:"durable"` BatchSize int `yaml:"batch_size"` FetchTimeoutMS int `yaml:"fetch_timeout_ms"` IdleSleepMS int `yaml:"idle_sleep_ms"` } func (c *IngestConfig) FetchTimeout() time.Duration { if c.FetchTimeoutMS <= 0 { return time.Duration(defaultIngestFetchTimeoutMS) * time.Millisecond } return time.Duration(c.FetchTimeoutMS) * time.Millisecond } func (c *IngestConfig) IdleSleep() time.Duration { if c.IdleSleepMS <= 0 { return time.Duration(defaultIngestIdleSleepMS) * time.Millisecond } return time.Duration(c.IdleSleepMS) * time.Millisecond } // DeliveryConfig controls dispatcher behavior. type DeliveryConfig struct { WorkerConcurrency int `yaml:"worker_concurrency"` WorkerPollMS int `yaml:"worker_poll_ms"` LockTTLSeconds int `yaml:"lock_ttl_seconds"` RequestTimeoutMS int `yaml:"request_timeout_ms"` MaxAttempts int `yaml:"max_attempts"` MinDelayMS int `yaml:"min_delay_ms"` MaxDelayMS int `yaml:"max_delay_ms"` JitterRatio float64 `yaml:"jitter_ratio"` } func (c *DeliveryConfig) WorkerPollInterval() time.Duration { if c.WorkerPollMS <= 0 { return time.Duration(defaultWorkerPollIntervalMS) * time.Millisecond } return time.Duration(c.WorkerPollMS) * time.Millisecond } func (c *DeliveryConfig) LockTTL() time.Duration { if c.LockTTLSeconds <= 0 { return time.Duration(defaultLockTTLSeconds) * time.Second } return time.Duration(c.LockTTLSeconds) * time.Second } func (c *DeliveryConfig) RequestTimeout() time.Duration { if c.RequestTimeoutMS <= 0 { return time.Duration(defaultRequestTimeoutMS) * time.Millisecond } return time.Duration(c.RequestTimeoutMS) * time.Millisecond } func (c *DeliveryConfig) MinDelay() time.Duration { if c.MinDelayMS <= 0 { return time.Duration(defaultMinDelayMS) * time.Millisecond } return time.Duration(c.MinDelayMS) * time.Millisecond } func (c *DeliveryConfig) MaxDelay() time.Duration { if c.MaxDelayMS <= 0 { return time.Duration(defaultMaxDelayMS) * time.Millisecond } return time.Duration(c.MaxDelayMS) * time.Millisecond } // SecurityConfig controls outbound callback safety checks. type SecurityConfig struct { RequireHTTPS bool `yaml:"require_https"` AllowedHosts []string `yaml:"allowed_hosts"` AllowedPorts []int `yaml:"allowed_ports"` DNSResolveTimeout int `yaml:"dns_resolve_timeout_ms"` } func (c *SecurityConfig) DNSResolveTimeoutMS() time.Duration { if c.DNSResolveTimeout <= 0 { return time.Duration(defaultDNSResolveTimeoutMS) * time.Millisecond } return time.Duration(c.DNSResolveTimeout) * time.Millisecond } // SecretsConfig controls secret lookup behavior. type SecretsConfig struct { CacheTTLSeconds int `yaml:"cache_ttl_seconds"` Static map[string]string `yaml:"static"` Vault VaultSecretsConfig `yaml:"vault"` } // VaultSecretsConfig controls Vault KV secret resolution. type VaultSecretsConfig struct { Address string `yaml:"address"` TokenEnv string `yaml:"token_env"` Namespace string `yaml:"namespace"` MountPath string `yaml:"mount_path"` DefaultField string `yaml:"default_field"` } func (c *SecretsConfig) CacheTTL() time.Duration { if c == nil || c.CacheTTLSeconds <= 0 { return 0 } return time.Duration(c.CacheTTLSeconds) * time.Second }