diff --git a/server/db.js b/server/db.js index 1217924..b9a0cdd 100644 --- a/server/db.js +++ b/server/db.js @@ -1,5 +1,5 @@ import Database from 'better-sqlite3' -import { mkdirSync } from 'fs' +import { mkdirSync, readdirSync, unlinkSync } from 'fs' import path from 'path' import { fileURLToPath } from 'url' @@ -10,6 +10,42 @@ mkdirSync(dataDir, { recursive: true }) const db = new Database(path.join(dataDir, 'stories.db')) db.pragma('journal_mode = WAL') db.pragma('foreign_keys = ON') +db.pragma('busy_timeout = 5000') // wait up to 5 s instead of failing immediately + +// Merge the WAL back into the main DB file once an hour. +// Without this, all writes live in the WAL indefinitely, and losing +// that file means losing all data written since the last checkpoint. +setInterval(() => { + try { db.pragma('wal_checkpoint(PASSIVE)') } + catch (err) { console.warn('[db] WAL checkpoint failed:', err.message) } +}, 60 * 60 * 1000) // every hour + +// Daily backup — keeps the last 7 daily snapshots in data/backups/ +const backupDir = path.join(dataDir, 'backups') +mkdirSync(backupDir, { recursive: true }) + +async function runBackup() { + try { + const stamp = new Date().toISOString().slice(0, 10) // YYYY-MM-DD + const dest = path.join(backupDir, `stories-${stamp}.db`) + await db.backup(dest) + console.log(`[db] Backup written → ${dest}`) + + // Prune backups older than 7 days + const files = readdirSync(backupDir) + .filter(f => f.startsWith('stories-') && f.endsWith('.db')) + .sort() + for (const old of files.slice(0, -7)) { + try { unlinkSync(path.join(backupDir, old)) } catch {} + } + } catch (err) { + console.warn('[db] Backup failed:', err.message) + } +} + +// Run once on startup, then every 24 h +runBackup() +setInterval(runBackup, 24 * 60 * 60 * 1000) db.exec(` CREATE TABLE IF NOT EXISTS users (