Skip to content

Commit e8ac702

Browse files
Standardize SQLite Database Path Management
1 parent aa29b29 commit e8ac702

22 files changed

Lines changed: 416 additions & 130 deletions

File tree

frontend/db.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[db]
2+
path = "db/all_dbs/"
3+
bannerdb = "banner-db.db"
4+
cheatsheetsdb = "cheatsheets-db-v5.db"
5+
emojidb = "emoji-db-v5.db"
6+
ipmdb = "ipm-db-v6.db"
7+
manpagesdb = "man-pages-db-v5.db"
8+
mcpdb = "mcp-db-v6.db"
9+
pngiconsdb = "png-icons-db-v5.db"
10+
svgiconsdb = "svg-icons-db-v5.db"
11+
tldrdb = "tldr-db-v5.db"

frontend/internal/config/config.go

Lines changed: 108 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
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

3954
var 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
4364
func 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+
}

frontend/internal/db/banner/utils.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ package banner
22

33
import (
44
"database/sql"
5-
"log"
6-
"path/filepath"
5+
"fdt-templ/internal/config"
6+
"fmt"
77
"sync"
88
"time"
99

@@ -17,20 +17,30 @@ var (
1717

1818
// GetDB returns a singleton database connection
1919
func GetDB() (*sql.DB, error) {
20-
var err error
20+
var initErr error
2121
dbOnce.Do(func() {
22-
dbPath := filepath.Join("db", "all_dbs", "banner-db.db")
23-
dbInstance, err = sql.Open("sqlite3", dbPath+"?mode=ro")
24-
if err != nil {
25-
log.Printf("Failed to open banner database: %v", err)
22+
// Ensure config is loaded
23+
if e := config.LoadDBToml(); e != nil {
24+
initErr = fmt.Errorf("failed to load db.toml for Banner DB: %w", e)
25+
return
26+
}
27+
dbPath := config.DBConfig.BannerDB
28+
if dbPath == "" {
29+
initErr = fmt.Errorf("Banner DB path is empty in db.toml")
30+
return
31+
}
32+
33+
dbInstance, initErr = sql.Open("sqlite3", dbPath+"?mode=ro")
34+
if initErr != nil {
35+
initErr = fmt.Errorf("failed to open banner database: %w", initErr)
2636
return
2737
}
2838
// Set connection pool settings
2939
dbInstance.SetMaxOpenConns(1)
3040
dbInstance.SetMaxIdleConns(1)
3141
dbInstance.SetConnMaxLifetime(time.Hour)
3242
})
33-
return dbInstance, err
43+
return dbInstance, initErr
3444
}
3545

3646
// CloseDB closes the database connection
@@ -39,4 +49,3 @@ func CloseDB() {
3949
dbInstance.Close()
4050
}
4151
}
42-

frontend/internal/db/cheatsheets/queries.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"time"
99

1010
db_config "fdt-templ/db/config"
11+
"fdt-templ/internal/config"
1112

1213
_ "github.com/mattn/go-sqlite3"
1314
)
@@ -43,12 +44,24 @@ func (db *DB) Close() error {
4344

4445
// GetDB returns a database instance
4546
func GetDB() (*DB, error) {
46-
dbPath := GetDBPath()
47+
if err := config.LoadDBToml(); err != nil {
48+
return nil, fmt.Errorf("failed to load db.toml for Cheatsheets DB: %w", err)
49+
}
50+
dbPath := config.DBConfig.CheatsheetsDB
51+
if dbPath == "" {
52+
return nil, fmt.Errorf("Cheatsheets DB path is empty in db.toml")
53+
}
54+
4755
absPath, err := filepath.Abs(dbPath)
4856
if err != nil {
49-
return nil, err
57+
return nil, fmt.Errorf("failed to resolve absolute path for Cheatsheets DB: %w", err)
58+
}
59+
60+
db, err := NewDB(absPath)
61+
if err != nil {
62+
return nil, fmt.Errorf("failed to open Cheatsheets DB: %w", err)
5063
}
51-
return NewDB(absPath)
64+
return db, nil
5265
}
5366

5467
// GetTotalCheatsheets returns the total number of cheatsheets

frontend/internal/db/cheatsheets/utils.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,21 @@ package cheatsheets
33
import (
44
"crypto/sha256"
55
"encoding/binary"
6-
"path/filepath"
6+
"fdt-templ/internal/config"
7+
"fmt"
78
)
89

910
// GetDBPath returns the path to the cheatsheets database
10-
func GetDBPath() string {
11-
return filepath.Join("db", "all_dbs", "cheatsheets-db-v5.db")
11+
func GetDBPath() (string, error) {
12+
if config.DBConfig == nil {
13+
if err := config.LoadDBToml(); err != nil {
14+
return "", err
15+
}
16+
}
17+
if config.DBConfig.CheatsheetsDB == "" {
18+
return "", fmt.Errorf("Cheatsheets DB path is empty in db.toml")
19+
}
20+
return config.DBConfig.CheatsheetsDB, nil
1221
}
1322

1423
// HashURLToKeyInt generates a hash ID from category and slug.

frontend/internal/db/emojis/queries.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"time"
1313

1414
db_config "fdt-templ/db/config"
15+
"fdt-templ/internal/config"
1516

1617
_ "github.com/mattn/go-sqlite3"
1718
)
@@ -468,13 +469,24 @@ func (db *DB) GetSitemapEmojis() ([]SitemapEmoji, error) {
468469

469470
// GetDB returns a database instance using the default path
470471
func GetDB() (*DB, error) {
471-
dbPath := GetDBPath()
472-
// Resolve to absolute path
472+
if err := config.LoadDBToml(); err != nil {
473+
return nil, fmt.Errorf("failed to load db.toml for Emoji DB: %w", err)
474+
}
475+
dbPath := config.DBConfig.EmojiDB
476+
if dbPath == "" {
477+
return nil, fmt.Errorf("Emoji DB path is empty in db.toml")
478+
}
479+
473480
absPath, err := filepath.Abs(dbPath)
474481
if err != nil {
475-
return nil, err
482+
return nil, fmt.Errorf("failed to resolve absolute path for Emoji DB: %w", err)
483+
}
484+
485+
db, err := NewDB(absPath)
486+
if err != nil {
487+
return nil, fmt.Errorf("failed to open Emoji DB: %w", err)
476488
}
477-
return NewDB(absPath)
489+
return db, nil
478490
}
479491

480492
// GetEmojiCategoryUpdatedAt returns the updated_at timestamp for a category
Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
package emojis
22

33
import (
4-
"path/filepath"
4+
"fdt-templ/internal/config"
5+
"fmt"
56
)
67

78
// GetDBPath returns the path to the emoji database
8-
func GetDBPath() string {
9-
// Assuming we're running from project root
10-
return filepath.Join("db", "all_dbs", "emoji-db-v6.db")
9+
func GetDBPath() (string, error) {
10+
if config.DBConfig == nil {
11+
if err := config.LoadDBToml(); err != nil {
12+
return "", err
13+
}
14+
}
15+
if config.DBConfig.EmojiDB == "" {
16+
return "", fmt.Errorf("Emoji DB path is empty in db.toml")
17+
}
18+
return config.DBConfig.EmojiDB, nil
1119
}

0 commit comments

Comments
 (0)