got basic requests from the database
This commit is contained in:
parent
a9476556b1
commit
386e119326
10 changed files with 94 additions and 57 deletions
|
@ -6,10 +6,13 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
crossbeam-channel = "0.5.8"
|
||||||
crossterm = "0.26.1"
|
crossterm = "0.26.1"
|
||||||
futures = "0.3.28"
|
futures = "0.3.28"
|
||||||
log = "0.4.19"
|
log = "0.4.19"
|
||||||
ratatui = "0.21.0"
|
ratatui = "0.21.0"
|
||||||
|
rust_decimal = "1.31.0"
|
||||||
simplelog = "0.12.1"
|
simplelog = "0.12.1"
|
||||||
sqlx = { version = "0.6.3", features = ["postgres", "runtime-tokio-native-tls", "sqlite"] }
|
sqlx = { version = "0.6.3", features = ["postgres", "runtime-tokio-native-tls", "sqlite", "decimal", "time"] }
|
||||||
|
time = "0.3.25"
|
||||||
tokio = { version = "1.28.2", features = ["full"] }
|
tokio = { version = "1.28.2", features = ["full"] }
|
||||||
|
|
|
@ -37,13 +37,16 @@ CREATE TABLE rcnt.buckets (
|
||||||
|
|
||||||
CREATE TABLE rcnt.transaction_breakdown (
|
CREATE TABLE rcnt.transaction_breakdown (
|
||||||
trns_brkdwn_id int4 NOT NULL GENERATED ALWAYS AS IDENTITY,
|
trns_brkdwn_id int4 NOT NULL GENERATED ALWAYS AS IDENTITY,
|
||||||
trns_brkdwn_amount numeric(10, 3) NOT NULL,
|
trns_brkdwn_amount money NOT NULL,
|
||||||
trns_brkdwn_parent_transaction int4 NOT NULL,
|
trns_brkdwn_parent_transaction int4 NOT NULL,
|
||||||
trns_brkdwn_catagory int4 NULL,
|
trns_brkdwn_catagory int4 NULL,
|
||||||
trns_brkdwn_bucket int4 NULL,
|
trns_brkdwn_bucket int4 NULL,
|
||||||
CONSTRAINT transaction_breakdown_pkey PRIMARY KEY (trns_brkdwn_id)
|
CONSTRAINT transaction_breakdown_pkey PRIMARY KEY (trns_brkdwn_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
alter table rcnt.transaction_breakdown add constraint transaction_breakdown_parent_transaction_fkey foreign key (trns_brkdwn_parent_transaction) references rcnt.transactions(trns_id);
|
||||||
|
alter table rcnt.transaction_breakdown add constraint transaction_breakdown_catagory_fkey foreign key (trns_brkdwn_catagory) references rcnt.transaction_categories(trns_ctgry_id);
|
||||||
|
alter table rcnt.transaction_breakdown add constraint transaction_breakdown_bucket_fkey foreign key (trns_brkdwn_bucket) references rcnt.buckets(bkt_id);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,7 +58,7 @@ CREATE TABLE rcnt.transaction_breakdown (
|
||||||
|
|
||||||
CREATE TABLE rcnt.transactions (
|
CREATE TABLE rcnt.transactions (
|
||||||
trns_id int4 NOT NULL GENERATED ALWAYS AS IDENTITY,
|
trns_id int4 NOT NULL GENERATED ALWAYS AS IDENTITY,
|
||||||
trns_amount numeric(10, 3) NOT NULL,
|
trns_amount money NOT NULL,
|
||||||
trns_description varchar(250) NULL,
|
trns_description varchar(250) NULL,
|
||||||
trns_account int4 NOT NULL,
|
trns_account int4 NOT NULL,
|
||||||
trns_bucket int4 NULL,
|
trns_bucket int4 NULL,
|
||||||
|
@ -71,7 +74,7 @@ ALTER TABLE rcnt.transactions ADD CONSTRAINT transactions_trns_bucket_fkey FOREI
|
||||||
|
|
||||||
-- Drop table
|
-- Drop table
|
||||||
|
|
||||||
-- DROP TABLE rcnt.transaction_categories;
|
DROP TABLE rcnt.transaction_categories;
|
||||||
|
|
||||||
CREATE TABLE rcnt.transaction_categories (
|
CREATE TABLE rcnt.transaction_categories (
|
||||||
trns_ctgry_id int4 NOT NULL GENERATED ALWAYS AS IDENTITY,
|
trns_ctgry_id int4 NOT NULL GENERATED ALWAYS AS IDENTITY,
|
||||||
|
|
|
@ -1,50 +1,35 @@
|
||||||
|
|
||||||
INSERT INTO rcnt.accounts
|
INSERT INTO rcnt.accounts
|
||||||
(display_name, description)
|
(acnt_dsply_name, acnt_description)
|
||||||
VALUES('BECU Checking', 'BECU Checking Account');
|
VALUES('BECU Checking', 'BECU Checking Account');
|
||||||
|
|
||||||
|
|
||||||
INSERT INTO rcnt.buckets
|
INSERT INTO rcnt.buckets
|
||||||
(display_code, display_name, description)
|
(bkt_dsply_code, bkt_dsply_name, bkt_description)
|
||||||
VALUES('SVNGS', 'Savings', 'Long term savings');
|
values
|
||||||
|
('SVNGS', 'Savings', 'Long term savings'),
|
||||||
|
('SPEND', 'Spending', 'General Spening Category'),
|
||||||
|
('TITHE', 'Tight', '<Random Bible Verse About Tithes>');
|
||||||
|
|
||||||
|
|
||||||
INSERT INTO rcnt.buckets
|
INSERT INTO rcnt.transaction_categories
|
||||||
(display_code, display_name, description)
|
(trns_ctgry_dsply_code, trns_ctgry_dsply_name, trns_ctgry_description)
|
||||||
VALUES('SPEND', 'Spending', 'General Spening Category');
|
values
|
||||||
|
('TAX', 'Taxes', 'One of the only constants'),
|
||||||
|
('GAS', 'Gas', 'Buying gas');
|
||||||
INSERT INTO rcnt.buckets
|
|
||||||
(display_code, display_name, description)
|
|
||||||
VALUES('TITHE', 'Tight', '<Random Bible Verse About Tithes>');
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
INSERT INTO rcnt.transactioncategories
|
|
||||||
(display_code, display_name, description)
|
|
||||||
VALUES('GAS', 'Gas', 'Buying gas');
|
|
||||||
|
|
||||||
|
|
||||||
INSERT INTO rcnt.transactioncategories
|
|
||||||
(display_code, display_name, description)
|
|
||||||
VALUES('TAX', 'Taxes', 'One of the only constants');
|
|
||||||
|
|
||||||
|
|
||||||
INSERT INTO rcnt.transactions
|
INSERT INTO rcnt.transactions
|
||||||
(amount, description, account, transaction_bucket)
|
(trns_amount, trns_description, trns_account, trns_bucket, trns_date)
|
||||||
VALUES(10.00, 'Optional Text', 1, 1);
|
VALUES(10.00, 'Optional Text', 1, 1, '8-7-2023');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
INSERT INTO rcnt.transactionbreakdown
|
INSERT INTO rcnt.transaction_breakdown
|
||||||
(amount, parent_transaction, catagory, transaction_bucket)
|
(trns_brkdwn_amount, trns_brkdwn_parent_transaction, trns_brkdwn_catagory, trns_brkdwn_bucket)
|
||||||
VALUES(8.00, 1, 1, 1);
|
values
|
||||||
|
(8.00, 1, 1, 1),
|
||||||
|
(2.00, 1, 1, 2);
|
||||||
INSERT INTO rcnt.transactionbreakdown
|
|
||||||
(amount, parent_transaction, catagory, transaction_bucket)
|
|
||||||
VALUES(2.00, 1, 1, 2);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,8 @@ create or replace view rcnt.transaction_history_view as
|
||||||
left join rcnt.buckets b on b.bkt_id = t.trns_bucket
|
left join rcnt.buckets b on b.bkt_id = t.trns_bucket
|
||||||
left join rcnt.accounts a on a.acnt_id = t.trns_account
|
left join rcnt.accounts a on a.acnt_id = t.trns_account
|
||||||
|
|
||||||
--drop view rcnt.transaction_history_view
|
drop view rcnt.transaction_history_view;
|
||||||
|
drop view rcnt.transaction_detail_history_view;
|
||||||
|
|
||||||
select * from rcnt.transaction_history_view
|
select * from rcnt.transaction_history_view
|
||||||
|
|
||||||
|
@ -32,3 +33,9 @@ create or replace view rcnt.transaction_detail_history_view as
|
||||||
left join rcnt.transaction_categories tc on tc.trns_ctgry_id = t.trns_brkdwn_catagory
|
left join rcnt.transaction_categories tc on tc.trns_ctgry_id = t.trns_brkdwn_catagory
|
||||||
left join rcnt.buckets b on b.bkt_id = t.trns_brkdwn_bucket
|
left join rcnt.buckets b on b.bkt_id = t.trns_brkdwn_bucket
|
||||||
|
|
||||||
|
grant connect on database "Borealis" to rcntuser;
|
||||||
|
grant pg_read_all_data to rcntuser;
|
||||||
|
grant pg_write_all_data to rcntuser;
|
||||||
|
|
||||||
|
SELECT trns_id, trns_amount, trns_description, trns_account, trns_bucket, trns_date
|
||||||
|
FROM rcnt.transactions;
|
||||||
|
|
12
src/app.rs
12
src/app.rs
|
@ -6,6 +6,8 @@ use crossterm::event::{Event, self, KeyCode};
|
||||||
use tokio;
|
use tokio;
|
||||||
use ratatui::widgets::ListState;
|
use ratatui::widgets::ListState;
|
||||||
|
|
||||||
|
use crossbeam_channel::Receiver;
|
||||||
|
|
||||||
use log::warn;
|
use log::warn;
|
||||||
|
|
||||||
use crate::db::DB;
|
use crate::db::DB;
|
||||||
|
@ -79,10 +81,10 @@ pub struct App<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> App<'a> {
|
impl<'a> App<'a> {
|
||||||
pub fn new(db: DB, records: Arc<Mutex<Vec<TransactionRecord>>>) -> App<'a> {
|
pub fn new(db: DB, records: Arc<Mutex<Vec<TransactionRecord>>>, r: Receiver<bool>) -> App<'a> {
|
||||||
App {
|
App {
|
||||||
running: true,
|
running: true,
|
||||||
states: States::new(),
|
states: States::new(r),
|
||||||
input_mode: InputMode::Normal,
|
input_mode: InputMode::Normal,
|
||||||
db: Arc::new(tokio::sync::Mutex::new(db)),
|
db: Arc::new(tokio::sync::Mutex::new(db)),
|
||||||
records,
|
records,
|
||||||
|
@ -158,16 +160,20 @@ pub struct States<'a> {
|
||||||
pub transactions: NewTransactionTabState<'a>,
|
pub transactions: NewTransactionTabState<'a>,
|
||||||
pub history: HistoryState,
|
pub history: HistoryState,
|
||||||
|
|
||||||
|
pub new_data: Receiver<bool>,
|
||||||
|
|
||||||
pub active_frame: ActiveFrame,
|
pub active_frame: ActiveFrame,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> States<'a> {
|
impl<'a> States<'a> {
|
||||||
pub fn new() -> States<'a> {
|
pub fn new(r: Receiver<bool>) -> States<'a> {
|
||||||
States {
|
States {
|
||||||
nav_state: NavigationState::new(),
|
nav_state: NavigationState::new(),
|
||||||
transactions: NewTransactionTabState::new(),
|
transactions: NewTransactionTabState::new(),
|
||||||
history: HistoryState::new(),
|
history: HistoryState::new(),
|
||||||
|
|
||||||
|
new_data: r,
|
||||||
|
|
||||||
active_frame: ActiveFrame::Navigation,
|
active_frame: ActiveFrame::Navigation,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,50 +2,60 @@
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crossbeam_channel::Sender;
|
||||||
|
|
||||||
use futures::TryStreamExt;
|
use futures::TryStreamExt;
|
||||||
|
|
||||||
use sqlx::Error;
|
use sqlx::Error;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use sqlx::Row;
|
use sqlx::Row;
|
||||||
use sqlx::postgres::PgPoolOptions;
|
use sqlx::postgres::PgPoolOptions;
|
||||||
|
use sqlx::postgres::types::PgMoney;
|
||||||
|
use time::Date;
|
||||||
|
|
||||||
use crate::db::transaction::TransactionRecord;
|
use crate::db::transaction::TransactionRecord;
|
||||||
|
|
||||||
pub struct DB {
|
pub struct DB {
|
||||||
conn_pool: PgPool,
|
conn_pool: PgPool,
|
||||||
|
|
||||||
|
new_data_notify: Sender<bool>,
|
||||||
records: Arc<Mutex<Vec<TransactionRecord>>>,
|
records: Arc<Mutex<Vec<TransactionRecord>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl DB {
|
impl DB {
|
||||||
pub async fn new(records: Arc<Mutex<Vec<TransactionRecord>>>) -> Result<DB, sqlx::Error> {
|
pub async fn new(records: Arc<Mutex<Vec<TransactionRecord>>>, s: Sender<bool>) -> Result<DB, sqlx::Error> {
|
||||||
let connection_pool = PgPoolOptions::new()
|
let connection_pool = PgPoolOptions::new()
|
||||||
.max_connections(3)
|
.max_connections(3)
|
||||||
.connect("postgres://rcntuser:Devel@pmentPa$$w0rd@10.0.0.183/Borealis").await?;
|
.connect("postgres://rcntuser:Devel@pmentPa$$w0rd@10.0.0.183/Borealis").await?;
|
||||||
Ok(DB {
|
Ok(DB {
|
||||||
conn_pool: connection_pool,
|
conn_pool: connection_pool,
|
||||||
|
new_data_notify: s,
|
||||||
records,
|
records,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_all_records(&mut self) -> Result<(), Error> {
|
pub async fn get_all_records(&mut self) -> Result<(), Error> {
|
||||||
let mut rows = sqlx::query("SELECT trns_id, trns_amount, trns_description, trns_account, trns_bucket trns_date FROM rcnt.transactions")
|
let mut rows = sqlx::query("SELECT trns_id, trns_amount, trns_description, trns_account, trns_bucket, trns_date FROM rcnt.transactions")
|
||||||
.fetch(&self.conn_pool);
|
.fetch(&self.conn_pool);
|
||||||
|
|
||||||
while let Some(row) = rows.try_next().await? {
|
while let Some(row) = rows.try_next().await? {
|
||||||
let id: i64 = row.try_get("trns_id")?;
|
let id: i32 = row.try_get("trns_id")?;
|
||||||
let amount: &str = row.try_get("trns_amount")?;
|
let amount: PgMoney = row.try_get("trns_amount")?;
|
||||||
let date: &str = row.try_get("trns_date")?;
|
let date: Date = row.try_get("trns_date")?;
|
||||||
|
self.records.lock().unwrap().clear();
|
||||||
|
|
||||||
self.records.lock().unwrap().push(
|
self.records.lock().unwrap().push(
|
||||||
TransactionRecord {
|
TransactionRecord {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
amount: amount.to_string(),
|
amount: amount.to_decimal(2).to_string(),
|
||||||
date: date.to_string()
|
date: date.to_string()
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.new_data_notify.try_send(true).unwrap();
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ use std::borrow::Cow;
|
||||||
|
|
||||||
|
|
||||||
// cargo add crust_decimal
|
// cargo add crust_decimal
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct TransactionRecord {
|
pub struct TransactionRecord {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
// pub amount: Decimal,
|
// pub amount: Decimal,
|
||||||
|
|
22
src/main.rs
22
src/main.rs
|
@ -4,6 +4,8 @@ use std::time::Duration;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crossbeam_channel::bounded;
|
||||||
|
|
||||||
use simplelog::*;
|
use simplelog::*;
|
||||||
|
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
|
@ -14,7 +16,7 @@ use ratatui::{
|
||||||
use recount::app::{App, AppResult};
|
use recount::app::{App, AppResult};
|
||||||
use recount::tui::Tui;
|
use recount::tui::Tui;
|
||||||
use recount::db::DB;
|
use recount::db::DB;
|
||||||
use recount::db::transaction::TransactionRecord;
|
//use recount::db::transaction::TransactionRecord;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> AppResult<()> {
|
async fn main() -> AppResult<()> {
|
||||||
|
@ -23,10 +25,12 @@ async fn main() -> AppResult<()> {
|
||||||
init_logger(log_file);
|
init_logger(log_file);
|
||||||
|
|
||||||
let records = Arc::new(Mutex::new(Vec::new()));
|
let records = Arc::new(Mutex::new(Vec::new()));
|
||||||
let db = DB::new(Arc::clone(&records)).await?;
|
let (s, r) = bounded::<bool>(2);
|
||||||
|
let db = DB::new(Arc::clone(&records), s).await?;
|
||||||
|
|
||||||
|
|
||||||
// Create an application.
|
// Create an application.
|
||||||
let mut app = App::new(db, records);
|
let mut app = App::new(db, records, r);
|
||||||
|
|
||||||
// Initialize the terminal user interface.
|
// Initialize the terminal user interface.
|
||||||
let backend = CrosstermBackend::new(io::stderr());
|
let backend = CrosstermBackend::new(io::stderr());
|
||||||
|
@ -54,12 +58,12 @@ async fn main() -> AppResult<()> {
|
||||||
pub fn init_logger(output_file: String) {
|
pub fn init_logger(output_file: String) {
|
||||||
// TODO: configure the log levels to something appropriate
|
// TODO: configure the log levels to something appropriate
|
||||||
CombinedLogger::init(vec![
|
CombinedLogger::init(vec![
|
||||||
TermLogger::new(
|
// TermLogger::new(
|
||||||
LevelFilter::Info,
|
// LevelFilter::Info,
|
||||||
Config::default(),
|
// Config::default(),
|
||||||
TerminalMode::Mixed,
|
// TerminalMode::Mixed,
|
||||||
ColorChoice::Auto,
|
// ColorChoice::Auto,
|
||||||
),
|
// ),
|
||||||
WriteLogger::new(
|
WriteLogger::new(
|
||||||
LevelFilter::Info,
|
LevelFilter::Info,
|
||||||
Config::default(),
|
Config::default(),
|
||||||
|
|
|
@ -5,6 +5,7 @@ use ratatui::{
|
||||||
widgets::{Block, Borders},
|
widgets::{Block, Borders},
|
||||||
Frame, style::Style,
|
Frame, style::Style,
|
||||||
};
|
};
|
||||||
|
use log;
|
||||||
|
|
||||||
use crate::uis::{render_navigation_frame, render_new_transaction_tab};
|
use crate::uis::{render_navigation_frame, render_new_transaction_tab};
|
||||||
|
|
||||||
|
|
|
@ -31,10 +31,27 @@ impl HistoryState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_transactions(app: &mut App) {
|
||||||
|
app.states.history.transacts_list.items.clear();
|
||||||
|
for i in app.records.lock().unwrap().iter() {
|
||||||
|
app.states.history.transacts_list.items.push(i.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_history_tab<B: Backend> (f: &mut Frame<B>, body_rect: Rect, app: &mut App) {
|
pub fn render_history_tab<B: Backend> (f: &mut Frame<B>, body_rect: Rect, app: &mut App) {
|
||||||
|
|
||||||
|
let do_update = match app.states.new_data.try_recv() {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(_) => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if do_update {
|
||||||
|
HistoryState::update_transactions(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
let split_body = Layout::default()
|
let split_body = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||||
|
|
Loading…
Reference in a new issue