package db import ( "database/sql" "embed" "fmt" "io/fs" "sort" "strings" _ "modernc.org/sqlite" ) //go:embed migrations/*.sql var migrationsFS embed.FS type DB struct { conn *sql.DB } func Open(path string) (*DB, error) { conn, err := sql.Open("sqlite", path) if err != nil { return nil, fmt.Errorf("open sqlite: %w", err) } if _, err := conn.Exec(`PRAGMA foreign_keys = ON`); err != nil { _ = conn.Close() return nil, fmt.Errorf("enable foreign keys: %w", err) } return &DB{conn: conn}, nil } func (d *DB) Close() error { return d.conn.Close() } func (d *DB) Conn() *sql.DB { return d.conn } func (d *DB) Migrate() error { entries, err := fs.ReadDir(migrationsFS, "migrations") if err != nil { return fmt.Errorf("read migrations: %w", err) } sort.Slice(entries, func(i, j int) bool { return entries[i].Name() < entries[j].Name() }) for _, entry := range entries { if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".sql") { continue } content, err := migrationsFS.ReadFile("migrations/" + entry.Name()) if err != nil { return fmt.Errorf("read migration %s: %w", entry.Name(), err) } if _, err := d.conn.Exec(string(content)); err != nil { return fmt.Errorf("apply migration %s: %w", entry.Name(), err) } } return nil }