fangorn/huorn-minecraft
public
ref:main
use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::sync::Mutex;
use std::time::{Duration, Instant};
pub struct AuditLogger {
file: Mutex<File>,
enabled: bool,
}
impl AuditLogger {
pub fn new(path: &str) -> Result<Self, String> {
// Create parent directories if needed
if let Some(parent) = std::path::Path::new(path).parent() {
let _ = std::fs::create_dir_all(parent);
}
let file = OpenOptions::new()
.create(true)
.append(true)
.open(path)
.map_err(|e| format!("Failed to open audit log: {}", e))?;
Ok(Self {
file: Mutex::new(file),
enabled: true,
})
}
pub fn log_event(&self, event_type: &str, payload_json: &str) {
if !self.enabled {
return;
}
let ts = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
// Merge timestamp and event type into the payload JSON
let line = if payload_json.starts_with('{') && payload_json.len() > 2 {
format!(
r#"{{"ts":"{}","event":"{}",{}}}"#,
ts,
event_type,
&payload_json[1..payload_json.len() - 1]
)
} else {
format!(r#"{{"ts":"{}","event":"{}"}}"#, ts, event_type)
};
if let Ok(mut f) = self.file.lock() {
let _ = writeln!(f, "{}", line);
let _ = f.flush();
}
}
}
pub struct IdleTracker {
timeout: Duration,
sessions: HashMap<u64, Instant>,
}
impl IdleTracker {
pub fn new(timeout: Duration) -> Self {
Self {
timeout,
sessions: HashMap::new(),
}
}
pub fn register(&mut self, session_id: u64) {
self.sessions.insert(session_id, Instant::now());
}
pub fn touch(&mut self, session_id: u64) {
if let Some(ts) = self.sessions.get_mut(&session_id) {
*ts = Instant::now();
}
}
pub fn unregister(&mut self, session_id: u64) {
self.sessions.remove(&session_id);
}
pub fn check_expired(&self) -> Vec<u64> {
let now = Instant::now();
self.sessions
.iter()
.filter(|(_, ts)| now.duration_since(**ts) > self.timeout)
.map(|(id, _)| *id)
.collect()
}
}