[#26] Moved all routing to server binary

master^2
Jef Roosens 2021-04-16 00:32:03 +02:00
parent bffbb61124
commit d19fe5c42e
Signed by: Jef Roosens
GPG Key ID: 955C0660072F691F
11 changed files with 130 additions and 134 deletions

View File

@ -2,7 +2,7 @@
# This hook lints the code, and if we're on develop or master, also forces the tests to pass. # 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 || { ./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; exit 1;
} }

View File

@ -1,7 +1,9 @@
#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use] #[macro_use]
extern crate rocket; extern crate rocket;
mod catchers;
use fej::{catchers, ivago}; mod routes;
// Very temporary solution for CORS // Very temporary solution for CORS
// https://stackoverflow.com/questions/62412361/how-to-set-up-cors-or-options-for-rocket-rs // 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 { fn rocket() -> rocket::Rocket {
rocket::ignite() rocket::ignite()
.attach(CORS) .attach(CORS)
.mount("/ivago", ivago::routes()) .mount("/ivago", routes::ivago())
.register(catchers![catchers::not_found]) .register(catchers![catchers::not_found])
} }

View File

@ -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?<q>")]
pub fn route_search_streets(q: String) -> Result<Json<Vec<Street>>, 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("/?<street>&<number>&<start_date>&<end_date>")]
pub fn route_get_pickup_times(
street: Street,
number: u32,
start_date: BasicDate,
end_date: BasicDate,
) -> Result<Json<Vec<PickupTime>>, Status> {
Ok(Json(get_pickup_times(
&street,
&number,
&start_date.0,
&end_date.0,
)?))
}

View File

@ -0,0 +1,5 @@
mod ivago;
pub fn ivago() -> Vec<rocket::Route> {
routes![ivago::route_search_streets, ivago::route_get_pickup_times]
}

View File

@ -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<Vec<Street>, FejError> {
let client = reqwest::Client::new();
let response = client.get(SEARCH_URL).query(&[("q", search_term)]).send()?;
let data: Vec<HashMap<String, String>> = 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<Tz>,
end_date: &DateTime<Tz>,
) -> Result<Vec<PickupTime>, 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<HashMap<String, String>> = response.json()?;
let mut output: Vec<PickupTime> = 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)
}

View File

@ -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}; mod basic_date;
use controller::{BasicDate, PickupTime, Street}; mod pickup_time;
use rocket::http::Status; mod street;
use rocket_contrib::json::Json;
pub fn routes() -> Vec<rocket::Route> { pub use basic_date::BasicDate;
routes![route_search_streets, route_get_pickup_times] pub use pickup_time::PickupTime;
} pub use street::Street;
/// This route handles the Ivago search endpoint. It returns a list of streets, /// Endpoint for the search feature
/// consisting of a street name & a city. 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 /// # Arguments
/// ///
/// * `search_term` - Search term to use to look for streets /// * `search_term` - Search term to use to look for streets
#[get("/search?<q>")] pub fn search_streets(search_term: &str) -> Result<Vec<Street>, FejError> {
pub fn route_search_streets(q: String) -> Result<Json<Vec<Street>>, Status> { let client = reqwest::Client::new();
Ok(Json(search_streets(q.as_str())?)) let response = client.get(SEARCH_URL).query(&[("q", search_term)]).send()?;
let data: Vec<HashMap<String, String>> = 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 /// Returns the pickup times for the various trash types.
/// of pickup times, containing a date and a description of the trash type.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `street` - Street to look up /// * `street` - Street to look up
/// * `number` - House number in the given street /// * `number` - House number in given street
/// * `start_date` - Earliest date that can be returned /// * `start_date` - Earliest date for the results
/// * `end_date` - Latest date that can be returned /// * `end_date` - Latest date for the results
#[get("/?<street>&<number>&<start_date>&<end_date>")] pub fn get_pickup_times(
pub fn route_get_pickup_times( street: &Street,
street: Street, number: &u32,
number: u32, start_date: &DateTime<Tz>,
start_date: BasicDate, end_date: &DateTime<Tz>,
end_date: BasicDate, ) -> Result<Vec<PickupTime>, FejError> {
) -> Result<Json<Vec<PickupTime>>, Status> { let client = reqwest::Client::builder().cookie_store(true).build()?;
Ok(Json(get_pickup_times(
&street, // This populates the cookies with the necessary values
&number, client
&start_date.0, .post(BASE_URL)
&end_date.0, .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<HashMap<String, String>> = response.json()?;
let mut output: Vec<PickupTime> = 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)
} }

View File

@ -1,11 +1,7 @@
#![feature(proc_macro_hygiene, decl_macro)] #![feature(proc_macro_hygiene, decl_macro)]
#[macro_use]
extern crate rocket;
// Route modules // Route modules
pub mod ivago; pub mod ivago;
// Helper modules // Helper modules
pub mod catchers;
pub mod errors; pub mod errors;