package repository import ( "database/sql" ) 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"` } type LibraryRepository struct { db *sql.DB } func NewLibraryRepository(db *sql.DB) *LibraryRepository { return &LibraryRepository{db: db} } func (r *LibraryRepository) GetAll() ([]Library, error) { rows, err := r.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 (r *LibraryRepository) GetByID(id int) (Library, error) { row := r.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 (r *LibraryRepository) Create(name, path string) (Library, error) { result, err := r.db.Exec("INSERT INTO libraries (name, path) VALUES (?, ?)", name, path) if err != nil { return Library{}, err } id, err := result.LastInsertId() if err != nil { return Library{}, err } return Library{ ID: int(id), Name: name, Path: path, }, nil } func (r *LibraryRepository) UpdateName(id int, name string) error { _, err := r.db.Exec("UPDATE libraries SET name = ? WHERE id = ?", name, id) return err } func (r *LibraryRepository) UpdatePath(id int, path string) error { _, err := r.db.Exec("UPDATE libraries SET path = ? WHERE id = ?", path, id) return err } func (r *LibraryRepository) Delete(id int) error { tx, err := r.db.Begin() if err != nil { return err } defer tx.Rollback() // Delete the library — CASCADE removes media_files and songs automatically _, err = tx.Exec("DELETE FROM libraries WHERE id = ?", id) if err != nil { return err } // Clean up orphan albums (no songs reference them) _, err = tx.Exec(` DELETE FROM albums WHERE id NOT IN ( SELECT DISTINCT album_id FROM songs WHERE album_id IS NOT NULL ) `) if err != nil { return err } // Clean up orphan artists (no songs and no albums reference them) _, err = tx.Exec(` DELETE FROM artists WHERE id NOT IN ( SELECT DISTINCT artist_id FROM songs WHERE artist_id IS NOT NULL UNION SELECT DISTINCT artist_id FROM albums WHERE artist_id IS NOT NULL ) `) if err != nil { return err } return tx.Commit() } func (r *LibraryRepository) GetSongsByLibraryWithDetails(libraryID int) ([]SongDetail, error) { rows, err := r.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 (r *LibraryRepository) GetMediaFileByPath(path string) (MediaFile, error) { row := r.db.QueryRow("SELECT id, path, library_id FROM media_files WHERE path = ?", path) var mediaFile MediaFile if err := row.Scan(&mediaFile.ID, &mediaFile.Path, &mediaFile.LibraryID); err != nil { return MediaFile{}, err } return mediaFile, nil } func (r *LibraryRepository) CreateMediaFile(path string, libraryID int) (MediaFile, error) { result, err := r.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 (r *LibraryRepository) GetArtistByName(name string) (Artist, error) { row := r.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 (r *LibraryRepository) CreateArtist(name string) (Artist, error) { result, err := r.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 (r *LibraryRepository) GetAlbumByTitleAndArtist(title string, artistID int) (Album, error) { row := r.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 (r *LibraryRepository) CreateAlbum(title string, artistID int) (Album, error) { result, err := r.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 (r *LibraryRepository) CreateSong(title string, artistID, albumID, duration, mediaFileID int) (Song, error) { result, err := r.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 (r *LibraryRepository) GetArtists() ([]Artist, error) { rows, err := r.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 (r *LibraryRepository) GetAlbums() ([]Album, error) { rows, err := r.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 }