Add scheduled series auto-launch for series card, Start Here, and study guide; v2.0.36
Publish Container / docker (push) Successful in 14m20s
Publish Container / docker (push) Successful in 14m20s
One scheduled date/time in the Current Series admin panel archives the active series to Finished Books and atomically swaps in new content for the series card, Start Here steps, and Companion Study Guide. Server checks every hour via setInterval; also runs on restart so the transition fires even if the server was down at the scheduled moment. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
06f89d6436
commit
f254e3ec19
5 files changed
+252
-2
No files matched your search
@@ -53,6 +53,73 @@ function invokeWebhook(url, action) {
|
||||
.catch(err => ({ ok: false, message: err instanceof Error ? err.message : `${action} webhook failed.` }))
|
||||
}
|
||||
|
||||
// Applies a scheduled series transition if its triggersAt time has passed.
|
||||
// Called on a server interval (see server.js). Safe to call if no transition is set.
|
||||
export async function runScheduledSeriesTransition() {
|
||||
const content = state.cachedSiteContent
|
||||
if (!content) return
|
||||
|
||||
const sched = content.scheduledSeriesTransition
|
||||
if (!sched || !sched.triggersAt || !sched.newTitle?.trim()) return
|
||||
if (new Date(sched.triggersAt) > new Date()) return
|
||||
|
||||
console.log('[series-scheduler] Firing scheduled series transition to:', sched.newTitle)
|
||||
|
||||
const archivedBook = {
|
||||
id: Date.now().toString(36),
|
||||
title: content.seriesTitle ?? '',
|
||||
description: content.seriesDescription ?? '',
|
||||
imageUrl: content.seriesImageUrl ?? '',
|
||||
season: (sched.newSeason ?? 2) - 1 || 1,
|
||||
}
|
||||
|
||||
const startHerePatch = {}
|
||||
if (sched.newStartHereHeading?.trim()) startHerePatch.startHereHeading = sched.newStartHereHeading.trim()
|
||||
if (sched.newStartHereIntro?.trim()) startHerePatch.startHereIntro = sched.newStartHereIntro.trim()
|
||||
if (sched.newStartHereStep1Title?.trim()) startHerePatch.startHereStep1Title = sched.newStartHereStep1Title.trim()
|
||||
if (sched.newStartHereStep1Body?.trim()) startHerePatch.startHereStep1Body = sched.newStartHereStep1Body.trim()
|
||||
if (sched.newStartHereStep1Cta?.trim()) startHerePatch.startHereStep1Cta = sched.newStartHereStep1Cta.trim()
|
||||
if (sched.newStartHereStep2Title?.trim()) startHerePatch.startHereStep2Title = sched.newStartHereStep2Title.trim()
|
||||
if (sched.newStartHereStep2Body?.trim()) startHerePatch.startHereStep2Body = sched.newStartHereStep2Body.trim()
|
||||
if (sched.newStartHereStep2Cta?.trim()) startHerePatch.startHereStep2Cta = sched.newStartHereStep2Cta.trim()
|
||||
if (sched.newStartHereStep3Title?.trim()) startHerePatch.startHereStep3Title = sched.newStartHereStep3Title.trim()
|
||||
if (sched.newStartHereStep3Body?.trim()) startHerePatch.startHereStep3Body = sched.newStartHereStep3Body.trim()
|
||||
if (sched.newStartHereStep3Cta?.trim()) startHerePatch.startHereStep3Cta = sched.newStartHereStep3Cta.trim()
|
||||
|
||||
const studyGuidePatch = {}
|
||||
if (sched.newStudyGuideTitle?.trim()) studyGuidePatch.studyGuideTitle = sched.newStudyGuideTitle.trim()
|
||||
if (sched.newStudyGuideDescription?.trim()) studyGuidePatch.studyGuideDescription = sched.newStudyGuideDescription.trim()
|
||||
if (sched.newStudyGuideDownloadUrl?.trim()) studyGuidePatch.studyGuideDownloadUrl = sched.newStudyGuideDownloadUrl.trim()
|
||||
if (sched.newStudyGuideUrl?.trim()) studyGuidePatch.studyGuideUrl = sched.newStudyGuideUrl.trim()
|
||||
if (sched.newStudyGuideAmazonButtonLabel?.trim()) studyGuidePatch.studyGuideAmazonButtonLabel = sched.newStudyGuideAmazonButtonLabel.trim()
|
||||
if (sched.newStudyGuideAppleBooksUrl?.trim()) studyGuidePatch.studyGuideAppleBooksUrl = sched.newStudyGuideAppleBooksUrl.trim()
|
||||
if (sched.newStudyGuideAppleBooksLabel?.trim()) studyGuidePatch.studyGuideAppleBooksLabel = sched.newStudyGuideAppleBooksLabel.trim()
|
||||
|
||||
const updatedContent = sanitizeSiteContent({
|
||||
...content,
|
||||
seriesTitle: sched.newTitle.trim(),
|
||||
seriesDescription: sched.newDescription?.trim() ?? '',
|
||||
seriesImageUrl: sched.newImageUrl?.trim() ?? content.seriesImageUrl ?? '',
|
||||
seriesLabel: 'Now Playing',
|
||||
podcastFeaturedLinks: [],
|
||||
finishedBooks: [...(Array.isArray(content.finishedBooks) ? content.finishedBooks : []), archivedBook],
|
||||
...startHerePatch,
|
||||
...studyGuidePatch,
|
||||
scheduledSeriesTransition: undefined,
|
||||
})
|
||||
|
||||
const publishedAt = new Date().toISOString()
|
||||
try {
|
||||
await mkdir(DATA_DIR, { recursive: true })
|
||||
await writeFile(DATA_FILE, JSON.stringify({ siteContent: updatedContent, updatedAt: publishedAt }, null, 2), 'utf8')
|
||||
state.cachedSiteContent = updatedContent
|
||||
state.publishState.publishedAt = publishedAt
|
||||
console.log('[series-scheduler] Transition complete — archived:', archivedBook.title)
|
||||
} catch (err) {
|
||||
console.error('[series-scheduler] Failed to write transition:', err)
|
||||
}
|
||||
}
|
||||
|
||||
export function register(app) {
|
||||
app.get('/api/admin-content', async (req, res) => {
|
||||
const source = req.query?.source === 'draft' ? 'draft' : 'published'
|
||||
|
||||
Reference in new issue
Block a user