diff --git a/Cargo.lock b/Cargo.lock index b257d33..da12c2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1141,6 +1141,41 @@ dependencies = [ "uncased", ] +[[package]] +name = "rust-embed" +version = "6.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb133b9a38b5543fad3807fb2028ea47c5f2b566f4f5e28a11902f1a358348b6" +dependencies = [ + "rocket", + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "6.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4e0f0ced47ded9a68374ac145edd65a6c1fa13a96447b873660b2a568a0fd7" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "7.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rustversion" version = "1.0.11" @@ -1165,6 +1200,15 @@ dependencies = [ "libm", ] +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -1533,6 +1577,33 @@ dependencies = [ "serde", ] +[[package]] +name = "ufmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a64846ec02b57e9108d6469d98d1648782ad6bb150a95a9baac26900bbeab9d" +dependencies = [ + "ufmt-macros", + "ufmt-write", +] + +[[package]] +name = "ufmt-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d337d3be617449165cb4633c8dece429afd83f84051024079f97ad32a9663716" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ufmt-write" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69" + [[package]] name = "uncased" version = "0.9.7" @@ -1577,6 +1648,17 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + [[package]] name = "want" version = "0.3.0" @@ -1652,8 +1734,10 @@ name = "web" version = "0.1.0" dependencies = [ "rocket", + "rust-embed", "serde", "tokio", + "ufmt", "xpin", ] @@ -1673,6 +1757,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/web/Cargo.toml b/web/Cargo.toml index 40cf3f1..86f5efd 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -10,3 +10,5 @@ rocket = {version="0.5.0-rc.2", features=["json"]} tokio = {version="1"} xpin={path=".."} serde = {version="1", features=["derive"]} +rust-embed = { version = "6.6.0", features = ["rocket"] } +ufmt = "0.2.0" diff --git a/web/src/api.rs b/web/src/api.rs new file mode 100644 index 0000000..e13cc36 --- /dev/null +++ b/web/src/api.rs @@ -0,0 +1,65 @@ +use std::str::FromStr; + +use rocket::{get, serde::json::Json, Responder}; +use serde::{Deserialize, Serialize}; +use xpin::Address; + +#[get("/address?&")] +pub(crate) fn get_address(lat: Option, lon: Option) -> Result { + let (lat, lon) = lat.zip(lon).ok_or(ApiError::InvalidRequest( + "lat and lon parameters are required", + ))?; + let lat = lat + .parse::() + .map_err(|_| ApiError::InvalidRequest("Invalid lat parameter"))?; + let lon = lon + .parse::() + .map_err(|_| ApiError::InvalidRequest("Invalid lon parameter"))?; + + Ok(Address::from_lat_lon(lat, lon)?.to_string()) +} + +#[get("/coords?
")] +pub(crate) fn get_coords(address: Option) -> Result, ApiError> { + let address = address.ok_or(ApiError::InvalidRequest("address parameter required"))?; + + Address::from_str(&address) + .as_ref() + .map_err(Into::into) + .map(Address::as_lat_lon) + .map(Coords::from) + .map(Json) +} + +#[derive(Serialize, Deserialize)] +pub(crate) struct Coords { + lat: f64, + lon: f64, +} + +impl From<(f64, f64)> for Coords { + fn from((lat, lon): (f64, f64)) -> Self { + Self { lat, lon } + } +} + +#[derive(Responder)] +#[response(status = 400)] +pub(crate) enum ApiError { + #[response(status = 400)] + AlgorithmError(String), + #[response(status = 400)] + InvalidRequest(&'static str), +} + +impl From<&xpin::Error> for ApiError { + fn from(e: &xpin::Error) -> Self { + Self::AlgorithmError(e.to_string()) + } +} + +impl From for ApiError { + fn from(e: xpin::Error) -> Self { + Self::from(&e) + } +} diff --git a/web/src/main.rs b/web/src/main.rs index 759380c..5e84247 100644 --- a/web/src/main.rs +++ b/web/src/main.rs @@ -1,78 +1,17 @@ -use std::str::FromStr; +mod api; +mod static_assets; -use rocket::{get, routes, serde::json::Json, Responder}; -use serde::{Deserialize, Serialize}; -use xpin::Address; +use rocket::routes; #[tokio::main] async fn main() -> Result<(), Box> { let _r = rocket::build() // Add the API - .mount("/api/v1", routes![get_address, get_coords]) + .mount("/api/v1", routes![api::get_address, api::get_coords]) // Add the webui frontend - // TODO: ./mount("/", routes![webui]) + .mount("/", routes![static_assets::dist]) .launch() .await?; Ok(()) } - -#[get("/address?&")] -async fn get_address(lat: Option, lon: Option) -> Result { - let (lat, lon) = lat.zip(lon).ok_or(ApiError::InvalidRequest( - "lat and lon parameters are required", - ))?; - let lat = lat - .parse::() - .map_err(|_| ApiError::InvalidRequest("Invalid lat parameter"))?; - let lon = lon - .parse::() - .map_err(|_| ApiError::InvalidRequest("Invalid lon parameter"))?; - - Ok(Address::from_lat_lon(lat, lon)?.to_string()) -} - -#[get("/coords?
")] -async fn get_coords(address: Option) -> Result, ApiError> { - let address = address.ok_or(ApiError::InvalidRequest("address parameter required"))?; - - Address::from_str(&address) - .as_ref() - .map_err(Into::into) - .map(Address::as_lat_lon) - .map(Coords::from) - .map(Json) -} - -#[derive(Serialize, Deserialize)] -struct Coords { - lat: f64, - lon: f64, -} - -impl From<(f64, f64)> for Coords { - fn from((lat, lon): (f64, f64)) -> Self { - Self { lat, lon } - } -} - -#[derive(Responder)] -#[response(status = 400)] -enum ApiError { - #[response(status = 400)] - AlgorithmError(String), - #[response(status = 400)] - InvalidRequest(&'static str), -} - -impl From<&xpin::Error> for ApiError { - fn from(e: &xpin::Error) -> Self { - Self::AlgorithmError(e.to_string()) - } -} - -impl From for ApiError { - fn from(e: xpin::Error) -> Self { - Self::from(&e) - } -} diff --git a/web/src/static_assets.rs b/web/src/static_assets.rs new file mode 100644 index 0000000..88b15af --- /dev/null +++ b/web/src/static_assets.rs @@ -0,0 +1,30 @@ +use rocket::get; +use rocket::http::ContentType; +use rust_embed::RustEmbed; + +use std::borrow::Cow; +use std::ffi::OsStr; +use std::path::PathBuf; + +#[derive(RustEmbed)] +#[folder = "../build/"] +struct Asset; + +#[get("/")] +pub(crate) fn dist(file: PathBuf) -> Option<(ContentType, Cow<'static, [u8]>)> { + if let Some(a) = Asset::get(&format!("{}", file.display().to_string())) { + let content_type = file + .extension() + .and_then(OsStr::to_str) + .and_then(ContentType::from_extension) + .unwrap_or(ContentType::Bytes); + + return Some((content_type, a.data)); + } + + [".html", "index.html"].iter().find_map(|ext| { + let p = format!("{}{}", file.display().to_string(), ext); + + Asset::get(&p).map(|a| (ContentType::HTML, a.data)) + }) +}