fix(podcast): address CodeRabbit review feedback

- Remove total HTTP client timeout from episode downloads (use transport
  phase timeouts instead) and delete partial files on failure
- Keep SSE progress listener subscribed while the show view is mounted
- Return podcastEpisode (with streamId) from getNewestPodcasts
- Clean up dependent rows and MediaFiles in DeleteChannel within a tx
- Limit RSS feed body size to 32 MiB
- Block additional reserved IP ranges in the SSRF guard
- Fall back to enclosure URL when an item has no <guid>
- Handle clipboard write failure in the podcast list
This commit is contained in:
Jiho Andrew Lee 2026-09-30 15:42:14 +09:00
commit 4bebfad0ed
10 changed files with 126 additions and 52 deletions

View file

@ -299,6 +299,7 @@
"downloadStarted": "Download started",
"channelAdded": "Channel added",
"urlCopied": "Feed URL copied to clipboard",
"urlCopyFailed": "Could not copy the feed URL",
"alreadyExists": "This channel is already registered.",
"channelDeleted": "Channel deleted",
"episodeDeleted": "Episode deleted"

View file

@ -70,8 +70,10 @@ const FeedUrlField = ({ record }) => {
if (!record?.url) return null
const handleCopy = (e) => {
e.stopPropagation()
navigator.clipboard.writeText(record.url)
notify('resources.podcast.notifications.urlCopied')
navigator.clipboard
.writeText(record.url)
.then(() => notify('resources.podcast.notifications.urlCopied'))
.catch(() => notify('resources.podcast.notifications.urlCopyFailed', 'warning'))
}
return (
<span style={{ display: 'flex', alignItems: 'center', gap: 4 }}>

View file

@ -124,10 +124,9 @@ const PodcastShow = (props) => {
useEffect(loadEpisodes, [record?.id])
// Subscribe to SSE progress only while episodes are downloading
const hasDownloading = episodes.some((ep) => ep.status === 'downloading')
// Stay subscribed to SSE progress while the view is mounted: a download
// started from here may not be flagged 'downloading' yet when we refresh.
useEffect(() => {
if (!hasDownloading) return
const handler = (e) => {
const { episodeId, downloadedBytes, size, duration, status } = e.detail
if (status === 'completed' || status === 'error') {
@ -145,7 +144,7 @@ const PodcastShow = (props) => {
}
window.addEventListener('podcastEpisodeProgress', handler)
return () => window.removeEventListener('podcastEpisodeProgress', handler)
}, [hasDownloading])
}, [record?.id])
if (!record) return null