From 35d8ba32e568e5003c674544f0dbe83d0d7ecc9c Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Wed, 21 Oct 2026 20:23:42 -0400 Subject: [PATCH] fix(media): metadata editor save no longer wipes audio_metadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PUT /api/media-items/:id (the metadata editor) writes the FULL row via UpdateMediaItem, whose query sets audio_metadata unconditionally — and audio_metadata has no editor field, so the params literal left it nil and every save NULLed the payload. A plain metadata edit (genre tweak, title rename) dropped any audiobook back to "1 second long" with no track manifest: the same incident class as the scanner wipe, sitting on the most-used admin path in the app. Carry existing.AudioMetadata through the write. Write-after-success: only the audio chain (probe-backed extraction / UpdateMediaItemAudioBookPayload) ever replaces that column. --- internal/handlers/media.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/handlers/media.go b/internal/handlers/media.go index 6a1631d7..ec9365ce 100644 --- a/internal/handlers/media.go +++ b/internal/handlers/media.go @@ -1610,6 +1610,14 @@ func (mh *MediaHandler) UpdateMediaItem(c *echo.Context) error { Summary: pgtype.Text{String: req.Summary, Valid: req.Summary != ""}, } + // audio_metadata is scan-owned (track manifest, chapters, duration) + // and has no editor field: the full-row update writes it + // unconditionally, so an unguarded save NULLed every audiobook's + // payload — books dropped back to "1 second long" on a plain + // metadata edit (2026-10-21 incident class). Write-after-success: + // carry the existing bytes; only the audio chain replaces them. + params.AudioMetadata = existing.AudioMetadata + // Fields the user actually changed become overrides so future scans keep // the custom values. An explicit cover upload/removal always overrides. overrides := utils.DetectMetadataOverrides(params, existing)