quintodrome/server
Deluan Quintão 9b0bfc606b
fix(subsonic): always emit required created field on AlbumID3 (#5340)
* fix(subsonic): always emit required `created` field on AlbumID3

Strict OpenSubsonic clients (e.g. Navic via dev.zt64.subsonic) reject
search3/getAlbum/getAlbumList2 responses that omit the `created` field,
which the spec marks as required. Navidrome was dropping it whenever
the album's CreatedAt was zero.

Root cause was threefold:

1. buildAlbumID3/childFromAlbum conditionally emitted `created`, so a
   zero CreatedAt became a missing JSON key.
2. ToAlbum's `older()` helper treated a zero BirthTime as the minimum,
   so a single track with missing filesystem birth time could poison
   the album aggregation.
3. phase_1_folders' CopyAttributes copied `created_at` from the previous
   album row unconditionally, propagating an already-zero value forward
   on every metadata-driven album ID change. Since sql_base_repository
   drops `created_at` on UPDATE, a poisoned row could never self-heal.

Fixes:
- Always emit `created`, falling back to UpdatedAt/ImportedAt when
  CreatedAt is zero. Adds albumCreatedAt() helper used by both
  buildAlbumID3 and childFromAlbum.
- Guard `older()` against a zero second argument.
- Skip the CopyAttributes call in phase_1_folders when the previous
  album's created_at is zero, so the freshly-computed value survives.
- New migration backfills existing broken rows from media_file.birth_time
  (falling back to updated_at).

Tested against a real DB: repaired 605/6922 affected rows, no side
effects on healthy rows.

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

* refactor(subsonic): return albumCreatedAt by value to avoid heap escape

Returning *time.Time from albumCreatedAt caused Go escape analysis to
move the entire model.Album parameter to the heap, since the returned
pointer aliased a field of the value receiver. For hot endpoints like
getAlbumList2 and search3, this meant one full-struct heap allocation
per album result.

Return time.Time by value and let callers wrap it with gg.P() to take
the address locally. Only the small time.Time value escapes; the
model.Album struct stays on the stack. Also corrects the doc comment
to reflect the actual guarantee ("best-effort" rather than "non-zero"),
matching the test case that exercises the all-zero fallback.

---------

Signed-off-by: Deluan <deluan@navidrome.org>
2026-04-10 19:29:20 -04:00
..
backgrounds chore(deps): bump golangci-lint to v2.10.0 and suppress new gosec false positives 2026-02-17 09:28:42 -05:00
e2e fix(transcoding): include ffprobe in MSI and fall back gracefully when absent (#5326) 2026-04-07 20:11:38 -04:00
events fix(ui): activity Indicator switching constantly between online/offline (#5054) 2026-02-17 14:47:20 -05:00
nativeapi refactor: rename EnableCoverArtUpload to EnableArtworkUpload 2026-03-27 19:33:46 -04:00
public fix(artwork): address WebP performance regression on low-power hardware (#5286) 2026-04-04 15:17:01 -04:00
subsonic fix(subsonic): always emit required created field on AlbumID3 (#5340) 2026-04-10 19:29:20 -04:00
testdata fix(server): improve error message for encrypted TLS private keys (#4742) 2025-11-28 17:08:34 -05:00
auth.go refactor(auth): replace untyped JWT claims with typed Claims struct 2026-03-02 14:03:27 -05:00
auth_test.go refactor: run Go modernize (#5002) 2026-02-08 09:57:30 -05:00
initial_setup.go fix(transcoding): include ffprobe in MSI and fall back gracefully when absent (#5326) 2026-04-07 20:11:38 -04:00
initial_setup_test.go Upgrade Ginkgo to V2 2022-07-26 16:53:17 -04:00
middlewares.go refactor: run Go modernize (#5002) 2026-02-08 09:57:30 -05:00
middlewares_test.go feat(ui): show user's lastAccess (#3342) 2024-09-30 20:46:10 -04:00
serve_index.go fix(artwork): address WebP performance regression on low-power hardware (#5286) 2026-04-04 15:17:01 -04:00
serve_index_test.go fix(artwork): address WebP performance regression on low-power hardware (#5286) 2026-04-04 15:17:01 -04:00
server.go chore(deps): bump golangci-lint to v2.10.0 and suppress new gosec false positives 2026-02-17 09:28:42 -05:00
server_suite_test.go Rename log.LevelCritical to log.LevelFatal 2022-12-21 14:53:36 -05:00
server_test.go feat(plugins): New Plugin System with multi-language PDK support (#4833) 2026-01-14 19:22:48 -05:00