rovenue/cache/
virtual_currencies.rs

1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use rusqlite::params;
5
6use super::store::CacheStore;
7use crate::error::RovenueResult;
8
9/// Per-scope multi-currency balances. Keyed by (user_scope, code); a scope
10/// may hold balances in several currencies at once.
11pub struct VirtualCurrencyRepo {
12    store: Arc<CacheStore>,
13}
14
15impl VirtualCurrencyRepo {
16    pub fn new(store: Arc<CacheStore>) -> Self {
17        Self { store }
18    }
19
20    /// All balances for a scope, ordered by code for stable snapshotting.
21    pub fn get_all(&self, user_scope: &str) -> RovenueResult<BTreeMap<String, i64>> {
22        self.store.with_conn(|c| {
23            let mut stmt = c.prepare(
24                "SELECT code, balance FROM virtual_currency_balance WHERE user_scope = ?1 ORDER BY code",
25            )?;
26            let mut rows = stmt.query(params![user_scope])?;
27            let mut out = BTreeMap::new();
28            while let Some(r) = rows.next()? {
29                let code: String = r.get(0)?;
30                let balance: i64 = r.get(1)?;
31                out.insert(code, balance);
32            }
33            Ok(out)
34        })
35    }
36
37    /// Single-currency convenience.
38    pub fn get(&self, user_scope: &str, code: &str) -> RovenueResult<Option<i64>> {
39        self.store.with_conn(|c| {
40            let mut stmt = c.prepare(
41                "SELECT balance FROM virtual_currency_balance WHERE user_scope = ?1 AND code = ?2",
42            )?;
43            let mut rows = stmt.query(params![user_scope, code])?;
44            if let Some(r) = rows.next()? {
45                Ok(Some(r.get::<_, i64>(0)?))
46            } else {
47                Ok(None)
48            }
49        })
50    }
51
52    /// Replace the full balance set for a scope (the server response is
53    /// authoritative — currencies absent from `balances` are removed).
54    pub fn upsert_all(
55        &self,
56        user_scope: &str,
57        balances: &BTreeMap<String, i64>,
58        updated_at_ms: u64,
59    ) -> RovenueResult<()> {
60        self.store.with_conn(|c| {
61            // Delete all existing rows for this scope first (server is authoritative).
62            c.execute(
63                "DELETE FROM virtual_currency_balance WHERE user_scope = ?1",
64                params![user_scope],
65            )?;
66            for (code, balance) in balances {
67                c.execute(
68                    "INSERT INTO virtual_currency_balance (user_scope, code, balance, updated_at_ms)
69                     VALUES (?1, ?2, ?3, ?4)",
70                    params![user_scope, code, balance, updated_at_ms as i64],
71                )?;
72            }
73            Ok(())
74        })
75    }
76}