adding message functionality, changed args layout
This commit is contained in:
@@ -24,14 +24,22 @@ create table training (
|
|||||||
constraint fk_exercise foreign key (exercise) references exercise(id),
|
constraint fk_exercise foreign key (exercise) references exercise(id),
|
||||||
constraint fk_account foreign key (account) references account(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
|
create view dailylift as select
|
||||||
date(time) as time, account, exercise, sum(runs * reps * kilos) as lift
|
date(time) as time, account, exercise, sum(runs * reps * kilos) as lift
|
||||||
from training group by 1, 2, 3;
|
from training group by 1, 2, 3;
|
||||||
|
|
||||||
create role training_user;
|
create role training_user;
|
||||||
grant select on account to create_user;
|
grant select on account to training_user;
|
||||||
grant select on exercise to create_user;
|
grant select on exercise to training_user;
|
||||||
grant select on shorthand to create_user;
|
grant select on shorthand to training_user;
|
||||||
grant insert on training to create_user;
|
grant insert on training to training_user;
|
||||||
grant select on training to create_user;
|
grant select on training to training_user;
|
||||||
grant usage on training_id_seq to create_user;
|
grant insert on message to training_user;
|
||||||
|
grant usage on training_id_seq to training_user;
|
||||||
|
|||||||
@@ -1,51 +1,74 @@
|
|||||||
use clap::Parser;
|
use clap::{Parser, Subcommand};
|
||||||
use postgres::{Client, NoTls};
|
use postgres::{Client, NoTls};
|
||||||
use whoami;
|
use whoami;
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
use rust_decimal::prelude::FromPrimitive;
|
use rust_decimal::prelude::FromPrimitive;
|
||||||
use chrono::{DateTime, Local, FixedOffset, TimeZone, NaiveTime, LocalResult, NaiveDateTime};
|
use chrono::{DateTime, Local, FixedOffset, TimeZone, NaiveTime, LocalResult, NaiveDateTime};
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser)]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
struct TrainingArgs {
|
struct Cli {
|
||||||
/// name of exercise: abs, squat, bp, biceps, triceps
|
#[command(subcommand)]
|
||||||
exercise: String,
|
command: Commands,
|
||||||
|
}
|
||||||
|
|
||||||
/// number of runs
|
#[derive(Subcommand)]
|
||||||
runs: i16,
|
enum Commands {
|
||||||
|
Report {
|
||||||
|
/// name of exercise: abs, squat, bp, biceps, triceps
|
||||||
|
exercise: String,
|
||||||
|
|
||||||
/// number of reps per run
|
/// number of runs
|
||||||
reps: i16,
|
runs: i16,
|
||||||
|
|
||||||
/// kilos
|
/// number of reps per run
|
||||||
kilos: f32,
|
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<String>,
|
||||||
|
},
|
||||||
|
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<String>,
|
||||||
|
}
|
||||||
|
|
||||||
/// time, either "2025-05-20T14:15:20+0200", "2025-12-24 18:00:00" or "17:15:00"
|
|
||||||
time: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = TrainingArgs::parse();
|
let args = Cli::parse();
|
||||||
insert_training(args);
|
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<DateTime<FixedOffset>> {
|
fn get_client() -> Client {
|
||||||
|
Client::connect("dbname=training host=/var/run/postgresql", NoTls).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_time (time: &String) -> Option<DateTime<FixedOffset>> {
|
||||||
// check if time is an rfc3339 formatted timestamp, i.e. "2025-05-20T14:30:10+0200" (can also
|
// check if time is an rfc3339 formatted timestamp, i.e. "2025-05-20T14:30:10+0200" (can also
|
||||||
// have milliseconds
|
// have milliseconds
|
||||||
let datetimetz = DateTime::parse_from_rfc3339(&time);
|
let datetimetz = DateTime::parse_from_rfc3339(time);
|
||||||
if let Ok(dt) = datetimetz {
|
if let Ok(dt) = datetimetz {
|
||||||
return Some(dt);
|
return Some(dt);
|
||||||
}
|
}
|
||||||
// check if time is a simple "date time" without timezone, then apply user session
|
// check if time is a simple "date time" without timezone, then apply user session
|
||||||
// time zone
|
// 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 {
|
if let Ok(ndt) = naive_datetime {
|
||||||
let dt: DateTime<Local> = Local.from_local_datetime(&ndt).unwrap();
|
let dt: DateTime<Local> = Local.from_local_datetime(&ndt).unwrap();
|
||||||
return Some(dt.into());
|
return Some(dt.into());
|
||||||
}
|
}
|
||||||
// check if time is merely a simple hour:minute:second -- then apply
|
// check if time is merely a simple hour:minute:second -- then apply
|
||||||
// current date from user session
|
// 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 {
|
if let Ok(nt) = naive_time {
|
||||||
return match Local::now().with_time(nt) {
|
return match Local::now().with_time(nt) {
|
||||||
LocalResult::Single(dt) => Some(dt.into()),
|
LocalResult::Single(dt) => Some(dt.into()),
|
||||||
@@ -56,27 +79,43 @@ fn parse_time (time: String) -> Option<DateTime<FixedOffset>> {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn with_valid_time<F>(time: &Option<String>, closure: F) ->()
|
||||||
fn insert_training (args: TrainingArgs) {
|
where F: Fn(Client, DateTime<FixedOffset>) -> () {
|
||||||
let mut client = Client::connect("dbname=training host=/var/run/postgresql", NoTls).unwrap();
|
let client = get_client();
|
||||||
let dec_kilos = Decimal::from_f32(args.kilos).unwrap();
|
let dt = match time {
|
||||||
let dt = match args.time {
|
|
||||||
Some(time) => parse_time(time),
|
Some(time) => parse_time(time),
|
||||||
None => Some(Local::now().into()),
|
None => Some(Local::now().into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
match dt {
|
match dt {
|
||||||
None => println!("Invalid time/date specified"),
|
None => println!("Invalid time/date specified"),
|
||||||
Some(time) => {
|
Some(time) => {
|
||||||
let res = client.execute("insert into training values
|
closure(client, time);
|
||||||
(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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn insert_message (message: &String, time: &Option<String>) {
|
||||||
|
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<String>) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user