package main import ( "butterfliu/migrations" "database/sql" "fmt" "log" "os" "path/filepath" ) var DB *sql.DB func GetDB() *sql.DB { return DB } func InitDB(dbPath string) error { dir := filepath.Dir(dbPath) if _, err := os.Stat(dir); os.IsNotExist(err) { if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("failed to create database directory %s: %v", dir, err) } } var err error DB, _ = sql.Open("sqlite3", dbPath) DB.Ping() if err = migrations.InitMigrationTable(DB); err != nil { return fmt.Errorf("failed to initialize migration table: %v", err) } // Auto-migrate by default, can be disabled with AUTO_MIGRATE=false if os.Getenv("AUTO_MIGRATE") != "false" { log.Println("Auto-migration enabled, applying pending migrations...") if err = migrations.MigrateUp(DB); err != nil { log.Printf("Warning: Auto migration failed: %v", err) } else { log.Println("Auto-migration completed successfully") } } else { log.Println("Auto-migration disabled") } return nil } func CloseDB() error { if DB != nil { return DB.Close() } return nil } func AddLibrary(name, path string) (Library, error) { absPath, err := filepath.Abs(path) if err != nil { return Library{}, err } result, err := DB.Exec( "INSERT INTO libraries (name, path) VALUES (?, ?)", name, absPath, ) if err != nil { return Library{}, err } id, err := result.LastInsertId() if err != nil { return Library{}, err } return Library{ ID: int(id), Name: name, Path: absPath, }, nil } func GetLibraries() ([]Library, error) { rows, err := DB.Query("SELECT id, name, path FROM libraries") if err != nil { return nil, err } defer rows.Close() libraries := []Library{} for rows.Next() { var lib Library if err := rows.Scan(&lib.ID, &lib.Name, &lib.Path); err != nil { return nil, err } libraries = append(libraries, lib) } return libraries, nil } func GetLibraryByID(id int) (Library, error) { row := DB.QueryRow("SELECT id, name, path FROM libraries WHERE id = ?", id) var lib Library if err := row.Scan(&lib.ID, &lib.Name, &lib.Path); err != nil { return Library{}, err } return lib, nil } func UpdateLibraryName(id int, name string) error { _, err := DB.Exec( "UPDATE libraries SET name = ? WHERE id = ?", name, id, ) return err } func UpdateLibraryPath(id int, path string) error { _, err := DB.Exec( "UPDATE libraries SET path = ? WHERE id = ?", path, id, ) return err } func DeleteLibrary(id int) error { _, err := DB.Exec("DELETE FROM libraries WHERE id = ?", id) return err } func AddMediaFile(path string, libraryID int) (MediaFile, error) { result, err := DB.Exec("INSERT INTO media_files (path, library_id) VALUES (?, ?)", path, libraryID) if err != nil { return MediaFile{}, err } id, err := result.LastInsertId() if err != nil { return MediaFile{}, err } return MediaFile{ ID: int(id), Path: path, }, nil } func GetMediaFileByPath(path string) (MediaFile, error) { row := DB.QueryRow("SELECT id, path FROM media_files WHERE path = ?", path) var mediaFile MediaFile if err := row.Scan(&mediaFile.ID, &mediaFile.Path); err != nil { return MediaFile{}, err } return mediaFile, nil } func AddArtist(name string) (Artist, error) { result, err := DB.Exec("INSERT INTO artists (name) VALUES (?)", name) if err != nil { return Artist{}, err } id, err := result.LastInsertId() if err != nil { return Artist{}, err } return Artist{ ID: int(id), Name: name, }, nil } func GetArtists() ([]Artist, error) { rows, err := DB.Query("SELECT id, name FROM artists") if err != nil { return nil, err } defer rows.Close() artists := []Artist{} for rows.Next() { var artist Artist if err := rows.Scan(&artist.ID, &artist.Name); err != nil { return nil, err } artists = append(artists, artist) } return artists, nil } func GetArtistByID(id int) (Artist, error) { row := DB.QueryRow("SELECT id, name FROM artists WHERE id = ?", id) var artist Artist if err := row.Scan(&artist.ID, &artist.Name); err != nil { return Artist{}, err } return artist, nil } func GetArtistByName(name string) (Artist, error) { row := DB.QueryRow("SELECT id, name FROM artists WHERE name = ?", name) var artist Artist if err := row.Scan(&artist.ID, &artist.Name); err != nil { return Artist{}, err } return artist, nil } func UpdateArtistName(id int, name string) error { _, err := DB.Exec("UPDATE artists SET name = ? WHERE id = ?", name, id) return err } func DeleteArtist(id int) error { _, err := DB.Exec("DELETE FROM artists WHERE id = ?", id) return err } func AddAlbum(title string, artistID int) (Album, error) { result, err := DB.Exec("INSERT INTO albums (title, artist_id) VALUES (?, ?)", title, artistID) if err != nil { return Album{}, err } id, err := result.LastInsertId() if err != nil { return Album{}, err } return Album{ ID: int(id), Title: title, ArtistID: artistID, }, nil } func GetAlbums() ([]Album, error) { rows, err := DB.Query("SELECT id, title, artist_id FROM albums") if err != nil { return nil, err } defer rows.Close() albums := []Album{} for rows.Next() { var album Album if err := rows.Scan(&album.ID, &album.Title, &album.ArtistID); err != nil { return nil, err } albums = append(albums, album) } return albums, nil } func GetAlbumByID(id int) (Album, error) { row := DB.QueryRow("SELECT id, title, artist_id FROM albums WHERE id = ?", id) var album Album if err := row.Scan(&album.ID, &album.Title, &album.ArtistID); err != nil { return Album{}, err } return album, nil } func GetAlbumByTitle(title string) (Album, error) { row := DB.QueryRow("SELECT id, title, artist_id FROM albums WHERE title = ?", title) var album Album if err := row.Scan(&album.ID, &album.Title, &album.ArtistID); err != nil { return Album{}, err } return album, nil } func GetAlbumByTitleAndArtist(title string, artistID int) (Album, error) { row := DB.QueryRow("SELECT id, title, artist_id FROM albums WHERE title = ? AND artist_id = ?", title, artistID) var album Album if err := row.Scan(&album.ID, &album.Title, &album.ArtistID); err != nil { return Album{}, err } return album, nil } func GetAlbumsByArtist(artistID int) ([]Album, error) { rows, err := DB.Query("SELECT id, title, artist_id FROM albums WHERE artist_id = ?", artistID) if err != nil { return nil, err } defer rows.Close() albums := []Album{} for rows.Next() { var album Album if err := rows.Scan(&album.ID, &album.Title, &album.ArtistID); err != nil { return nil, err } albums = append(albums, album) } return albums, nil } func UpdateAlbumTitle(id int, title string) error { _, err := DB.Exec("UPDATE albums SET title = ? WHERE id = ?", title, id) return err } func UpdateAlbumArtistID(id, artistID int) error { _, err := DB.Exec("UPDATE albums SET artist_id = ?", artistID) return err } func DeleteAlbum(id int) error { _, err := DB.Exec("DELETE FROM albums WHERE id = ?", id) return err } func AddSong(title string, artistID, albumID, duration, mediaFileID int) (Song, error) { result, err := DB.Exec("INSERT INTO songs (title, artist_id, album_id, duration, media_file_id) VALUES (?, ?, ?, ?, ?)", title, artistID, albumID, duration, mediaFileID) if err != nil { return Song{}, err } id, err := result.LastInsertId() if err != nil { return Song{}, err } return Song{ ID: int(id), Title: title, ArtistID: artistID, AlbumID: albumID, Duration: duration, MediaFileID: mediaFileID, }, nil } func GetSongs() ([]Song, error) { rows, err := DB.Query("SELECT id, artist_id, album_id, duration, media_file_id FROM songs") if err != nil { return nil, err } defer rows.Close() songs := []Song{} for rows.Next() { var song Song if err := rows.Scan(&song.ID, &song.ArtistID, &song.AlbumID, &song.Duration, &song.MediaFileID); err != nil { return nil, err } songs = append(songs, song) } return songs, nil } func UpdateSongTitle(id int, title string) error { _, err := DB.Exec("UPDATE songs SET title = ? WHERE id = ?", title, id) return err } func UpdateSongArtistID(id int, artistID int) error { _, err := DB.Exec("UPDATE songs SET artist_id = ?", artistID) return err } func UpdateSongAlbumID(id, albumID int) error { _, err := DB.Exec("UPDATE songs SET album_id = ?", albumID) return err } func UpdateSongDuration(id, duration int) error { _, err := DB.Exec("UPDATE songs SET duration = ?", duration) return err } func DeleteSong(id int) error { _, err := DB.Exec("DELETE FROM songs WHERE id = ?", id) return err } func GetSongsByLibrary(libraryID int) ([]Song, error) { rows, err := DB.Query(` SELECT s.id, s.title, s.artist_id, s.album_id, s.duration, s.media_file_id FROM songs s INNER JOIN media_files mf ON s.media_file_id = mf.id WHERE mf.library_id = ? ORDER BY s.title `, libraryID) if err != nil { return nil, err } defer rows.Close() songs := []Song{} for rows.Next() { var song Song if err := rows.Scan(&song.ID, &song.Title, &song.ArtistID, &song.AlbumID, &song.Duration, &song.MediaFileID); err != nil { return nil, err } songs = append(songs, song) } return songs, nil } type SongDetail struct { ID int `json:"id"` Title string `json:"title"` Artist string `json:"artist"` Album string `json:"album"` Duration int `json:"duration"` Path string `json:"path"` } func GetSongsByLibraryWithDetails(libraryID int) ([]SongDetail, error) { rows, err := DB.Query(` SELECT s.id, s.title, a.name as artist_name, al.title as album_title, s.duration, mf.path FROM songs s INNER JOIN media_files mf ON s.media_file_id = mf.id INNER JOIN artists a ON s.artist_id = a.id INNER JOIN albums al ON s.album_id = al.id WHERE mf.library_id = ? ORDER BY s.title `, libraryID) if err != nil { return nil, err } defer rows.Close() songs := []SongDetail{} for rows.Next() { var song SongDetail if err := rows.Scan(&song.ID, &song.Title, &song.Artist, &song.Album, &song.Duration, &song.Path); err != nil { return nil, err } songs = append(songs, song) } return songs, nil } func GetAllSongsWithDetails() ([]SongDetail, error) { rows, err := DB.Query(` SELECT s.id, s.title, a.name as artist_name, al.title as album_title, s.duration, mf.path FROM songs s INNER JOIN media_files mf ON s.media_file_id = mf.id INNER JOIN artists a ON s.artist_id = a.id INNER JOIN albums al ON s.album_id = al.id ORDER BY s.title `) if err != nil { return nil, err } defer rows.Close() songs := []SongDetail{} for rows.Next() { var song SongDetail if err := rows.Scan(&song.ID, &song.Title, &song.Artist, &song.Album, &song.Duration, &song.Path); err != nil { return nil, err } songs = append(songs, song) } return songs, nil }