Edit; List; Delete functionality
This commit is contained in:
parent
6a3620ae7d
commit
a0f9877f54
4 changed files with 345 additions and 45 deletions
BIN
ingestions.db
BIN
ingestions.db
Binary file not shown.
|
@ -1,4 +1,4 @@
|
|||
use crate::{SubstanceEntry, Unit};
|
||||
use crate::{IngestionEntry, Unit};
|
||||
use sqlite::{Connection, Result};
|
||||
|
||||
pub fn init_db_conn() -> Result<Connection> {
|
||||
|
@ -45,7 +45,7 @@ pub fn create_db() -> Result<()> {
|
|||
// Err(e) => e,
|
||||
// };
|
||||
// }
|
||||
pub fn get_all_ingestions(conn: &sqlite::Connection) -> Result<Vec<SubstanceEntry>> {
|
||||
pub fn get_all_ingestions(conn: &sqlite::Connection) -> Result<Vec<IngestionEntry>> {
|
||||
let mut statement = conn.prepare(
|
||||
"SELECT substance, route, amount, unit, ingestion_time FROM ingestions ORDER BY ingestion_time DESC"
|
||||
)?;
|
||||
|
@ -80,7 +80,7 @@ pub fn get_all_ingestions(conn: &sqlite::Connection) -> Result<Vec<SubstanceEntr
|
|||
Err(_) => continue, // Skip invalid dates
|
||||
};
|
||||
|
||||
result.push(SubstanceEntry {
|
||||
result.push(IngestionEntry {
|
||||
substance,
|
||||
route,
|
||||
amount,
|
||||
|
@ -93,7 +93,7 @@ pub fn get_all_ingestions(conn: &sqlite::Connection) -> Result<Vec<SubstanceEntr
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn save_ingestion_to_db(conn: &sqlite::Connection, entry: &SubstanceEntry) -> Result<()> {
|
||||
pub fn save_ingestion_to_db(conn: &sqlite::Connection, entry: &IngestionEntry) -> Result<()> {
|
||||
// Prepare the statement with named parameters
|
||||
let query = format!(
|
||||
"INSERT INTO ingestions (substance, route, amount, unit, ingestion_time, created_at)
|
||||
|
|
98
src/main.rs
98
src/main.rs
|
@ -3,6 +3,7 @@ use db::create_db;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::error::Error;
|
||||
use strum::EnumIter;
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::Display;
|
||||
use util::gather_ingestion_data;
|
||||
|
||||
|
@ -12,6 +13,14 @@ use util::read_substances_from_file;
|
|||
mod db;
|
||||
use db::init_db_conn;
|
||||
|
||||
#[derive(Debug, Display, Serialize, Deserialize, EnumIter)]
|
||||
pub enum Command {
|
||||
AddIngestion,
|
||||
EditIngestion,
|
||||
ListIngestions,
|
||||
DeleteIngestion,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Display, Serialize, Deserialize, EnumIter)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum Unit {
|
||||
|
@ -22,7 +31,7 @@ pub enum Unit {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SubstanceEntry {
|
||||
pub struct IngestionEntry {
|
||||
substance: String,
|
||||
route: String,
|
||||
amount: f64,
|
||||
|
@ -49,46 +58,59 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
|
||||
println!("Successfully loaded {} substances", substances.len());
|
||||
|
||||
let ingestion = gather_ingestion_data(substances)?;
|
||||
let command =
|
||||
inquire::Select::new("What do you want to do?", Command::iter().collect()).prompt()?;
|
||||
match command {
|
||||
Command::AddIngestion => {
|
||||
let ingestion = gather_ingestion_data(substances)?;
|
||||
|
||||
// Display the recorded information
|
||||
println!("\nRecorded Information:");
|
||||
println!("---------------------");
|
||||
println!("Substance: {}", ingestion.entry.substance);
|
||||
println!("Route: {}", ingestion.entry.route);
|
||||
println!("Amount: {}{}", ingestion.entry.amount, ingestion.entry.unit);
|
||||
println!(
|
||||
"Time: {}\n",
|
||||
ingestion.entry.ingestion_time.format("%Y-%m-%d %H:%M")
|
||||
);
|
||||
// Display the recorded information
|
||||
println!("\nRecorded Information:");
|
||||
println!("---------------------");
|
||||
println!("Substance: {}", ingestion.entry.substance);
|
||||
println!("Route: {}", ingestion.entry.route);
|
||||
println!("Amount: {}{}", ingestion.entry.amount, ingestion.entry.unit);
|
||||
println!(
|
||||
"Time: {}\n",
|
||||
ingestion.entry.ingestion_time.format("%Y-%m-%d %H:%M")
|
||||
);
|
||||
|
||||
// Show onset, duration, and after-effects if available
|
||||
if let Some(onset) = ingestion.data["formatted_onset"]["value"].as_str() {
|
||||
let unit = ingestion.data["formatted_onset"]["_unit"]
|
||||
.as_str()
|
||||
.unwrap_or("");
|
||||
println!("Expected onset: {} {}", onset, unit);
|
||||
// Show onset, duration, and after-effects if available
|
||||
if let Some(onset) = ingestion.data["formatted_onset"]["value"].as_str() {
|
||||
let unit = ingestion.data["formatted_onset"]["_unit"]
|
||||
.as_str()
|
||||
.unwrap_or("");
|
||||
println!("Expected onset: {} {}", onset, unit);
|
||||
}
|
||||
|
||||
if let Some(duration) = ingestion.data["formatted_duration"]["value"].as_str() {
|
||||
let unit = ingestion.data["formatted_duration"]["_unit"]
|
||||
.as_str()
|
||||
.unwrap_or("");
|
||||
println!("Expected duration: {} {}", duration, unit);
|
||||
}
|
||||
|
||||
if let Some(aftereffects) = ingestion.data["formatted_aftereffects"]["value"].as_str() {
|
||||
let unit = ingestion.data["formatted_aftereffects"]["_unit"]
|
||||
.as_str()
|
||||
.unwrap_or("");
|
||||
println!("Expected after-effects: {} {}", aftereffects, unit);
|
||||
}
|
||||
|
||||
if inquire::prompt_confirmation("Save to DB?").unwrap() {
|
||||
db::save_ingestion_to_db(&init_db_conn()?, &ingestion.entry)?;
|
||||
let _ingestions = db::get_all_ingestions(&init_db_conn()?)?;
|
||||
println!("Entry saved successfully");
|
||||
};
|
||||
}
|
||||
Command::ListIngestions => {
|
||||
util::list_ingestions()?;
|
||||
}
|
||||
Command::DeleteIngestion => {
|
||||
util::delete_ingestion_entries(&init_db_conn()?)?;
|
||||
}
|
||||
Command::EditIngestion => util::edit_ingestion_entry(&init_db_conn()?)?,
|
||||
}
|
||||
|
||||
if let Some(duration) = ingestion.data["formatted_duration"]["value"].as_str() {
|
||||
let unit = ingestion.data["formatted_duration"]["_unit"]
|
||||
.as_str()
|
||||
.unwrap_or("");
|
||||
println!("Expected duration: {} {}", duration, unit);
|
||||
}
|
||||
|
||||
if let Some(aftereffects) = ingestion.data["formatted_aftereffects"]["value"].as_str() {
|
||||
let unit = ingestion.data["formatted_aftereffects"]["_unit"]
|
||||
.as_str()
|
||||
.unwrap_or("");
|
||||
println!("Expected after-effects: {} {}", aftereffects, unit);
|
||||
}
|
||||
|
||||
if inquire::prompt_confirmation("Save to DB?").unwrap() {
|
||||
db::save_ingestion_to_db(&init_db_conn()?, &ingestion.entry)?;
|
||||
let _ingestions = db::get_all_ingestions(&init_db_conn()?)?;
|
||||
println!("Entry saved successfully");
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
284
src/util.rs
284
src/util.rs
|
@ -8,11 +8,11 @@ use std::io::BufReader;
|
|||
use std::path::Path;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::{SubstanceEntry, Unit};
|
||||
use crate::{IngestionEntry, Unit, db::init_db_conn};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct IngestionResponse {
|
||||
pub entry: SubstanceEntry,
|
||||
pub entry: IngestionEntry,
|
||||
pub data: Value,
|
||||
}
|
||||
|
||||
|
@ -130,7 +130,7 @@ pub fn gather_ingestion_data(
|
|||
.unwrap();
|
||||
|
||||
// Create an entry
|
||||
let entry = SubstanceEntry {
|
||||
let entry = IngestionEntry {
|
||||
substance: substance.clone(),
|
||||
route,
|
||||
amount,
|
||||
|
@ -144,3 +144,281 @@ pub fn gather_ingestion_data(
|
|||
};
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub fn list_ingestions() -> Result<(), Box<dyn Error>> {
|
||||
let mut ingestions: Vec<IngestionEntry> = crate::db::get_all_ingestions(&init_db_conn()?)?;
|
||||
ingestions.reverse();
|
||||
for ingestion in ingestions {
|
||||
println!(
|
||||
"Date: {}",
|
||||
ingestion.ingestion_time.format("%d/%m/%Y %H:%M")
|
||||
);
|
||||
println!(
|
||||
"Substance: {}",
|
||||
capitalize_first_letter(&ingestion.substance)
|
||||
);
|
||||
println!("Route of administration: {}", ingestion.route);
|
||||
println!("Ingested amount: {}{}", ingestion.amount, ingestion.unit);
|
||||
println!("");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_ingestion_entries(conn: &sqlite::Connection) -> Result<(), Box<dyn Error>> {
|
||||
let entries = crate::db::get_all_ingestions(conn)?;
|
||||
|
||||
if entries.is_empty() {
|
||||
println!("No ingestion entries found in the database.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let entry_options: Vec<String> = entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, entry)| {
|
||||
format!(
|
||||
"#{}: {} {} of {} via {} on {}",
|
||||
i + 1,
|
||||
entry.amount,
|
||||
entry.unit,
|
||||
entry.substance,
|
||||
entry.route,
|
||||
entry.ingestion_time.format("%Y-%m-%d %H:%M")
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut options = entry_options.clone();
|
||||
options.push("Cancel - Don't delete anything".to_string());
|
||||
|
||||
let selection = Select::new("Select an entry to delete:", options).prompt()?;
|
||||
|
||||
if selection == "Cancel - Don't delete anything" {
|
||||
println!("Operation canceled. No entries were deleted.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Find the selected entry index
|
||||
let selected_index = entry_options.iter().position(|e| e == &selection).unwrap();
|
||||
let selected_entry = &entries[selected_index];
|
||||
|
||||
let confirm = Select::new(
|
||||
&format!("Are you sure you want to delete this entry: {}?", selection),
|
||||
vec!["No, cancel", "Yes, delete it"],
|
||||
)
|
||||
.prompt()?;
|
||||
|
||||
if confirm == "Yes, delete it" {
|
||||
let query = format!(
|
||||
"DELETE FROM ingestions WHERE substance = '{}' AND route = '{}' AND amount = {} AND unit = '{}' AND ingestion_time = '{}'",
|
||||
selected_entry.substance,
|
||||
selected_entry.route,
|
||||
selected_entry.amount,
|
||||
selected_entry.unit.to_string().to_lowercase(),
|
||||
selected_entry.ingestion_time.to_rfc3339()
|
||||
);
|
||||
|
||||
match conn.execute(query) {
|
||||
Ok(_) => println!("Entry deleted successfully."),
|
||||
Err(e) => println!("Failed to delete entry: {}", e),
|
||||
}
|
||||
} else {
|
||||
println!("Deletion canceled.");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn edit_ingestion_entry(conn: &sqlite::Connection) -> Result<(), Box<dyn Error>> {
|
||||
// First, get all entries to display them
|
||||
let entries = crate::db::get_all_ingestions(conn)?;
|
||||
|
||||
if entries.is_empty() {
|
||||
println!("No ingestion entries found in the database.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Create formatted strings for selection
|
||||
let entry_options: Vec<String> = entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, entry)| {
|
||||
format!(
|
||||
"#{}: {} {} of {} via {} on {}",
|
||||
i + 1,
|
||||
entry.amount,
|
||||
entry.unit,
|
||||
entry.substance,
|
||||
entry.route,
|
||||
entry.ingestion_time.format("%Y-%m-%d %H:%M")
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Add an option to cancel
|
||||
let mut options = entry_options.clone();
|
||||
options.push("Cancel - Don't edit anything".to_string());
|
||||
|
||||
// Prompt the user to select an entry to edit
|
||||
let selection = Select::new("Select an entry to edit:", options).prompt()?;
|
||||
|
||||
// Check if the user selected the cancel option
|
||||
if selection == "Cancel - Don't edit anything" {
|
||||
println!("Operation canceled. No entries were edited.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Find the selected entry index
|
||||
let selected_index = entry_options.iter().position(|e| e == &selection).unwrap();
|
||||
let selected_entry = &entries[selected_index];
|
||||
|
||||
// Store the original values for the WHERE clause in the update query
|
||||
let original_substance = selected_entry.substance.clone();
|
||||
let original_route = selected_entry.route.clone();
|
||||
let original_amount = selected_entry.amount;
|
||||
let original_unit = selected_entry.unit.to_string().to_lowercase();
|
||||
let original_time = selected_entry.ingestion_time.to_rfc3339();
|
||||
|
||||
// Create a new entry with the original values as defaults
|
||||
// Load substances from JSON file for selection
|
||||
let substances = read_substances_from_file("drugs.json")?;
|
||||
// let substances = match read_substances_from_file("drugs_example.json") {
|
||||
// Ok(s) => s,
|
||||
// Err(_) => {
|
||||
// eprintln!("Error loading substances file: {}", e);
|
||||
// return Err(());
|
||||
// }
|
||||
// };
|
||||
|
||||
// Create a list of substance names for selection
|
||||
let mut substance_names: Vec<String> = substances.keys().map(|k| k.to_string()).collect();
|
||||
substance_names.sort();
|
||||
|
||||
// Select a substance (default to the original)
|
||||
let substance = Select::new("Select a substance:", substance_names.clone())
|
||||
.with_starting_cursor(
|
||||
substance_names
|
||||
.clone()
|
||||
.iter()
|
||||
.position(|s| s == &selected_entry.substance)
|
||||
.unwrap_or(0),
|
||||
)
|
||||
.prompt()?;
|
||||
|
||||
// Get substance data
|
||||
let substance_data = &substances[&substance];
|
||||
|
||||
// Extract routes of administration
|
||||
let mut routes = Vec::new();
|
||||
if let Some(formatted_dose) = substance_data["formatted_dose"].as_object() {
|
||||
routes = formatted_dose.keys().map(|k| k.to_string()).collect();
|
||||
} else {
|
||||
// Default routes if not specified in the JSON
|
||||
routes = vec![
|
||||
"Oral".to_string(),
|
||||
"Insufflated".to_string(),
|
||||
"Sublingual".to_string(),
|
||||
];
|
||||
}
|
||||
|
||||
// Select route of administration (default to the original if it exists in the routes)
|
||||
let default_route_index = routes
|
||||
.iter()
|
||||
.position(|r| r == &selected_entry.route)
|
||||
.unwrap_or(0);
|
||||
let route = Select::new("Select route of administration:", routes)
|
||||
.with_starting_cursor(default_route_index)
|
||||
.prompt()?;
|
||||
|
||||
// Ask for the amount (default to the original)
|
||||
let amount = CustomType::<f64>::new("Enter the amount ingested (numeric value):")
|
||||
.with_parser(&|input| match input.parse::<f64>() {
|
||||
Ok(value) => Ok(value),
|
||||
Err(_) => Err(()),
|
||||
})
|
||||
.with_error_message("Please enter a valid number")
|
||||
.with_default(selected_entry.amount)
|
||||
.prompt()?;
|
||||
|
||||
// Ask for the unit (default to the original)
|
||||
let unit_options = vec![Unit::Ug, Unit::Mg, Unit::G, Unit::Ml];
|
||||
let default_unit_index = unit_options
|
||||
.iter()
|
||||
.position(|u| u.to_string().to_lowercase() == original_unit)
|
||||
.unwrap_or(0);
|
||||
let unit = Select::new("Select unit:", unit_options)
|
||||
.with_starting_cursor(default_unit_index)
|
||||
.prompt()?;
|
||||
|
||||
// Ask for the ingestion time (default to the original)
|
||||
let default_date = selected_entry.ingestion_time.date().naive_local();
|
||||
let ingestion_date = DateSelect::new("Select ingestion date:")
|
||||
.with_default(default_date)
|
||||
.prompt()?;
|
||||
|
||||
let default_time = selected_entry.ingestion_time.time();
|
||||
let ingestion_time = CustomType::<chrono::NaiveTime>::new("Enter ingestion time (HH:MM):")
|
||||
.with_parser(
|
||||
&|input| match chrono::NaiveTime::parse_from_str(input, "%H:%M") {
|
||||
Ok(time) => Ok(time),
|
||||
Err(_) => Err(()),
|
||||
},
|
||||
)
|
||||
.with_error_message("Please enter a valid time in the format HH:MM")
|
||||
.with_default(default_time)
|
||||
.prompt()?;
|
||||
|
||||
// Combine date and time
|
||||
let datetime = chrono::Local
|
||||
.from_local_datetime(&ingestion_date.and_time(ingestion_time))
|
||||
.single()
|
||||
.unwrap();
|
||||
|
||||
// Create updated entry
|
||||
let updated_entry = IngestionEntry {
|
||||
substance: substance.clone(),
|
||||
route,
|
||||
amount,
|
||||
unit,
|
||||
ingestion_time: datetime,
|
||||
created_at: None,
|
||||
};
|
||||
|
||||
// Confirm update
|
||||
let confirm = Select::new(
|
||||
"Are you sure you want to update this entry?",
|
||||
vec!["No, cancel", "Yes, update it"],
|
||||
)
|
||||
.prompt()?;
|
||||
|
||||
if confirm == "Yes, update it" {
|
||||
// Update the entry in the database
|
||||
let query = format!(
|
||||
"UPDATE ingestions SET substance = '{}', route = '{}', amount = {}, unit = '{}', ingestion_time = '{}'
|
||||
WHERE substance = '{}' AND route = '{}' AND amount = {} AND unit = '{}' AND ingestion_time = '{}'",
|
||||
updated_entry.substance,
|
||||
updated_entry.route,
|
||||
updated_entry.amount,
|
||||
updated_entry.unit.to_string().to_lowercase(),
|
||||
updated_entry.ingestion_time.to_rfc3339(),
|
||||
original_substance,
|
||||
original_route,
|
||||
original_amount,
|
||||
original_unit,
|
||||
original_time
|
||||
);
|
||||
|
||||
match conn.execute(query) {
|
||||
Ok(_) => println!("Entry updated successfully."),
|
||||
Err(e) => println!("Failed to update entry: {}", e),
|
||||
}
|
||||
} else {
|
||||
println!("Update canceled.");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn capitalize_first_letter(s: &str) -> String {
|
||||
s[0..1].to_uppercase() + &s[1..]
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue