Skip to content

Commit e0f6c45

Browse files
committed
feat: moved acc cache and repo to their own modules
Signed-off-by: Martin <martin@hotmail.com.br>
1 parent bc5a248 commit e0f6c45

File tree

3 files changed

+163
-76
lines changed

3 files changed

+163
-76
lines changed

src/api/mod.rs

Lines changed: 14 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -147,84 +147,22 @@ impl BankAccountService {
147147
}
148148
}
149149

150-
#[derive(Debug, Serialize, Deserialize)]
151-
struct Statementredis {
152-
balance: i32,
153-
transactions: Vec<Transaction>,
154-
}
155-
156-
struct TransactionRepository {
157-
conn: deadpool_postgres::Pool,
158-
}
159-
160-
impl TransactionRepository {
161-
async fn save_transaction(&self, user_id: i32, transaction: &Transaction) {
162-
let conn = self.conn.get().await.unwrap();
163-
164-
let query = r#"
165-
WITH insertion
166-
AS (INSERT INTO transaction (amount, description, account_id) VALUES ($1, $2, $3)
167-
RETURNING account_id, amount )
168-
SELECT rb.account_id, running_balance + i.amount
169-
FROM running_balance rb
170-
INNER JOIN insertion i ON rb.account_id = i.account_id;"#;
171-
let stmt = conn.prepare_cached(query).await.unwrap();
172-
173-
let desc = transaction.descricao.0.as_str();
174-
let amount = transaction.valor;
175-
176-
let res = conn
177-
.execute(&stmt, &[&i32::from(amount), &desc, &user_id])
178-
.await
179-
.unwrap();
180-
}
181-
}
182-
183-
struct TransactionCache {
184-
re_conn: redis::aio::ConnectionManager,
185-
}
150+
let (acc, _) = trans_cache.get_account(user, false).await;
151+
let acc = acc
152+
.add_transaction(&transaction)
153+
.or_else(|_| bail!("no credits bro"))?;
186154

187-
impl TransactionCache {
188-
const TRANSACTIONS_KEY: &'static str = "transactions";
189-
190-
fn key_fn(user_id: i32) -> CompactString {
191-
let key = Self::TRANSACTIONS_KEY;
192-
compact_str::format_compact!("{key}:{user_id}")
193-
}
194-
195-
pub async fn get_latest_n(&self, n: i32) -> Vec<Transaction> {
196-
let key = Self::key_fn(1);
197-
198-
let stream_result: StreamRangeReply = self
199-
.re_conn
200-
.clone()
201-
.xrevrange_count(key.as_str(), "+", "-", n)
202-
.await
203-
.unwrap();
204-
205-
stream_result
206-
.ids
207-
.into_iter()
208-
.flat_map(|v| v.map.into_iter())
209-
.filter_map(|(_, val)| {
210-
if let Value::Data(data) = val {
211-
let transaction = bitcode::deserialize::<Transaction>(&data).unwrap();
212-
Some(transaction)
213-
} else {
214-
None
155+
{
156+
// let guard = redis_lock.acquire().await.unwrap();
157+
trans_repo.save_and_get_balance(user, &transaction).await?;
158+
trans_cache
159+
.save_account(user, &acc, Some(&transaction))
160+
.await;
161+
// guard.release().await;
215162
}
216-
})
217-
.collect::<Vec<Transaction>>()
218-
}
219-
220-
pub async fn append(&self, user_id: i32, transaction: &Transaction) {
221-
let serialized = bitcode::serialize(&transaction).unwrap();
222-
let key = Self::key_fn(user_id);
223163

224-
self.re_conn
225-
.clone()
226-
.xadd(key.as_str(), "*", &[(user_id, serialized)])
227-
.await
228-
.unwrap()
164+
Ok(())
165+
}
166+
}
229167
}
230168
}

src/application/cache.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
use crate::domain::account::Account;
2+
use crate::domain::transaction::Transaction;
3+
use compact_str::CompactString;
4+
use redis::streams::StreamRangeReply;
5+
use redis::{AsyncCommands, Value};
6+
7+
pub struct AccountCache {
8+
pub re_conn: redis::aio::ConnectionManager,
9+
}
10+
11+
impl AccountCache {
12+
const ACCOUNT_KEY: &'static str = "account";
13+
const TRANSACTIONS_KEY: &'static str = "transactions";
14+
15+
fn key_trans_fn(user_id: i32) -> CompactString {
16+
let key = Self::TRANSACTIONS_KEY;
17+
compact_str::format_compact!("{key}:{user_id}")
18+
}
19+
fn key_acc_fn(user_id: i32) -> CompactString {
20+
let key = Self::ACCOUNT_KEY;
21+
compact_str::format_compact!("{key}:{user_id}")
22+
}
23+
24+
pub async fn get_account(
25+
&self,
26+
user_id: i32,
27+
with_transactions: bool,
28+
) -> (Account, Option<impl Iterator<Item = Transaction>>) {
29+
let trans_key = Self::key_trans_fn(user_id);
30+
let acc_key = Self::key_acc_fn(user_id);
31+
32+
if with_transactions {
33+
let mut pipe = redis::pipe();
34+
let mut conn = self.re_conn.clone();
35+
let res: (StreamRangeReply, Vec<u8>) = pipe
36+
.xrevrange_count(trans_key.as_str(), "+", "-", 10)
37+
.get(acc_key.as_str())
38+
.query_async(&mut conn)
39+
.await
40+
.unwrap();
41+
42+
let acc = bitcode::deserialize::<Account>(&res.1).unwrap();
43+
let transactions = res
44+
.0
45+
.ids
46+
.into_iter()
47+
.flat_map(|v| v.map.into_iter())
48+
.filter_map(|(_, val)| {
49+
if let Value::Data(data) = val {
50+
let transaction = bitcode::deserialize::<Transaction>(&data).unwrap();
51+
Some(transaction)
52+
} else {
53+
None
54+
}
55+
});
56+
57+
(acc, Some(transactions))
58+
} else {
59+
let acc = self
60+
.re_conn
61+
.clone()
62+
.get(acc_key.as_str())
63+
.await
64+
.ok()
65+
.and_then(|bytes: Vec<u8>| bitcode::deserialize::<Account>(&bytes).ok())
66+
.unwrap();
67+
(acc, None)
68+
}
69+
}
70+
71+
pub async fn save_account(
72+
&self,
73+
user_id: i32,
74+
acc: &Account,
75+
with_transaction: Option<&Transaction>,
76+
) {
77+
let acc_serialized = bitcode::serialize(&acc).unwrap();
78+
let trans_key = Self::key_trans_fn(user_id);
79+
let acc_key = Self::key_acc_fn(user_id);
80+
81+
let mut like = self.re_conn.clone();
82+
let mut pipeline = redis::pipe();
83+
pipeline.set(acc_key.as_str(), acc_serialized);
84+
85+
if let Some(trans) = with_transaction {
86+
let trans_serialized = bitcode::serialize(&trans).unwrap();
87+
pipeline
88+
.xadd(trans_key.as_str(), "*", &[(user_id, trans_serialized)])
89+
.ignore();
90+
}
91+
92+
pipeline.query_async(&mut like).await.unwrap()
93+
}
94+
}

src/application/repositories.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use crate::domain::account::Account;
2+
use crate::domain::transaction::Transaction;
3+
use crate::AnyResult;
4+
use eyre::bail;
5+
6+
#[derive(Debug)]
7+
pub struct TransactionRepository {
8+
pub conn: deadpool_postgres::Pool,
9+
}
10+
11+
impl TransactionRepository {
12+
pub async fn get_accounts(&self) -> impl IntoIterator<Item = Account> {
13+
let conn = self.conn.get().await.unwrap();
14+
15+
let query = r#"
16+
SELECT id
17+
, balance
18+
, credit_limit
19+
FROM account;"#;
20+
let stmt = conn.prepare_cached(query).await.unwrap();
21+
let res = conn.query(&stmt, &[]).await.unwrap();
22+
23+
res.into_iter().map(|r| Account {
24+
id: r.get(0),
25+
balance: r.get(1),
26+
credit_limit: r.get::<usize, i32>(2) as u32,
27+
})
28+
}
29+
30+
/// Returns the source-of-truth balance for user_id.
31+
pub async fn save_and_get_balance(
32+
&self,
33+
user_id: i32,
34+
transaction: &Transaction,
35+
) -> AnyResult<()> {
36+
let conn = self.conn.get().await?;
37+
38+
let query = r#"
39+
WITH insertion
40+
AS (INSERT INTO transaction (amount, description, account_id) VALUES ($1, $2, $3) RETURNING account_id, amount )
41+
UPDATE account acc
42+
SET balance = balance + insertion.amount
43+
FROM insertion
44+
WHERE acc.id = insertion.account_id;"#;
45+
let stmt = conn.prepare_cached(query).await?;
46+
47+
let desc = transaction.descricao.0.as_str();
48+
let amount = transaction.valor;
49+
50+
conn.query_one(&stmt, &[&i32::from(amount), &desc, &user_id])
51+
.await
52+
.map(|_| ())
53+
.or_else(|_| bail!("problem querying postgres"))
54+
}
55+
}

0 commit comments

Comments
 (0)