// Package store отвечает за подключение к SQLite и миграции схемы. package store import ( "embed" "fmt" "os" "path/filepath" "github.com/jmoiron/sqlx" "github.com/pressly/goose/v3" _ "modernc.org/sqlite" // драйвер database/sql, имя "sqlite" ) //go:embed migrations/*.sql var migrationsFS embed.FS // Store оборачивает подключение к базе. type Store struct { DB *sqlx.DB } // Open открывает (создавая при необходимости) базу по пути dbPath, // прогоняет миграции и возвращает готовый Store. func Open(dbPath string) (*Store, error) { if dir := filepath.Dir(dbPath); dir != "" { if err := os.MkdirAll(dir, 0o755); err != nil { return nil, fmt.Errorf("create db dir %q: %w", dir, err) } } dsn := fmt.Sprintf( "file:%s?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)&_pragma=foreign_keys(1)", dbPath, ) db, err := sqlx.Connect("sqlite", dsn) if err != nil { return nil, fmt.Errorf("open sqlite %q: %w", dbPath, err) } if err := migrate(db); err != nil { _ = db.Close() return nil, err } return &Store{DB: db}, nil } func migrate(db *sqlx.DB) error { goose.SetBaseFS(migrationsFS) goose.SetLogger(goose.NopLogger()) // не ломать JSON-логи; ошибки идут через return if err := goose.SetDialect("sqlite3"); err != nil { return fmt.Errorf("set goose dialect: %w", err) } if err := goose.Up(db.DB, "migrations"); err != nil { return fmt.Errorf("run migrations: %w", err) } return nil } // Close закрывает подключение к базе. func (s *Store) Close() error { return s.DB.Close() }