Skip to main content

bamboo_memory/ledger_store/
paths.rs

1use std::path::PathBuf;
2
3use bamboo_config::paths::bamboo_dir;
4use bamboo_domain::ledger::LedgerScope;
5
6pub const LEDGER_ROOT_DIR: &str = "ledger";
7pub const LEDGER_VERSION_DIR: &str = "v1";
8pub const SCOPES_DIR: &str = "scopes";
9pub const GLOBAL_DIR: &str = "global";
10pub const PROJECTS_DIR: &str = "projects";
11pub const RECORDS_DIR: &str = "records";
12pub const INDEXES_DIR: &str = "indexes";
13pub const VIEWS_DIR: &str = "views";
14pub const LOGS_DIR: &str = "logs";
15
16/// Path resolver for the ledger's dedicated on-disk area, parallel to the
17/// memory store's `memory/v1`:
18///
19/// ```text
20/// {data_dir}/ledger/v1/scopes/{global|projects/<key>}/
21///     records/<record_id>.md
22///     indexes/{by_time.json, by_status.json}
23///     views/{AGENDA.md, TODO.md}
24///     logs/audit.jsonl
25/// ```
26#[derive(Debug, Clone)]
27pub struct LedgerPathResolver {
28    root: PathBuf,
29}
30
31impl Default for LedgerPathResolver {
32    fn default() -> Self {
33        Self::from_data_dir(bamboo_dir())
34    }
35}
36
37impl LedgerPathResolver {
38    pub fn from_data_dir(data_dir: impl Into<PathBuf>) -> Self {
39        let root = data_dir
40            .into()
41            .join(LEDGER_ROOT_DIR)
42            .join(LEDGER_VERSION_DIR);
43        Self { root }
44    }
45
46    pub fn root(&self) -> PathBuf {
47        self.root.clone()
48    }
49
50    pub fn projects_root(&self) -> PathBuf {
51        self.root.join(SCOPES_DIR).join(PROJECTS_DIR)
52    }
53
54    pub fn scope_root(&self, scope: LedgerScope, project_key: Option<&str>) -> PathBuf {
55        let scopes = self.root.join(SCOPES_DIR);
56        match scope {
57            LedgerScope::Global => scopes.join(GLOBAL_DIR),
58            LedgerScope::Project => scopes
59                .join(PROJECTS_DIR)
60                .join(project_key.unwrap_or("unknown")),
61        }
62    }
63
64    pub fn records_dir(&self, scope: LedgerScope, project_key: Option<&str>) -> PathBuf {
65        self.scope_root(scope, project_key).join(RECORDS_DIR)
66    }
67
68    pub fn record_path(
69        &self,
70        scope: LedgerScope,
71        project_key: Option<&str>,
72        record_id: &str,
73    ) -> PathBuf {
74        self.records_dir(scope, project_key)
75            .join(format!("{}.md", record_id))
76    }
77
78    pub fn indexes_dir(&self, scope: LedgerScope, project_key: Option<&str>) -> PathBuf {
79        self.scope_root(scope, project_key).join(INDEXES_DIR)
80    }
81
82    pub fn views_dir(&self, scope: LedgerScope, project_key: Option<&str>) -> PathBuf {
83        self.scope_root(scope, project_key).join(VIEWS_DIR)
84    }
85
86    pub fn logs_dir(&self, scope: LedgerScope, project_key: Option<&str>) -> PathBuf {
87        self.scope_root(scope, project_key).join(LOGS_DIR)
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn paths_follow_v1_layout() {
97        let resolver = LedgerPathResolver::from_data_dir("/tmp/.bamboo");
98        assert_eq!(
99            resolver.record_path(LedgerScope::Global, None, "rec_1"),
100            PathBuf::from("/tmp/.bamboo/ledger/v1/scopes/global/records/rec_1.md")
101        );
102        assert_eq!(
103            resolver.record_path(LedgerScope::Project, Some("proj-1"), "rec_2"),
104            PathBuf::from("/tmp/.bamboo/ledger/v1/scopes/projects/proj-1/records/rec_2.md")
105        );
106    }
107}