Skip to content

Commit

Permalink
refactor: Handle SQLite storage in a dedicated SqliteBirthdayStorage …
Browse files Browse the repository at this point in the history
…struct
  • Loading branch information
ducdetronquito committed Feb 2, 2024
1 parent c442ca4 commit be810c6
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 70 deletions.
122 changes: 57 additions & 65 deletions src/birthday_store.rs
Original file line number Diff line number Diff line change
@@ -1,81 +1,73 @@
use std::path::{Path, PathBuf};
use std::path::Path;

use anyhow::Result;
use chrono::{NaiveDate, NaiveDateTime};
use directories::ProjectDirs;
use rusqlite::Connection;

use crate::Birthday;

pub fn add(name: String, date: NaiveDate) -> Result<()> {
let db = get_db()?;
let timestamp = to_timestamp(date);
db.execute(
"INSERT INTO birthdays(name, date_timestamp) VALUES(?1, ?2)",
(name, timestamp),
)?;
Ok(())
pub struct SqliteBirthdayStore {
conn: Connection,
}

fn get_db_path() -> Result<PathBuf> {
let mut data_dir = match std::env::var("BIRTHDAY_DATA") {
Ok(path) => PathBuf::from(path),
Err(_) => match ProjectDirs::from("", "", "birthday") {
Some(project_dirs) => project_dirs.data_dir().to_owned(),
None => Path::new("./").to_owned(),
},
};

std::fs::create_dir_all(&data_dir)?;
data_dir.push("test.db");
Ok(data_dir)
}
impl SqliteBirthdayStore {
pub fn open(data_dir: &Path, store_name: String) -> Result<SqliteBirthdayStore> {
let mut db_path = data_dir.to_owned();
db_path.push(store_name + ".db");
let conn = Connection::open(db_path)?;
conn.execute(
"CREATE TABLE IF NOT EXISTS birthdays (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
date_timestamp INTEGER NOT NULL
) STRICT",
(),
)?;
Ok(SqliteBirthdayStore { conn })
}

pub fn get_all() -> Result<Vec<Birthday>> {
let db = get_db()?;
let mut statement = db.prepare("SELECT id, name, date_timestamp FROM birthdays")?;
let birthday_iter = statement.query_map([], |row| {
let id = row.get(0)?;
let name = row.get(1)?;
let timestamp = row.get(2)?;
let date = from_timestamp(timestamp);
Ok(Birthday { id, name, date })
})?;
let birthdays = birthday_iter.collect::<Result<Vec<Birthday>, rusqlite::Error>>()?;
Ok(birthdays)
}
pub fn add(&self, name: String, date: NaiveDate) -> Result<()> {
let timestamp = to_timestamp(date);
self.conn.execute(
"INSERT INTO birthdays(name, date_timestamp) VALUES(?1, ?2)",
(name, timestamp),
)?;
Ok(())
}

pub fn remove(id: i32) -> Result<Option<Birthday>> {
let db = get_db()?;
let mut statement =
db.prepare("DELETE FROM birthdays WHERE id = :id RETURNING id, name, date_timestamp")?;
let birthday_iter = statement.query_map(&[(":id", id.to_string().as_str())], |row| {
let id = row.get(0)?;
let name = row.get(1)?;
let timestamp = row.get(2)?;
let date = from_timestamp(timestamp);
Ok(Birthday { id, name, date })
})?;
let birthdays = birthday_iter.collect::<Result<Vec<Birthday>, rusqlite::Error>>()?;
if birthdays.is_empty() {
Ok(None)
} else {
Ok(Some(birthdays[0].clone()))
pub fn get_all(&self) -> Result<Vec<Birthday>> {
let mut statement = self
.conn
.prepare("SELECT id, name, date_timestamp FROM birthdays")?;
let birthday_iter = statement.query_map([], |row| {
let id = row.get(0)?;
let name = row.get(1)?;
let timestamp = row.get(2)?;
let date = from_timestamp(timestamp);
Ok(Birthday { id, name, date })
})?;
let birthdays = birthday_iter.collect::<Result<Vec<Birthday>, rusqlite::Error>>()?;
Ok(birthdays)
}
}

fn get_db() -> Result<Connection> {
let db_path = get_db_path()?;
let db = Connection::open(db_path)?;
db.execute(
"CREATE TABLE IF NOT EXISTS birthdays (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
date_timestamp INTEGER NOT NULL
) STRICT",
(),
)?;
Ok(db)
pub fn remove(&self, id: i32) -> Result<Option<Birthday>> {
let mut statement = self
.conn
.prepare("DELETE FROM birthdays WHERE id = :id RETURNING id, name, date_timestamp")?;
let birthday_iter = statement.query_map(&[(":id", id.to_string().as_str())], |row| {
let id = row.get(0)?;
let name = row.get(1)?;
let timestamp = row.get(2)?;
let date = from_timestamp(timestamp);
Ok(Birthday { id, name, date })
})?;
let birthdays = birthday_iter.collect::<Result<Vec<Birthday>, rusqlite::Error>>()?;
if birthdays.is_empty() {
Ok(None)
} else {
Ok(Some(birthdays[0].clone()))
}
}
}

fn to_timestamp(date: NaiveDate) -> i64 {
Expand Down
38 changes: 33 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,46 @@
mod birthday;
mod birthday_store;
use std::path::{Path, PathBuf};

use anyhow::{bail, Result};
pub use birthday::Birthday;
use birthday_store::SqliteBirthdayStore;
use chrono::{Datelike, NaiveDate};
use directories::ProjectDirs;

fn get_db_path() -> Result<PathBuf> {
let mut data_dir = match std::env::var("BIRTHDAY_DATA") {
Ok(path) => PathBuf::from(path),
Err(_) => match ProjectDirs::from("", "", "birthday") {
Some(project_dirs) => project_dirs.data_dir().to_owned(),
None => Path::new("./").to_owned(),
},
};

std::fs::create_dir_all(&data_dir)?;
data_dir.push("test.db");
Ok(data_dir)
}

fn open_store() -> Result<SqliteBirthdayStore> {
let db_path = get_db_path()?;
SqliteBirthdayStore::open(db_path.as_path(), "birthday".to_string())
}

pub fn add(name: String, day: u32, month: u32, year: i32) -> Result<()> {
let store = open_store()?;
let birthdate = NaiveDate::from_ymd_opt(year, month, day).expect("Invalid date");
birthday_store::add(name, birthdate)
store.add(name, birthdate)
}

pub fn get_all() -> Result<Vec<Birthday>> {
birthday_store::get_all()
let store = open_store()?;
store.get_all()
}

pub fn get_next(today: NaiveDate) -> Result<Option<Birthday>> {
let mut birthdays = birthday_store::get_all()?;
let store = open_store()?;
let mut birthdays = store.get_all()?;
birthdays.sort_by_key(|birthday| birthday.next(today));
Ok(birthdays.into_iter().next())
}
Expand All @@ -25,7 +51,8 @@ pub fn search(
month: Option<u32>,
day: Option<u32>,
) -> Result<Vec<Birthday>> {
let mut birthdays = birthday_store::get_all()?;
let store = open_store()?;
let mut birthdays = store.get_all()?;

if let Some(name) = name {
birthdays.retain(|birthday| birthday.name.contains(&name));
Expand Down Expand Up @@ -53,5 +80,6 @@ pub fn search(
}

pub fn forget(id: i32) -> Result<Option<Birthday>> {
birthday_store::remove(id)
let store = open_store()?;
store.remove(id)
}

0 comments on commit be810c6

Please sign in to comment.