Add registry, ingest, timeline, tickets features

This commit is contained in:
2026-02-05 22:49:11 +03:00
parent 1aba59fca5
commit fe9e08f1a6
44 changed files with 3514 additions and 31 deletions

View File

@@ -1,8 +1,10 @@
package config
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strconv"
"time"
)
@@ -33,14 +35,22 @@ func Load() (Config, error) {
return Config{}, err
}
return Config{
cfg := Config{
HTTPAddr: envOrDefault("HTTP_ADDR", ":9999"),
ReadTimeout: readTimeout,
WriteTimeout: writeTimeout,
ShutdownGrace: shutdownGrace,
DatabaseDSN: os.Getenv("DATABASE_DSN"),
MigrationsDir: envOrDefault("MIGRATIONS_DIR", "migrations"),
}, nil
}
fileCfg, err := loadFileConfig()
if err != nil {
return Config{}, err
}
applyFileConfig(&cfg, fileCfg)
return cfg, nil
}
func envOrDefault(key, fallback string) string {
@@ -66,3 +76,111 @@ func envDuration(key string, fallback time.Duration) (time.Duration, error) {
return time.Duration(seconds) * time.Second, nil
}
type fileConfig struct {
HTTPAddr *string `json:"http_addr"`
ReadTimeoutSeconds *int `json:"read_timeout_seconds"`
WriteTimeoutSeconds *int `json:"write_timeout_seconds"`
ShutdownGraceSeconds *int `json:"shutdown_grace_seconds"`
DatabaseDSN *string `json:"database_dsn"`
Database *dbFileConfig `json:"database"`
MigrationsDir *string `json:"migrations_dir"`
}
type dbFileConfig struct {
User *string `json:"user"`
Password *string `json:"password"`
Host *string `json:"host"`
Port *int `json:"port"`
Name *string `json:"name"`
Params *string `json:"params"`
}
func loadFileConfig() (fileConfig, error) {
path := os.Getenv("CONFIG_FILE")
if path == "" {
path = "config.json"
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return fileConfig{}, nil
}
return fileConfig{}, err
}
} else {
if _, err := os.Stat(path); err != nil {
return fileConfig{}, fmt.Errorf("config file %s: %w", path, err)
}
}
raw, err := os.ReadFile(filepath.Clean(path))
if err != nil {
return fileConfig{}, err
}
var cfg fileConfig
if err := json.Unmarshal(raw, &cfg); err != nil {
return fileConfig{}, fmt.Errorf("parse config file %s: %w", path, err)
}
return cfg, nil
}
func applyFileConfig(cfg *Config, fileCfg fileConfig) {
if fileCfg.HTTPAddr != nil && os.Getenv("HTTP_ADDR") == "" {
cfg.HTTPAddr = *fileCfg.HTTPAddr
}
if fileCfg.ReadTimeoutSeconds != nil && os.Getenv("READ_TIMEOUT") == "" {
if *fileCfg.ReadTimeoutSeconds > 0 {
cfg.ReadTimeout = time.Duration(*fileCfg.ReadTimeoutSeconds) * time.Second
}
}
if fileCfg.WriteTimeoutSeconds != nil && os.Getenv("WRITE_TIMEOUT") == "" {
if *fileCfg.WriteTimeoutSeconds > 0 {
cfg.WriteTimeout = time.Duration(*fileCfg.WriteTimeoutSeconds) * time.Second
}
}
if fileCfg.ShutdownGraceSeconds != nil && os.Getenv("SHUTDOWN_GRACE") == "" {
if *fileCfg.ShutdownGraceSeconds > 0 {
cfg.ShutdownGrace = time.Duration(*fileCfg.ShutdownGraceSeconds) * time.Second
}
}
if fileCfg.DatabaseDSN != nil && os.Getenv("DATABASE_DSN") == "" {
cfg.DatabaseDSN = *fileCfg.DatabaseDSN
}
if cfg.DatabaseDSN == "" && os.Getenv("DATABASE_DSN") == "" {
if dsn := buildDSN(fileCfg.Database); dsn != "" {
cfg.DatabaseDSN = dsn
}
}
if fileCfg.MigrationsDir != nil && os.Getenv("MIGRATIONS_DIR") == "" {
cfg.MigrationsDir = *fileCfg.MigrationsDir
}
}
func buildDSN(db *dbFileConfig) string {
if db == nil || db.Name == nil {
return ""
}
user := valueOrDefault(db.User, "reanimator")
password := valueOrDefault(db.Password, "reanimator")
host := valueOrDefault(db.Host, "127.0.0.1")
port := intValueOrDefault(db.Port, 3306)
params := valueOrDefault(db.Params, "parseTime=true")
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?%s", user, password, host, port, *db.Name, params)
}
func valueOrDefault(value *string, fallback string) string {
if value == nil || *value == "" {
return fallback
}
return *value
}
func intValueOrDefault(value *int, fallback int) int {
if value == nil || *value <= 0 {
return fallback
}
return *value
}