88 "path/filepath"
99 "strconv"
1010 "strings"
11+ "sync"
1112
1213 "github.com/pelletier/go-toml/v2"
1314)
@@ -21,10 +22,24 @@ type Config struct {
2122 B2AccountID string `toml:"b2_account_id"`
2223 B2ApplicationKey string `toml:"b2_application_key"`
2324 MeiliWriteKey string `toml:"meili_write_key"`
24- GeminiKeys string `toml:"gemini_keys"`
25+ GeminiKeys string `toml:"gemini_keys"`
2526 EnableAds bool `toml:"enable_ads"`
2627 Ads map [string ][]string `toml:"ads"`
27- FdtPgDB FdtPgDBConfig `toml:"fdt_pg_db"`
28+ FdtPgDB FdtPgDBConfig `toml:"fdt_pg_db"`
29+ }
30+
31+ // DBTomlConfig holds the dynamic database paths and filenames from db.toml
32+ type DBTomlConfig struct {
33+ Path string `toml:"path"`
34+ BannerDB string `toml:"bannerdb"`
35+ CheatsheetsDB string `toml:"cheatsheetsdb"`
36+ EmojiDB string `toml:"emojidb"`
37+ IpmDB string `toml:"ipmdb"`
38+ ManPagesDB string `toml:"manpagesdb"`
39+ McpDB string `toml:"mcpdb"`
40+ PngIconsDB string `toml:"pngiconsdb"`
41+ SvgIconsDB string `toml:"svgiconsdb"`
42+ TldrDB string `toml:"tldrdb"`
2843}
2944
3045// FdtPgDBConfig holds PostgreSQL database configuration for Free DevTools
@@ -38,6 +53,12 @@ type FdtPgDBConfig struct {
3853
3954var appConfig * Config
4055
56+ var (
57+ DBConfig * DBTomlConfig
58+ dbTomlOnce sync.Once
59+ dbTomlErr error
60+ )
61+
4162// loadNodeEnvFromDotEnv reads NODE_ENV from .env file
4263// Returns the value if found, otherwise returns empty string
4364func loadNodeEnvFromDotEnv () string {
@@ -82,6 +103,10 @@ func LoadConfig() (*Config, error) {
82103 return appConfig , nil
83104 }
84105
106+ if err := LoadDBToml (); err != nil {
107+ log .Printf ("Warning: Failed to load db.toml: %v" , err )
108+ }
109+
85110 // First try to read from .env file
86111 env := loadNodeEnvFromDotEnv ()
87112 // If not found in .env, try environment variable
@@ -105,17 +130,17 @@ func LoadConfig() (*Config, error) {
105130 NodeEnv : "dev" ,
106131 B2AccountID : "" ,
107132 B2ApplicationKey : "" ,
108- MeiliWriteKey : "" ,
109- GeminiKeys : "" ,
133+ MeiliWriteKey : "" ,
134+ GeminiKeys : "" ,
110135 EnableAds : false ,
111136 Ads : make (map [string ][]string ),
112- FdtPgDB : FdtPgDBConfig {
113- Host : "" ,
114- Port : "5432" ,
115- User : "freedevtools_user" ,
116- Password : "" ,
117- DBName : "freedevtools" ,
118- },
137+ FdtPgDB : FdtPgDBConfig {
138+ Host : "" ,
139+ Port : "5432" ,
140+ User : "freedevtools_user" ,
141+ Password : "" ,
142+ DBName : "freedevtools" ,
143+ },
119144 }
120145 return appConfig , nil
121146 }
@@ -163,8 +188,8 @@ func GetConfig() *Config {
163188 NodeEnv : "dev" ,
164189 B2AccountID : "" ,
165190 B2ApplicationKey : "" ,
166- MeiliWriteKey : "" ,
167- GeminiKeys : "" ,
191+ MeiliWriteKey : "" ,
192+ GeminiKeys : "" ,
168193 EnableAds : false ,
169194 }
170195 } else {
@@ -364,3 +389,73 @@ func LoadConfigFromPath(path string) (*Config, error) {
364389
365390 return & config , nil
366391}
392+
393+ // LoadDBToml loads database versions and paths from db.toml in a thread-safe manner
394+ func LoadDBToml () error {
395+ dbTomlOnce .Do (func () {
396+ dbTomlErr = loadDBTomlInternal ()
397+ })
398+ return dbTomlErr
399+ }
400+
401+ func loadDBTomlInternal () error {
402+ var tomlPath string
403+ fallbackPaths := []string {
404+ "db.toml" ,
405+ "../db.toml" ,
406+ "../../db.toml" ,
407+ }
408+
409+ for _ , p := range fallbackPaths {
410+ if _ , err := os .Stat (p ); ! os .IsNotExist (err ) {
411+ tomlPath = p
412+ break
413+ }
414+ }
415+
416+ if tomlPath == "" {
417+ return fmt .Errorf ("could not find db.toml file" )
418+ }
419+
420+ data , err := os .ReadFile (tomlPath )
421+ if err != nil {
422+ return fmt .Errorf ("failed to read db.toml from %s: %w" , tomlPath , err )
423+ }
424+
425+ // Wrap struct to match the [db] section in TOML
426+ var wrapper struct {
427+ DB DBTomlConfig `toml:"db"`
428+ }
429+
430+ if err := toml .Unmarshal (data , & wrapper ); err != nil {
431+ return fmt .Errorf ("failed to parse db.toml: %w" , err )
432+ }
433+
434+ basePathStr := wrapper .DB .Path
435+ if basePathStr == "" {
436+ basePathStr = "db/all_dbs/"
437+ }
438+
439+ // Prepend paths safely
440+ safeJoin := func (filename string ) string {
441+ if filename == "" {
442+ return ""
443+ }
444+ return filepath .Join (basePathStr , filename )
445+ }
446+
447+ DBConfig = & DBTomlConfig {
448+ Path : basePathStr ,
449+ BannerDB : safeJoin (wrapper .DB .BannerDB ),
450+ CheatsheetsDB : safeJoin (wrapper .DB .CheatsheetsDB ),
451+ EmojiDB : safeJoin (wrapper .DB .EmojiDB ),
452+ IpmDB : safeJoin (wrapper .DB .IpmDB ),
453+ ManPagesDB : safeJoin (wrapper .DB .ManPagesDB ),
454+ McpDB : safeJoin (wrapper .DB .McpDB ),
455+ PngIconsDB : safeJoin (wrapper .DB .PngIconsDB ),
456+ SvgIconsDB : safeJoin (wrapper .DB .SvgIconsDB ),
457+ TldrDB : safeJoin (wrapper .DB .TldrDB ),
458+ }
459+
460+ return nil
461+ }
0 commit comments