Guarantee cached audio host: global response filter for all audioUrl fields - #135
Merged
Conversation
Endpoints that dump raw metadataRaw (episode-with-chapters, fetch-adjacent-paragraphs, +5 more) still emitted the raw DigitalOcean host in audioUrl, bypassing the Cloudflare cache — per-key wrapping (#133/#134) can't catch audioUrl nested inside dumped metadata. Add rewriteAudioUrlsDeep() + a res.json middleware that deep-rewrites every audioUrl field in every JSON response to audio.pullthatupjamie.ai. Idempotent, no-ops on non-bucket/podcaster/non-string values. SSE (res.write) unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Follow-up to #133 / #134. Those wrapped explicit
audioUrl:fields, but severalagent-facing endpoints return
audioUrlinside a dumpedmetadataRawobject,which per-key wrapping can't catch. Confirmed live — these still served the raw
DigitalOcean origin (cache bypass):
GET /api/episode-with-chapters/:guid→episode.metadata.audioUrl(raw)GET /api/fetch-adjacent-paragraphs→ rawmetadata: doc.metadataRawdump sites injamieExploreRoutes/researchSessionsAn agent using any of these pulls audio from outside the Cloudflare cache.
Change
Add a global response filter so no endpoint — current or future — can hand out
the raw host:
utils/audioFormat.js:rewriteAudioUrlsDeep(value)— recursively rewritesevery
audioUrlstring field (in arrays, nestedmetadata, chapters, etc.) tothe cached host. Idempotent; no-ops on non-bucket URLs and non-strings.
server.js: a small middleware (right afterexpress.json()) that wrapsres.jsonto runrewriteAudioUrlsDeepover every JSON body.This is the backstop that guarantees the invariant "
audioUrlis always thecached host." The per-call-site wraps from #133/#134 remain as idempotent no-ops.
SSE endpoints use
res.write(notres.json) and only carry clip IDs, so they'reunaffected.
Blast radius
Only string values under a key literally named
audioUrl, and only thecascdr-…audio-bucket host →
audio.pullthatupjamie.ai. Podcaster-hosted RSS URLs,already-cached URLs, non-audio URLs (video/images), and nulls are untouched.
Tests
node --checkclean onserver.js+utils/audioFormat.js.episode-with-chaptersdump —verifies nested
metadata, chapter arrays, both raw host forms (.cdn.+direct), podcaster-host pass-through, already-cached no-op, and
nullpreservation.
filter closes them all.
🤖 Generated with Claude Code