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}