Files
zesdex/crates/zesdex-iam/src/infrastructure/persistence/session_repo.rs
T

81 lines
2.7 KiB
Rust
Raw Normal View History

#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_precision_loss,
clippy::cast_possible_wrap
)]
//! Filesystem-backed `SessionRepository` implementation.
//!
//! Each session is stored as `<base_dir>/sessions/<id>/session.json`.
//! 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;
/// Concrete filesystem session repository.
#[derive(Debug, Clone, Default)]
pub struct FileSystemSessionRepository;
impl FileSystemSessionRepository {
/// Create a new filesystem session repository.
pub fn new() -> Self {
FileSystemSessionRepository
}
}
impl SessionRepository for FileSystemSessionRepository {
fn list_sessions(&self, base_dir: &Path) -> anyhow::Result<Vec<Session>> {
let sessions_dir = base_dir.join("sessions");
let Ok(entries) = std::fs::read_dir(&sessions_dir) else {
return Ok(Vec::new());
};
let mut sessions = Vec::new();
for entry in entries.flatten() {
if !entry.path().is_dir() {
continue;
}
let id = entry.file_name().to_string_lossy().to_string();
if let Ok(session) = self.load_session(base_dir, &id) {
sessions.push(session);
}
}
Ok(sessions)
}
fn load_session(&self, base_dir: &Path, id: &str) -> anyhow::Result<Session> {
// Directory-traversal prevention.
if id.contains('/') || id.contains('\\') || id.contains("..") {
anyhow::bail!("invalid session id '{id}': must not contain path separators");
}
let path = base_dir.join("sessions").join(id).join("session.json");
if !path.exists() {
anyhow::bail!("session not found: {id}");
}
let data = std::fs::read_to_string(&path)?;
let session: Session = serde_json::from_str(&data)?;
Ok(session)
}
fn save_session(&self, base_dir: &Path, session: &Session) -> anyhow::Result<()> {
let dir = session.session_dir(base_dir);
std::fs::create_dir_all(&dir)?;
let path = dir.join("session.json");
write_json_atomic(&path, session, None)?;
Ok(())
}
fn delete_session(&self, base_dir: &Path, id: &str) -> anyhow::Result<()> {
if id.contains('/') || id.contains('\\') || id.contains("..") {
anyhow::bail!("invalid session id '{id}': must not contain path separators");
}
let dir = base_dir.join("sessions").join(id);
if dir.exists() {
std::fs::remove_dir_all(&dir)?;
}
Ok(())
}
}