diff options
author | Kjetil Orbekk <kjetil.orbekk@gmail.com> | 2020-01-29 19:15:28 -0500 |
---|---|---|
committer | Kjetil Orbekk <kjetil.orbekk@gmail.com> | 2020-01-29 19:15:28 -0500 |
commit | c8db39dea2cf50dd1fa6c499600e09818b8db44a (patch) | |
tree | b2eb940f9769323cd1d3e1c92bbe3c16d38d9dd6 /src | |
parent | 6c16bcd190328443f15029fc3ee2467b6c270eed (diff) |
Add database support
Diffstat (limited to 'src')
-rw-r--r-- | src/db.rs | 36 | ||||
-rw-r--r-- | src/error.rs | 36 | ||||
-rw-r--r-- | src/lib.rs | 51 | ||||
-rw-r--r-- | src/main.rs | 109 | ||||
-rw-r--r-- | src/models.rs | 26 | ||||
-rw-r--r-- | src/schema.rs | 20 | ||||
-rw-r--r-- | src/server.rs | 50 | ||||
-rw-r--r-- | src/strava.rs | 32 |
8 files changed, 262 insertions, 98 deletions
diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..71490d8 --- /dev/null +++ b/src/db.rs @@ -0,0 +1,36 @@ +use crate::models; +use crate::error::Error; +use diesel::connection::Connection; +use diesel::pg::PgConnection; +use diesel::RunQueryDsl; +use rand::Rng; +use rand; +use base64; +use bcrypt; + +pub const COST: u32 = 12; + +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 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::User::new(username, &hashed)).execute(conn)?; + if rows != 1 { + Err(Error::AlreadyExists)?; + } + Ok(()) +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..6ab0741 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,36 @@ +use std::convert::From; +use std::error::Error as StdError; +use bcrypt::BcryptError; +use diesel::result::Error as DieselErr; +use std::fmt; + +#[derive(Debug)] +pub enum Error { + DieselError(DieselErr), + PasswordError(BcryptError), + AlreadyExists, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Error::DieselError(ref e) => e.fmt(f), + Error::PasswordError(ref e) => e.fmt(f), + Error::AlreadyExists => f.write_str("AlreadyExists"), + } + } +} + +impl From<DieselErr> for Error { + fn from(e: DieselErr) -> Error { + Error::DieselError(e) + } +} + +impl From<BcryptError> for Error { + fn from(e: BcryptError) -> Error { + Error::PasswordError(e) + } +} + +impl StdError for Error {} @@ -2,12 +2,9 @@ #![feature(decl_macro)] #[macro_use] extern crate rocket; -use rocket::response; -use rocket::State; -use rocket_contrib::templates::Template; -use std::collections::HashMap; -mod strava; +#[macro_use] +extern crate diesel; #[derive(Debug)] pub struct Config { @@ -16,41 +13,9 @@ pub struct Config { pub base_url: String, } -#[get("/")] -fn index() -> Template { - let mut context = HashMap::new(); - context.insert("parent", "layout"); - context.insert("message", "Hello, World"); - Template::render("index", context) -} - -#[get("/link_strava_callback?<code>")] -fn link_strava_callback(config: State<Config>, code: String) -> String { - strava::exchange_token(&config.client_id, &config.client_secret, &code); - "OK".to_string() -} - -#[get("/link_strava")] -fn link_strava(config: State<Config>) -> response::Redirect { - response::Redirect::to( - format!( - concat!( - "https://www.strava.com/oauth/authorize?", - "client_id={}&", - "response_type=code&", - "redirect_uri={}&", - "approval_prompt=force&", - "scope=read", - ), - config.client_id, - format!("{}/link_strava_callback", config.base_url)) - ) -} - -pub fn start_server(config: Config) { - rocket::ignite() - .manage(config) - .mount("/", routes![index, link_strava, link_strava_callback]) - .attach(Template::fairing()) - .launch(); -} +pub mod error; +pub mod db; +pub mod models; +mod schema; +pub mod server; +mod strava; diff --git a/src/main.rs b/src/main.rs index 32333f1..f88b7e2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,36 +1,85 @@ #[macro_use] extern crate clap; +use clap::App; +use clap::Arg; +use clap::SubCommand; +use diesel::connection::Connection; +use diesel::pg::PgConnection; fn main() { - let matches = clap_app!(pjournal => - (version: "0.1") - (author: "KJ Ørbekk <kj@orbekk.com>") - (about: "Practice Journaling") - (@arg strava_client_secret: - --strava_client_secret - +required +takes_value - "Client secret for strava authentication") - (@arg strava_client_id: - --strava_client_id - +required +takes_value - "Client id for strava authentication") - (@arg base_url: - --base_url - +takes_value - "Endpoint for this web app") - ) - .get_matches(); + let matches = App::new("pjournal") + .version("0.1") + .author("KJ Ørbekk <kj@orbekk.com>") + .about("Practice Journaling") + .arg( + Arg::with_name("database_url") + .long("database_url") + .required(true) + .takes_value(true) + .help("URL to postgresql database"), + ) + .arg( + Arg::with_name("base_url") + .long("base_url") + .takes_value(true) + .help("Endpoint for this web server"), + ) + .subcommand( + SubCommand::with_name("init") + .about("initialize database config") + .arg( + Arg::with_name("rocket_secret_key") + .long("rocket_secret_key") + .takes_value(true) + .required(true) + .help("Secret passed to rocket for encrypted cookies"), + ) + .arg( + Arg::with_name("strava_client_secret") + .long("strava_client_secret") + .takes_value(true) + .required(true) + .help("Client secret for strava authentication"), + ) + .arg( + Arg::with_name("strava_client_id") + .long("strava_client_id") + .takes_value(true) + .required(true) + .help("Client id for strava authentication"), + ), + ) + .subcommand( + SubCommand::with_name("adduser") + .about("add a user account") + .arg(Arg::with_name("USERNAME").required(true).index(1)) + .arg(Arg::with_name("PASSWORD").required(true).index(2)), + ) + .get_matches(); - let config = pjournal::Config { - client_id: matches - .value_of("strava_client_id") - .unwrap().to_string(), - client_secret: matches - .value_of("strava_client_secret") - .unwrap().to_string(), - base_url: matches - .value_of("base_url") - .unwrap_or("http://localhost:8000").to_string(), - }; - pjournal::start_server(config); + let base_url = matches + .value_of("base_url") + .unwrap_or("http://localhost:8000"); + + let db_url = matches.value_of("database_url").unwrap(); + let conn = PgConnection::establish(db_url).unwrap(); + + if let Some(matches) = matches.subcommand_matches("init") { + let config = pjournal::models::Config { + strava_client_id: matches.value_of("strava_client_id").unwrap(), + strava_client_secret: matches.value_of("strava_client_secret").unwrap(), + rocket_secret_key: matches.value_of("rocket_secret_key").unwrap(), + }; + + pjournal::db::create_config(&conn, &config); + } else if let Some(matches) = matches.subcommand_matches("adduser") { + let user = matches.value_of("USERNAME").unwrap(); + let password = matches.value_of("PASSWORD").unwrap(); + pjournal::db::adduser(&conn, user, password).unwrap(); + } else { + let config = pjournal::server::Params { + base_url: base_url.to_string(), + }; + pjournal::server::start(db_url, config); + } } diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..8bee887 --- /dev/null +++ b/src/models.rs @@ -0,0 +1,26 @@ +use crate::schema::config; +use crate::schema::users; + +#[derive(Insertable, Queryable)] +#[table_name = "config"] +pub struct Config<'a> { + pub strava_client_secret: &'a str, + pub strava_client_id: &'a str, + pub rocket_secret_key: &'a str, +} + +#[derive(Insertable, Queryable)] +#[table_name = "users"] +pub struct User<'a> { + pub username: &'a str, + password: &'a str, +} + +impl<'a> User<'a> { + pub fn new(username: &'a str, password: &'a str) -> User<'a> { + User { + username: username, + password: password, + } + } +} diff --git a/src/schema.rs b/src/schema.rs new file mode 100644 index 0000000..809706c --- /dev/null +++ b/src/schema.rs @@ -0,0 +1,20 @@ +table! { + config (singleton) { + strava_client_secret -> Varchar, + strava_client_id -> Varchar, + rocket_secret_key -> Varchar, + singleton -> Bool, + } +} + +table! { + users (username) { + username -> Varchar, + password -> Varchar, + } +} + +allow_tables_to_appear_in_same_query!( + config, + users, +); diff --git a/src/server.rs b/src/server.rs index 6f05afe..2c7baad 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,11 +1,17 @@ +use rocket::config; +use rocket::config::Environment; +use rocket::config::Value; use rocket::response; use rocket::State; use rocket_contrib::templates::Template; use std::collections::HashMap; -use crate::Config; use crate::strava; +pub struct Params { + pub base_url: String, +} + #[get("/")] fn index() -> Template { let mut context = HashMap::new(); @@ -15,30 +21,36 @@ fn index() -> Template { } #[get("/link_strava_callback?<code>")] -fn link_strava_callback(config: State<Config>, code: String) -> Result<String, impl std::error::Error> { - strava::exchange_token( - &config.client_id, &config.client_secret, &code) +fn link_strava_callback( + config: State<Params>, + code: String, +) -> Result<String, impl std::error::Error> { + strava::exchange_token("&config.client_id", "&config.client_secret", &code) .map(|t| format!("{:#?}", t)) } #[get("/link_strava")] -fn link_strava(config: State<Config>) -> response::Redirect { - response::Redirect::to( - format!( - concat!( - "https://www.strava.com/oauth/authorize?", - "client_id={}&", - "response_type=code&", - "redirect_uri={}&", - "approval_prompt=force&", - "scope=read", - ), - config.client_id, - format!("{}/link_strava_callback", config.base_url)) - ) +fn link_strava(config: State<Params>) -> response::Redirect { + response::Redirect::to(format!( + concat!( + "https://www.strava.com/oauth/authorize?", + "client_id={}&", + "response_type=code&", + "redirect_uri={}&", + "approval_prompt=force&", + "scope=read", + ), + "config.client_id", + format!("{}/link_strava_callback", config.base_url) + )) } -pub fn start(config: Config) { +pub fn start(db_url: &str, config: Params) { + let mut database_config = HashMap::new(); + let mut databases = HashMap::new(); + database_config.insert("url", Value::from(db_url)); + databases.insert("db", Value::from(database_config)); + rocket::ignite() .manage(config) .mount("/", routes![index, link_strava, link_strava_callback]) diff --git a/src/strava.rs b/src/strava.rs index 490964b..dcd8dc1 100644 --- a/src/strava.rs +++ b/src/strava.rs @@ -1,15 +1,35 @@ use reqwest; +use serde::Deserialize; +use serde::Serialize; + +#[derive(Serialize, Deserialize, Debug)] +pub struct AthleteSummary { + id: i64, + username: String, + firstname: String, + lastname: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Token { + expires_in: i64, + refresh_token: String, + access_token: String, + athlete: AthleteSummary, +} pub fn exchange_token( client_id: &str, client_secret: &str, - code: &str) { + code: &str, +) -> Result<Token, reqwest::Error> { let client = reqwest::blocking::Client::new(); - let params = [("client_id", client_id), - ("client_secret", client_secret), - ("code", code)]; + let params = [ + ("client_id", client_id), + ("client_secret", client_secret), + ("code", code), + ]; let uri = "https://www.strava.com/oauth/token"; let req = client.post(uri).form(¶ms); - let mut res = req.send().unwrap().text(); - println!("{:?}", res); + req.send().map(|r| r.json())? } |