MovieMapper/Rust/src/service/audit_logger.rs
Jarian Cottingham 323df5de2e feat: Add Rust implementation for performance-critical components
- Implement core Rust backend with FFmpeg integration
- Add TheTVDB API client with token caching
- Implement directory scanner with progress callbacks
- Create file manager with rename and move operations
- Add audit logging functionality
- Implement file mapping for TV episode renaming
- Build Node.js native addon via NAPI
- Include comprehensive unit and integration tests
- Update gitignore to exclude build artifacts and temp files

Resolves #TBD
2026-02-28 09:52:08 -06:00

121 lines
3.0 KiB
Rust

//! Audit logging functionality
use crate::utils::{FileError, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::io::Write;
use std::path::Path;
/// Represents an action that can be logged
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "action")]
pub enum AuditAction {
#[serde(rename = "directory_selected")]
DirectorySelected { path: String },
#[serde(rename = "rename_file")]
RenameFile {
old_path: String,
new_path: String,
old_name: String,
new_name: String,
},
#[serde(rename = "move_file")]
MoveFile {
original_path: String,
new_path: String,
folder: String,
},
#[serde(rename = "map_files")]
MapFiles {
directory: String,
renamed_count: u32,
error_count: u32,
},
#[serde(rename = "tag_file")]
TagFile { file_path: String, tag: String },
#[serde(rename = "untag_file")]
UntagFile { file_path: String, tag: String },
}
/// Logger for audit events
pub struct AuditLogger {
directory: String,
}
impl AuditLogger {
/// Create a new AuditLogger for a directory
pub fn new(directory: &str) -> Self {
Self {
directory: directory.to_string(),
}
}
/// Log an audit event
pub fn log_event(&self, action: AuditAction) -> Result<()> {
let timestamp = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
let entry = AuditEntry {
timestamp,
action,
details: HashMap::new(),
};
let audit_path = Path::new(&self.directory).join(".audit");
let line =
serde_json::to_string(&entry).map_err(|e| FileError::Io(std::io::Error::other(e)))?;
fs::OpenOptions::new()
.create(true)
.append(true)
.open(&audit_path)
.map_err(FileError::Io)?
.write_all(format!("{}\n", line).as_bytes())
.map_err(FileError::Io)?;
Ok(())
}
}
/// Represents a single audit log entry
#[derive(Debug, Serialize, Deserialize)]
struct AuditEntry {
timestamp: String,
action: AuditAction,
details: HashMap<String, String>,
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::remove_dir_all;
use tempfile::tempdir;
#[test]
fn test_audit_logger() {
let temp_dir = tempdir().unwrap();
let logger = AuditLogger::new(temp_dir.path().to_str().unwrap());
// Test directory selection logging
let action = AuditAction::DirectorySelected {
path: temp_dir.path().to_string_lossy().to_string(),
};
logger.log_event(action).unwrap();
// Verify .audit file was created
let audit_path = temp_dir.path().join(".audit");
assert!(audit_path.exists());
// Read and verify content
let content = fs::read_to_string(&audit_path).unwrap();
assert!(content.contains("directory_selected"));
remove_dir_all(temp_dir.path()).unwrap();
}
}