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