cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm leaflet readability golang
29
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 181 lines 5.1 kB view raw
1package store 2 3import ( 4 "database/sql" 5 "embed" 6 "fmt" 7 "os" 8 "path/filepath" 9 "runtime" 10 11 _ "github.com/mattn/go-sqlite3" 12) 13 14var ( 15 sqlOpen = sql.Open 16 pragmaExec = func(db *sql.DB, stmt string) (sql.Result, error) { return db.Exec(stmt) } 17 createMigrationRunner = CreateMigrationRunner 18 getRuntime = func() string { return runtime.GOOS } 19 getHomeDir = os.UserHomeDir 20 mkdirAll = os.MkdirAll 21) 22 23//go:embed sql/migrations 24var migrationFiles embed.FS 25 26// Database wraps [sql.DB] with application-specific methods 27type Database struct { 28 *sql.DB 29 path string 30} 31 32// GetConfigDir returns the appropriate configuration directory based on [runtime.GOOS] 33var GetConfigDir = func() (string, error) { 34 var configDir string 35 36 switch getRuntime() { 37 case "windows": 38 appData := os.Getenv("APPDATA") 39 if appData == "" { 40 return "", fmt.Errorf("APPDATA environment variable not set") 41 } 42 configDir = filepath.Join(appData, "noteleaf") 43 case "darwin": 44 homeDir, err := getHomeDir() 45 if err != nil { 46 return "", fmt.Errorf("failed to get user home directory: %w", err) 47 } 48 configDir = filepath.Join(homeDir, "Library", "Application Support", "noteleaf") 49 default: 50 xdgConfigHome := os.Getenv("XDG_CONFIG_HOME") 51 if xdgConfigHome == "" { 52 homeDir, err := getHomeDir() 53 if err != nil { 54 return "", fmt.Errorf("failed to get user home directory: %w", err) 55 } 56 xdgConfigHome = filepath.Join(homeDir, ".config") 57 } 58 configDir = filepath.Join(xdgConfigHome, "noteleaf") 59 } 60 61 if err := mkdirAll(configDir, 0755); err != nil { 62 return "", fmt.Errorf("failed to create config directory: %w", err) 63 } 64 65 return configDir, nil 66} 67 68// GetDataDir returns the appropriate data directory based on [runtime.GOOS] or NOTELEAF_DATA_DIR 69var GetDataDir = func() (string, error) { 70 if envDataDir := os.Getenv("NOTELEAF_DATA_DIR"); envDataDir != "" { 71 if err := mkdirAll(envDataDir, 0755); err != nil { 72 return "", fmt.Errorf("failed to create data directory: %w", err) 73 } 74 return envDataDir, nil 75 } 76 77 var dataDir string 78 79 switch getRuntime() { 80 case "windows": 81 localAppData := os.Getenv("LOCALAPPDATA") 82 if localAppData == "" { 83 return "", fmt.Errorf("LOCALAPPDATA environment variable not set") 84 } 85 dataDir = filepath.Join(localAppData, "noteleaf") 86 case "darwin": 87 homeDir, err := getHomeDir() 88 if err != nil { 89 return "", fmt.Errorf("failed to get user home directory: %w", err) 90 } 91 dataDir = filepath.Join(homeDir, "Library", "Application Support", "noteleaf") 92 default: 93 xdgDataHome := os.Getenv("XDG_DATA_HOME") 94 if xdgDataHome == "" { 95 homeDir, err := getHomeDir() 96 if err != nil { 97 return "", fmt.Errorf("failed to get user home directory: %w", err) 98 } 99 xdgDataHome = filepath.Join(homeDir, ".local", "share") 100 } 101 dataDir = filepath.Join(xdgDataHome, "noteleaf") 102 } 103 104 if err := mkdirAll(dataDir, 0755); err != nil { 105 return "", fmt.Errorf("failed to create data directory: %w", err) 106 } 107 108 return dataDir, nil 109} 110 111// NewDatabase creates and initializes a new database connection 112var NewDatabase = func() (*Database, error) { 113 return NewDatabaseWithConfig(nil) 114} 115 116// NewDatabaseWithConfig creates and initializes a new [Database] connection using the provided [Config] 117func NewDatabaseWithConfig(config *Config) (*Database, error) { 118 if config == nil { 119 var err error 120 config, err = LoadConfig() 121 if err != nil { 122 return nil, fmt.Errorf("failed to load config: %w", err) 123 } 124 } 125 126 var dbPath string 127 if config.DatabasePath != "" { 128 dbPath = config.DatabasePath 129 dbDir := filepath.Dir(dbPath) 130 if err := mkdirAll(dbDir, 0755); err != nil { 131 return nil, fmt.Errorf("failed to create database directory: %w", err) 132 } 133 } else if config.DataDir != "" { 134 dbPath = filepath.Join(config.DataDir, "noteleaf.db") 135 } else { 136 dataDir, err := GetDataDir() 137 if err != nil { 138 return nil, fmt.Errorf("failed to get data directory: %w", err) 139 } 140 dbPath = filepath.Join(dataDir, "noteleaf.db") 141 } 142 143 db, err := sqlOpen("sqlite3", dbPath) 144 if err != nil { 145 return nil, fmt.Errorf("failed to open database: %w", err) 146 } 147 148 if _, err := pragmaExec(db, "PRAGMA foreign_keys = ON"); err != nil { 149 db.Close() 150 return nil, fmt.Errorf("failed to enable foreign keys: %w", err) 151 } 152 153 if _, err := pragmaExec(db, "PRAGMA journal_mode = WAL"); err != nil { 154 db.Close() 155 return nil, fmt.Errorf("failed to enable WAL mode: %w", err) 156 } 157 158 database := &Database{DB: db, path: dbPath} 159 runner := createMigrationRunner(db, migrationFiles) 160 if err := runner.RunMigrations(); err != nil { 161 db.Close() 162 return nil, fmt.Errorf("failed to run migrations: %w", err) 163 } 164 165 return database, nil 166} 167 168// NewMigrationRunner creates a new migration runner from a Database instance 169func NewMigrationRunner(db *Database) *MigrationRunner { 170 return createMigrationRunner(db.DB, migrationFiles) 171} 172 173// GetPath returns the database file path 174func (db *Database) GetPath() string { 175 return db.path 176} 177 178// Close closes the database connection 179func (db *Database) Close() error { 180 return db.DB.Close() 181}