Compare commits
9 Commits
b8efec9295
...
v0.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 94bc22071a | |||
| 0c706f73c6 | |||
| 8fc449d36a | |||
| 3a3d121511 | |||
| 090d869bad | |||
| bde98e4785 | |||
| bd74c2897f | |||
| 53f488e40c | |||
| 8ebb72b4e6 |
@@ -11,7 +11,18 @@ create table account (
|
|||||||
id serial primary key,
|
id serial primary key,
|
||||||
name varchar not null,
|
name varchar not null,
|
||||||
login varchar not null,
|
login varchar not null,
|
||||||
birthdate date not null
|
birthdate date not null,
|
||||||
|
bodymass integer,
|
||||||
|
constraint fk_bodymass foreign key (bodymass) references bodymass(id)
|
||||||
|
);
|
||||||
|
create table bodymass (
|
||||||
|
id serial primary key,
|
||||||
|
account integer not null,
|
||||||
|
time timestamptz not null,
|
||||||
|
kilos numeric(4,1) not null,
|
||||||
|
constraint fk_account foreign key (account) references account(id),
|
||||||
|
constraint unique_account_time unique (account, time),
|
||||||
|
constraint time_not_infty check (time <> 'infinity'::timestamptz)
|
||||||
);
|
);
|
||||||
create table training (
|
create table training (
|
||||||
id serial primary key,
|
id serial primary key,
|
||||||
@@ -24,8 +35,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_trunc('day', 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;
|
||||||
|
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;
|
||||||
|
|||||||
26
db/readme.md
Normal file
26
db/readme.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
## Joining to bodymass table by time
|
||||||
|
|
||||||
|
Data in the bodymass table can be transformed as (for account 1):
|
||||||
|
|
||||||
|
```sql
|
||||||
|
select kilos, time as start, (case when lead(time) over (order by time asc) is null then 'infinity'::timestamptz else lead(time) over (order by time asc) end) as end from bodymass where account=1;
|
||||||
|
```
|
||||||
|
|
||||||
|
Thereby giving a table with clear timespans. Joining can then be performed by a nested table:
|
||||||
|
|
||||||
|
```
|
||||||
|
with bm_report as (
|
||||||
|
select kilos, time as start,
|
||||||
|
(case when lead(time) over (order by time asc) is null
|
||||||
|
then 'infinity'::timestamptz
|
||||||
|
else lead(time) over (order by time asc) end) as end from bodymass
|
||||||
|
where account=1)
|
||||||
|
select
|
||||||
|
t.time, t.exercise, t.kilos, b.kilos as bodymass, round(100*t.kilos/b.kilos, 1) as ratio
|
||||||
|
from training t
|
||||||
|
inner join bm_report b on t.time >= b.start and t.time < b.end
|
||||||
|
where t.account=1 and exercise=1
|
||||||
|
order by 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
286
grafana/training-dashboard.json
Normal file
286
grafana/training-dashboard.json
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"builtIn": 1,
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana",
|
||||||
|
"uid": "-- Grafana --"
|
||||||
|
},
|
||||||
|
"enable": true,
|
||||||
|
"hide": true,
|
||||||
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
|
"name": "Annotations & Alerts",
|
||||||
|
"type": "dashboard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 1,
|
||||||
|
"id": 22,
|
||||||
|
"links": [],
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-postgresql-datasource",
|
||||||
|
"uid": "aelloyt3t3q4gd"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineStyle": {
|
||||||
|
"fill": "solid"
|
||||||
|
},
|
||||||
|
"lineWidth": 4,
|
||||||
|
"pointSize": 10,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"spanNulls": true,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "masskg"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 12,
|
||||||
|
"w": 18,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 1,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.6.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-postgresql-datasource",
|
||||||
|
"uid": "aelloyt3t3q4gd"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"format": "time_series",
|
||||||
|
"rawQuery": true,
|
||||||
|
"rawSql": "select t.time, e.name as metric, t.kilos \nfrom training t \ninner join exercise e on e.id = t.exercise \nwhere t.account=$account\n and $__timeFilter(time) \norder by 1,2;\n",
|
||||||
|
"refId": "A",
|
||||||
|
"sql": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"parameters": [],
|
||||||
|
"type": "function"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"groupBy": [
|
||||||
|
{
|
||||||
|
"property": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "groupBy"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"limit": 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Kilos per Exercise",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-postgresql-datasource",
|
||||||
|
"uid": "aelloyt3t3q4gd"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"fillOpacity": 80,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"lineWidth": 1,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "masst"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 12,
|
||||||
|
"w": 18,
|
||||||
|
"x": 0,
|
||||||
|
"y": 12
|
||||||
|
},
|
||||||
|
"id": 2,
|
||||||
|
"options": {
|
||||||
|
"barRadius": 0,
|
||||||
|
"barWidth": 0.97,
|
||||||
|
"fullHighlight": false,
|
||||||
|
"groupWidth": 0.7,
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"orientation": "auto",
|
||||||
|
"showValue": "auto",
|
||||||
|
"stacking": "none",
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
},
|
||||||
|
"xTickLabelRotation": 0,
|
||||||
|
"xTickLabelSpacing": 0
|
||||||
|
},
|
||||||
|
"pluginVersion": "11.6.0",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-postgresql-datasource",
|
||||||
|
"uid": "aelloyt3t3q4gd"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"format": "time_series",
|
||||||
|
"rawQuery": true,
|
||||||
|
"rawSql": "select d.time, e.name as metric, d.lift/1000\nfrom dailylift d \ninner join exercise e on e.id = d.exercise \nwhere d.account=$account\n and $__timeFilter(time) \norder by 1,2;",
|
||||||
|
"refId": "A",
|
||||||
|
"sql": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"parameters": [],
|
||||||
|
"type": "function"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"groupBy": [
|
||||||
|
{
|
||||||
|
"property": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "groupBy"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"limit": 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Daily Lift",
|
||||||
|
"type": "barchart"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"preload": false,
|
||||||
|
"schemaVersion": 41,
|
||||||
|
"tags": [],
|
||||||
|
"templating": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"current": {
|
||||||
|
"text": "Jakob Dalsgaard",
|
||||||
|
"value": "1"
|
||||||
|
},
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-postgresql-datasource",
|
||||||
|
"uid": "aelloyt3t3q4gd"
|
||||||
|
},
|
||||||
|
"definition": "select id as __value, name as __text from account;",
|
||||||
|
"name": "account",
|
||||||
|
"options": [],
|
||||||
|
"query": "select id as __value, name as __text from account;",
|
||||||
|
"refresh": 1,
|
||||||
|
"regex": "",
|
||||||
|
"type": "query"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-7d",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {},
|
||||||
|
"timezone": "utc",
|
||||||
|
"title": "Exercise",
|
||||||
|
"uid": "eeln0f0zcu2gwe",
|
||||||
|
"version": 17
|
||||||
|
}
|
||||||
37
readme.md
37
readme.md
@@ -41,16 +41,16 @@ of unix domain socket in `/var/run/postgresql`.
|
|||||||
Now training can be invoked with:
|
Now training can be invoked with:
|
||||||
|
|
||||||
```
|
```
|
||||||
train squat 3 10 60
|
train report squat 3 10 60
|
||||||
```
|
```
|
||||||
|
|
||||||
Which would register squauts, 3 runs of 10 reps of 60kg -- at local time and date. Optionally a
|
Which would register squauts, 3 runs of 10 reps of 60kg -- at local time and date. Optionally a
|
||||||
time, date time or rfc3339 timestamp can be specified:
|
time, date time or rfc3339 timestamp can be specified:
|
||||||
|
|
||||||
```
|
```
|
||||||
train squat 3 10 60 "12:05:00"
|
train report squat 3 10 60 "12:05:00"
|
||||||
train squat 3 10 60 "2025-12-24 18:00:00"
|
train report squat 3 10 60 "2025-12-24 18:00:00"
|
||||||
train squat 3 10 60 "2025-12-24T18:00:00+0200"
|
train report squat 3 10 60 "2025-12-24T18:00:00+0200"
|
||||||
```
|
```
|
||||||
|
|
||||||
The two former will source missing date and timezone information from the user session, i.e.
|
The two former will source missing date and timezone information from the user session, i.e.
|
||||||
@@ -59,7 +59,32 @@ type `date` on your commandline to see what you have.
|
|||||||
When travelling you might opt for specifying a specific location on the command line, like:
|
When travelling you might opt for specifying a specific location on the command line, like:
|
||||||
|
|
||||||
```
|
```
|
||||||
TZ=Australia/Sydney train squat 3 10 60
|
TZ=Australia/Sydney train report squat 3 10 60
|
||||||
```
|
```
|
||||||
|
|
||||||
Data in the database can be visualized with, for example, Grafana, more info to follow.
|
To insert messages into the graph, use the `messsage` command:
|
||||||
|
|
||||||
|
```
|
||||||
|
train message "Still F55.3"
|
||||||
|
```
|
||||||
|
|
||||||
|
The message command takes time as an optional 2nd arguments, just as the report command.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Grafana Visaulisation
|
||||||
|
=====================
|
||||||
|
|
||||||
|
In `grafana/training-dashboard.json` a small dashboard is presented, a Postgres data source
|
||||||
|
must be defined, and the data base user must have access to select from tables in the training database.
|
||||||
|
|
||||||
|
```
|
||||||
|
grant select on account to grafana_query;
|
||||||
|
grant select on training to grafan_query;
|
||||||
|
grant select on exercise to grafan_query;
|
||||||
|
grant select on dailylist to grafan_query;
|
||||||
|
grant select on message to grafana_query;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
2541
train-cli/Cargo.lock
generated
Normal file
2541
train-cli/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,16 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "train-cli"
|
name = "train-cli"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
postgres = { version = "0.19.10", features = ["with-chrono-0_4"] }
|
postgres = { version = "0.19.12", features = ["with-chrono-0_4"] }
|
||||||
sqlx = { version = "0.8.5", features = ["rust_decimal"] }
|
sqlx = { version = "0.8.6", features = ["rust_decimal"] }
|
||||||
clap = { version = "4.5.37", features = ["derive"] }
|
clap = { version = "4.5.50", features = ["derive"] }
|
||||||
whoami = "1.6.0"
|
whoami = "1.6.1"
|
||||||
rust_decimal = { version = "1.37.1", features = ["db-postgres"] }
|
rust_decimal = { version = "1.39.0", features = ["db-postgres"] }
|
||||||
chrono = "0.4.41"
|
chrono = "0.4.42"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +1,76 @@
|
|||||||
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
|
#[command(subcommand)]
|
||||||
|
command: Commands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
enum Commands {
|
||||||
|
Report {
|
||||||
|
/// name of exercise: abs, squat, bp, biceps, triceps
|
||||||
exercise: String,
|
exercise: String,
|
||||||
|
|
||||||
// number of runs
|
/// number of runs
|
||||||
runs: i16,
|
runs: i16,
|
||||||
|
|
||||||
// number of reps per run
|
/// number of reps per run
|
||||||
reps: i16,
|
reps: i16,
|
||||||
|
|
||||||
// kilos
|
/// kilos
|
||||||
kilos: f32,
|
kilos: f32,
|
||||||
|
|
||||||
// time
|
/// time, either "2025-05-20T14:15:20+0200", "2025-12-24 18:00:00" or "17:15:00"
|
||||||
time: Option<String>,
|
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>,
|
||||||
|
},
|
||||||
|
Latest {
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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),
|
||||||
|
Commands::Latest {} => get_latest(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +81,62 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
(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)",
|
(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]);
|
&[&whoami::username(), &t, &exercise, &runs, &reps, &dec_kilos]);
|
||||||
match res {
|
match res {
|
||||||
Ok(_inserted) => println!("Training inserted"),
|
Ok(_inserted) => println!("Training inserted"),
|
||||||
Err(e) => println!("Training not inserted, since: {}", e)
|
Err(e) => println!("Training not inserted, since: {}", e)
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_latest () {
|
||||||
|
let mut client = get_client();
|
||||||
|
let res = client.query("with report as (select distinct on (t.exercise) t.time, t.exercise, t.kilos
|
||||||
|
from training t inner join account a on a.id=t.account where a.login=$1 order by 2, 1 desc)
|
||||||
|
select date_trunc('second', r.time), e.name, r.kilos from report r inner join exercise e on e.id=r.exercise order by 2",
|
||||||
|
&[&whoami::username()]);
|
||||||
|
match res {
|
||||||
|
Ok(resultset) => {
|
||||||
|
for r in resultset {
|
||||||
|
let time : DateTime<Local> = r.get(0);
|
||||||
|
let exercise : String= r.get(1);
|
||||||
|
let kilos: Decimal = r.get(2);
|
||||||
|
println!("{time} {exercise:>15} {kilos:>5}", time=time, exercise=exercise, kilos=kilos);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => println!("Report not fetched, since: {}", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user