Fix admin publish data loss for unreleased lessons and harden data persistence

This commit is contained in:
nmemmert
2026-05-21 12:39:28 -04:00
parent da5f77c3c6
commit bd047b029c
4 changed files with 83 additions and 12 deletions
+63 -5
View File
@@ -46,7 +46,11 @@ import {
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const DATA_DIR = path.join(__dirname, 'data')
const DEFAULT_DATA_DIR = path.join(__dirname, 'data')
const configuredDataDir = typeof process.env.SITEFORGE_DATA_DIR === 'string' ? process.env.SITEFORGE_DATA_DIR.trim() : ''
const DATA_DIR = configuredDataDir
? (path.isAbsolute(configuredDataDir) ? configuredDataDir : path.resolve(__dirname, configuredDataDir))
: DEFAULT_DATA_DIR
const DATA_FILE = path.join(DATA_DIR, 'admin-content.json')
const DRAFT_DATA_FILE = path.join(DATA_DIR, 'admin-content-draft.json')
const HIT_STATS_FILE = path.join(DATA_DIR, 'hit-stats.json')
@@ -386,6 +390,51 @@ async function loadSiteContentFile(filePath) {
}
}
async function checkDataDirWritable() {
try {
await mkdir(DATA_DIR, { recursive: true })
const marker = path.join(DATA_DIR, `.write-test-${Date.now()}-${Math.random().toString(36).slice(2)}.tmp`)
await writeFile(marker, 'ok', 'utf8')
await unlink(marker)
return { ok: true, error: null }
} catch (err) {
return { ok: false, error: err instanceof Error ? err.message : 'Unknown write test error' }
}
}
async function getStorageStatus() {
const writable = await checkDataDirWritable()
const files = {}
for (const [key, filePath] of Object.entries({
adminContent: DATA_FILE,
adminContentDraft: DRAFT_DATA_FILE,
studyUsers: STUDY_USERS_FILE,
})) {
try {
const fileStat = await stat(filePath)
files[key] = {
path: filePath,
exists: true,
sizeBytes: fileStat.size,
mtime: fileStat.mtime.toISOString(),
}
} catch {
files[key] = {
path: filePath,
exists: false,
sizeBytes: 0,
mtime: null,
}
}
}
return {
dataDir: DATA_DIR,
writable,
files,
}
}
async function refreshContentCaches() {
try {
const published = await loadSiteContentFile(DATA_FILE)
@@ -1520,6 +1569,11 @@ app.get('/api/admin-content-state', requireAdminAuth, (_req, res) => {
})
})
app.get('/api/admin-storage-status', requireAdminAuth, async (_req, res) => {
const status = await getStorageStatus()
res.json(status)
})
app.get('/api/admin-podcast-checklist', requireAdminAuth, (_req, res) => {
res.json({ checklist: podcastChecklist })
})
@@ -1583,8 +1637,10 @@ app.put('/api/admin-content-draft', requireAdminAuth, async (req, res) => {
publishState.draftUpdatedAt = updatedAt
res.json({ ok: true, updatedAt })
} catch {
res.status(500).json({ message: 'Failed to persist admin draft content.' })
} catch (err) {
console.error('[admin-content-draft] persist error:', err)
const reason = err instanceof Error ? err.message : 'Unknown write error'
res.status(500).json({ message: `Failed to persist admin draft content to ${DATA_DIR}: ${reason}` })
}
})
@@ -1619,8 +1675,10 @@ app.post('/api/admin-content/publish', requireAdminAuth, async (_req, res) => {
await createBackupSnapshot('post-publish')
res.json({ ok: true, publishedAt })
} catch {
res.status(500).json({ message: 'Failed to publish draft content.' })
} catch (err) {
console.error('[admin-content-publish] persist error:', err)
const reason = err instanceof Error ? err.message : 'Unknown write error'
res.status(500).json({ message: `Failed to publish draft content to ${DATA_DIR}: ${reason}` })
}
})