Fix admin publish data loss for unreleased lessons and harden data persistence
This commit is contained in:
@@ -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}` })
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user