From bde98e4785a5798bb9d7dc7f2f70bae701a773a8 Mon Sep 17 00:00:00 2001 From: Jakob Dalsgaard Date: Tue, 13 May 2025 22:28:39 +0200 Subject: [PATCH] adding message functionality, changed args layout --- db/createdb.sql | 20 +++++--- train-cli/src/main.rs | 107 ++++++++++++++++++++++++++++-------------- 2 files changed, 87 insertions(+), 40 deletions(-) diff --git a/db/createdb.sql b/db/createdb.sql index ffa3261..34f28b0 100644 --- a/db/createdb.sql +++ b/db/createdb.sql @@ -24,14 +24,22 @@ create table training ( constraint fk_exercise foreign key (exercise) references exercise(id), constraint fk_account foreign key (account) references account(id) ); +create table message ( + id serial primary key, + account integer not null, + time timestamptz not null default timezone('utc', now()), + message varchar not null, + constraint fk_account foreign key (account) references account(id) +); create view dailylift as select date(time) as time, account, exercise, sum(runs * reps * kilos) as lift from training group by 1, 2, 3; create role training_user; -grant select on account to create_user; -grant select on exercise to create_user; -grant select on shorthand to create_user; -grant insert on training to create_user; -grant select on training to create_user; -grant usage on training_id_seq to create_user; +grant select on account to training_user; +grant select on exercise to training_user; +grant select on shorthand to training_user; +grant insert on training to training_user; +grant select on training to training_user; +grant insert on message to training_user; +grant usage on training_id_seq to training_user; diff --git a/train-cli/src/main.rs b/train-cli/src/main.rs index 2f06bde..f52fedb 100644 --- a/train-cli/src/main.rs +++ b/train-cli/src/main.rs @@ -1,51 +1,74 @@ -use clap::Parser; +use clap::{Parser, Subcommand}; use postgres::{Client, NoTls}; use whoami; use rust_decimal::Decimal; use rust_decimal::prelude::FromPrimitive; use chrono::{DateTime, Local, FixedOffset, TimeZone, NaiveTime, LocalResult, NaiveDateTime}; -#[derive(Parser, Debug)] +#[derive(Parser)] #[command(version, about, long_about = None)] -struct TrainingArgs { - /// name of exercise: abs, squat, bp, biceps, triceps - exercise: String, +struct Cli { + #[command(subcommand)] + command: Commands, +} - /// number of runs - runs: i16, +#[derive(Subcommand)] +enum Commands { + Report { + /// name of exercise: abs, squat, bp, biceps, triceps + exercise: String, - /// number of reps per run - reps: i16, + /// number of runs + runs: i16, - /// kilos - kilos: f32, + /// number of reps per run + reps: i16, + + /// kilos + kilos: f32, + + /// time, either "2025-05-20T14:15:20+0200", "2025-12-24 18:00:00" or "17:15:00" + time: Option, + }, + Message { + /// message to insert + text: String, + + /// time, either "2025-05-20T14:15:20+0200", "2025-12-24 18:00:00" or "17:15:00" + time: Option, + } - /// time, either "2025-05-20T14:15:20+0200", "2025-12-24 18:00:00" or "17:15:00" - time: Option, } fn main() { - let args = TrainingArgs::parse(); - insert_training(args); + let args = Cli::parse(); + match &args.command { + Commands::Report { exercise, runs, reps, kilos, time } => insert_training(exercise, runs, reps, kilos, time), + Commands::Message { text, time } => insert_message(text, time), + } } -fn parse_time (time: String) -> Option> { +fn get_client() -> Client { + Client::connect("dbname=training host=/var/run/postgresql", NoTls).unwrap() +} + +fn parse_time (time: &String) -> Option> { // check if time is an rfc3339 formatted timestamp, i.e. "2025-05-20T14:30:10+0200" (can also // have milliseconds - let datetimetz = DateTime::parse_from_rfc3339(&time); + let datetimetz = DateTime::parse_from_rfc3339(time); if let Ok(dt) = datetimetz { return Some(dt); } // check if time is a simple "date time" without timezone, then apply user session // time zone - let naive_datetime = NaiveDateTime::parse_from_str(&time, "%Y-%m-%d %H:%M:%S"); + let naive_datetime = NaiveDateTime::parse_from_str(time, "%Y-%m-%d %H:%M:%S"); if let Ok(ndt) = naive_datetime { let dt: DateTime = Local.from_local_datetime(&ndt).unwrap(); return Some(dt.into()); } // check if time is merely a simple hour:minute:second -- then apply // current date from user session - let naive_time = NaiveTime::parse_from_str(&time, "%H:%M:%S"); + let naive_time = NaiveTime::parse_from_str(time, "%H:%M:%S"); if let Ok(nt) = naive_time { return match Local::now().with_time(nt) { LocalResult::Single(dt) => Some(dt.into()), @@ -56,27 +79,43 @@ fn parse_time (time: String) -> Option> { return None; } - -fn insert_training (args: TrainingArgs) { - let mut client = Client::connect("dbname=training host=/var/run/postgresql", NoTls).unwrap(); - let dec_kilos = Decimal::from_f32(args.kilos).unwrap(); - let dt = match args.time { +fn with_valid_time(time: &Option, closure: F) ->() + where F: Fn(Client, DateTime) -> () { + let client = get_client(); + let dt = match time { Some(time) => parse_time(time), None => Some(Local::now().into()), }; - match dt { None => println!("Invalid time/date specified"), Some(time) => { - let res = client.execute("insert into training values - (default, (select id from account where login=$1), $2, - (select e.id from exercise e inner join shorthand s on s.exercise=e.id where s.name=$3), $4, $5, $6)", - &[&whoami::username(), &time, &args.exercise, &args.runs, &args.reps, &dec_kilos]); - match res { - Ok(_inserted) => println!("Training inserted"), - Err(e) => println!("Training not inserted, since: {}", e) - } + closure(client, time); } } - +} + + +fn insert_message (message: &String, time: &Option) { + with_valid_time(time, move |mut c, t| { + let res = c.execute("insert into message (account, time, message) values ((select id from account where login=$1), $2, $3)", + &[&whoami::username(), &t, &message]); + match res { + Ok(_inserted) => println!("Message inserted"), + Err(e) => println!("Message not inserted, since {}", e), + } + }); +} + +fn insert_training (exercise: &String, runs: &i16, reps: &i16, kilos: &f32, time: &Option) { + with_valid_time(time, move |mut c, t| { + let dec_kilos = Decimal::from_f32(*kilos).unwrap(); + let res = c.execute("insert into training values + (default, (select id from account where login=$1), $2, + (select e.id from exercise e inner join shorthand s on s.exercise=e.id where s.name=$3), $4, $5, $6)", + &[&whoami::username(), &t, &exercise, &runs, &reps, &dec_kilos]); + match res { + Ok(_inserted) => println!("Training inserted"), + Err(e) => println!("Training not inserted, since: {}", e) + } + }); }