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
@@ -11,6 +11,8 @@
//! 0o600 on Unix.
use std::path::Path;
use zesdex_utils::write_json_atomic;
use crate::domain::oauth::OAuthToken;
use crate::domain::repository::OAuthRepository;
@@ -30,20 +32,7 @@ impl OAuthRepository for FileSystemOAuthRepository {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let data = serde_json::to_string_pretty(token)?;
let tmp = path.with_extension("tmp");
std::fs::write(&tmp, data)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&tmp, std::fs::Permissions::from_mode(0o600))?;
}
let f = std::fs::File::open(&tmp)?;
f.sync_all()?;
std::fs::rename(&tmp, path)?;
if let Some(parent) = path.parent() {
let _ = std::fs::File::open(parent).and_then(|d| d.sync_all());
}
write_json_atomic(path, token, Some(0o600))?;
Ok(())
}
@@ -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(())
}