rovenue/cache/
entitlements.rs

1use rusqlite::params;
2
3use crate::error::RovenueResult;
4
5use super::CacheStore;
6
7#[derive(Debug, Clone, PartialEq)]
8pub struct EntitlementRow {
9    pub entitlement_id: String,
10    pub is_active: bool,
11    pub product_id: Option<String>,
12    pub product_identifier: String,
13    pub store: String,
14    pub expires_iso: Option<String>,
15    pub expires_at_ms: Option<u64>,
16    pub updated_at_ms: u64,
17}
18
19pub struct EntitlementsRepo<'a> {
20    store: &'a CacheStore,
21}
22
23impl<'a> EntitlementsRepo<'a> {
24    pub fn new(store: &'a CacheStore) -> Self {
25        Self { store }
26    }
27
28    pub fn upsert_many(&self, user_scope: &str, rows: &[EntitlementRow]) -> RovenueResult<()> {
29        self.store.with_conn(|c| {
30            let tx = c.unchecked_transaction()?;
31            {
32                let mut stmt = tx.prepare(
33                    "INSERT INTO entitlements
34                       (user_scope, entitlement_id, is_active, product_id,
35                        expires_at_ms, updated_at_ms, store, product_identifier, expires_iso)
36                     VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)
37                     ON CONFLICT(user_scope, entitlement_id) DO UPDATE SET
38                       is_active = excluded.is_active,
39                       product_id = excluded.product_id,
40                       expires_at_ms = excluded.expires_at_ms,
41                       updated_at_ms = excluded.updated_at_ms,
42                       store = excluded.store,
43                       product_identifier = excluded.product_identifier,
44                       expires_iso = excluded.expires_iso",
45                )?;
46                for r in rows {
47                    stmt.execute(params![
48                        user_scope,
49                        r.entitlement_id,
50                        r.is_active as i64,
51                        r.product_id,
52                        r.expires_at_ms.map(|v| v as i64),
53                        r.updated_at_ms as i64,
54                        r.store,
55                        r.product_identifier,
56                        r.expires_iso,
57                    ])?;
58                }
59            }
60            tx.commit()?;
61            Ok(())
62        })
63    }
64
65    pub fn get(
66        &self,
67        user_scope: &str,
68        entitlement_id: &str,
69    ) -> RovenueResult<Option<EntitlementRow>> {
70        self.store.with_conn(|c| {
71            let mut stmt = c.prepare(
72                "SELECT entitlement_id, is_active, product_id, expires_at_ms, updated_at_ms,
73                        store, product_identifier, expires_iso
74                 FROM entitlements WHERE user_scope = ?1 AND entitlement_id = ?2",
75            )?;
76            let mut rows = stmt.query(params![user_scope, entitlement_id])?;
77            if let Some(r) = rows.next()? {
78                Ok(Some(EntitlementRow {
79                    entitlement_id: r.get(0)?,
80                    is_active: r.get::<_, i64>(1)? != 0,
81                    product_id: r.get(2)?,
82                    expires_at_ms: r.get::<_, Option<i64>>(3)?.map(|v| v as u64),
83                    updated_at_ms: r.get::<_, i64>(4)? as u64,
84                    store: r.get(5)?,
85                    product_identifier: r.get(6)?,
86                    expires_iso: r.get(7)?,
87                }))
88            } else {
89                Ok(None)
90            }
91        })
92    }
93
94    pub fn list(&self, user_scope: &str) -> RovenueResult<Vec<EntitlementRow>> {
95        self.store.with_conn(|c| {
96            let mut stmt = c.prepare(
97                "SELECT entitlement_id, is_active, product_id, expires_at_ms, updated_at_ms,
98                        store, product_identifier, expires_iso
99                 FROM entitlements WHERE user_scope = ?1",
100            )?;
101            let mut rows = stmt.query(params![user_scope])?;
102            let mut out = Vec::new();
103            while let Some(r) = rows.next()? {
104                out.push(EntitlementRow {
105                    entitlement_id: r.get(0)?,
106                    is_active: r.get::<_, i64>(1)? != 0,
107                    product_id: r.get(2)?,
108                    expires_at_ms: r.get::<_, Option<i64>>(3)?.map(|v| v as u64),
109                    updated_at_ms: r.get::<_, i64>(4)? as u64,
110                    store: r.get(5)?,
111                    product_identifier: r.get(6)?,
112                    expires_iso: r.get(7)?,
113                });
114            }
115            Ok(out)
116        })
117    }
118}