diff --git a/.dockerignore b/.dockerignore index 17aaf81..590ee51 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,4 +2,5 @@ cache target !target/x86_64-unknown-linux-musl/release/this-week-in-past -!target/release/this-week-in-past \ No newline at end of file +!target/release/this-week-in-past +test.sh \ No newline at end of file diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 0939331..11a7c00 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -80,6 +80,9 @@ jobs: test: name: Run application tests runs-on: ubuntu-latest + env: + BIGDATA_CLOUD_API_KEY: ${{ secrets.BIGDATA_CLOUD_API_KEY }} + OPEN_WEATHER_MAP_API_KEY: ${{ secrets.OPEN_WEATHER_MAP_API_KEY }} steps: - name: Checkout code uses: actions/checkout@v3 diff --git a/.gitignore b/.gitignore index 2f8fa0f..c0acf16 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target .idea cache +test.sh \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 81556c5..8b0cf8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1983,15 +1983,6 @@ dependencies = [ "winreg", ] -[[package]] -name = "roxmltree" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b" -dependencies = [ - "xmlparser", -] - [[package]] name = "rustc_version" version = "0.4.0" @@ -2288,7 +2279,6 @@ dependencies = [ "rayon", "regex", "reqwest", - "roxmltree", "serde", "serde_json", "time 0.3.9", @@ -2700,12 +2690,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "xmlparser" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8" - [[package]] name = "zstd" version = "0.10.0+zstd.1.5.2" diff --git a/README.md b/README.md index 1f443db..16e54a1 100644 --- a/README.md +++ b/README.md @@ -31,14 +31,16 @@ docker run -p 8080:8080 \ All configuration is done via environment parameter: -| Name | Description | Default value | -|--------------------------|---------------------------------------------------------|---------------| -| RESOURCE_PATHS | Paths to the resources to load (comma separated) | | -| CACHE_DIR | Path to the caching to load, needs to read/write rights | | -| SLIDESHOW_INTERVAL | Interval of the slideshow in milliseconds | 10000 | -| WEATHER_ENABLED | Indicates if weather should be shown in the slideshow | true | -| LOCATION_NAME | Weather location | Berlin | -| LANGUAGE | Weather language | en | -| HOME_ASSISTANT_BASE_URL | Home assistant base url | | -| HOME_ASSISTANT_ENTITY_ID | Home assistant entity id to load the weather from | | -| HOME_ASSISTANT_API_TOKEN | Home assistant api access token | | +| Name | Description | Default value | +|--------------------------|------------------------------------------------------------------------------------|---------------| +| RESOURCE_PATHS | Paths to the resources to load (comma separated) | | +| CACHE_DIR | Path to the caching to load, needs to read/write rights | | +| SLIDESHOW_INTERVAL | Interval of the slideshow in milliseconds | 10000 | +| WEATHER_ENABLED | Indicates if weather should be shown in the slideshow | true | +| BIGDATA_CLOUD_API_KEY | To resolve geo coordinates to city name. Obtain here: https://www.bigdatacloud.com | | +| OPEN_WEATHER_MAP_API_KEY | To receive weather live data. Obtain here: https://openweathermap.org/api | | +| LOCATION_NAME | Weather location | Berlin | +| LANGUAGE | Weather language | en | +| HOME_ASSISTANT_BASE_URL | Home assistant base url | | +| HOME_ASSISTANT_ENTITY_ID | Home assistant entity id to load the weather from | | +| HOME_ASSISTANT_API_TOKEN | Home assistant api access token | | diff --git a/src/geo_location.rs b/src/geo_location.rs index 7a5efe6..92f47b6 100644 --- a/src/geo_location.rs +++ b/src/geo_location.rs @@ -1,8 +1,11 @@ +use std::collections::HashMap; +use std::env; use std::fmt::{Display, Formatter}; use lazy_static::lazy_static; use regex::{Captures, Regex}; use serde::{Deserialize, Serialize}; +use serde_json::Value; /// Struct representing a geo location #[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)] @@ -114,3 +117,49 @@ pub fn from_degrees_minutes_seconds(latitude: String, longitude: String) -> Opti None } } + +/// Returns the city name for the specified geo location +/// The city name is resolved from the geo location using the bigdatacloud api +pub async fn resolve_city_name(geo_location: GeoLocation) -> Option { + if env::var("BIGDATA_CLOUD_API_KEY").is_err() { + return None; + } + + let response = reqwest::get(format!( + "https://api.bigdatacloud.net/data/reverse-geocode?latitude={}&longitude={}&localityLanguage=de&key={}", + geo_location.latitude, + geo_location.longitude, + env::var("BIGDATA_CLOUD_API_KEY").unwrap(), + )).await; + + if response.is_err() { + return None; + } + + let response_json = + response.unwrap().text().await.ok().and_then(|json_string| { + serde_json::from_str::>(&json_string).ok() + }); + + let mut city_name = response_json + .as_ref() + .and_then(|json_data| get_string_value("city", json_data)) + .filter(|city_name| !city_name.trim().is_empty()); + + if city_name.is_none() { + city_name = response_json + .as_ref() + .and_then(|json_data| get_string_value("locality", json_data)) + .filter(|city_name| !city_name.trim().is_empty()); + } + + city_name +} + +/// Returns the string value for the specified key of an hash map +fn get_string_value(field_name: &str, json_data: &HashMap) -> Option { + json_data + .get(field_name) + .and_then(|field_value| field_value.as_str()) + .map(|field_string_value| field_string_value.to_string()) +} diff --git a/src/resource_processor.rs b/src/resource_processor.rs index a8ca4ad..33dfadf 100644 --- a/src/resource_processor.rs +++ b/src/resource_processor.rs @@ -1,13 +1,11 @@ -use std::collections::HashMap; use std::env; use evmap::ReadHandle; use rand::prelude::SliceRandom; use rand::Rng; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; -use serde_json::Value; -use crate::geo_location::GeoLocation; +use crate::geo_location; use crate::resource_reader::RemoteResource; pub fn md5(string: &str) -> String { @@ -58,7 +56,7 @@ pub async fn build_display_value(resource: RemoteResource) -> String { // Append city name if let Some(resource_location) = resource.location { - let city_name = resolve_city_name(resource_location).await; + let city_name = geo_location::resolve_city_name(resource_location).await; if let Some(city_name) = city_name { display_value.push_str(", "); @@ -69,48 +67,6 @@ pub async fn build_display_value(resource: RemoteResource) -> String { display_value.trim().to_string() } -/// Returns the city name for the specified geo location -/// The city name is resolved from the geo location using the bigdatacloud api -pub async fn resolve_city_name(geo_location: GeoLocation) -> Option { - let response = reqwest::get(format!( - "https://api.bigdatacloud.net/data/reverse-geocode?latitude={}&longitude={}&localityLanguage=de&key={}", - geo_location.latitude, - geo_location.longitude, - "6b8aad17eba7449d9d93c533359b0384", - )).await; - - if response.is_err() { - return None; - } - - let response_json = - response.unwrap().text().await.ok().and_then(|json_string| { - serde_json::from_str::>(&json_string).ok() - }); - - let mut city_name = response_json - .as_ref() - .and_then(|json_data| get_string_value("city", json_data)) - .filter(|city_name| !city_name.trim().is_empty()); - - if city_name.is_none() { - city_name = response_json - .as_ref() - .and_then(|json_data| get_string_value("locality", json_data)) - .filter(|city_name| !city_name.trim().is_empty()); - } - - city_name -} - -/// Returns the string value for the specified key of an hash map -fn get_string_value(field_name: &str, json_data: &HashMap) -> Option { - json_data - .get(field_name) - .and_then(|field_value| field_value.as_str()) - .map(|field_string_value| field_string_value.to_string()) -} - /// Selects a random entry from the specified resource provider /// The id of the resource is returned pub fn random_entry(kv_reader: &ReadHandle) -> Option { diff --git a/src/resource_processor_test.rs b/src/resource_processor_test.rs index d941c15..ef4edad 100644 --- a/src/resource_processor_test.rs +++ b/src/resource_processor_test.rs @@ -1,7 +1,7 @@ use assertor::*; use crate::geo_location::GeoLocation; -use crate::resource_processor; +use crate::{geo_location, resource_processor}; #[actix_rt::test] async fn resolve_koblenz() { @@ -12,7 +12,7 @@ async fn resolve_koblenz() { }; // WHEN resolving the city name - let city_name = resource_processor::resolve_city_name(geo_location).await; + let city_name = geo_location::resolve_city_name(geo_location).await; // THEN the resolved city name should be Koblenz assert_that!(city_name).is_equal_to(Some("Koblenz".to_string())); @@ -27,7 +27,7 @@ async fn resolve_amsterdam() { }; // WHEN resolving the city name - let city_name = resource_processor::resolve_city_name(geo_location).await; + let city_name = geo_location::resolve_city_name(geo_location).await; // THEN the resolved city name should be Amsterdam assert_that!(city_name).is_equal_to(Some("Amsterdam".to_string())); @@ -42,7 +42,7 @@ async fn resolve_kottenheim() { }; // WHEN resolving the city name - let city_name = resource_processor::resolve_city_name(geo_location).await; + let city_name = geo_location::resolve_city_name(geo_location).await; // THEN the resolved city name should be Kottenheim assert_that!(city_name).is_equal_to(Some("Kottenheim".to_string())); @@ -57,7 +57,7 @@ async fn resolve_invalid_data() { }; // WHEN resolving the city name - let city_name = resource_processor::resolve_city_name(geo_location).await; + let city_name = geo_location::resolve_city_name(geo_location).await; // THEN the resolved city name should be None assert_that!(city_name).is_equal_to(None); diff --git a/src/weather_processor.rs b/src/weather_processor.rs index 5cfedc5..863571a 100644 --- a/src/weather_processor.rs +++ b/src/weather_processor.rs @@ -1,17 +1,20 @@ use std::env; -const OPEN_WEATHER_MAP_API_KEY: &str = "4021b60be2b322c8cfc749a6503bb553"; - /// Returns the current weather data provided by OpenWeatherMap /// The data is selected by the configured location /// Returns None if the data could not be retrieved or the weather json data pub async fn get_current_weather() -> Option { + if env::var("OPEN_WEATHER_MAP_API_KEY").is_err() { + return None; + } + + let api_key: String = env::var("OPEN_WEATHER_MAP_API_KEY").unwrap(); let city: String = env::var("LOCATION_NAME").unwrap_or_else(|_| "Berlin".to_string()); let units: String = env::var("UNITS").unwrap_or_else(|_| "metric".to_string()); let language: String = env::var("LANGUAGE").unwrap_or_else(|_| "en".to_string()); let response = reqwest::get(format!( - "https://api.openweathermap.org/data/2.5/weather?q={city}&appid={OPEN_WEATHER_MAP_API_KEY}&units={units}&lang={language}" + "https://api.openweathermap.org/data/2.5/weather?q={city}&appid={api_key}&units={units}&lang={language}" )).await; if response.is_ok() {