- Go module with Gin, GORM, JWT, excelize dependencies - Configuration loading from YAML with all settings - GORM models for users, categories, components, configurations, alerts - Repository layer for all entities - Services: auth (JWT), pricing (median/average/weighted), components, quotes, configurations, export (CSV/XLSX), alerts - Middleware: JWT auth, role-based access, CORS - HTTP handlers for all API endpoints - Main server with dependency injection and graceful shutdown Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
177 lines
4.7 KiB
Go
177 lines
4.7 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type Config struct {
|
|
Server ServerConfig `yaml:"server"`
|
|
Database DatabaseConfig `yaml:"database"`
|
|
Auth AuthConfig `yaml:"auth"`
|
|
Pricing PricingConfig `yaml:"pricing"`
|
|
Export ExportConfig `yaml:"export"`
|
|
Alerts AlertsConfig `yaml:"alerts"`
|
|
Notifications NotificationsConfig `yaml:"notifications"`
|
|
Logging LoggingConfig `yaml:"logging"`
|
|
}
|
|
|
|
type ServerConfig struct {
|
|
Host string `yaml:"host"`
|
|
Port int `yaml:"port"`
|
|
Mode string `yaml:"mode"`
|
|
ReadTimeout time.Duration `yaml:"read_timeout"`
|
|
WriteTimeout time.Duration `yaml:"write_timeout"`
|
|
}
|
|
|
|
type DatabaseConfig struct {
|
|
Host string `yaml:"host"`
|
|
Port int `yaml:"port"`
|
|
Name string `yaml:"name"`
|
|
User string `yaml:"user"`
|
|
Password string `yaml:"password"`
|
|
MaxOpenConns int `yaml:"max_open_conns"`
|
|
MaxIdleConns int `yaml:"max_idle_conns"`
|
|
ConnMaxLifetime time.Duration `yaml:"conn_max_lifetime"`
|
|
}
|
|
|
|
func (d *DatabaseConfig) DSN() string {
|
|
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
|
d.User, d.Password, d.Host, d.Port, d.Name)
|
|
}
|
|
|
|
type AuthConfig struct {
|
|
JWTSecret string `yaml:"jwt_secret"`
|
|
TokenExpiry time.Duration `yaml:"token_expiry"`
|
|
RefreshExpiry time.Duration `yaml:"refresh_expiry"`
|
|
}
|
|
|
|
type PricingConfig struct {
|
|
DefaultMethod string `yaml:"default_method"`
|
|
DefaultPeriodDays int `yaml:"default_period_days"`
|
|
FreshnessGreenDays int `yaml:"freshness_green_days"`
|
|
FreshnessYellowDays int `yaml:"freshness_yellow_days"`
|
|
FreshnessRedDays int `yaml:"freshness_red_days"`
|
|
MinQuotesForMedian int `yaml:"min_quotes_for_median"`
|
|
PopularityDecayDays int `yaml:"popularity_decay_days"`
|
|
}
|
|
|
|
type ExportConfig struct {
|
|
TempDir string `yaml:"temp_dir"`
|
|
MaxFileAge time.Duration `yaml:"max_file_age"`
|
|
CompanyName string `yaml:"company_name"`
|
|
}
|
|
|
|
type AlertsConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
CheckInterval time.Duration `yaml:"check_interval"`
|
|
HighDemandThreshold int `yaml:"high_demand_threshold"`
|
|
TrendingThresholdPercent int `yaml:"trending_threshold_percent"`
|
|
}
|
|
|
|
type NotificationsConfig struct {
|
|
EmailEnabled bool `yaml:"email_enabled"`
|
|
SMTPHost string `yaml:"smtp_host"`
|
|
SMTPPort int `yaml:"smtp_port"`
|
|
SMTPUser string `yaml:"smtp_user"`
|
|
SMTPPassword string `yaml:"smtp_password"`
|
|
FromAddress string `yaml:"from_address"`
|
|
}
|
|
|
|
type LoggingConfig struct {
|
|
Level string `yaml:"level"`
|
|
Format string `yaml:"format"`
|
|
Output string `yaml:"output"`
|
|
FilePath string `yaml:"file_path"`
|
|
}
|
|
|
|
func Load(path string) (*Config, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading config file: %w", err)
|
|
}
|
|
|
|
var cfg Config
|
|
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
|
return nil, fmt.Errorf("parsing config file: %w", err)
|
|
}
|
|
|
|
cfg.setDefaults()
|
|
|
|
return &cfg, nil
|
|
}
|
|
|
|
func (c *Config) setDefaults() {
|
|
if c.Server.Host == "" {
|
|
c.Server.Host = "0.0.0.0"
|
|
}
|
|
if c.Server.Port == 0 {
|
|
c.Server.Port = 8080
|
|
}
|
|
if c.Server.Mode == "" {
|
|
c.Server.Mode = "release"
|
|
}
|
|
if c.Server.ReadTimeout == 0 {
|
|
c.Server.ReadTimeout = 30 * time.Second
|
|
}
|
|
if c.Server.WriteTimeout == 0 {
|
|
c.Server.WriteTimeout = 30 * time.Second
|
|
}
|
|
|
|
if c.Database.Port == 0 {
|
|
c.Database.Port = 3306
|
|
}
|
|
if c.Database.MaxOpenConns == 0 {
|
|
c.Database.MaxOpenConns = 25
|
|
}
|
|
if c.Database.MaxIdleConns == 0 {
|
|
c.Database.MaxIdleConns = 5
|
|
}
|
|
if c.Database.ConnMaxLifetime == 0 {
|
|
c.Database.ConnMaxLifetime = 5 * time.Minute
|
|
}
|
|
|
|
if c.Auth.TokenExpiry == 0 {
|
|
c.Auth.TokenExpiry = 24 * time.Hour
|
|
}
|
|
if c.Auth.RefreshExpiry == 0 {
|
|
c.Auth.RefreshExpiry = 7 * 24 * time.Hour
|
|
}
|
|
|
|
if c.Pricing.DefaultMethod == "" {
|
|
c.Pricing.DefaultMethod = "weighted_median"
|
|
}
|
|
if c.Pricing.DefaultPeriodDays == 0 {
|
|
c.Pricing.DefaultPeriodDays = 90
|
|
}
|
|
if c.Pricing.FreshnessGreenDays == 0 {
|
|
c.Pricing.FreshnessGreenDays = 30
|
|
}
|
|
if c.Pricing.FreshnessYellowDays == 0 {
|
|
c.Pricing.FreshnessYellowDays = 60
|
|
}
|
|
if c.Pricing.FreshnessRedDays == 0 {
|
|
c.Pricing.FreshnessRedDays = 90
|
|
}
|
|
if c.Pricing.MinQuotesForMedian == 0 {
|
|
c.Pricing.MinQuotesForMedian = 3
|
|
}
|
|
|
|
if c.Logging.Level == "" {
|
|
c.Logging.Level = "info"
|
|
}
|
|
if c.Logging.Format == "" {
|
|
c.Logging.Format = "json"
|
|
}
|
|
if c.Logging.Output == "" {
|
|
c.Logging.Output = "stdout"
|
|
}
|
|
}
|
|
|
|
func (c *Config) Address() string {
|
|
return fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port)
|
|
}
|