Commit graph

4,951 commits

Author SHA1 Message Date
Deluan
200ae482c3 fix(jellyfin): include MediaSources with Size/Bitrate on tracks so clients show download size 2026-07-05 21:31:06 -04:00
Deluan
da451cf927 fix(jellyfin): read lowercase ids/entryIds playlist params and add playlist-users endpoints 2026-07-05 21:23:55 -04:00
Deluan
17c1b44e60 fix(jellyfin): resolve playlist ids in getItem 2026-07-05 21:15:14 -04:00
Deluan
0c4e6b2c70 fix(jellyfin): serve direct file at /Items/{id}/File and accept ApiKey query param 2026-07-05 21:06:28 -04:00
Deluan
4e277b1929 fix(jellyfin): resolve library-view ids in getItem so clients can load the library 2026-07-05 20:55:09 -04:00
Deluan
da51d3f1c6 feat(jellyfin): implement /socket WebSocket for real-time client sessions 2026-07-05 20:43:03 -04:00
Deluan
3a155e23e8 fix(jellyfin): support Playlist type, multi-type Items queries, and PlayCount/DatePlayed sort
Real Finamp requests break against three /Items query engine bugs: Playlist
requests fell through to albums, multi-type IncludeItemTypes (e.g. Finamp's
favorites screen) only returned the first requested type, and comma-separated
SortBy lists (e.g. "DateCreated,SortName") were matched as one opaque string
so they always fell through to the repo default.

Also extends tests/mock_playlist_repo.go with SetData/GetAll so playlist
listing is testable like the other mock repos.
2026-07-05 20:28:20 -04:00
Deluan
ae78d779ca fix(jellyfin): return User Policy and Configuration so Finamp completes login 2026-07-05 19:56:26 -04:00
Deluan
50e0f91032 refactor(server): extract case-insensitive path routing to a shared helper 2026-07-05 18:19:41 -04:00
Deluan
8e55e64337 fix(jellyfin): case-insensitive path routing for Jellyfin client compatibility 2026-07-05 18:10:53 -04:00
Deluan
46f3b4dbc8 docs(jellyfin): clarify public image endpoint and accessibleLibraryIDs comments 2026-07-05 17:57:53 -04:00
Deluan
106a1e2501 feat(jellyfin): unknown-route logging, generic 500s, rating clamp, docs
Hardening pass ahead of real client testing: unmatched routes and
unsupported methods now return a logged, JSON 404 instead of chi's
default plain-text response, so a missing endpoint a client needs is
easy to spot in the logs. Internal errors (ffmpeg output, file paths,
etc.) no longer leak into 500 response bodies -- a shared
internalError helper logs the real error server-side and always
returns a generic message. Inbound Jellyfin ratings are clamped to
0-10 before being halved into Navidrome's 0-5 scale, and /System/Ping
now replies with a bare plain-text body as real Jellyfin servers do.
Adds a README with an enable/curl walkthrough and known limitations.
2026-07-05 17:33:41 -04:00
Deluan
15f5ba16e8 test(jellyfin): cover empty Ids/EntryIds in playlist add/remove 2026-07-05 17:27:17 -04:00
Deluan
34c1ebc4be feat(jellyfin): playlist read and write-back
Adds POST /Playlists, GET/POST/DELETE /Playlists/{id}/Items. Tags each
playlist item with PlaylistItemId (the entry's position within the
playlist) so DELETE .../Items?EntryIds=... can remove a specific
occurrence by the id core/playlists.RemoveTracks actually expects,
rather than the song id used everywhere else.
2026-07-05 17:21:42 -04:00
Deluan
ca38e50c4f feat(jellyfin): playback reporting and scrobbling
Add /Sessions/Playing[/Progress|/Stopped] and /Sessions/Capabilities[/Full]
handlers, backed by core/scrobbler.PlayTracker. A new withPlayer middleware
resolves/registers a model.Player from the Emby DeviceId header (used
directly as the stable player id, unlike Subsonic's cookie fallback) and
injects it into the request context for scrobbling.

The Stopped report calls ReportPlayback with IgnoreScrobble to end the
now-playing session without double-counting, then calls Submit as the
single source of the play-count increment and external scrobble.
2026-07-05 17:05:43 -04:00
Deluan
d28adb2676 feat(jellyfin): favorites and rating write-back with library access control
Adds POST/DELETE handlers for /Users/{userId}/FavoriteItems/{itemId} and
/Users/{userId}/Items/{itemId}/Rating. A shared resolveAnnotated helper
probes album/artist/media file (mirroring getItem's order) and 404s before
writing if the user lacks access to the album/song's library; artists are
exempt since they span multiple libraries. Ratings are halved coming in
(Jellyfin 0-10 -> Navidrome 0-5) to match the doubling in dto.UserData.

Also teaches MockMediaFileRepo and MockArtistRepo's SetStar/SetRating (and
MockAlbumRepo's, previously a no-op) to actually mutate the backing data so
write-back can be asserted in tests.
2026-07-05 16:51:02 -04:00
Deluan
7e26f1fa8c feat(jellyfin): audio streaming and PlaybackInfo with library access control 2026-07-05 16:33:48 -04:00
Deluan
926a36acfa fix(jellyfin): let net/http sniff image Content-Type instead of forcing jpeg 2026-07-05 16:27:25 -04:00
Deluan
6e4a180feb feat(jellyfin): item image endpoint via artwork service 2026-07-05 16:20:39 -04:00
Deluan
30d3efebb9 refactor(jellyfin): share resolveLibraryScope between items and artists 2026-07-05 16:16:26 -04:00
Deluan
65b92168f9 feat(jellyfin): Artists and Genres endpoints
Add /Artists, /Artists/AlbumArtists, /Genres and /MusicGenres. Artists
listing is library-scoped (defaulting to the user's accessible
libraries, narrowed by an accessible ParentId), delegating access
control to listArtists/ApplyArtistLibraryFilter. Genres are global and
unscoped, matching the ML decision already made for listGenres.
2026-07-05 16:10:07 -04:00
Deluan
c7d1f69c1b test(jellyfin): cover admin library-access path and drop nil test contexts 2026-07-05 16:04:10 -04:00
Deluan
fbce1d3500 feat(jellyfin): full multi-library support with per-user access scoping
Replace the single hardcoded "music" UserView with one CollectionFolder
view per library the user can access, and scope every /Items browse
query (albums, songs, artists, /Latest) to those libraries via
server/filter's ApplyLibraryFilter/ApplyArtistLibraryFilter.

ParentId is now disambiguated: a numeric value the user has access to is
treated as a library scope (browsing a UserView), otherwise it falls
through as an entity id (artist/album), which safely matches nothing
rather than leaking another library's content. getItem now 404s when
fetching an album or song outside the user's accessible libraries;
artists are skipped (they can span multiple libraries) with a TODO.

Genres remain unscoped since they're global tags, not per-library
entities.
2026-07-05 15:53:02 -04:00
Deluan
d921bb6a95 refactor(server): extract shared query filter builders to server/filter
Moves server/subsonic/filter to server/filter so server/jellyfin can use
the shared query-option builders without importing server/subsonic,
enforcing the rule that no API package imports another API's package.
2026-07-05 15:42:20 -04:00
Deluan
0b9fbcd048 feat(jellyfin): Items query engine, item detail, and latest
Adds the /Items universal query endpoint, dispatching by IncludeItemTypes
over albums/artists/songs/genres with ParentId, SearchTerm, Filters=IsFavorite,
SortBy/SortOrder and StartIndex/Limit support, plus GET /Items/{itemId} and
/Users/{userId}/Items/Latest. Reuses server/subsonic/filter builders (by
artist/album/starred) instead of hand-rolled squirrel filters, and resolves
sort keys per item type since each repository maps sort names to different
real/aliased columns.

Also adds the missing CountAll to tests.MockArtistRepo, exposed by this task
(the mock embedded a nil ArtistRepository for it and would panic on use).
2026-07-05 15:27:53 -04:00
Deluan
aacc46f7f5 feat(jellyfin): user views and current-user endpoints 2026-07-05 15:07:43 -04:00
Deluan
d5c6e9e451 refactor(jellyfin): use new(x) builtin instead of intPtr helper 2026-07-05 15:03:23 -04:00
Deluan
92c66178de fix(jellyfin): reject empty login password, wire ServerName, add negative auth tests 2026-07-05 15:01:02 -04:00
Deluan
535d252d17 feat(jellyfin): authentication middleware and AuthenticateByName login 2026-07-05 14:51:46 -04:00
Deluan
ce7e7513fb fix(jellyfin): map LastPlayedDate and omit zero track/disc numbers 2026-07-05 14:46:12 -04:00
Deluan
2a18863c06 feat(jellyfin): advertise Jellyfin-compatible version, brand ServerName with Navidrome version 2026-07-05 14:38:43 -04:00
Deluan
da9bb25313 test(jellyfin): cover GenreToBaseItem mapper 2026-07-05 14:27:14 -04:00
Deluan
2e0e3264c7 feat(jellyfin): add Jellyfin DTOs and model mappers
Adds BaseItemDto, QueryResult, UserItemDataDto, UserDto,
AuthenticationResult, MediaSourceInfo, PlaybackInfoResponse,
NameGuidPair and SessionInfo DTOs to server/jellyfin/dto, plus
model-to-DTO mappers for songs, albums, artists and genres that
later browse/stream/write endpoints will consume.
2026-07-05 14:26:24 -04:00
Deluan
8a4b7d1221 feat(jellyfin): wire and mount the router behind Jellyfin.Enabled
Add jellyfin.New to the shared Wire provider set and a
CreateJellyfinAPIRouter injector, then mount the router at /jellyfin
in startServer(), gated by conf.Server.Jellyfin.Enabled, mirroring the
existing LastFM/ListenBrainz router mounts.
2026-07-05 14:18:39 -04:00
Deluan
73376f519d fix(jellyfin): memoize stable server Id and cover get-or-create path 2026-07-05 14:13:18 -04:00
Deluan
910c239e29 test(jellyfin): cover ping and quickConnectEnabled handlers
Both were added alongside the System/Info/Public handshake endpoint but
had no direct test coverage.
2026-07-05 14:05:09 -04:00
Deluan
b38750fd5b feat(jellyfin): package skeleton with System handshake endpoints
Adds server/jellyfin: the Router (mirrors server/subsonic and
server/public), its Wire-friendly New(...) constructor, a chi routes()
table, and the ok() JSON response helper. Implements the unauthenticated
handshake surface Jellyfin clients probe first: GET /System/Info/Public,
GET+POST /System/Ping, and GET /QuickConnect/Enabled (quick connect is
unsupported, so it always reports disabled).

The public info payload's Id must be stable across restarts (Jellyfin
clients cache ServerId), so it reuses the get-or-create Property pattern
already used for InsightsID in core/metrics/insights.go: fetch
consts.JellyfinServerIDKey from the Property repository, generating and
persisting a new UUID on first read. A dedicated key (rather than the
existing InsightsID) keeps the anonymous telemetry identifier from being
exposed on this unauthenticated endpoint.
2026-07-05 14:03:40 -04:00
Deluan
95e33c92ca fix(jellyfin): default Jellyfin ServerName to "Navidrome" 2026-07-05 13:57:21 -04:00
Deluan
36240d4582 feat(jellyfin): add config flag and URL path constant
Adds the disabled-by-default Server.Jellyfin config option (Enabled,
ServerName) and consts.URLPathJellyfinAPI, following the existing
LastFM/ListenBrainz patterns. Later tasks will use these to mount the
Jellyfin-compatible API router.
2026-07-05 13:53:16 -04:00
Deluan
5e913ae6a4 feat(sharing): enable sharing by default
Flip the EnableSharing default from false to true so new installations have the sharing feature available out of the box. Users can still disable it via the EnableSharing config option.

The native API only registers the /share route when sharing is enabled, so the nativeapi tests that build the router without wiring a share service now explicitly disable sharing in their setup to avoid registering a route backed by a nil service.
2026-07-04 19:49:19 -04:00
Deluan Quintão
d4387c5502
perf(db): index media_file album/artist sort orders (#5706)
* perf(db): add composite indexes for song list album/artist sorts

The media_file sort mappings for album, artist and albumArtist expand to
multi-column ORDER BY clauses that no existing index could satisfy, so SQLite
fell back to a full table scan plus a temp B-tree sort of every row (including
the large lyrics/tags/full_text columns) even for a single 15-item page. On a
96K-track library this made /api/song?_sort=album take 3.6s on a cold cache.

Add composite indexes matching the three sort mappings, allowing the query to
walk the index and stop at the page size, in both directions. Drop the now
redundant single-column order_album_name/order_artist_name indexes (strict
prefixes of the new composites) and three indexes with no query path:
birth_time is only read in Go code, and artist/album_artist text column
lookups go through the media_file_artists table instead.

* fix(ui): make composer and track number columns non-sortable in song list

Clicking the Composer header was a silent no-op: composer is not a media_file
column, so the native API's sanitizeSort drops the sort and returns rows in
table order. Track number sorting across the whole library is not meaningful
and cannot use an index (the existing index leads with disc_number). Mark both
columns sortable={false}, like quality and mood.

* test(persistence): add sort index coverage test for large tables

Guard against sort options silently losing index support: every sort mapping
on media_file, album and artist is now verified with EXPLAIN QUERY PLAN to be
satisfiable by an index (both directions), so adding a mapping or dropping an
index that reintroduces a full-table temp B-tree sort fails the test. Sorts
that genuinely cannot use an index (random, annotation-join columns, JSON
expressions) must be declared in an exceptions list with the reason, keeping
the trade-off visible in review.

To make the sort mappings the complete declared sort surface, add identity
mappings for the media_file columns the UI sorts by without a mapping (year,
genre, duration, channels, bpm, path, comment, play_count, play_date, rating).
These are behaviorally no-ops: the same ORDER BY was previously produced by
the field whitelist fallback.

* perf(db): drop PreferSortTags expression indexes from media_file

The media_file sort_title/sort_artist_name/sort_album_name expression indexes
are only usable when PreferSortTags is enabled - a config reported by ~0.1% of
installations (insights, week of 2026-06-22) - yet every install pays their
storage (~8.6MB on a 96K-track library) and scanner write overhead. Drop them:
PreferSortTags installs fall back to a full sort for title/artist/album orders,
everyone else gets smaller DBs and cheaper writes. The order_album_name and
order_artist_name collation checks remain valid, now satisfied by the composite
sort indexes.

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
2026-07-03 08:58:55 -04:00
Deluan Quintão
ae9b8a5fe6
feat(search): rank exact matches above prefix matches (#5704)
* feat(search): boost exact token matches over prefix matches

buildFTS5Query now emits (word OR word*) instead of word* for plain tokens. The match set is unchanged (exact is a subset of prefix), but bm25 gives the rare exact token a high-IDF contribution, so rows containing the literal query word rank above prefix-only matches. The degraded-query check keeps evaluating the plain prefix form, preserving the LIKE fallback for queries like "1+" and "C++".

* feat(search): weight artist search_normalized equal to name in bm25

For the artist table, search_normalized holds only the artist's name in alternate spelling (transliterated/punctuation-stripped), so a hit there is as meaningful as a name hit. Combined with exact-token boosting, artists like MØ now rank in the top results for the query "MO" instead of dead last. media_file and album keep weight 1.0 because their search_normalized mixes title, album, and artist variants.

* test(persistence): add exact-match ranking regression test

Seeds MØ, Morrissey, and Modest Mouse and asserts MØ ranks first for the queries "MO" and "MØ": the exact transliterated hit in search_normalized must outrank name-prefix matches. The corpus deliberately has no competing exact-word names, since exact-vs-exact ordering depends on corpus statistics rather than the guaranteed exact-over-prefix property. Rows are inserted per-test (with their library_artist associations) and cleaned up to avoid disturbing the shared seed fixtures and their count assertions.

* docs(search): document exact-token OR emission in buildFTS5Query

* test(persistence): harden exact-match ranking test fixtures

Register the corpus cleanup before the insert loop so a mid-loop assertion failure cannot leak fts-rank-% rows into the shared integration DB, and reuse the existing createArtistWithLibrary helper instead of hand-rolling Put+AddArtist (which also replaces the ad-hoc context.TODO with the helper's GinkgoT().Context).

* fix(search): flag multi-word degraded queries for the LIKE fallback

The degradation probe was joined with explicit " AND " like the real query, so ftsQueryDegraded counted the literal AND as a long token and never flagged queries where every term degrades to a short token (e.g. "1+ 2+"). This predates this branch (the old code passed the same AND-joined string), but the probe now exists separately, so join it with spaces — it only feeds ftsQueryDegraded, which needs no explicit operators.
2026-07-02 15:51:03 -04:00
Deluan Quintão
427d4b9bce
fix(search): artists with atomic non-ASCII names unfindable after FTS5 migration (#5703)
* fix(scanner): update artist search_normalized when rescanning

The FTS5 migration back-fills artist.search_normalized with a SQL
punctuation-strip approximation, relying on the next scan to compute the
precise value in Go (normalizeForFTS transliterates atomic letters like
Ø/æ/ß that FTS5's remove_diacritics cannot fold). But the scanner
persisted artists with an explicit column list that omitted
search_normalized, so not even a full scan ever repaired it: an artist
migrated from a pre-FTS database (e.g. "GØGGS") stayed unfindable by
any ASCII search, while their albums and songs, which are saved with all
columns, were fixed by a full scan. Add search_normalized to the column
list so a full scan re-indexes the artist via the artist_fts trigger.

* refactor(persistence): move normalizeForFTS to utils/str

Export it as str.NormalizeForFTS so the upcoming migration can reuse the
exact index-time normalization. Migrations cannot import the persistence
package (persistence -> db -> db/migrations would be an import cycle).

* fix(persistence): backfill artist search_normalized via migration

Recompute artist.search_normalized with the precise Go normalization for
databases migrated from pre-FTS5 versions, where the SQL back-fill could
not transliterate atomic letters (Ø/æ/ß) and the scanner never rewrote
the column. Only changed rows are updated, so the artist_fts update
trigger re-indexes exactly the affected artists, making artists like
GØGGS or MØ findable again without requiring a full scan.

* refactor(persistence): share FTS punctuation-strip regex via utils/str

Index-time normalization (NormalizeForFTS) and query-time processing
(buildFTS5Query/ftsQueryDegraded) must produce matching tokens, so keep
the punctuation-strip pattern in a single exported symbol instead of two
identical private copies that could drift. Also document that derived
columns computed in dbArtist.PostMapArgs must be listed in the scanner's
artist Put, which is how search_normalized went stale in the first place.

* chore(migrations): announce artist search backfill in the log

Match the FTS5 migration's notice() pattern so startup isn't silent
while the backfill runs on large libraries.

* docs: tighten comments added in this branch

* docs: describe FTSPunctStrip by what it matches, not one replacement
2026-07-02 12:53:10 -04:00
Deluan Quintão
01b7c86f90
fix(scanner): stop logging expected lyrics sniff misses as warnings (#5702)
* fix(scanner): stop logging expected lyrics sniff misses as warnings

During a scan, embedded lyrics are parsed with an empty suffix, which puts
ParseLyrics into content-sniffing mode: it tries the TTML, SRT and Lyricsfile
YAML parsers in turn before falling back to plain text. Every plain-text or LRC
lyric therefore fails the structured probes on its way to the fallback, and each
failure was logged at warning level with no indication of which file triggered
it, flooding the scan log with benign "Error parsing lyrics, falling back to
plain text" messages.

A probe rejecting content it does not own during sniffing is expected control
flow, so it is now logged at trace instead. A parse failure under an explicitly
requested suffix (e.g. a malformed .yaml/.srt/.ttml sidecar) still warns, since
the user declared that format. ParseLyrics gains ctx and path parameters so any
warning names the offending file and carries request context where available;
all call sites are updated accordingly.

Also fixes a test-isolation bug in the new logging spec: the BeforeEach swapped
the process-global default logger via SetDefaultLogger but only restored the log
level on cleanup, leaking the null logger and its hook into later specs in the
shared model suite.

* test: use spec-scoped contexts instead of context.Background in lyrics tests

Replace context.Background() with GinkgoT().Context() (and b.Context() in the
parse benchmarks) across the lyrics-related tests, so contexts are cancelled
when each spec ends. The embeddedLyrics fixture in core/lyrics is now a
hand-written literal like its sibling fixtures, removing the construction-time
ParseLyrics call that could not use a spec-scoped context.

* refactor(model): attach lyrics parse log attribution via context

Narrow ParseLyrics back to (ctx, suffix, lang, contents), dropping the path
parameter added by the previous commit. Attribution now uses the codebase's
existing idiom: callers that know the source attach it with log.NewContext
(e.g. "file" for the media file or sidecar), and the plugin adapter tags both
the plugin name and the track, fixing probe-miss logs that misattributed
plugin-returned content to the file's own tags. This removes three adjacent
string parameters that were easy to swap silently, and the "" placeholder most
call sites had to pass.

Also hardens the logging spec from the previous commit: the null test logger is
now swapped in before raising the level (SetLevel forces the current default
logger to trace, so the old order left the null logger at info and trace
entries never reached the hook), the sniff test now asserts probe misses are
observable at trace with file attribution instead of only asserting the absence
of warnings, and cleanup restores the actual previous logger — via a new return
value on log.SetDefaultLogger — instead of a bare logrus.New() that would
discard hooks configured on the process-wide logger.

* refactor(lyrics): hoist attributed log contexts out of loops

Address review feedback on #5702: build the log-attributed context once per
operation instead of per iteration, and reuse it on the surrounding log calls
so the error/trace lines around ParseLyrics carry the same attribution fields.
In fromExternalFile the sidecar path now rides the context for all log lines
in the function, replacing the repeated explicit "path" field.

* style(model): pass lyrics parse errors as final log arguments

Per the project logging convention, errors go as the last argument (the log
package normalizes them via its error case) instead of a keyed "error" pair,
which stores the raw error value and bypasses that handling. Flagged by review
on #5702; the keyed form was inherited from the original warning line.
2026-07-02 09:46:57 -04:00
Deluan Quintão
4cbba2ae49
feat(scanner): add ArtistSplitExceptions to protect artist names from splitting (#5701)
* refactor(scanner): make tag value splitting position-based

Replaces the ZWSP substitution trick with index-based cutting, in
preparation for artist split exceptions, which need match positions.

* feat(scanner): protect whitelisted names in tag value splitting

Separator matches inside word-bounded exception matches no longer split.
Matching is case-insensitive and longest-first; boundaries are rune-aware.

* feat(scanner): add Scanner.ArtistSplitExceptions config option

* feat(scanner): honor artist split exceptions for participant tags

Applies Scanner.ArtistSplitExceptions to artist, albumartist and role tag
splitting. Generic tags (genre, mood, ...) are unaffected.

* fix(scanner): apply split exceptions when per-tag Split overrides participant tags

Per-tag Tags.<name>.Split makes the generic ingestion path split the tag
before participant mapping runs, bypassing the whitelist. Attach the
exceptions to participant tag mappings (including sort variants) in clean().

* feat(scanner): split performer names and honor split exceptions

Performer pair values were never split; multiple names in one PERFORMER
value stayed a single artist. Split them with the roles separators, using
the same whitelist protection as other participant tags.

* test(scanner): lock MBID ordering for split performer values

* refactor(scanner): consolidate split-exception wiring and drop hot-path lock

ArtistSplitExceptionsRx is called per tag mapping per scanned file across
concurrent goroutines; replace the mutex+joined-key cache with an atomic
pointer compared via slices.Equal. Route all participant call sites through
WithParticipantExceptions and a shared splitParticipantValues helper.

* refactor(scanner): unexport artistSplitExceptionsRx

All external callers go through WithParticipantExceptions, so the accessor
does not need to be part of the model package API.
2026-07-02 08:29:44 -04:00
Kendall Garner
a77834ce73
fix(logger): handle file log exception for log.Log (#5700)
* fix(logger): handle file log exception for log.Log

* simplify logger, enhance test

---------

Co-authored-by: Deluan Quintão <deluan@navidrome.org>
2026-07-02 00:27:39 -04:00
Deluan Quintão
e80a7937e8
fix(ui): make self-service profile edits report their outcome (#5699)
* fix(ui): make self-service profile edits report their outcome

When a non-admin user saved their own profile (e.g. changing their
password via EnableUserEditing), the data provider followed the user
update with a call to the admin-only PUT /api/user/{id}/library
endpoint, which always failed with 403. The save error handler then
crashed reading error.body.errors on the plain-text response, so the
user got no notification at all - while the profile change had in fact
already been applied. This made password changes look like they were
silently ignored, and follow-up attempts failed with 'password does not
match' since the current password had already changed. Present since
the multi-library support introduced in v0.58.0 (#4181).

Only call the user-library association endpoint when the logged-in user
is an admin (the server manages assignments for self-edits), and make
the save error handler tolerate error bodies without field errors,
notifying a generic error instead of crashing.

* fix(ui): tolerate nullish rejection values in user save handler

Address review feedback: use optional chaining on the error itself in
the UserEdit save handler, so a nullish rejection value also results in
the generic error notification instead of a TypeError.
2026-07-01 21:31:29 -04:00
dependabot[bot]
b405252f51
chore(deps): bump golang.org/x/net in /plugins/cmd/ndpgen (#5698)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.49.0 to 0.55.0.
- [Commits](https://github.com/golang/net/compare/v0.49.0...v0.55.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.55.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-01 19:24:47 -04:00
Deluan Quintão
08cfc55d52
test(db): fix flaky applyLibraryFilter specs on shared DB (#5697)
The 'sees all libraries' specs hard-coded the user's libraries as {1, 2}
and assumed the shared test DB held exactly two libraries. applyLibraryFilter
only skips the filter when granted count == total library count, so when
another spec left an extra library behind (Ginkgo randomizes spec order),
the count was 3, the filter was applied, and the SQL assertion failed. This
surfaced on the Windows CI run but reproduces on any platform.

Grant the user exactly the library IDs that actually exist in the DB at
runtime instead of hard-coding them.
2026-07-01 13:58:33 -04:00
Deluan
2d53360f89 docs(plugins): document plugin library scope authorization logic
Signed-off-by: Deluan <deluan@navidrome.org>
2026-07-01 13:20:31 -04:00