Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ dist-ssr
*.sln
*.sw?

target
target
history.txt
75 changes: 75 additions & 0 deletions cli/src/currency.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use surrealdb::{engine::local::Db, RecordId, Surreal};

use crate::Error;

#[derive(ts_rs::TS)]
#[ts(export)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Currency {
pub name: String,
pub symbol: Option<String>,
pub decimal_digits: usize,
}

#[derive(ts_rs::TS)]
#[ts(export)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct CurrencyWithId {
#[serde(flatten)]
pub data: Currency,
#[ts(type = "{ tb: string, id: { String: string }}")]
pub id: RecordId,
}

#[derive(ts_rs::TS)]
#[ts(export)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct AddCurrencyOptions {
pub name: String,
pub symbol: Option<String>,
pub decimal_digits: usize,
}

pub async fn create(
db: &Surreal<Db>,
options: AddCurrencyOptions,
) -> Result<CurrencyWithId, Error> {
let currency: Option<CurrencyWithId> = db
.create("currency")
.content(options)
.await
.map_err::<Error, _>(core::convert::Into::into)?;

currency.ok_or(Error::RecordNotFound)
}

pub async fn read(db: &Surreal<Db>, id: RecordId) -> Result<CurrencyWithId, Error> {
db.select(id).await?.ok_or(Error::RecordNotFound)
}

#[derive(ts_rs::TS)]
#[ts(export)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct UpdateCurrencyOptions {
#[ts(type = "{ tb: string, id: { String: string }}")]
pub id: RecordId,
pub name: Option<String>,
pub symbol: Option<String>,
pub decimal_digits: Option<usize>,
}

pub async fn update(
db: &Surreal<Db>,
options: UpdateCurrencyOptions,
) -> Result<(), surrealdb::Error> {
let _: Option<crate::Record> = db.update(options.id.clone()).merge(options).await?;

Ok(())
}

// FIXME: opens a lot of complexity => what happens for accounts that uses this currency ?
pub async fn delete(db: &Surreal<Db>, id: RecordId) -> Result<(), surrealdb::Error> {
let _: Option<crate::Record> = db.delete(id).await?;

Ok(())
}
1 change: 1 addition & 0 deletions cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use surrealdb::RecordId;

pub mod account;
pub mod budget;
pub mod currency;
pub mod migrations;
pub mod portfolio;
pub mod settings;
Expand Down
2 changes: 1 addition & 1 deletion desktop/src-tauri/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

# Generated by Tauri
# will have schema files for capabilities auto-completion
/gen/schemas
/gen/schemas
72 changes: 70 additions & 2 deletions desktop/src-tauri/src/commands/currency.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use surrealdb::engine::local::Db;
use surrealdb::Surreal;
use surrealdb::{engine::local::Db, RecordId};
use tauri::State;
use thunes_cli::portfolio::currency::{Currency, ReadCurrencyOptions};
use thunes_cli::currency::UpdateCurrencyOptions;
use thunes_cli::{
currency::{AddCurrencyOptions, CurrencyWithId},
portfolio::currency::{Currency, ReadCurrencyOptions},
};

#[tauri::command]
#[tracing::instrument(skip(database), ret(level = tracing::Level::DEBUG))]
Expand Down Expand Up @@ -33,3 +37,67 @@ pub async fn get_currency(
"failed to get currency data".to_string()
})
}

#[tauri::command]
#[tracing::instrument(skip(database), ret(level = tracing::Level::DEBUG))]
pub async fn add_currency(
database: State<'_, tokio::sync::Mutex<Surreal<Db>>>,
options: AddCurrencyOptions,
) -> Result<CurrencyWithId, String> {
let database = database.lock().await;

thunes_cli::currency::create(&database, options)
.await
.map_err(|error| {
error.trace();
"failed to create currency".to_string()
})
}

#[tauri::command]
#[tracing::instrument(skip(database), ret(level = tracing::Level::DEBUG))]
pub async fn get_currency_2(
database: State<'_, tokio::sync::Mutex<Surreal<Db>>>,
id: RecordId,
) -> Result<CurrencyWithId, String> {
let database = database.lock().await;

thunes_cli::currency::read(&database, id)
.await
.map_err(|error| {
error.trace();
"failed to get currency".to_string()
})
}

#[tauri::command]
#[tracing::instrument(skip(database), ret(level = tracing::Level::DEBUG))]
pub async fn update_currency(
database: State<'_, tokio::sync::Mutex<Surreal<Db>>>,
options: UpdateCurrencyOptions,
) -> Result<(), String> {
let database = database.lock().await;

thunes_cli::currency::update(&database, options)
.await
.map_err(|error| {
tracing::error!(%error, "database error");
"failed to update currency".to_string()
})
}

#[tauri::command]
#[tracing::instrument(skip(database), ret(level = tracing::Level::DEBUG))]
pub async fn delete_currency(
database: State<'_, tokio::sync::Mutex<Surreal<Db>>>,
id: surrealdb::RecordId,
) -> Result<(), String> {
let database = database.lock().await;

thunes_cli::currency::delete(&database, id)
.await
.map_err(|error| {
tracing::error!(%error, "database error");
"failed to delete currency".to_string()
})
}
47 changes: 47 additions & 0 deletions desktop/src-tauri/tests/currency.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#[cfg(test)]
mod common;

#[cfg(test)]
mod tests {
use tauri::Manager;
use thunes_cli::currency::{AddCurrencyOptions, CurrencyWithId};
use thunes_lib::commands::currency::add_currency;

async fn setup() -> (tauri::App<tauri::test::MockRuntime>, CurrencyWithId) {
let app = crate::common::setup().await;
let currency = add_currency(
app.state(),
AddCurrencyOptions {
name: "Euro".to_string(),
symbol: Some("€".to_string()),
decimal_digits: 2,
},
)
.await
.expect("failed to create currency");

(app, currency)
}

#[tokio::test]
pub async fn test_add_currency() {
let (app, currency1) = setup().await;
let currency2 = add_currency(
app.state(),
AddCurrencyOptions {
name: "Bitcoin".to_string(),
symbol: Some("₿".to_string()),
decimal_digits: 8,
},
)
.await
.expect("failed to create currency");

assert_eq!(currency1.data.name, "Euro".to_string());
assert_eq!(currency1.data.symbol, Some("€".to_string()));
assert_eq!(currency1.data.decimal_digits, 2);
assert_eq!(currency2.data.name, "Bitcoin".to_string());
assert_eq!(currency2.data.symbol, Some("₿".to_string()));
assert_eq!(currency2.data.decimal_digits, 8);
}
}
2 changes: 2 additions & 0 deletions desktop/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { SnackbarProvider } from "./contexts/Snackbar";
import Budget from "./pages/Budget";
import { useSettingStore } from "./stores/setting";
import Currency from "./pages/Currency";

function Layout() {
const store = useSettingStore();
Expand Down Expand Up @@ -138,6 +139,7 @@ export default function App() {
<Route path="account/:id?" element={<Account />} />
<Route path="budget/:id?" element={<Budget />} />
<Route path="settings" element={<Settings />} />
<Route path="currency" element={<Currency />} />
</Route>
</Routes>
);
Expand Down
Loading
Loading