Skip to main content
← dvs documentation Rust API reference

dvs/
audit.rs

1use std::collections::HashSet;
2use std::io::BufRead;
3use std::path::PathBuf;
4
5use crate::Hashes;
6use crate::config::{Compression, Config};
7use anyhow::Result;
8use jiff::Timestamp;
9use serde::{Deserialize, Serialize};
10use uuid::Uuid;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13#[serde(rename_all = "lowercase")]
14pub enum Action {
15    Add {
16        file: AuditFile,
17        compression: Compression,
18    },
19    Init {
20        settings: Config,
21        project_path: PathBuf,
22    },
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct AuditFile {
27    pub path: PathBuf,
28    pub hashes: Hashes,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct AuditEntry {
33    pub operation_id: String,
34    pub timestamp: i64,
35    pub user: String,
36    pub action: Action,
37}
38
39impl AuditEntry {
40    pub fn new_add(operation_id: Uuid, file: AuditFile, compression: Compression) -> Self {
41        let timestamp = Timestamp::now().as_second();
42        let user = whoami::username().unwrap_or_else(|_| "unknown".to_string());
43
44        Self {
45            operation_id: operation_id.to_string(),
46            timestamp,
47            user,
48            action: Action::Add { file, compression },
49        }
50    }
51
52    pub fn new_init(operation_id: Uuid, config: Config, project_path: PathBuf) -> Self {
53        let timestamp = Timestamp::now().as_second();
54        let user = whoami::username().unwrap_or_else(|_| "unknown".to_string());
55        let project_path = project_path.canonicalize().unwrap_or(project_path);
56
57        Self {
58            operation_id: operation_id.to_string(),
59            timestamp,
60            user,
61            action: Action::Init {
62                settings: config,
63                project_path,
64            },
65        }
66    }
67}
68
69pub fn parse_audit_log(
70    reader: impl BufRead,
71    only_files: &HashSet<PathBuf>,
72) -> Result<Vec<AuditEntry>> {
73    reader
74        .lines()
75        .map(|line| line.map_err(anyhow::Error::from))
76        .filter_map(|line| match line {
77            Ok(l) if l.trim().is_empty() => None,
78            other => Some(other),
79        })
80        .map(|line| Ok(serde_json::from_str::<AuditEntry>(&line?)?))
81        .filter(|entry| match entry {
82            Ok(e) => {
83                if only_files.is_empty() {
84                    true
85                } else {
86                    match &e.action {
87                        Action::Add { file, .. } => only_files.contains(&file.path),
88                        Action::Init { .. } => false,
89                    }
90                }
91            }
92            Err(_) => true, // propagate errors
93        })
94        .collect()
95}