summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKjetil Orbekk <kjetil.orbekk@gmail.com>2020-02-05 22:26:37 -0500
committerKjetil Orbekk <kjetil.orbekk@gmail.com>2020-02-05 22:26:37 -0500
commitcbe2ff86f1a6873b88e314011ee64b6c25f3359c (patch)
tree03f20093fd59edccb9dcafd1dbb7d1d65fd96c02 /src
parent97b82520aacacbe600c8918b26b5b29b8d47d4d1 (diff)
Extract basics info from strava and display it
Diffstat (limited to 'src')
-rw-r--r--src/db.rs62
-rw-r--r--src/error.rs9
-rw-r--r--src/importer.rs59
-rw-r--r--src/main.rs12
-rw-r--r--src/models.rs15
-rw-r--r--src/schema.rs22
-rw-r--r--src/server.rs65
7 files changed, 165 insertions, 79 deletions
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<usize,
Ok(rows)
}
-pub fn insert_entry_data(
+pub fn link_data(
conn: &PgConnection,
- entry_data: &models::EntryData,
+ data: models::RawData,
+ entry_type: &str,
+ entry_id: i64,
) -> Result<usize, Error> {
- 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<Vec<models::RawDataKey>,
.get_results::<models::RawDataKey>(conn)?;
Ok(rows)
}
+
+pub fn get_entries(conn: &PgConnection, username: &str) -> Result<Vec<models::Entry>, Error> {
+ use crate::schema::entries;
+ let r = entries::table
+ .filter(entries::username.eq(username))
+ .get_results::<models::Entry>(conn)?;
+ Ok(r)
+}
+
+// pub fn get_entries_with_data(
+// conn: &PgConnection,
+// username: &str,
+// ) -> Result<Vec<(models::Entry, models::EntryData, models::RawData)>, 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::<Vec<(models::Entry, models::EntryData, models::RawData)>>(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<chrono::format::ParseError> for Error {
+ fn from(e: chrono::format::ParseError) -> Error {
+ Error::ParseTimeError(e)
+ }
+}
+
impl From<StravaApiError> 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<S: strava::StravaApi>(shared: Arc<ImporterSharedData<S>>) {
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<S: strava::StravaApi>(shared: Arc<ImporterSharedData<S>>) {
},
}
- thread::sleep(Duration::from_secs(10));
+ thread::sleep(Duration::from_millis(10));
}
}
@@ -151,8 +151,11 @@ fn to_run(data: &Value) -> Result<Value, Error> {
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<S: strava::StravaApi>(
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<S: strava::StravaApi>(
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<S: strava::StravaApi>(
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<String>,
+ pub entry_id: Option<i64>,
}
#[derive(Insertable, Debug, Serialize, Deserialize, Clone)]
@@ -169,13 +170,3 @@ pub struct Entry {
pub timestamp: Option<DateTime<Utc>>,
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
@@ -18,21 +18,13 @@ 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<Varchar>,
+ entry_id -> Nullable<Int8>,
}
}
@@ -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<String, Json> {
+ 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<LoggedInUser>) -> 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/<username>")]
+fn profile(conn: Db, username: String) -> Result<Template, Error> {
+ 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::<Vec<_>>());
+ context.insert("headings".to_string(), json!(headings));
+ context.insert(
+ "entries".to_string(),
+ json!(entries
+ .into_iter()
+ .map(|e| e.payload)
+ .collect::<Vec<_>>()));
+
+ 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<Template, Error> {
+ let mut context = default_context();
+ context.insert("message".to_string(), json!("Hello, World"));
+ Ok(Template::render("index", context))
}
#[get("/login?<failed>")]
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<Params>) -> 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();