diff --git a/project/src/api.rs b/project/src/api.rs new file mode 100644 index 0000000..8f13129 --- /dev/null +++ b/project/src/api.rs @@ -0,0 +1,101 @@ +// Libraries +mod retreiver; +mod bouncer; + +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; +use std::error::Error; + +use tokio::task; +use warp::{self, Filter}; + +use bouncer::ApiUser; +use crate::base::{conf::Conf, database::Database}; + +// Structures +pub struct API { + endpoint: String, + port: u16, + + database: Arc> +} + +// Functions +async fn receive_request(params: HashMap, api: Arc) -> Result { + // Checking reqiurements + if !params.contains_key("api") || !params.contains_key("symbol") { + let usage: String = format!("Usage: http(s)://path.to.neurostock/v1?api=[APIKEY]&symbol=[SYMBOL]\n"); + return Ok(warp::reply::with_status(usage, warp::http::StatusCode::BAD_REQUEST)); + } + + // Getting params + let symbol: &String = params.get("symbol").unwrap(); + let key: &String = params.get("api").unwrap(); + + // Getting the API User + let user: ApiUser = bouncer::get_api_user(Arc::clone(&api.database), key).unwrap(); + + // Verifying the API Key + if !user.success { + return Ok(warp::reply::with_status( + format!("API Key Not Accepted by Server ({}).\n", user.message), + warp::http::StatusCode::FORBIDDEN + )); + } + + // Success + return Ok(warp::reply::with_status( + format!("{}\n", api.recieve(symbol).unwrap()), + warp::http::StatusCode::OK + )); +} + +// Implementations +impl API { + // Constructors + pub fn init(conf: &Conf, database: Arc>) -> Result> { + // Getting API Endpoint + let endpoint: String = conf.get_string("api.endpoint")?; + let port: u16 = conf.get_i32("api.port")? as u16; + + // Returning API + return Ok(API { + endpoint: endpoint, + port: port, + + database: database, + }); + } + + // Functions + pub fn start(&self, self_reference: Arc) -> Result, Box> { + // Getting the server port + let address: ([u8; 4], u16) = ([0,0,0,0], self.port.clone()); + + // Creating a WARP server + let warp_server = warp::path(self.endpoint.clone()) + .and(warp::query::>()) + .and(warp::any().map(move || Arc::clone(&self_reference))) + .and_then(receive_request); + + // Starting the server on a new thread + let warp_future: task::JoinHandle<()> = task::spawn(async move { + warp::serve(warp_server).run(address).await; + }); + + // Success + return Ok(warp_future); + } + pub fn recieve(&self, symbol: &str) -> Result> { + // Getting a mutable reference for database + let database: &mut Database = { + &mut self.database.lock().unwrap() + }; + + // Getting result data + let result: serde_json::Value = retreiver::data_symbol(database, symbol)?; + + // Success + return Ok(result.to_string()); + } +} \ No newline at end of file diff --git a/project/src/api/bouncer.rs b/project/src/api/bouncer.rs new file mode 100644 index 0000000..8a2992f --- /dev/null +++ b/project/src/api/bouncer.rs @@ -0,0 +1,68 @@ +// Libraries +use std::error::Error; +use std::sync::{Arc, Mutex}; + +use crate::base::database::Database; + +// Structures +pub struct ApiUser { + pub success: bool, + pub message: String +} + +// Functions +pub fn get_api_user(database: Arc>, key: &str) -> Result> { + // Unlock Database Mutex + let db: &mut Database = { + &mut *database.lock().unwrap() + }; + + // Initilize API table + db.inst_table("API", "`key` TEXT, `requests` INT, `limit` INT")?; + + // Getting the API User with the key the requester has provided + let rows: Vec = db.get( + "API", + "*", + &format!(" WHERE `key`='{}'", key) + )?; + + // Going through all rows + for row in rows { + // Getting row details + let row_key: String = row.get(0).unwrap(); + let row_requests: u32 = row.get(1).unwrap(); + let row_limit: u32 = row.get(2).unwrap(); + + // Double check the API key + if row_key != key {continue;} + + // Checking if our requests is lower than our limit + if row_requests >= row_limit { + return Ok(ApiUser { + success: false, + message: "API Request Limit Reached".to_string() + }); + } + + // Asking database to increase requests + db.update( + "API", + "requests", + &format!("{}", row_requests + 1), + &format!("`key`='{}'", key) + )?; + + // Success! + return Ok(ApiUser { + success: true, + message: "N/A".to_string() + }); + } + + // Didn't find our user + return Ok(ApiUser { + success: false, + message: "Invalid API Key".to_string() + }); +} \ No newline at end of file diff --git a/project/src/api/retreiver.rs b/project/src/api/retreiver.rs new file mode 100644 index 0000000..4fe1e55 --- /dev/null +++ b/project/src/api/retreiver.rs @@ -0,0 +1,46 @@ +// Libraries +use std::error::Error; +use serde_json::{self, json, Value}; + +use crate::base::database::Database; + +// Functions +pub fn data_symbol(database: &mut Database, symbol: &str) -> Result> { + // Checking if the table exists + if !database.exists_table(symbol)? { + return Ok(json!({ + "message": format!("Couldn't find data for {}", symbol) + })); + } + + // Query Database to see if we have that symbol collected. + let rows: Vec = database.get(symbol, "*", "")?; + + // Creating JSON result + let mut result: Vec = Vec::new(); + + // Going through all rows + for row in rows { + // Getting column data + let date: String = row.get(0).unwrap(); + let open: f32 = row.get(1).unwrap(); + let high: f32 = row.get(2).unwrap(); + let low: f32 = row.get(3).unwrap(); + let close: f32 = row.get(4).unwrap(); + + // Compiling result + result.push(json!({ + "date": date, + "open": open, + "high": high, + "low": low, + "close": close + })); + } + + // Success + return Ok(json!({ + "symbol": symbol, + "data": result + })); +} \ No newline at end of file