use crate::diesel::BoolExpressionMethods; use crate::error::Error; use crate::models; use crate::template; use bcrypt; use chrono::DateTime; use chrono::Utc; use diesel::connection::Connection; use diesel::pg::PgConnection; use diesel::ExpressionMethods; use diesel::QueryDsl; use diesel::RunQueryDsl; pub const COST: u32 = 10; #[macro_export] macro_rules! insert { ($conn:expr, $table:expr, $values:expr, $returning:expr, $t:ty) => {{ use crate::diesel::RunQueryDsl; let conn: &PgConnection = &$conn; let r: Result<$t, Error> = diesel::insert_into($table) .values($values) .returning($returning) .get_result(conn) .map_err(From::from); r }}; ($conn:expr, $table:expr, $values:expr) => {{ use crate::diesel::RunQueryDsl; let conn: &PgConnection = &$conn; let r: Result = diesel::insert_into($table) .values($values) .execute(conn) .map_err(From::from); r }}; (entries <= $conn:expr, $values:expr) => { insert!( $conn, schema::entries::table, $values, schema::entries::id, i64 ) }; } pub fn create_config(conn: &PgConnection, config: &models::Config) -> Result<(), Error> { use crate::schema::config; conn.transaction(|| { diesel::delete(config::table).execute(conn)?; diesel::insert_into(config::table) .values(config) .execute(conn)?; Ok(()) }) } pub fn insert_strava_token(conn: &PgConnection, token: &models::StravaToken) -> Result<(), Error> { use crate::schema::strava_tokens; conn.transaction(|| { diesel::delete(strava_tokens::table) .filter(strava_tokens::username.eq(&token.username)) .execute(conn)?; diesel::insert_into(strava_tokens::table) .values(token) .execute(conn)?; Ok(()) }) } pub fn get_config(conn: &PgConnection) -> Result { use crate::schema::config; config::table .get_result::(conn) .map_err(From::from) } pub fn adduser(conn: &PgConnection, username: &str, password: &str) -> Result<(), Error> { use crate::schema::users; let hashed = bcrypt::hash(password, COST)?; let rows = diesel::insert_into(users::table) .values(models::NewUser { username, password: &hashed, }) .execute(conn)?; if rows != 1 { Err(Error::AlreadyExists)?; } Ok(()) } pub fn authenticate( conn: &PgConnection, username: &str, typed_password: &str, ) -> Result { use crate::schema::users; let mut user = users::table .filter(users::username.eq(username)) .get_result::(conn)?; if bcrypt::verify(typed_password, &user.password)? { user.password = "".to_string(); Ok(user) } else { Err(Error::NotFound) } } pub fn get_user(conn: &PgConnection, username: &str) -> Result { use crate::schema::users; let mut user = users::table .filter(users::username.eq(username)) .get_result::(conn)?; user.password = "".to_string(); Ok(user) } pub fn get_strava_token( conn: &PgConnection, user: &models::User, ) -> Result { use crate::schema::strava_tokens; let token = strava_tokens::table .filter(strava_tokens::username.eq(&user.username)) .get_result::(conn)?; Ok(token) } pub fn update_strava_token(conn: &PgConnection, token: &models::StravaToken) -> Result<(), Error> { use crate::schema::strava_tokens; diesel::update(strava_tokens::table) .set(token) .execute(conn)?; Ok(()) } pub fn insert_task(conn: &PgConnection, task: &models::NewTask) -> Result { use crate::schema::tasks; let id = diesel::insert_into(tasks::table) .values(task) .returning(tasks::id) .get_result(conn)?; Ok(id) } fn update_task_inner(conn: &PgConnection, task: models::Task) -> Result { use crate::schema::tasks; diesel::delete(tasks::table.filter(tasks::columns::id.eq(task.id))).execute(conn)?; let new_id = insert_task( conn, &models::NewTask { start_at: task.start_at, state: task.state, username: &task.username, payload: &task.payload, }, )?; let new_task = tasks::table.find(new_id).get_result::(conn)?; Ok(new_task) } pub fn update_task(conn: &PgConnection, task: models::Task) -> Result { conn.transaction(|| update_task_inner(conn, task)) } pub fn take_task( conn: &PgConnection, state: models::TaskState, start_before: DateTime, eta: DateTime, ) -> Result { use crate::schema::tasks; conn.transaction(|| { let mut task = tasks::table .filter(tasks::state.eq(state)) .filter(tasks::start_at.lt(start_before)) .order(tasks::start_at.asc()) .first::(conn)?; task.start_at = eta; let task = update_task_inner(conn, task)?; Ok(task) }) } pub fn find_missing_data( conn: &PgConnection, username: &str, data_type: models::DataType, ids: &[i64], ) -> Result, Error> { use crate::schema::raw_data; use diesel::pg::expression::dsl::any; use std::collections::HashSet; let present: HashSet = raw_data::table .select(raw_data::id) .filter( raw_data::username .eq(username) .and(raw_data::data_type.eq(data_type)) .and(raw_data::id.eq(any(ids))), ) .get_results::(conn)? .into_iter() .collect(); let ids: HashSet = ids.iter().map(|v| *v).collect(); let missing = ids.difference(&present); Ok(missing.map(|v| *v).collect()) } pub fn insert_data(conn: &PgConnection, data: &models::RawData) -> Result { use crate::schema::raw_data; let rows = diesel::insert_into(raw_data::table) .values(data) .execute(conn)?; Ok(rows) } pub fn link_data( conn: &PgConnection, data: models::RawData, entry_type: &str, entry_id: i64, ) -> Result { 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) } pub fn get_raw_data( conn: &PgConnection, key: &models::RawDataKey, ) -> Result { use crate::schema::raw_data; let data = raw_data::table .find((key.data_type, key.id)) .get_result::(conn)?; Ok(data) } pub fn get_raw_data_keys(conn: &PgConnection) -> Result, Error> { use crate::schema::raw_data; let rows = raw_data::table .select((raw_data::data_type, raw_data::id, raw_data::username)) .get_results::(conn)?; Ok(rows) } pub fn get_entries( conn: &PgConnection, username: &str, entry_type: &str, ) -> Result, Error> { use crate::schema::entries; let r = entries::table .filter( entries::username .eq(username) .and(entries::entry_type.eq(entry_type)), ) .order(entries::timestamp.desc()) .get_results::(conn)?; Ok(r) } pub fn get_template( _conn: &PgConnection, entry_type: &str, ) -> Result { match entry_type { "run" => Ok(template::running_template()), _ => Err(Error::NotFound), } } // 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) // }