- 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
121 lines
3.0 KiB
Rust
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();
|
|
}
|
|
}
|