Fix scheduled series launch: read schedule from draft, apply to draft+published, check every minute, local-time picker; v2.8.1
Publish Container / build (push) Successful in 16m22s
Publish Container / build (push) Successful in 16m22s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
134a2c403e
commit
0ff0b32b97
4 files changed
+68
-43
No files matched your search
@@ -53,18 +53,8 @@ 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)
|
||||
|
||||
// Builds the content that results from applying a scheduled series transition.
|
||||
function applySeriesTransition(content, sched) {
|
||||
const archivedBook = {
|
||||
id: Date.now().toString(36),
|
||||
title: content.seriesTitle ?? '',
|
||||
@@ -73,40 +63,56 @@ export async function runScheduledSeriesTransition() {
|
||||
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 patch = {}
|
||||
const copy = (from, to) => {
|
||||
if (typeof sched[from] === 'string' && sched[from].trim()) patch[to] = sched[from].trim()
|
||||
}
|
||||
copy('newStartHereHeading', 'startHereHeading')
|
||||
copy('newStartHereIntro', 'startHereIntro')
|
||||
for (const n of [1, 2, 3]) {
|
||||
copy(`newStartHereStep${n}Title`, `startHereStep${n}Title`)
|
||||
copy(`newStartHereStep${n}Body`, `startHereStep${n}Body`)
|
||||
copy(`newStartHereStep${n}Cta`, `startHereStep${n}Cta`)
|
||||
}
|
||||
copy('newStudyGuideTitle', 'studyGuideTitle')
|
||||
copy('newStudyGuideDescription', 'studyGuideDescription')
|
||||
copy('newStudyGuideDownloadUrl', 'studyGuideDownloadUrl')
|
||||
copy('newStudyGuideUrl', 'studyGuideUrl')
|
||||
copy('newStudyGuideAmazonButtonLabel', 'studyGuideAmazonButtonLabel')
|
||||
copy('newStudyGuideAppleBooksUrl', 'studyGuideAppleBooksUrl')
|
||||
copy('newStudyGuideAppleBooksLabel', 'studyGuideAppleBooksLabel')
|
||||
|
||||
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({
|
||||
const next = sanitizeSiteContent({
|
||||
...content,
|
||||
seriesTitle: sched.newTitle.trim(),
|
||||
seriesDescription: sched.newDescription?.trim() ?? '',
|
||||
seriesImageUrl: sched.newImageUrl?.trim() ?? content.seriesImageUrl ?? '',
|
||||
seriesImageUrl: sched.newImageUrl?.trim() || content.seriesImageUrl || '',
|
||||
seriesLabel: 'Now Playing',
|
||||
podcastFeaturedLinks: [],
|
||||
finishedBooks: [...(Array.isArray(content.finishedBooks) ? content.finishedBooks : []), archivedBook],
|
||||
...startHerePatch,
|
||||
...studyGuidePatch,
|
||||
scheduledSeriesTransition: undefined,
|
||||
...patch,
|
||||
})
|
||||
delete next.scheduledSeriesTransition
|
||||
return { next, archivedBook }
|
||||
}
|
||||
|
||||
// Applies a scheduled series transition if its triggersAt time has passed.
|
||||
// The schedule is saved with the draft (Save Schedule), so look in the draft as
|
||||
// well as published content. Applies to both so a later publish doesn't undo it.
|
||||
// Called on a server interval (see server.js). Safe to call if no transition is set.
|
||||
export async function runScheduledSeriesTransition() {
|
||||
const published = state.cachedSiteContent
|
||||
const draft = state.cachedDraftSiteContent
|
||||
if (!published) return
|
||||
|
||||
const sched = draft?.scheduledSeriesTransition ?? published.scheduledSeriesTransition
|
||||
if (!sched || !sched.triggersAt || !sched.newTitle?.trim()) return
|
||||
const triggerTime = new Date(sched.triggersAt).getTime()
|
||||
if (Number.isNaN(triggerTime) || triggerTime > Date.now()) return
|
||||
|
||||
console.log('[series-scheduler] Firing scheduled series transition to:', sched.newTitle)
|
||||
|
||||
const { next: updatedContent, archivedBook } = applySeriesTransition(published, sched)
|
||||
|
||||
const publishedAt = new Date().toISOString()
|
||||
try {
|
||||
@@ -117,6 +123,18 @@ export async function runScheduledSeriesTransition() {
|
||||
console.log('[series-scheduler] Transition complete — archived:', archivedBook.title)
|
||||
} catch (err) {
|
||||
console.error('[series-scheduler] Failed to write transition:', err)
|
||||
return
|
||||
}
|
||||
|
||||
if (draft) {
|
||||
try {
|
||||
const { next: updatedDraft } = applySeriesTransition(draft, sched)
|
||||
await writeFile(DRAFT_DATA_FILE, JSON.stringify({ siteContent: updatedDraft, updatedAt: publishedAt }, null, 2), 'utf8')
|
||||
state.cachedDraftSiteContent = updatedDraft
|
||||
state.publishState.draftUpdatedAt = publishedAt
|
||||
} catch (err) {
|
||||
console.error('[series-scheduler] Failed to update draft after transition:', err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user