mirror of
https://github.com/navidrome/navidrome.git
synced 2026-11-06 18:28:39 +01:00
* feat(playlists): add average_rating column to playlist table
* feat(playlists): store and read per-user starred/rating annotations
* feat(playlists): clean up annotations when a playlist is deleted
* feat(subsonic): route star/unstar of a playlist to the playlist repository
* feat(subsonic): route setRating of a playlist to the playlist repository
* test(subsonic): guard that playlist responses never expose annotations
* fix(playlists): clean stale mis-typed annotations on upgrade; cover GetAll read-back
* fix(playlists): scope annotation join by item_type and harden delete
Address code-review findings on the playlist-annotations branch:
- withAnnotation: add an item_type predicate to the LEFT JOIN so a
mis-typed annotation row sharing an id can no longer leak into (or
duplicate) another entity's read. Correct for every caller since each
repo writes annotations with item_type = tableName. Regression test added.
- migration: reclassify legacy media_file-typed rows for playlist ids to
item_type='playlist' (instead of deleting them), preserving users' prior
playlist star/rating; run before the average_rating backfill so those
ratings are included.
- playlist Delete: replace the per-request full-table cleanAnnotations()
anti-join with a targeted, permission-safe (rows-affected gated),
best-effort delete so a cleanup failure no longer misreports an
already-committed delete as an error.
- MockPlaylistRepo: implement GetAll/IncPlayCount/ReassignAnnotation to
remove the dead All field and the nil-interface panic traps.
- test: use slices.IndexFunc instead of a hand-rolled find loop.
* feat(playlists): streamline playlist deletion by relying on annotation sweep
* docs(playlists): trim comments in annotation migration and test
Condense the verbose comments added in this branch per the project's
comment-minimalism guideline, keeping only the non-obvious rationale.
The migration's reclassify block is shortened while preserving the safety
invariant (playlist and media_file ids never collide, so the item_type
rewrite touches only mis-typed rows and cannot violate the unique key) and
the ordering note. The redundant 'Populate average_rating' comment is
dropped since the UPDATE is self-evident. The repository test's leakage
comment is condensed to two lines. No code behavior changes.
* refactor(subsonic): resolve setStar targets via GetEntityByID
Replace setStar's Album/Artist/Playlist Exists probe chain with a single
model.GetEntityByID lookup and a type switch, mirroring setRating. This
removes three per-id existence queries and keeps the two annotation paths
consistent.
An id that resolves to no known entity is logged and skipped rather than
filed as a spurious media_file annotation, and a lookup failure on one id no
longer aborts the whole batch. Also drop a duplicate empty-ids guard.
* refactor(playlists): drop no-op reclassify/backfill from migration
The average_rating migration carried two data-fix UPDATEs that are no-ops on
any real database:
- The media_file->playlist reclassification only matches rows no released
build ever created: playlists were never annotatable, so star/setRating of
a playlist id was never written as item_type='playlist'. Any stray
media_file-typed row for a playlist id is already removed by the media_file
annotation GC sweep (item_id not in media_file).
- The average_rating backfill runs before any item_type='playlist' row can
exist, so it can only ever write the default 0. Going forward SetRating
keeps average_rating current via updateAvgRating.
Reduce the migration to the column add/drop.
* refactor(persistence): bind annotation join params, derive idField from tableName
Address PR review: use Squirrel parameter binding for item_type/user_id in
the shared withAnnotation join instead of string concatenation, and pass
r.tableName+".id" from selectPlaylist so the join field stays consistent
with the surrounding r.tableName usage.
* fix(subsonic): surface datastore errors in setStar instead of skipping
Address PR review: setStar swallowed every GetEntityByID error and continued,
so a real datastore failure would still commit the transaction and emit a
refresh event as if the star succeeded. Skip only on model.ErrNotFound (an
unknown id); return any other error so the request fails and rolls back.
* test(subsonic): assert absent JSON keys instead of substring matches
Address PR review: substring checks are brittle ("starred" matches "starredAt",
"rating" matches "userRating"). Unmarshal the response and assert the
annotation keys are absent.
* fix(subsonic): skip refresh broadcast when a star request changes nothing
Address PR review (Codex): once setStar began skipping unknown ids, a request
containing only unresolvable ids left the RefreshResource empty, which
SendMessage serializes as a {*:*} wildcard that forces every client to
refresh. Only broadcast when at least one id was actually starred.
* fix(db): rebase playlist average_rating migration timestamp past master
The 20260708011823 migration predated the newest migration merged to
master (20260712211040_add_primary_key...), which Goose would silently
skip on already-upgraded databases. Rename it to a current timestamp so
it applies in order.
|
||
|---|---|---|
| .. | ||
| 20200130083147_create_schema.go | ||
| 20200131183653_standardize_item_type.go | ||
| 20200208222418_add_defaults_to_annotations.go | ||
| 20200220143731_change_duration_to_float.go | ||
| 20200310171621_enable_search_by_albumartist.go | ||
| 20200310181627_add_transcoding_and_player_tables.go | ||
| 20200319211049_merge_search_into_main_tables.go | ||
| 20200325185135_add_album_artist_id.go | ||
| 20200326090707_fix_album_artists_importing.go | ||
| 20200327193744_add_year_range_to_album.go | ||
| 20200404214704_add_indexes.go | ||
| 20200409002249_enable_search_by_tracks_artists.go | ||
| 20200411164603_add_created_and_updated_fields_to_playlists.go | ||
| 20200418110522_reindex_to_fix_album_years.go | ||
| 20200419222708_reindex_to_change_full_text_search.go | ||
| 20200423204116_add_sort_fields.go | ||
| 20200508093059_add_artist_song_count.go | ||
| 20200512104202_add_disc_subtitle.go | ||
| 20200516140647_add_playlist_tracks_table.go | ||
| 20200608153717_referential_integrity.go | ||
| 20200706231659_add_default_transcodings.go | ||
| 20200710211442_add_playlist_path.go | ||
| 20200731095603_create_play_queues_table.go | ||
| 20200801101355_create_bookmark_table.go | ||
| 20200819111809_drop_email_unique_constraint.go | ||
| 20201003111749_add_starred_at_index.go | ||
| 20201010162350_add_album_size.go | ||
| 20201012210022_add_artist_playlist_size.go | ||
| 20201021085410_add_mbids.go | ||
| 20201021093209_add_media_file_indexes.go | ||
| 20201021135455_add_media_file_artist_index.go | ||
| 20201030162009_add_artist_info_table.go | ||
| 20201110205344_add_comments_and_lyrics.go | ||
| 20201128100726_add_real-path_option.go | ||
| 20201213124814_add_all_artist_ids_to_album.go | ||
| 20210322132848_add_timestamp_indexes.go | ||
| 20210418232815_fix_album_comments.go | ||
| 20210430212322_add_bpm_metadata.go | ||
| 20210530121921_create_shares_table.go | ||
| 20210601231734_update_share_fieldnames.go | ||
| 20210616150710_encrypt_all_passwords.go | ||
| 20210619231716_drop_player_name_unique_constraint.go | ||
| 20210623155401_add_user_prefs_player_scrobbler_enabled.go | ||
| 20210625223901_add_referential_integrity_to_user_props.go | ||
| 20210626213026_add_scrobble_buffer.go | ||
| 20210715151153_add_genre_tables.go | ||
| 20210821212604_add_mediafile_channels.go | ||
| 20211008205505_add_smart_playlist.go | ||
| 20211023184825_add_order_title_to_media_file.go | ||
| 20211026191915_unescape_lyrics_and_comments.go | ||
| 20211029213200_add_userid_to_playlist.go | ||
| 20211102215414_add_alphabetical_by_artist_index.go | ||
| 20211105162746_remove_invalid_artist_ids.go | ||
| 20220724231849_add_musicbrainz_release_track_id.go | ||
| 20221219112733_add_album_image_paths.go | ||
| 20221219140528_remove_cover_art_id.go | ||
| 20230112111457_add_album_paths.go | ||
| 20230114121537_touch_playlists.go | ||
| 20230115103212_create_internet_radio.go | ||
| 20230117155559_add_replaygain_metadata.go | ||
| 20230117180400_add_album_info.go | ||
| 20230119152657_recreate_share_table.go | ||
| 20230202143713_change_path_list_separator.go | ||
| 20230209181414_change_image_files_list_separator.go | ||
| 20230310222612_add_download_to_share.go | ||
| 20230404104309_empty_sql_migration.sql | ||
| 20230515184510_add_release_date.go | ||
| 20230616214944_rename_musicbrainz_recording_id.go | ||
| 20231208182311_add_discs_to_album.go | ||
| 20231209211223_alter_lyric_column.go | ||
| 20240122223340_add_default_values_to_null_columns.go.go | ||
| 20240426202913_add_id_to_scrobble_buffer.go | ||
| 20240511210036_add_sample_rate.go | ||
| 20240511220020_add_library_table.go | ||
| 20240629152843_remove_annotation_id.go | ||
| 20240802044339_player_use_user_id_over_username.go | ||
| 20241020003138_add_sort_tags_index.sql | ||
| 20241024125533_fix_sort_tags_collation_and_index.sql | ||
| 20241026183640_support_new_scanner.go | ||
| 20250522013904_share_user_id_on_delete.sql | ||
| 20250611010101_playqueue_current_to_index.go | ||
| 20250701010101_add_folder_hash.go | ||
| 20250701010102_add_annotation_user_foreign_key.sql | ||
| 20250701010103_add_library_stats.go | ||
| 20250701010104_make_replaygain_fields_nullable.go | ||
| 20250701010105_remove_dangling_items.sql | ||
| 20250701010106_add_participant_stats_to_all_artists.sql | ||
| 20250701010107_add_mbid_indexes.sql | ||
| 20250701010108_add_multi_library_support.go | ||
| 20250823142158_make_playqueue_position_int.sql | ||
| 20251109010105_add_annotation_rating_date.sql | ||
| 20251206013022_create_scrobbles_table.sql | ||
| 20260104203627_playlist_case_insensitive_name.sql | ||
| 20260106000620_create_plugin_table.sql | ||
| 20260117201522_add_avg_rating_column.sql | ||
| 20260220173400_add_fts5_search.go | ||
| 20260228020813_add_plugin_allow_write_access.sql | ||
| 20260228172956_add_playlist_image_file.go | ||
| 20260302021413_rename_playlist_image_fields.go | ||
| 20260307175815_add_codec_and_update_transcodings.go | ||
| 20260309120007_fix_probe_data_null.go | ||
| 20260309203355_ensure_default_transcodings.go | ||
| 20260310113858_fix_aac_transcode_command.go | ||
| 20260315233131_add_artist_uploaded_image.go | ||
| 20260316000000_normalize_timestamps.sql | ||
| 20260318182414_add_radio_uploaded_image.go | ||
| 20260405124200_fix_schema_inconsistencies.sql | ||
| 20260410201914_fix_zero_album_created_at.sql | ||
| 20260513173954_move_ss_before_input.go | ||
| 20260520211813_add_media_file_artists_composite_index.sql | ||
| 20260612171826_add_media_file_missing_library_id_index.sql | ||
| 20260612222838_make_bpm_bitdepth_nullable.sql | ||
| 20260618120509_add_metadata_to_default_transcodings.go | ||
| 20260629123100_recently_added_plain_indexes.sql | ||
| 20260702152457_backfill_artist_search_normalized.go | ||
| 20260703013908_optimize_media_file_sort_indexes.sql | ||
| 20260712211040_add_primary_key_and_update_index_for_scrobbles.sql | ||
| 20260714120000_add_playlist_average_rating.sql | ||
| migration.go | ||