1use crate::catalog::{Catalog, db_err};
4use orbok_core::{OrbokResult, StorageCategory, now_iso8601};
5use rusqlite::params;
6
7#[derive(Debug, Clone)]
9pub struct StorageRow {
10 pub category: StorageCategory,
11 pub size_bytes: u64,
12 pub item_count: u64,
13 pub updated_at: String,
14}
15
16pub struct StorageAccountingRepository<'a> {
17 catalog: &'a Catalog,
18}
19
20impl<'a> StorageAccountingRepository<'a> {
21 pub fn new(catalog: &'a Catalog) -> Self {
22 Self { catalog }
23 }
24
25 pub fn upsert(
27 &self,
28 category: StorageCategory,
29 size_bytes: u64,
30 item_count: u64,
31 ) -> OrbokResult<()> {
32 let conn = self.catalog.lock();
33 conn.execute(
34 "INSERT INTO storage_accounting (category, size_bytes, item_count, updated_at) \
35 VALUES (?1, ?2, ?3, ?4) \
36 ON CONFLICT(category) DO UPDATE SET size_bytes = ?2, item_count = ?3, updated_at = ?4",
37 params![
38 category.as_str(),
39 size_bytes as i64,
40 item_count as i64,
41 now_iso8601()
42 ],
43 )
44 .map_err(db_err)?;
45 Ok(())
46 }
47
48 pub fn all(&self) -> OrbokResult<Vec<StorageRow>> {
50 let conn = self.catalog.lock();
51 let mut stmt = conn
52 .prepare("SELECT category, size_bytes, item_count, updated_at FROM storage_accounting")
53 .map_err(db_err)?;
54 let rows = stmt
55 .query_map([], |row| {
56 Ok((
57 row.get::<_, String>(0)?,
58 row.get::<_, i64>(1)?,
59 row.get::<_, i64>(2)?,
60 row.get::<_, String>(3)?,
61 ))
62 })
63 .map_err(db_err)?;
64 let mut out = Vec::new();
65 for row in rows {
66 let (cat, size, count, updated) = row.map_err(db_err)?;
67 if let Some(category) = StorageCategory::ALL
69 .iter()
70 .find(|c| c.as_str() == cat)
71 .copied()
72 {
73 out.push(StorageRow {
74 category,
75 size_bytes: size as u64,
76 item_count: count as u64,
77 updated_at: updated,
78 });
79 }
80 }
81 Ok(out)
82 }
83}