Publish Container / build (push) Successful in 16m20s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
100 lines
3.3 KiB
JavaScript
100 lines
3.3 KiB
JavaScript
import { parseRssItems } from '../rss-helpers.js'
|
|
import { state } from '../state.js'
|
|
import { requireAdminAuth } from '../auth.js'
|
|
import { queueNuggetsTagsWrite } from '../data.js'
|
|
|
|
let cache = null // { url, episodes }
|
|
let cacheAt = 0
|
|
const CACHE_TTL = 30 * 60 * 1000
|
|
|
|
function feedUrl() {
|
|
return state.cachedSiteContent?.nuggets?.rssFeedUrl?.trim() ?? ''
|
|
}
|
|
|
|
export async function fetchNuggetsEpisodes() {
|
|
const url = feedUrl()
|
|
if (!url) { cache = null; return [] }
|
|
|
|
const now = Date.now()
|
|
if (cache && cache.url === url && (now - cacheAt) < CACHE_TTL) {
|
|
return cache.episodes
|
|
}
|
|
|
|
const controller = new AbortController()
|
|
const timeout = setTimeout(() => controller.abort(), 8000)
|
|
try {
|
|
const response = await fetch(url, { signal: controller.signal })
|
|
clearTimeout(timeout)
|
|
if (!response.ok) throw new Error(`RSS fetch failed: ${response.status}`)
|
|
const xml = await response.text()
|
|
const episodes = parseRssItems(xml)
|
|
cache = { url, episodes }
|
|
cacheAt = now
|
|
return episodes
|
|
} catch (err) {
|
|
clearTimeout(timeout)
|
|
// Serve stale cache for this same URL rather than a hard failure, if we have one.
|
|
if (cache && cache.url === url) return cache.episodes
|
|
throw err
|
|
}
|
|
}
|
|
|
|
function withTags(episodes) {
|
|
return episodes.map(ep => ({ ...ep, tags: state.nuggetsEpisodeTags[ep.audioUrl] ?? [] }))
|
|
}
|
|
|
|
function cleanTags(input) {
|
|
const list = Array.isArray(input) ? input : []
|
|
const seen = new Set()
|
|
const out = []
|
|
for (const t of list) {
|
|
const tag = String(t ?? '').trim().replace(/\s+/g, ' ').slice(0, 40)
|
|
const key = tag.toLowerCase()
|
|
if (tag && !seen.has(key)) { seen.add(key); out.push(tag) }
|
|
}
|
|
return out.slice(0, 12)
|
|
}
|
|
|
|
export function register(app) {
|
|
app.get('/api/admin/nuggets/episode-tags', requireAdminAuth, async (_req, res) => {
|
|
try {
|
|
const episodes = await fetchNuggetsEpisodes()
|
|
res.json({ episodes: withTags(episodes).map(({ title, pubDate, audioUrl, episode, tags }) => ({ title, pubDate, audioUrl, episode, tags })) })
|
|
} catch (err) {
|
|
res.status(502).json({ message: 'Could not load episodes from the feed.' })
|
|
}
|
|
})
|
|
|
|
app.put('/api/admin/nuggets/episode-tags', requireAdminAuth, (req, res) => {
|
|
const audioUrl = String(req.body?.audioUrl ?? '')
|
|
if (!audioUrl) { res.status(400).json({ message: 'audioUrl is required.' }); return }
|
|
const tags = cleanTags(req.body?.tags)
|
|
if (tags.length) state.nuggetsEpisodeTags[audioUrl] = tags
|
|
else delete state.nuggetsEpisodeTags[audioUrl]
|
|
queueNuggetsTagsWrite()
|
|
res.json({ ok: true, tags })
|
|
})
|
|
|
|
app.get('/api/nuggets/episodes', async (_req, res) => {
|
|
const configured = Boolean(feedUrl())
|
|
try {
|
|
const episodes = await fetchNuggetsEpisodes()
|
|
res.json({ episodes: withTags(episodes.slice(0, 6)), configured })
|
|
} catch (err) {
|
|
console.error('[nuggets/episodes] RSS fetch error:', err.message)
|
|
res.json({ episodes: [], configured })
|
|
}
|
|
})
|
|
|
|
app.get('/api/nuggets/episodes/all', async (_req, res) => {
|
|
const configured = Boolean(feedUrl())
|
|
try {
|
|
const episodes = await fetchNuggetsEpisodes()
|
|
res.json({ episodes: withTags(episodes), configured })
|
|
} catch (err) {
|
|
console.error('[nuggets/episodes/all] RSS fetch error:', err.message)
|
|
res.json({ episodes: [], configured })
|
|
}
|
|
})
|
|
}
|