refactor: extract write_json_atomic helper, DRY 8 call sites

Move the crash-safe write-then-rename pattern into
zesdex-utils::write_json_atomic and apply it across:

- zesdex-cms: app_config_repo, conversation_repo, settings_repo
- zesdex-iam: oauth_repo, session_repo
- zesdex-entities: Conversation::save_conversation, Session::save

Excluded (non-JSON format):
- rewind_blob_repo (binary blob)
- memory_repo (markdown + frontmatter, not JSON)
- session_lock (PID string, not JSON)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
asepharyana
2026-07-18 03:18:27 +07:00
co-authored by Claude Opus 4.8
parent 9a6ab62562
commit 4cd38c9291
11 changed files with 74 additions and 125 deletions
@@ -10,6 +10,8 @@
//! Writes use a write-then-rename + fsync pattern for crash safety.
use std::path::Path;
use zesdex_utils::write_json_atomic;
use crate::domain::repository::SessionRepository;
use crate::domain::session::Session;
@@ -61,17 +63,7 @@ impl SessionRepository for FileSystemSessionRepository {
let dir = session.session_dir(base_dir);
std::fs::create_dir_all(&dir)?;
let path = dir.join("session.json");
let data = serde_json::to_string_pretty(session)?;
let tmp = dir.join("session.json.tmp");
std::fs::write(&tmp, data)?;
// fsync before rename ensures the data is on disk.
let f = std::fs::File::open(&tmp)?;
f.sync_all()?;
std::fs::rename(&tmp, path)?;
// fsync the parent directory so the rename survives a crash.
if let Some(parent) = dir.parent() {
let _ = std::fs::File::open(parent).and_then(|d| d.sync_all());
}
write_json_atomic(&path, session, None)?;
Ok(())
}