From d19fe5c42e29af96e3be02662ca6e507b90f19b2 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Fri, 16 Apr 2021 00:32:03 +0200 Subject: [PATCH] [#26] Moved all routing to server binary --- .hooks/pre-commit | 2 +- src/{ => bin/server}/catchers.rs | 0 src/bin/server/main.rs | 8 +- src/bin/server/routes/ivago.rs | 38 ++++++++ src/bin/server/routes/mod.rs | 5 + src/ivago/{controller => }/basic_date.rs | 0 src/ivago/controller/mod.rs | 95 ------------------ src/ivago/mod.rs | 112 ++++++++++++++++------ src/ivago/{controller => }/pickup_time.rs | 0 src/ivago/{controller => }/street.rs | 0 src/lib.rs | 4 - 11 files changed, 130 insertions(+), 134 deletions(-) rename src/{ => bin/server}/catchers.rs (100%) create mode 100644 src/bin/server/routes/ivago.rs create mode 100644 src/bin/server/routes/mod.rs rename src/ivago/{controller => }/basic_date.rs (100%) delete mode 100644 src/ivago/controller/mod.rs rename src/ivago/{controller => }/pickup_time.rs (100%) rename src/ivago/{controller => }/street.rs (100%) diff --git a/.hooks/pre-commit b/.hooks/pre-commit index 6947bc7..fe55771 100755 --- a/.hooks/pre-commit +++ b/.hooks/pre-commit @@ -2,7 +2,7 @@ # This hook lints the code, and if we're on develop or master, also forces the tests to pass. ./fejctl lint &> /dev/null 2>&1 || { - >&2 echo "Format check failed, use 'make lint' for more information."; + >&2 echo "Format check failed, use './fejctl lint' for more information."; exit 1; } diff --git a/src/catchers.rs b/src/bin/server/catchers.rs similarity index 100% rename from src/catchers.rs rename to src/bin/server/catchers.rs diff --git a/src/bin/server/main.rs b/src/bin/server/main.rs index a27e00f..3fde2c7 100644 --- a/src/bin/server/main.rs +++ b/src/bin/server/main.rs @@ -1,7 +1,9 @@ +#![feature(proc_macro_hygiene, decl_macro)] + #[macro_use] extern crate rocket; - -use fej::{catchers, ivago}; +mod catchers; +mod routes; // Very temporary solution for CORS // https://stackoverflow.com/questions/62412361/how-to-set-up-cors-or-options-for-rocket-rs @@ -33,7 +35,7 @@ impl Fairing for CORS { fn rocket() -> rocket::Rocket { rocket::ignite() .attach(CORS) - .mount("/ivago", ivago::routes()) + .mount("/ivago", routes::ivago()) .register(catchers![catchers::not_found]) } diff --git a/src/bin/server/routes/ivago.rs b/src/bin/server/routes/ivago.rs new file mode 100644 index 0000000..d8318bd --- /dev/null +++ b/src/bin/server/routes/ivago.rs @@ -0,0 +1,38 @@ +use fej::ivago::{get_pickup_times, search_streets, BasicDate, PickupTime, Street}; +use rocket::http::Status; +use rocket_contrib::json::Json; + +/// This route handles the Ivago search endpoint. It returns a list of streets, +/// consisting of a street name & a city. +/// +/// # Arguments +/// +/// * `search_term` - Search term to use to look for streets +#[get("/search?")] +pub fn route_search_streets(q: String) -> Result>, Status> { + Ok(Json(search_streets(q.as_str())?)) +} + +/// Handles returning of pickup times for a specific address. It returns a list +/// of pickup times, containing a date and a description of the trash type. +/// +/// # Arguments +/// +/// * `street` - Street to look up +/// * `number` - House number in the given street +/// * `start_date` - Earliest date that can be returned +/// * `end_date` - Latest date that can be returned +#[get("/?&&&")] +pub fn route_get_pickup_times( + street: Street, + number: u32, + start_date: BasicDate, + end_date: BasicDate, +) -> Result>, Status> { + Ok(Json(get_pickup_times( + &street, + &number, + &start_date.0, + &end_date.0, + )?)) +} diff --git a/src/bin/server/routes/mod.rs b/src/bin/server/routes/mod.rs new file mode 100644 index 0000000..449f82f --- /dev/null +++ b/src/bin/server/routes/mod.rs @@ -0,0 +1,5 @@ +mod ivago; + +pub fn ivago() -> Vec { + routes![ivago::route_search_streets, ivago::route_get_pickup_times] +} diff --git a/src/ivago/controller/basic_date.rs b/src/ivago/basic_date.rs similarity index 100% rename from src/ivago/controller/basic_date.rs rename to src/ivago/basic_date.rs diff --git a/src/ivago/controller/mod.rs b/src/ivago/controller/mod.rs deleted file mode 100644 index 7d3a9f9..0000000 --- a/src/ivago/controller/mod.rs +++ /dev/null @@ -1,95 +0,0 @@ -use crate::errors::FejError; -use chrono::DateTime; -use chrono_tz::Tz; -use reqwest::blocking as reqwest; -use std::collections::HashMap; -use std::convert::{From, TryFrom}; - -mod basic_date; -mod pickup_time; -mod street; - -pub use basic_date::BasicDate; -pub use pickup_time::PickupTime; -pub use street::Street; - -/// Endpoint for the search feature -const SEARCH_URL: &str = "https://www.ivago.be/nl/particulier/autocomplete/garbage/streets"; -/// Endpoint for populating the initial cookies -const BASE_URL: &str = "https://www.ivago.be/nl/particulier/afval/ophaling"; -/// Endpoint for the actual calendar output -const CAL_URL: &str = "https://www.ivago.be/nl/particulier/garbage/pick-up/pickups"; - -/// Searches the Ivago API for streets in the given city. -/// -/// # Arguments -/// -/// * `search_term` - Search term to use to look for streets -pub fn search_streets(search_term: &str) -> Result, FejError> { - let client = reqwest::Client::new(); - let response = client.get(SEARCH_URL).query(&[("q", search_term)]).send()?; - let data: Vec> = response.json()?; - - // This is pretty cool, filter_map first does get() on all the maps, and - // then filters out any None values - // Then, we do the same thing for streets - Ok(data - .iter() - .filter_map(|m| m.get("value")) - .filter_map(|v| Street::try_from(v.as_str()).ok()) - .collect()) -} - -/// Returns the pickup times for the various trash types. -/// -/// # Arguments -/// -/// * `street` - Street to look up -/// * `number` - House number in given street -/// * `start_date` - Earliest date for the results -/// * `end_date` - Latest date for the results -pub fn get_pickup_times( - street: &Street, - number: &u32, - start_date: &DateTime, - end_date: &DateTime, -) -> Result, FejError> { - let client = reqwest::Client::builder().cookie_store(true).build()?; - - // This populates the cookies with the necessary values - client - .post(BASE_URL) - .form(&[ - ("garbage_type", ""), - ("ivago_street", &String::from(street)), - ("number", &number.to_string()), - ("form_id", "garbage_address_form"), - ]) - .send()?; - - let response = client - .get(CAL_URL) - .query(&[ - ("_format", "json"), - ("type", ""), - ("start", &start_date.timestamp().to_string()), - ("end", &end_date.timestamp().to_string()), - ]) - .send()?; - let data: Vec> = response.json()?; - - let mut output: Vec = Vec::new(); - - for map in data - .iter() - .filter(|m| m.contains_key("date") && m.contains_key("label")) - { - // Because we filtered the maps in the loop, we can safely use unwrap - // here - if let Ok(date) = BasicDate::try_from(map.get("date").unwrap().as_str()) { - output.push(PickupTime::new(date, map.get("label").unwrap().to_string())) - } - } - - Ok(output) -} diff --git a/src/ivago/mod.rs b/src/ivago/mod.rs index 627e89b..7d3a9f9 100644 --- a/src/ivago/mod.rs +++ b/src/ivago/mod.rs @@ -1,45 +1,95 @@ -mod controller; +use crate::errors::FejError; +use chrono::DateTime; +use chrono_tz::Tz; +use reqwest::blocking as reqwest; +use std::collections::HashMap; +use std::convert::{From, TryFrom}; -use controller::{get_pickup_times, search_streets}; -use controller::{BasicDate, PickupTime, Street}; -use rocket::http::Status; -use rocket_contrib::json::Json; +mod basic_date; +mod pickup_time; +mod street; -pub fn routes() -> Vec { - routes![route_search_streets, route_get_pickup_times] -} +pub use basic_date::BasicDate; +pub use pickup_time::PickupTime; +pub use street::Street; -/// This route handles the Ivago search endpoint. It returns a list of streets, -/// consisting of a street name & a city. +/// Endpoint for the search feature +const SEARCH_URL: &str = "https://www.ivago.be/nl/particulier/autocomplete/garbage/streets"; +/// Endpoint for populating the initial cookies +const BASE_URL: &str = "https://www.ivago.be/nl/particulier/afval/ophaling"; +/// Endpoint for the actual calendar output +const CAL_URL: &str = "https://www.ivago.be/nl/particulier/garbage/pick-up/pickups"; + +/// Searches the Ivago API for streets in the given city. /// /// # Arguments /// /// * `search_term` - Search term to use to look for streets -#[get("/search?")] -pub fn route_search_streets(q: String) -> Result>, Status> { - Ok(Json(search_streets(q.as_str())?)) +pub fn search_streets(search_term: &str) -> Result, FejError> { + let client = reqwest::Client::new(); + let response = client.get(SEARCH_URL).query(&[("q", search_term)]).send()?; + let data: Vec> = response.json()?; + + // This is pretty cool, filter_map first does get() on all the maps, and + // then filters out any None values + // Then, we do the same thing for streets + Ok(data + .iter() + .filter_map(|m| m.get("value")) + .filter_map(|v| Street::try_from(v.as_str()).ok()) + .collect()) } -/// Handles returning of pickup times for a specific address. It returns a list -/// of pickup times, containing a date and a description of the trash type. +/// Returns the pickup times for the various trash types. /// /// # Arguments /// /// * `street` - Street to look up -/// * `number` - House number in the given street -/// * `start_date` - Earliest date that can be returned -/// * `end_date` - Latest date that can be returned -#[get("/?&&&")] -pub fn route_get_pickup_times( - street: Street, - number: u32, - start_date: BasicDate, - end_date: BasicDate, -) -> Result>, Status> { - Ok(Json(get_pickup_times( - &street, - &number, - &start_date.0, - &end_date.0, - )?)) +/// * `number` - House number in given street +/// * `start_date` - Earliest date for the results +/// * `end_date` - Latest date for the results +pub fn get_pickup_times( + street: &Street, + number: &u32, + start_date: &DateTime, + end_date: &DateTime, +) -> Result, FejError> { + let client = reqwest::Client::builder().cookie_store(true).build()?; + + // This populates the cookies with the necessary values + client + .post(BASE_URL) + .form(&[ + ("garbage_type", ""), + ("ivago_street", &String::from(street)), + ("number", &number.to_string()), + ("form_id", "garbage_address_form"), + ]) + .send()?; + + let response = client + .get(CAL_URL) + .query(&[ + ("_format", "json"), + ("type", ""), + ("start", &start_date.timestamp().to_string()), + ("end", &end_date.timestamp().to_string()), + ]) + .send()?; + let data: Vec> = response.json()?; + + let mut output: Vec = Vec::new(); + + for map in data + .iter() + .filter(|m| m.contains_key("date") && m.contains_key("label")) + { + // Because we filtered the maps in the loop, we can safely use unwrap + // here + if let Ok(date) = BasicDate::try_from(map.get("date").unwrap().as_str()) { + output.push(PickupTime::new(date, map.get("label").unwrap().to_string())) + } + } + + Ok(output) } diff --git a/src/ivago/controller/pickup_time.rs b/src/ivago/pickup_time.rs similarity index 100% rename from src/ivago/controller/pickup_time.rs rename to src/ivago/pickup_time.rs diff --git a/src/ivago/controller/street.rs b/src/ivago/street.rs similarity index 100% rename from src/ivago/controller/street.rs rename to src/ivago/street.rs diff --git a/src/lib.rs b/src/lib.rs index b1e2733..761c91b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,7 @@ #![feature(proc_macro_hygiene, decl_macro)] -#[macro_use] -extern crate rocket; - // Route modules pub mod ivago; // Helper modules -pub mod catchers; pub mod errors;