From d25d40cd468b19512de535fabdc605fdb3a0206e Mon Sep 17 00:00:00 2001 From: tolumide-ng Date: Sat, 12 Feb 2022 20:59:56 +0100 Subject: [PATCH 1/2] Add routerify and begins refactoring route handling --- Cargo.lock | 14 ++++++++ Cargo.toml | 3 +- src/app/server.rs | 56 +++++++++++++++++++---------- src/controllers/destroy.rs | 1 + src/controllers/health_check.rs | 11 +++--- src/controllers/not_found.rs | 6 ++-- src/routes/server.rs | 63 ++++++++++++++++++++++----------- 7 files changed, 106 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a18775d..f9effe3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1152,6 +1152,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "routerify" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496c1d3718081c45ba9c31fbfc07417900aa96f4070ff90dc29961836b7a9945" +dependencies = [ + "http", + "hyper", + "lazy_static", + "percent-encoding", + "regex", +] + [[package]] name = "rust-ini" version = "0.13.0" @@ -1690,6 +1703,7 @@ dependencies = [ "hyper-tls", "pkce", "redis", + "routerify", "secrecy", "serde 1.0.133", "serde_derive", diff --git a/Cargo.toml b/Cargo.toml index 7f466fb..f3d9a41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,4 +36,5 @@ serde_json = "1.0.75" derive_more = "0.99.17" thiserror = "1.0.30" anyhow = "1.0.53" -sqlx = { version = "0.5", features = [ "runtime-tokio-rustls", "json", "chrono", "uuid", "macros", "tls" ] } \ No newline at end of file +sqlx = { version = "0.5", features = [ "runtime-tokio-rustls", "json", "chrono", "uuid", "macros", "tls" ] } +routerify = "3.0.0" \ No newline at end of file diff --git a/src/app/server.rs b/src/app/server.rs index b62f4f0..9635175 100644 --- a/src/app/server.rs +++ b/src/app/server.rs @@ -1,13 +1,12 @@ -use http::Request; -use hyper::{Server, Client, Body}; -use hyper::service::{make_service_fn, service_fn}; +use hyper::{Server, Client}; use hyper_tls::HttpsConnector; +use routerify::RouterService; use std::{net::SocketAddr}; use dotenv::dotenv; use redis::{Client as RedisClient}; use crate::helpers::request::HyperClient; -use crate::routes::server::routes; +use crate::routes::server::{router}; use crate::setup::variables::SettingsVars; type GenericError = hyper::Error; @@ -16,16 +15,16 @@ type GenericError = hyper::Error; pub struct AppState { pub redis: RedisClient, pub hyper: HyperClient, - pub req: Request, + // pub req: Request, pub env_vars: SettingsVars, } impl AppState { - fn new( req: Request, hyper: HyperClient, redis: RedisClient) -> Self { + fn new(hyper: HyperClient, redis: RedisClient) -> Self { // is this expensive? Should this rather be done at the point of intializing the hyper_client e.t.c, and then implement // clone (Iterator trait) for settingsVars? let env_vars = SettingsVars::new(); - Self { redis, hyper, req, env_vars} + Self { redis, hyper, env_vars} } } @@ -35,23 +34,42 @@ pub async fn server() { let addr = SocketAddr::from(([127, 0, 0, 1], 8080)); let https = HttpsConnector::new(); let hyper_pool = Client::builder().build::<_, hyper::Body>(https); - let hyper_client = hyper_pool.clone(); + // let hyper_client = hyper_pool.clone(); let redis_client= RedisClient::open("redis://127.0.0.1/").expect("Redis connection failed"); - // let hyper_client = redis_client.get_async_connection().await.un + // let hyper_client = redis_client.get_async_connection().await.unwrap(); // let mgr = ConnectionManager::new(redis_client).await.unwrap(); - let service = make_service_fn(move|_| { - let redis = redis_client.clone(); - let client = hyper_client.clone(); + // let service = make_service_fn(move|_| { + // let redis = redis_client.clone(); + // let client = hyper_client.clone(); - async { - Ok::<_, GenericError>(service_fn(move |req| { - let state = AppState::new(req, client.to_owned(), redis.to_owned()); - routes(state) - })) - } - }); + // async { + // Ok::<_, GenericError>(service_fn(move |req| { + // let state = AppState::new(req, client.to_owned(), redis.to_owned()); + // routes(state) + // })) + // } + // }); + + // let service = make_service_fn(move|_| { + // let redis = redis_client.clone(); + // let client = hyper_client.clone(); + + // async { + // Ok::<_, GenericError>(service_fn(move |req| { + // routes(req, client.to_owned(), redis.to_owned()) + // })) + // } + // }); + + let env_vars = SettingsVars::new(); + + let state = AppState::new(hyper_pool.to_owned(), redis_client.to_owned()); + + let router = router(state); + + let service = RouterService::new(router).unwrap(); let server = Server::bind(&addr).serve(service); diff --git a/src/controllers/destroy.rs b/src/controllers/destroy.rs index c306842..3f58aa6 100644 --- a/src/controllers/destroy.rs +++ b/src/controllers/destroy.rs @@ -67,6 +67,7 @@ impl PostIds { if duplicates.is_some() || empty_string.is_some() || not_number.is_some() { panic!("{} must be an array of ids or an empty array", key) } + // Consider verifying that the string is a valid number when parsed - all twitter ids are numbers let ids = s.get(&key.to_string()).unwrap() .iter().map(|k| (k.clone(), key)).collect::>(); diff --git a/src/controllers/health_check.rs b/src/controllers/health_check.rs index 2fadf8e..fbebc20 100644 --- a/src/controllers/health_check.rs +++ b/src/controllers/health_check.rs @@ -1,8 +1,11 @@ -use hyper::{StatusCode}; +use hyper::{Body, StatusCode, Request}; +use routerify::prelude::*; -use crate::helpers::response::{TResult, ApiBody, ResponseBuilder}; +use crate::{helpers::response::{TResult, ApiBody, ResponseBuilder}, app::server::AppState}; -pub fn health_check() -> TResult { - ResponseBuilder::new("Ok".into(), Some(""), StatusCode::OK.as_u16()).reply() +pub async fn health_check(req: Request) -> TResult { + let state = req.data::().unwrap(); + let dc = ResponseBuilder::new("Ok".into(), Some(""), StatusCode::OK.as_u16()).reply(); + return dc; } \ No newline at end of file diff --git a/src/controllers/not_found.rs b/src/controllers/not_found.rs index bebe563..7963e29 100644 --- a/src/controllers/not_found.rs +++ b/src/controllers/not_found.rs @@ -1,8 +1,8 @@ -use hyper::{StatusCode}; +use hyper::{StatusCode, Body, Request}; -use crate::{helpers::response::{TResult, ApiBody, ResponseBuilder}, app::server::AppState}; +use crate::{helpers::response::{TResult, ApiBody, ResponseBuilder}}; -pub async fn not_found (_state: AppState) -> TResult { +pub async fn not_found (_req: Request) -> TResult { ResponseBuilder::new("Resorce not found".into(), Some(""), StatusCode::NOT_FOUND.as_u16()).reply() } diff --git a/src/routes/server.rs b/src/routes/server.rs index ae790e4..eedcd72 100644 --- a/src/routes/server.rs +++ b/src/routes/server.rs @@ -1,6 +1,13 @@ +use std::convert::Infallible; use hyper::{Request, Body, Method}; use crate::app::server::AppState; +use redis::{Client as RedisClient}; +use routerify::Router; + + +use crate::errors::response::TError; +use crate::helpers::request::HyperClient; use crate::helpers::response::{ApiBody}; use crate::{helpers::response::TResult}; use crate::controllers::{not_found, authorize_bot, @@ -10,25 +17,39 @@ use crate::controllers::{not_found, authorize_bot, -pub async fn routes( - state: AppState -) -> TResult { - // migrate this to [routerify](https://docs.rs/routerify/latest/routerify/) eventually - let req = &state.req; - - match (req.method(), req.uri().path(), req.uri().query()) { - (&Method::GET, "/", _) => health_check(), - (&Method::GET, "/enable", _) => authorize_bot(state).await, - (&Method::GET, "/oauth/callback", x) => handle_redirect(state).await, - (&Method::POST, "/revoke", _) => revoke_token(state).await, - (&Method::GET, "/refresh", _) => refresh_token(state).await, - (&Method::GET, "/user", x) => user_lookup(state).await, - (&Method::GET, "/timeline", x) => get_timeline(state).await, - (&Method::POST, "/remove", _) => handle_delete(state).await, - (&Method::GET, "/oauth1/request", _) => request_token(state).await, - (&Method::GET, "/oauth1/", _) => request_token(state).await, - _ => { - not_found(state).await - } - } +// pub async fn routes( +// state: AppState +// ) -> TResult { +// // migrate this to [routerify](https://docs.rs/routerify/latest/routerify/) eventually +// let req = &state.req; + +// match (req.method(), req.uri().path(), req.uri().query()) { +// (&Method::GET, "/", _) => health_check(), +// (&Method::GET, "/enable", _) => authorize_bot(state).await, +// (&Method::GET, "/oauth/callback", x) => handle_redirect(state).await, +// (&Method::POST, "/revoke", _) => revoke_token(state).await, +// (&Method::GET, "/refresh", _) => refresh_token(state).await, +// (&Method::GET, "/user", x) => user_lookup(state).await, +// (&Method::GET, "/timeline", x) => get_timeline(state).await, +// (&Method::POST, "/remove", _) => handle_delete(state).await, +// (&Method::GET, "/oauth1/request", _) => request_token(state).await, +// (&Method::GET, "/oauth1/", _) => request_token(state).await, +// _ => { +// not_found(state).await +// } +// } +// } + + +pub fn router(state: AppState) -> Router { + Router::builder() + // Specify the state data which will be available to every route handlers, + // error handler and middlewares. + .data(state) + // .middleware(Middleware::pre(logger)) + .get("/", health_check) + .any(not_found) + // .err_handler_with_info(error_handler) + .build() + .unwrap() } \ No newline at end of file From 3ec46e0b7247d8991726c487c4aabf84ad97bd27 Mon Sep 17 00:00:00 2001 From: tolumide-ng Date: Sat, 12 Feb 2022 22:04:23 +0100 Subject: [PATCH 2/2] Doesn't work :eyes --- Cargo.lock | 4 ++-- .../ Implement Iterator for SettingsVars t.rs | 2 ++ src/controllers/authorize_bot.rs | 4 +++- src/controllers/destroy.rs | 10 +++++++--- src/controllers/handle_redirect.rs | 12 ++++++------ src/controllers/health_check.rs | 3 +-- src/controllers/user_lookup.rs | 10 +++++++--- src/routes/server.rs | 5 ++++- 8 files changed, 32 insertions(+), 18 deletions(-) create mode 100644 src/controllers/ Implement Iterator for SettingsVars t.rs diff --git a/Cargo.lock b/Cargo.lock index f9effe3..9ceb68b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -491,9 +491,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9de88456263e249e241fcd211d3954e2c9b0ef7ccfc235a444eb367cae3689" +checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" dependencies = [ "bytes", "fnv", diff --git a/src/controllers/ Implement Iterator for SettingsVars t.rs b/src/controllers/ Implement Iterator for SettingsVars t.rs new file mode 100644 index 0000000..53326d7 --- /dev/null +++ b/src/controllers/ Implement Iterator for SettingsVars t.rs @@ -0,0 +1,2 @@ +// Implement Iterator for SettingsVars to allow clone +// Continue sqlx migration \ No newline at end of file diff --git a/src/controllers/authorize_bot.rs b/src/controllers/authorize_bot.rs index 785a666..540965e 100644 --- a/src/controllers/authorize_bot.rs +++ b/src/controllers/authorize_bot.rs @@ -1,4 +1,4 @@ -use hyper::{Method, Body, Response}; +use hyper::{Method, Body, Response, Request}; use redis::{AsyncCommands}; use crate::app::server::AppState; @@ -9,6 +9,8 @@ use crate::helpers::{ scope::Scope, keyval::KeyVal, }; + + use crate::setup::{variables::SettingsVars}; use crate::middlewares::request_builder::RequestBuilder; diff --git a/src/controllers/destroy.rs b/src/controllers/destroy.rs index 3f58aa6..4e1cf9a 100644 --- a/src/controllers/destroy.rs +++ b/src/controllers/destroy.rs @@ -1,5 +1,6 @@ use std::{collections::HashMap}; use hyper::{Body, Request, Method}; +use routerify::prelude::*; use futures::{stream, StreamExt}; use serde_json::Value; use tokio; @@ -86,14 +87,17 @@ impl PostIds { // rename this module to destory which then contains destory RTs and destory Posts -pub async fn handle_delete(app_state: AppState) -> TResult { - let AppState {redis, req, hyper, ..} = app_state; +pub async fn handle_delete(req: Request) -> TResult { + + let AppState {redis, hyper, ..} = req.data::().unwrap(); let mut con = redis.get_async_connection().await?; let access_token: String = redis::cmd("GET").arg(&["access_token"]).query_async(&mut con).await?; + let req_body = &req.into_body(); + // req body for the ids must be a vector of strings(id of tweets) - let req_body = req.into_body(); + // let req_body = req.into_body(); let byte_body = hyper::body::to_bytes(req_body).await?.to_owned(); let body: Ids = serde_json::from_slice(&byte_body)?; diff --git a/src/controllers/handle_redirect.rs b/src/controllers/handle_redirect.rs index 6012877..c4e9e2a 100644 --- a/src/controllers/handle_redirect.rs +++ b/src/controllers/handle_redirect.rs @@ -1,6 +1,6 @@ -use http::Method; -use hyper::{StatusCode}; +use hyper::{StatusCode, Body, Method, Request}; use redis::{Client as RedisClient}; +use routerify::prelude::*; use crate::{helpers::{ response::{TResult, ApiBody, make_request, ResponseBuilder}, request::{HyperClient}, keyval::KeyVal, commons::GrantType}, @@ -40,9 +40,9 @@ async fn access_token(hyper_client: HyperClient, redis_client: RedisClient, auth // req: Request, hyper_client: HyperClient, redis_client: RedisClient -pub async fn handle_redirect(app_state: AppState) -> TResult { +pub async fn handle_redirect(req: Request) -> TResult { - let AppState {redis, hyper, req, env_vars} = app_state; + let AppState {redis, hyper, env_vars} = req.data::().unwrap(); let SettingsVars{state, api_key, twitter_v1, ..} = env_vars; let mut con = redis.get_async_connection().await?; @@ -88,9 +88,9 @@ pub async fn handle_redirect(app_state: AppState) -> TResult { let is_v2_callback = query_params.verify_present(vec!["code".into(), "state".into()]); if let Some(dict) = is_v2_callback { - if query_params.validate("state".into(), state) { + if query_params.validate("state".into(), state.clone()) { let code = dict.get("code").unwrap().to_string(); - access_token(hyper.clone(), redis, code).await?; + access_token(hyper.clone(), redis.clone(), code).await?; return ResponseBuilder::new("Access Granted".into(), Some(""), StatusCode::OK.as_u16()).reply(); } diff --git a/src/controllers/health_check.rs b/src/controllers/health_check.rs index fbebc20..8b15033 100644 --- a/src/controllers/health_check.rs +++ b/src/controllers/health_check.rs @@ -6,6 +6,5 @@ use crate::{helpers::response::{TResult, ApiBody, ResponseBuilder}, app::server: pub async fn health_check(req: Request) -> TResult { let state = req.data::().unwrap(); - let dc = ResponseBuilder::new("Ok".into(), Some(""), StatusCode::OK.as_u16()).reply(); - return dc; + ResponseBuilder::new("Ok".into(), Some(""), StatusCode::OK.as_u16()).reply() } \ No newline at end of file diff --git a/src/controllers/user_lookup.rs b/src/controllers/user_lookup.rs index 4a181a0..4558f0d 100644 --- a/src/controllers/user_lookup.rs +++ b/src/controllers/user_lookup.rs @@ -1,4 +1,6 @@ -use hyper::{Method, StatusCode}; +use http::Request; +use hyper::{Method, StatusCode, Body}; +use routerify::prelude::*; use crate::{helpers::{ response::{ResponseBuilder, TResult, ApiBody, make_request, TwitterResponseHashData}}, @@ -8,9 +10,11 @@ use crate::{helpers::{ // use this endpoint to verify the validity of the username when they want to request for their timeline when using OAuth2.0 -pub async fn user_lookup(app_state: AppState) -> TResult { +pub async fn user_lookup(req: Request) -> TResult { + + let app_state = req.data::().unwrap(); // todo!() move this to params once route management is migrated to routerify - let AppState{redis, req, hyper, env_vars, ..} = app_state; + let AppState{redis, hyper, env_vars, ..} = app_state; let SettingsVars {twitter_v2, ..} = env_vars; let username = req.uri().query().unwrap().split("=").collect::>()[1]; diff --git a/src/routes/server.rs b/src/routes/server.rs index eedcd72..53ae2cb 100644 --- a/src/routes/server.rs +++ b/src/routes/server.rs @@ -3,7 +3,7 @@ use hyper::{Request, Body, Method}; use crate::app::server::AppState; use redis::{Client as RedisClient}; -use routerify::Router; +use routerify::{Router, Middleware}; use crate::errors::response::TError; @@ -41,6 +41,7 @@ use crate::controllers::{not_found, authorize_bot, // } + pub fn router(state: AppState) -> Router { Router::builder() // Specify the state data which will be available to every route handlers, @@ -48,6 +49,8 @@ pub fn router(state: AppState) -> Router { .data(state) // .middleware(Middleware::pre(logger)) .get("/", health_check) + .get("/oauth/callback", handle_redirect) + // .get("/enable", authorize_bot) .any(not_found) // .err_handler_with_info(error_handler) .build()