summaryrefslogtreecommitdiffstats
path: root/cli/src/model.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/src/model.rs')
-rw-r--r--cli/src/model.rs106
1 files changed, 106 insertions, 0 deletions
diff --git a/cli/src/model.rs b/cli/src/model.rs
new file mode 100644
index 0000000..b0dab7a
--- /dev/null
+++ b/cli/src/model.rs
@@ -0,0 +1,106 @@
+use rust_decimal::Decimal;
+use serde::{Deserialize, Serialize};
+use std::convert::AsRef;
+use std::fs;
+use std::hash::{Hash, Hasher};
+use std::path::{Path, PathBuf};
+use twox_hash::XxHash64;
+
+type Account = String;
+type Category = String;
+
+#[derive(Debug)]
+#[derive(Hash)]
+#[derive(Deserialize, Serialize)]
+pub enum TransactionKind {
+ Expense,
+ Income,
+ //TODO Transfer,
+}
+
+#[derive(Debug)]
+#[derive(Hash)]
+#[derive(Deserialize, Serialize)]
+pub struct Transaction {
+ description: String,
+ category: Category,
+ amount: Decimal,
+ kind: TransactionKind,
+ from: Account,
+ to: Account,
+}
+
+#[derive(Debug)]
+#[derive(Hash)]
+#[derive(Deserialize, Serialize)]
+pub struct Post {
+ transaction: Transaction,
+ removed: bool,
+}
+
+#[derive(Debug)]
+pub struct Store {
+ root: PathBuf,
+ posts: Vec<Post>,
+}
+
+impl Store {
+ //TODO Result
+ pub fn open(root: PathBuf) -> Option<Self> {
+ Some(Self {
+ posts: Self::open_dir(&root)?,
+ root,
+ })
+ }
+
+ //TODO check if hash matches
+ //TODO Result
+ //TODO overkill? maybe we can use subfolders later on
+ fn open_dir(dir: &Path) -> Option<Vec<Post>> {
+ let mut res = Vec::new();
+ for entry in std::fs::read_dir(dir).ok()? {
+ let entry = entry.ok()?;
+ if entry.file_type().ok()?.is_dir() {
+ let mut posts = Self::open_dir(&entry.path())?;
+ res.append(&mut posts);
+ } else {
+ res.push(Post::open(&entry.path())?);
+ }
+ }
+ Some(res)
+ }
+
+ pub fn write(&self) -> std::io::Result<()> {
+ for post in &self.posts {
+ let mut path = self.root.clone();
+ path.push(format!("{}", post.id()));
+ post.write(&path)?;
+ }
+ Ok(())
+ }
+}
+
+impl Post {
+ pub fn new(transaction: Transaction) -> Self {
+ Self {
+ transaction,
+ removed: false,
+ }
+ }
+
+ fn write<P: AsRef<Path>>(&self, p: &P) -> std::io::Result<()> {
+ fs::write(p, serde_json::to_string_pretty(self).unwrap()) //TODO control pretty or not
+ }
+
+ //TODO Result
+ fn open<P: AsRef<Path>>(p: &P) -> Option<Self> {
+ fs::read_to_string(p).ok().as_ref().and_then(|s| serde_json::from_str(s).ok())
+ }
+
+ fn id(&self) -> u64 {
+ let mut h = XxHash64::default();
+ self.hash(&mut h);
+ h.finish()
+ }
+}
+