navidrome/model/library.go
Deluan Quintão 96b051ffa7
fix(nativeapi): stop partial PUTs from clearing untouched columns (#6058)
* fix(nativeapi): stop partial PUTs from clearing untouched columns

The REST layer parses the request body's top-level JSON keys and passes them
to Repository.Update as colsToUpdate. The radio and library repositories
discarded that list and issued a full-row UPDATE, so any field absent from
the body was written as its zero value.

For radio this wiped uploaded_image, deleting the station's cover on every
partial update (the Web UI is unaffected because its form submits the whole
record). For library it silently cleared remote_path and default_new_users.

Thread the column list through to Put in both repositories, and extract the
column-selection half of filterUpdateValues into selectUpdateColumns so
library, which hand-builds its update map, shares the same rule instead of
copying it.

Fixes #6057

* refactor(persistence): drop pluginRepository's dead rest.Persistable methods

Save and Update had no callers: PUT /api/plugin/{id} is served by the
hand-written updatePlugin handler over a typed request struct, and the
route only wires rest.GetAll and rest.Get. Both methods delegated to Put,
which upserts all twelve columns, so wiring rest.Put to this repository
would have reintroduced the partial-update clobbering fixed in the previous
commit. Removing them, along with the rest.Persistable assertion, makes
that a compile error instead of a silent data loss.

Put itself is unchanged and still backs plugin discovery.
2026-08-31 11:21:32 -04:00

61 lines
2.2 KiB
Go

package model
import (
"time"
"github.com/navidrome/navidrome/utils/slice"
)
type Library struct {
ID int `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Path string `json:"path" db:"path"`
RemotePath string `json:"remotePath" db:"remote_path"`
LastScanAt time.Time `json:"lastScanAt" db:"last_scan_at"`
LastScanStartedAt time.Time `json:"lastScanStartedAt" db:"last_scan_started_at"`
FullScanInProgress bool `json:"fullScanInProgress" db:"full_scan_in_progress"`
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
CreatedAt time.Time `json:"createdAt" db:"created_at"`
TotalSongs int `json:"totalSongs" db:"total_songs"`
TotalAlbums int `json:"totalAlbums" db:"total_albums"`
TotalArtists int `json:"totalArtists" db:"total_artists"`
TotalFolders int `json:"totalFolders" db:"total_folders"`
TotalFiles int `json:"totalFiles" db:"total_files"`
TotalMissingFiles int `json:"totalMissingFiles" db:"total_missing_files"`
TotalSize int64 `json:"totalSize" db:"total_size"`
TotalDuration float64 `json:"totalDuration" db:"total_duration"`
DefaultNewUsers bool `json:"defaultNewUsers" db:"default_new_users"`
}
const (
DefaultLibraryID = 1
DefaultLibraryName = "Music Library"
)
type Libraries []Library
func (l Libraries) IDs() []int {
return slice.Map(l, func(lib Library) int { return lib.ID })
}
type LibraryRepository interface {
Get(id int) (*Library, error)
// GetPath returns the path of the library with the given ID.
// Its implementation must be optimized to avoid unnecessary queries.
GetPath(id int) (string, error)
GetAll(...QueryOptions) (Libraries, error)
CountAll(...QueryOptions) (int64, error)
Put(l *Library, colsToUpdate ...string) error
Delete(id int) error
StoreMusicFolder() error
AddArtist(id int, artistID string) error
// User-library association methods
GetUsersWithLibraryAccess(libraryID int) (Users, error)
// TODO These methods should be moved to a core service
ScanBegin(id int, fullScan bool) error
ScanEnd(id int) error
ScanInProgress() (bool, error)
RefreshStats(id int) error
}