feat: implement scenario-driven sales SOP platform
This commit is contained in:
134
codes/internal/config/config.go
Normal file
134
codes/internal/config/config.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ilyakaznacheev/cleanenv"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
App AppConfig `yaml:"app"`
|
||||
Server ServerConfig `yaml:"server"`
|
||||
Database DatabaseConfig `yaml:"database"`
|
||||
Auth AuthConfig `yaml:"auth"`
|
||||
Seed SeedConfig `yaml:"seed"`
|
||||
}
|
||||
|
||||
type AppConfig struct {
|
||||
Name string `yaml:"name" env:"APP_NAME" env-default:"iqudo-top1"`
|
||||
Env string `yaml:"env" env:"APP_ENV" env-default:"development"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Host string `yaml:"host" env:"APP_SERVER_HOST" env-default:"127.0.0.1"`
|
||||
Port int `yaml:"port" env:"APP_SERVER_PORT" env-default:"8080"`
|
||||
ReadTimeout time.Duration `yaml:"read_timeout" env:"APP_SERVER_READ_TIMEOUT" env-default:"10s"`
|
||||
WriteTimeout time.Duration `yaml:"write_timeout" env:"APP_SERVER_WRITE_TIMEOUT" env-default:"20s"`
|
||||
ShutdownTimeout time.Duration `yaml:"shutdown_timeout" env:"APP_SERVER_SHUTDOWN_TIMEOUT" env-default:"10s"`
|
||||
}
|
||||
|
||||
func (c ServerConfig) Address() string {
|
||||
return fmt.Sprintf("%s:%d", c.Host, c.Port)
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
Host string `yaml:"host" env:"APP_DATABASE_HOST" env-default:"127.0.0.1"`
|
||||
Port int `yaml:"port" env:"APP_DATABASE_PORT" env-default:"3306"`
|
||||
Name string `yaml:"name" env:"APP_DATABASE_NAME"`
|
||||
User string `yaml:"user" env:"APP_DATABASE_USER"`
|
||||
Password string `yaml:"password" env:"APP_DATABASE_PASSWORD"`
|
||||
MaxIdleConnections int `yaml:"max_idle_connections" env:"APP_DATABASE_MAX_IDLE_CONNECTIONS" env-default:"10"`
|
||||
MaxOpenConnections int `yaml:"max_open_connections" env:"APP_DATABASE_MAX_OPEN_CONNECTIONS" env-default:"40"`
|
||||
ConnectionMaxLifetime time.Duration `yaml:"connection_max_lifetime" env:"APP_DATABASE_CONNECTION_MAX_LIFETIME" env-default:"30m"`
|
||||
}
|
||||
|
||||
func (c DatabaseConfig) DSN() string {
|
||||
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&multiStatements=true", c.User, c.Password, c.Host, c.Port, c.Name)
|
||||
}
|
||||
|
||||
type AuthConfig struct {
|
||||
JWTSecret string `yaml:"jwt_secret" env:"APP_AUTH_JWT_SECRET"`
|
||||
AccessTokenTTL time.Duration `yaml:"access_token_ttl" env:"APP_AUTH_ACCESS_TOKEN_TTL" env-default:"2h"`
|
||||
RefreshTokenTTL time.Duration `yaml:"refresh_token_ttl" env:"APP_AUTH_REFRESH_TOKEN_TTL" env-default:"168h"`
|
||||
}
|
||||
|
||||
type SeedConfig struct {
|
||||
AdminUsername string `yaml:"admin_username" env:"APP_SEED_ADMIN_USERNAME"`
|
||||
AdminPassword string `yaml:"admin_password" env:"APP_SEED_ADMIN_PASSWORD"`
|
||||
AdminDisplayName string `yaml:"admin_display_name" env:"APP_SEED_ADMIN_DISPLAY_NAME"`
|
||||
}
|
||||
|
||||
type LoadOptions struct {
|
||||
Environment string
|
||||
ConfigDir string
|
||||
}
|
||||
|
||||
func Load(options LoadOptions) (Config, error) {
|
||||
configDir := options.ConfigDir
|
||||
if configDir == "" {
|
||||
configDir = "configs"
|
||||
}
|
||||
|
||||
environment := normalizeEnvironment(options.Environment)
|
||||
if environment == "" {
|
||||
environment = normalizeEnvironment(os.Getenv("APP_ENV"))
|
||||
}
|
||||
|
||||
var cfg Config
|
||||
basePath := filepath.Join(configDir, "config.yml")
|
||||
if err := cleanenv.ReadConfig(basePath, &cfg); err != nil {
|
||||
return Config{}, fmt.Errorf("load base config %s: %w", basePath, err)
|
||||
}
|
||||
|
||||
if environment == "test" || environment == "prod" {
|
||||
profilePath := filepath.Join(configDir, "config."+environment+".yml")
|
||||
if err := cleanenv.ReadConfig(profilePath, &cfg); err != nil {
|
||||
return Config{}, fmt.Errorf("load profile config %s: %w", profilePath, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := cleanenv.ReadEnv(&cfg); err != nil {
|
||||
return Config{}, fmt.Errorf("load environment variables: %w", err)
|
||||
}
|
||||
if environment != "" {
|
||||
cfg.App.Env = environment
|
||||
}
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func normalizeEnvironment(value string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
||||
case "production", "prod":
|
||||
return "prod"
|
||||
case "testing", "test":
|
||||
return "test"
|
||||
case "development", "dev", "local":
|
||||
return "development"
|
||||
default:
|
||||
return strings.ToLower(strings.TrimSpace(value))
|
||||
}
|
||||
}
|
||||
|
||||
func (c Config) Validate() error {
|
||||
if c.Database.Name == "" || c.Database.User == "" {
|
||||
return errors.New("database name and user are required")
|
||||
}
|
||||
if len(c.Auth.JWTSecret) < 16 {
|
||||
return errors.New("auth.jwt_secret must contain at least 16 characters")
|
||||
}
|
||||
if c.Server.Port < 1 || c.Server.Port > 65535 {
|
||||
return errors.New("server.port must be between 1 and 65535")
|
||||
}
|
||||
if c.App.Env == "prod" && c.Database.Password == "" {
|
||||
return errors.New("database password is required in production")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user