1package db
2
3import (
4 "database/sql"
5 "fmt"
6 "log"
7 "os"
8 "path/filepath"
9
10 "github.com/bnema/dumber/internal/config"
11 _ "github.com/ncruces/go-sqlite3/driver" // SQLite driver
12 _ "github.com/ncruces/go-sqlite3/embed" // Embed SQLite
13)
14
15// InitDB initializes the database connection and schema
16func InitDB(dbPath string) (*sql.DB, error) {
17 if dbPath == "" {
18 return nil, fmt.Errorf("database path cannot be empty")
19 }
20
21 // Ensure database directory exists
22 if err := os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil {
23 return nil, fmt.Errorf("failed to create database directory: %w", err)
24 }
25
26 // Open database connection
27 database, err := sql.Open("sqlite3", dbPath)
28 if err != nil {
29 return nil, fmt.Errorf("failed to open database: %w", err)
30 }
31
32 // Test the connection
33 if err := database.Ping(); err != nil {
34 if err := database.Close(); err != nil {
35 log.Printf("Warning: failed to close database: %v", err)
36 }
37 return nil, fmt.Errorf("failed to connect to database: %w", err)
38 }
39
40 // Initialize database schema
41 if err := initializeSchema(database); err != nil {
42 if err := database.Close(); err != nil {
43 log.Printf("Warning: failed to close database: %v", err)
44 }
45 return nil, fmt.Errorf("failed to initialize database schema: %w", err)
46 }
47
48 return database, nil
49}
50
51// InitDBWithConfig initializes the database with configuration-based shortcuts
52func InitDBWithConfig(dbPath string, cfg *config.Config) (*sql.DB, error) {
53 db, err := InitDB(dbPath)
54 if err != nil {
55 return nil, err
56 }
57
58 // Initialize shortcuts from configuration
59 if err := initializeShortcuts(db, cfg); err != nil {
60 if err := db.Close(); err != nil {
61 log.Printf("Warning: failed to close database: %v", err)
62 }
63 return nil, fmt.Errorf("failed to initialize shortcuts: %w", err)
64 }
65
66 return db, nil
67}
68
69// initializeSchema creates the database schema if it doesn't exist
70func initializeSchema(db *sql.DB) error {
71 schema := `
72 -- History tracking for visited URLs
73 CREATE TABLE IF NOT EXISTS history (
74 id INTEGER PRIMARY KEY AUTOINCREMENT,
75 url TEXT NOT NULL UNIQUE,
76 title TEXT,
77 visit_count INTEGER DEFAULT 1,
78 last_visited DATETIME DEFAULT CURRENT_TIMESTAMP,
79 created_at DATETIME DEFAULT CURRENT_TIMESTAMP
80 );
81
82 CREATE INDEX IF NOT EXISTS idx_history_url ON history(url);
83 CREATE INDEX IF NOT EXISTS idx_history_last_visited ON history(last_visited);
84
85 -- URL shortcuts configuration
86 CREATE TABLE IF NOT EXISTS shortcuts (
87 id INTEGER PRIMARY KEY AUTOINCREMENT,
88 shortcut TEXT NOT NULL UNIQUE,
89 url_template TEXT NOT NULL,
90 description TEXT,
91 created_at DATETIME DEFAULT CURRENT_TIMESTAMP
92 );
93
94 -- Zoom persistence per domain (not full URL)
95 CREATE TABLE IF NOT EXISTS zoom_levels (
96 domain TEXT PRIMARY KEY,
97 zoom_factor REAL NOT NULL DEFAULT 1.0 CHECK(zoom_factor >= 0.3 AND zoom_factor <= 5.0),
98 updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
99 );
100
101 CREATE INDEX IF NOT EXISTS idx_zoom_levels_updated_at ON zoom_levels(updated_at);
102 `
103
104 // Execute schema creation
105 _, err := db.Exec(schema)
106 return err
107}
108
109// initializeShortcuts inserts configured shortcuts into the database
110func initializeShortcuts(db *sql.DB, cfg *config.Config) error {
111 if cfg == nil || cfg.SearchShortcuts == nil {
112 return nil // No shortcuts to initialize
113 }
114
115 // Insert configured shortcuts
116 for shortcut, shortcutCfg := range cfg.SearchShortcuts {
117 _, err := db.Exec(
118 "INSERT OR IGNORE INTO shortcuts (shortcut, url_template, description) VALUES (?, ?, ?)",
119 shortcut, shortcutCfg.URL, shortcutCfg.Description,
120 )
121 if err != nil {
122 return fmt.Errorf("failed to insert shortcut %s: %w", shortcut, err)
123 }
124 }
125
126 return nil
127}