Files
yellowjacket/backend/database/database.go
T
logan 30b2480df4 fix: resolve all lint errors and make linting a required CI check (#62)
- Fix 10 err113 violations: extract dynamic errors to package-level sentinels
- Fix 12 errcheck violations: handle unchecked error returns in player,
  metadata, and config packages
- Fix 4 revive stutter warnings: rename player.PlayerState to player.State,
  player.PlayerVolume to player.Volume, queue.QueueTrack to queue.Track,
  queue.QueueState to queue.State
- Fix 2 staticcheck SA4001: simplify *&x to x in assets handler
- Fix 5 unused constants: remove dead AudioFileType iota block in models
- Fix gci/gofumpt/wsl formatting issues across multiple files
- Add gofumpt module-path setting to .golangci.yml for correct import grouping
- Fix player test: gate integration test behind YELLOWJACKET_INTEGRATION env var
  instead of only skipping in CI, and replace t.Errorf+t.Failed with t.Fatalf
- Remove continue-on-error from golangci-lint CI step so linting is now required
2026-02-14 01:52:27 -06:00

96 lines
2.2 KiB
Go

// Package database provides SQLite database access.
package database
import (
"context"
"database/sql"
"embed"
"fmt"
"io/fs"
"log/slog"
"path"
_ "modernc.org/sqlite" // Register sqlite driver.
"yellowjacket/backend/database/sql/sqlcgen"
"yellowjacket/backend/system"
)
//go:generate go tool sqlc generate
//go:embed sql/schemas/*.sql
var schemas embed.FS
// DB wraps the SQLite database connection and queries.
type DB struct {
db *sql.DB
Ctx context.Context
Queries *sqlcgen.Queries
logger *slog.Logger
}
// NewDB opens the database and applies schema migrations.
func NewDB(logger *slog.Logger) (*DB, error) {
dbCtx := context.Background()
userDataDir, err := system.GetUserDataDirPath()
if err != nil {
return nil, fmt.Errorf("could not get user data directory: %w", err)
}
sqliteDBFilePath := path.Join(userDataDir, "yj.db")
logger.Debug("opening sqlite database", "filepath", sqliteDBFilePath)
db, err := sql.Open("sqlite", sqliteDBFilePath+"?_busy_timeout=5000&_journal_mode=WAL")
if err != nil {
return nil, fmt.Errorf("could not connect to sqlite database: %w", err)
}
db.SetMaxOpenConns(1) // SQLite only supports one writer at a time
// Execute SQL files from the embedded schemas directory
logger.Debug("reading sql schema files from embedded directory")
dirEntries, err := schemas.ReadDir("sql/schemas")
if err != nil {
return nil, fmt.Errorf("could not read schemas directory: %w", err)
}
logger.Debug("executing all sql schema files")
for _, dirEntry := range dirEntries {
if !dirEntry.IsDir() {
filePath := path.Join("sql/schemas", dirEntry.Name())
sqlContent, err := fs.ReadFile(schemas, filePath)
if err != nil {
return nil, fmt.Errorf("could not read file %s: %w", filePath, err)
}
logger.Debug(
"executing sql schema file",
"filepath",
filePath,
"sql",
string(sqlContent),
)
_, err = db.ExecContext(dbCtx, string(sqlContent)) // Execute the SQL
if err != nil {
return nil, fmt.Errorf("error executing sql from file %s: %w", filePath, err)
}
}
}
// Get generated queries
queries := sqlcgen.New(db)
return &DB{
db: db,
Ctx: dbCtx,
Queries: queries,
logger: logger,
}, err
}