From cbe2ff86f1a6873b88e314011ee64b6c25f3359c Mon Sep 17 00:00:00 2001 From: Kjetil Orbekk Date: Wed, 5 Feb 2020 22:26:37 -0500 Subject: Extract basics info from strava and display it --- src/db.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++--------- src/error.rs | 9 ++++++++ src/importer.rs | 59 ++++++++++++++++++++++++++++++--------------------- src/main.rs | 12 ++++++++++- src/models.rs | 15 +++---------- src/schema.rs | 22 +++---------------- src/server.rs | 65 +++++++++++++++++++++++++++++++++++++++++++++------------ 7 files changed, 165 insertions(+), 79 deletions(-) (limited to 'src') diff --git a/src/db.rs b/src/db.rs index 5865231..ed1ce4d 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,3 +1,4 @@ +use crate::diesel::BoolExpressionMethods; use crate::error::Error; use crate::models; use bcrypt; @@ -6,7 +7,7 @@ use chrono::Utc; use diesel::connection::Connection; use diesel::pg::PgConnection; use diesel::ExpressionMethods; -use diesel::Insertable; +use diesel::JoinOnDsl; use diesel::QueryDsl; use diesel::RunQueryDsl; @@ -44,10 +45,6 @@ macro_rules! insert { i64 ) }; - - (entry_data <= $conn:expr, $values:expr) => { - insert!($conn, schema::entry_data::table, $values) - }; } pub fn create_config(conn: &PgConnection, config: &models::Config) -> Result<(), Error> { @@ -203,13 +200,24 @@ pub fn insert_data(conn: &PgConnection, data: &models::RawData) -> Result Result { - use crate::schema::entry_data; - let rows = diesel::insert_into(entry_data::table) - .values(entry_data) + use crate::schema::raw_data; + let target = raw_data::table.filter( + raw_data::data_type + .eq(data.data_type) + .and(raw_data::id.eq(data.id)), + ); + + let rows = diesel::update(target) + .set(( + raw_data::entry_type.eq(Some(entry_type)), + raw_data::entry_id.eq(Some(entry_id)), + )) .execute(conn)?; Ok(rows) } @@ -232,3 +240,37 @@ pub fn get_raw_data_keys(conn: &PgConnection) -> Result, .get_results::(conn)?; Ok(rows) } + +pub fn get_entries(conn: &PgConnection, username: &str) -> Result, Error> { + use crate::schema::entries; + let r = entries::table + .filter(entries::username.eq(username)) + .get_results::(conn)?; + Ok(r) +} + +// pub fn get_entries_with_data( +// conn: &PgConnection, +// username: &str, +// ) -> Result, Error> { +// use crate::schema::entries; +// use crate::schema::entry_data; +// use crate::schema::raw_data; + +// let r = entries::table +// .filter(entries::username.eq(username)) +// .left_join( +// entry_data::table.on(entries::username.eq(entry_data::username).and( +// entries::entry_type +// .eq(entry_data::entry_type) +// .and(entries::id.eq(entry_data::entry_id)), +// )), +// ) +// .left_join( +// raw_data::table.on(entry_data::data_type +// .eq(raw_data::data_type) +// .and(entry_data::data_id.eq(raw_data::id))), +// ) +// .get_results::>(conn)?; +// Ok(r) +// } diff --git a/src/error.rs b/src/error.rs index d27966a..22ff600 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,5 @@ use bcrypt::BcryptError; +use chrono::format::ParseResult; use diesel::result::Error as DieselErr; use serde_json::Value; use std::convert::From; @@ -80,6 +81,7 @@ pub enum Error { PasswordError(BcryptError), CommunicationError(reqwest::Error), ParseError(serde_json::error::Error), + ParseTimeError(chrono::format::ParseError), StravaApiError(StravaApiError), UnexpectedJson(Value), AlreadyExists, @@ -94,6 +96,7 @@ impl fmt::Display for Error { Error::PasswordError(ref e) => e.fmt(f), Error::CommunicationError(ref e) => e.fmt(f), Error::ParseError(ref e) => e.fmt(f), + Error::ParseTimeError(ref e) => e.fmt(f), Error::UnexpectedJson(_) => f.write_str("UnexpectedJson"), Error::StravaApiError(ref e) => e.fmt(f), Error::AlreadyExists => f.write_str("AlreadyExists"), @@ -103,6 +106,12 @@ impl fmt::Display for Error { } } +impl From for Error { + fn from(e: chrono::format::ParseError) -> Error { + Error::ParseTimeError(e) + } +} + impl From for Error { fn from(e: StravaApiError) -> Error { Error::StravaApiError(e) diff --git a/src/importer.rs b/src/importer.rs index f039d03..7cbc1db 100644 --- a/src/importer.rs +++ b/src/importer.rs @@ -1,10 +1,10 @@ +use chrono::DateTime; use chrono::Utc; use diesel::PgConnection; use serde::Deserialize; use serde::Serialize; use serde_json::to_value; use serde_json::Value; -use std::collections::HashMap; use std::sync::Arc; use std::sync::Mutex; use std::sync::RwLock; @@ -79,7 +79,7 @@ fn handle_tasks(shared: Arc>) { let task = (|| { let conn = shared.conn.lock().unwrap(); let now = Utc::now(); - let eta = now + chrono::Duration::seconds(5); + let eta = now + chrono::Duration::minutes(5); db::take_task(&conn, models::TaskState::NEW, now, eta) })(); @@ -99,7 +99,7 @@ fn handle_tasks(shared: Arc>) { }, } - thread::sleep(Duration::from_secs(10)); + thread::sleep(Duration::from_millis(10)); } } @@ -151,8 +151,11 @@ fn to_run(data: &Value) -> Result { Ok(json!({ "type": "run", - "distance": get!(data f64 "distance")?, - "elapsed_time": get!(data f64 "elapsed_time")?, + "start_timestamp": data["start_date"], + "distance": data["distance"], + "moving_time": data["moving_time"], + "elapsed_time": data["elapsed_time"], + "name": data["name"], })) } @@ -161,33 +164,41 @@ fn process_strava_activity( data: models::RawData, mut task: models::Task, ) -> Result<(), Error> { + if data.entry_type.is_some() && data.entry_id.is_some() { + return Err(Error::InternalError); + } + let json_error = || Error::UnexpectedJson(data.payload.clone()); let strava_type = data.payload["type"].as_str().ok_or_else(json_error)?; - let entry_type = match strava_type { - "Run" => Ok("run".to_string()), + let entry_payload = match strava_type { + "Run" => to_run(&data.payload), &_ => Err(Error::NotFound), }?; + let entry_type = entry_payload["type"] + .as_str() + .ok_or_else(json_error)? + .to_string(); + let timestamp = entry_payload["start_timestamp"] + .as_str() + .map(|t| DateTime::parse_from_rfc3339(t).map(|t| t.with_timezone(&Utc))) + .transpose()?; let conn = &shared.conn.lock().unwrap(); conn.transaction::<(), Error, _>(|| { - let entry = models::NewEntry { - username: data.username.as_str(), - entry_type: entry_type.as_str(), - timestamp: None, - payload: json!({}), + let (entry_type, id) = { + let entry = models::NewEntry { + username: data.username.as_str(), + entry_type: entry_type.as_str(), + timestamp: timestamp, + payload: entry_payload, + }; + info!("Inserting entry: {:#?}", entry); + let id = insert!(entries <= conn, &entry)?; + (entry.entry_type.to_string(), id) }; - let id = insert!(entries <= conn, &entry)?; - - let entry_data = models::EntryData { - username: entry.username.to_string(), - entry_type: entry.entry_type.to_string(), - entry_id: id, - data_type: data.data_type, - data_id: data.id, - }; - insert!(entry_data <= conn, &entry_data)?; + db::link_data(conn, data, &entry_type, id)?; task.state = models::TaskState::SUCCESSFUL; db::update_task(conn, task)?; @@ -203,8 +214,6 @@ fn process_raw_data( mut task: models::Task, ) -> Result<(), Error> { let data = db::get_raw_data(&shared.conn.lock().unwrap(), key)?; - println!("Process raw data: {:#?}", data); - match data.data_type { models::DataType::StravaActivity => process_strava_activity(shared.clone(), data, task)?, }; @@ -278,6 +287,8 @@ fn import_strava_user( id: id, username: username.to_string(), payload: activity.clone(), + entry_type: None, + entry_id: None, }, )?; } diff --git a/src/main.rs b/src/main.rs index 8d6eef8..cf0d308 100644 --- a/src/main.rs +++ b/src/main.rs @@ -87,6 +87,12 @@ fn main() { .takes_value(true) .help("Endpoint for this web server"), ) + .arg( + Arg::with_name("static_path") + .long("static_path") + .takes_value(true) + .help("Directory containing static files"), + ) .subcommand( SubCommand::with_name("init") .about("initialize database config") @@ -129,6 +135,10 @@ fn main() { .value_of("base_url") .unwrap_or("http://localhost:8000"); + let static_path = matches + .value_of("static_path") + .unwrap_or("./static"); + let db_url = matches.value_of("database_url").unwrap(); let conn = PgConnection::establish(db_url).unwrap(); @@ -162,6 +172,6 @@ fn main() { .expect("insert"); } else { info!("Start server"); - pjournal::server::start(conn, db_url, base_url); + pjournal::server::start(conn, db_url, base_url, static_path); } } diff --git a/src/models.rs b/src/models.rs index 15eafd7..83153fd 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,6 +1,5 @@ use crate::schema::config; use crate::schema::entries; -use crate::schema::entry_data; use crate::schema::raw_data; use crate::schema::strava_tokens; use crate::schema::tasks; @@ -143,13 +142,15 @@ pub struct RawDataKey { pub username: String, } -#[derive(Insertable, Queryable, Debug, Serialize, Deserialize, Clone)] +#[derive(Insertable, Queryable, Identifiable, Debug, Serialize, Deserialize, Clone)] #[table_name = "raw_data"] pub struct RawData { pub data_type: DataType, pub id: i64, pub username: String, pub payload: Value, + pub entry_type: Option, + pub entry_id: Option, } #[derive(Insertable, Debug, Serialize, Deserialize, Clone)] @@ -169,13 +170,3 @@ pub struct Entry { pub timestamp: Option>, pub payload: Value, } - -#[derive(Queryable, Insertable, Debug, Serialize, Deserialize, Clone)] -#[table_name = "entry_data"] -pub struct EntryData { - pub username: String, - pub entry_type: String, - pub entry_id: i64, - pub data_type: DataType, - pub data_id: i64, -} diff --git a/src/schema.rs b/src/schema.rs index 23690df..e69bed3 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -17,22 +17,14 @@ table! { } } -table! { - entry_data (username, entry_type, entry_id, data_type, data_id) { - username -> Varchar, - entry_type -> Varchar, - entry_id -> Int8, - data_type -> Varchar, - data_id -> Int8, - } -} - table! { raw_data (data_type, id) { data_type -> Varchar, id -> Int8, username -> Varchar, payload -> Jsonb, + entry_type -> Nullable, + entry_id -> Nullable, } } @@ -67,12 +59,4 @@ joinable!(raw_data -> users (username)); joinable!(strava_tokens -> users (username)); joinable!(tasks -> users (username)); -allow_tables_to_appear_in_same_query!( - config, - entries, - entry_data, - raw_data, - strava_tokens, - tasks, - users, -); +allow_tables_to_appear_in_same_query!(config, entries, raw_data, strava_tokens, tasks, users,); diff --git a/src/server.rs b/src/server.rs index 9c5618b..a203af9 100644 --- a/src/server.rs +++ b/src/server.rs @@ -14,8 +14,11 @@ use rocket::request::Request; use rocket::response::Redirect; use rocket::State; use rocket_contrib::templates::Template; +use serde_json::map::Map; use serde_json::to_value; +use serde_json::Value as Json; use std::collections::HashMap; +use rocket_contrib::serve::StaticFiles; use crate::db; use crate::error::Error; @@ -32,6 +35,12 @@ pub struct LoggedInUser { pub username: String, } +fn default_context() -> Map { + let mut data = Map::new(); + data.insert("parent".to_string(), json!("layout")); + data +} + impl<'a, 'r> FromRequest<'a, 'r> for LoggedInUser { type Error = Error; @@ -63,23 +72,47 @@ impl<'a, 'r> FromRequest<'a, 'r> for LoggedInUser { } } -#[get("/")] -fn index(user: Option) -> Template { - let mut context = HashMap::new(); - context.insert("parent", "layout".to_string()); - context.insert("message", "Hello, World".to_string()); - for user in user { - context.insert("user", user.username); - } - Template::render("index", context) +#[get("/p/")] +fn profile(conn: Db, username: String) -> Result { + let mut context = default_context(); + context.insert("title".to_string(), json!(username)); + + let entries = db::get_entries(&*conn, &username)?; + let headings = + entries.first() + .and_then(|e| e.payload.as_object()) + .map(|e| e.keys().collect::>()); + context.insert("headings".to_string(), json!(headings)); + context.insert( + "entries".to_string(), + json!(entries + .into_iter() + .map(|e| e.payload) + .collect::>())); + + Ok(Template::render("profile", context)) +} + +#[get("/", rank = 1)] +fn index_logged_in(user: LoggedInUser) -> Redirect { + Redirect::to(uri!(profile: user.username)) +} + +#[get("/", rank = 2)] +fn index() -> Result { + let mut context = default_context(); + context.insert("message".to_string(), json!("Hello, World")); + Ok(Template::render("index", context)) } #[get("/login?")] fn login(failed: bool) -> Template { - let mut context = HashMap::new(); - context.insert("parent", "layout"); + let mut context = default_context(); if failed { - context.insert("message", "Incorrect username or password"); + context.insert( + "message".to_string(), + json!("Incorrect username or password"), + ); } Template::render("login", context) } @@ -163,7 +196,8 @@ fn link_strava(params: State) -> Redirect { )) } -pub fn start(conn: diesel::PgConnection, db_url: &str, base_url: &str) { +pub fn start(conn: diesel::PgConnection, db_url: &str, base_url: &str, + static_path: &str) { let mut database_config = HashMap::new(); let mut databases = HashMap::new(); database_config.insert("url", Value::from(db_url)); @@ -196,13 +230,18 @@ pub fn start(conn: diesel::PgConnection, db_url: &str, base_url: &str) { "/", routes![ index, + index_logged_in, login, import_strava, + profile, login_submit, link_strava, link_strava_callback ], ) + .mount( + "/static", + StaticFiles::from(static_path)) .attach(Template::fairing()) .attach(Db::fairing()) .launch(); -- cgit v1.2.3