Hold episode highlights until the episode is in the Spotify feed; auto-fill study lesson audio from the feed by season + episode; v2.19.0
Publish Container / build (push) Successful in 16m16s
Publish Container / build (push) Successful in 16m16s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
69d445013a
commit
77bce2ae0c
11 files changed
+127
-17
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import { applyFeedRelease } from '../feed-release.js'
|
||||
import { mkdir, readFile } from 'node:fs/promises'
|
||||
import { writeFile } from '../atomic-write.js'
|
||||
import { assertPublicHttpsUrl } from '../url-safety.js'
|
||||
@@ -249,7 +250,7 @@ export function register(app) {
|
||||
try {
|
||||
const parsed = await loadSiteContentFile(source === 'draft' ? DRAFT_DATA_FILE : DATA_FILE)
|
||||
if (source === 'published') {
|
||||
const safeSiteContent = filterSiteContentByReleaseDate(parsed.siteContent)
|
||||
const safeSiteContent = filterSiteContentByReleaseDate(await applyFeedRelease(parsed.siteContent))
|
||||
res.json({ ...parsed, siteContent: safeSiteContent })
|
||||
return
|
||||
}
|
||||
@@ -279,7 +280,7 @@ export function register(app) {
|
||||
app.get('/api/site-config', async (_req, res) => {
|
||||
try {
|
||||
const parsed = await loadSiteContentFile(DATA_FILE)
|
||||
const siteContent = parsed.siteContent ?? {}
|
||||
const siteContent = await applyFeedRelease(parsed.siteContent ?? {})
|
||||
res.json({
|
||||
seo: siteContent.seo ?? DEFAULT_SEO,
|
||||
legal: siteContent.legal ?? DEFAULT_LEGAL,
|
||||
|
||||
@@ -5,7 +5,7 @@ const RSS_FEED_URL = 'https://anchor.fm/s/11068d290/podcast/rss'
|
||||
let episodesCache = null
|
||||
let episodesCacheAt = 0
|
||||
let rawXmlCache = null
|
||||
const EPISODES_CACHE_TTL = 30 * 60 * 1000
|
||||
const EPISODES_CACHE_TTL = 10 * 60 * 1000
|
||||
|
||||
export async function fetchAllEpisodes() {
|
||||
const now = Date.now()
|
||||
|
||||
@@ -11,7 +11,7 @@ const WORKFLOW_FILE = path.join(DATA_DIR, 'podcast-workflow.json')
|
||||
|
||||
const STEPS = ['readScript', 'record', 'edit', 'upload']
|
||||
|
||||
async function load() {
|
||||
export async function loadPodcastWorkflow() {
|
||||
try {
|
||||
const data = JSON.parse(await readFile(WORKFLOW_FILE, 'utf8'))
|
||||
return { planned: Array.isArray(data.planned) ? data.planned : [], items: data.items ?? {}, laneOverrides: data.laneOverrides ?? {}, laneSeasons: data.laneSeasons ?? {} }
|
||||
@@ -30,10 +30,9 @@ function emptyState() {
|
||||
}
|
||||
|
||||
// A lane's season comes from the workflow page's own setting, falling back to Current Series.
|
||||
function laneSeasonOf(lane, wf) {
|
||||
export function laneSeasonOf(lane, wf, content = state.cachedSiteContent ?? {}) {
|
||||
const saved = wf.laneSeasons?.[lane]
|
||||
if (/^\d+$/.test(String(saved ?? ''))) return String(saved)
|
||||
const content = state.cachedSiteContent ?? {}
|
||||
const fallback = lane === 'A' ? content.seriesSeason : content.secondSeries?.season
|
||||
return fallback ? String(fallback) : ''
|
||||
}
|
||||
@@ -55,7 +54,7 @@ function toIsoDate(pubDate) {
|
||||
|
||||
export function register(app) {
|
||||
app.get('/api/admin/podcast-workflow', requireAdminAuth, async (_req, res) => {
|
||||
const wf = await load()
|
||||
const wf = await loadPodcastWorkflow()
|
||||
let feed = []
|
||||
let feedOk = true
|
||||
try {
|
||||
@@ -119,7 +118,7 @@ export function register(app) {
|
||||
res.status(400).json({ message: 'Season and episode number must be whole numbers.' })
|
||||
return
|
||||
}
|
||||
const wf = await load()
|
||||
const wf = await loadPodcastWorkflow()
|
||||
const key = scriptKey(season, episodeNumber)
|
||||
if (wf.planned.some(p => plannedKey(p) === key)) {
|
||||
res.status(409).json({ message: `Season ${season} episode ${episodeNumber} has already been started.` })
|
||||
@@ -131,7 +130,7 @@ export function register(app) {
|
||||
})
|
||||
|
||||
app.delete('/api/admin/podcast-workflow/planned/:key', requireAdminAuth, async (req, res) => {
|
||||
const wf = await load()
|
||||
const wf = await loadPodcastWorkflow()
|
||||
wf.planned = wf.planned.filter(p => plannedKey(p) !== req.params.key)
|
||||
delete wf.items[req.params.key]
|
||||
delete wf.laneOverrides[req.params.key]
|
||||
@@ -141,7 +140,7 @@ export function register(app) {
|
||||
|
||||
// Set the season number each lane looks for ('' goes back to the Current Series value).
|
||||
app.put('/api/admin/podcast-workflow-lane-seasons', requireAdminAuth, async (req, res) => {
|
||||
const wf = await load()
|
||||
const wf = await loadPodcastWorkflow()
|
||||
for (const lane of ['A', 'B']) {
|
||||
const v = req.body?.[lane]
|
||||
if (v === undefined) continue
|
||||
@@ -161,7 +160,7 @@ export function register(app) {
|
||||
res.status(400).json({ message: 'Lane must be A, B, other or auto.' })
|
||||
return
|
||||
}
|
||||
const wf = await load()
|
||||
const wf = await loadPodcastWorkflow()
|
||||
if (lane === 'auto') delete wf.laneOverrides[req.params.key]
|
||||
else wf.laneOverrides[req.params.key] = lane
|
||||
await save(wf)
|
||||
@@ -170,7 +169,7 @@ export function register(app) {
|
||||
|
||||
app.put('/api/admin/podcast-workflow/:key', requireAdminAuth, async (req, res) => {
|
||||
const { key } = req.params
|
||||
const wf = await load()
|
||||
const wf = await loadPodcastWorkflow()
|
||||
const prev = { ...emptyState(), ...(wf.items[key] ?? {}) }
|
||||
const next = { ...prev, steps: { ...prev.steps } }
|
||||
const body = req.body ?? {}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import express from 'express'
|
||||
import { applyFeedRelease } from '../feed-release.js'
|
||||
import path from 'node:path'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { escapeHtml, escapeXml, injectSeoIntoHtml, normalizeSitemapPath } from '../helpers.js'
|
||||
@@ -59,6 +60,7 @@ export function register(app) {
|
||||
content = parsed.siteContent
|
||||
} catch { content = {} }
|
||||
}
|
||||
content = await applyFeedRelease(content)
|
||||
|
||||
const seo = content?.seo ?? DEFAULT_SEO
|
||||
const canonical = seo.canonicalUrl || DEFAULT_SEO.canonicalUrl
|
||||
@@ -196,7 +198,7 @@ export function register(app) {
|
||||
|
||||
// Scripture index — every passage covered across released study lessons and podcast episodes
|
||||
app.get('/api/public/scripture-index', async (_req, res) => {
|
||||
const content = state.cachedSiteContent
|
||||
const content = await applyFeedRelease(state.cachedSiteContent)
|
||||
const allStudies = Array.isArray(content?.studies) ? content.studies : []
|
||||
const allEpisodes = [
|
||||
...(Array.isArray(content?.podcastFeaturedLinks) ? content.podcastFeaturedLinks : []),
|
||||
|
||||
Reference in new issue
Block a user