diff --git a/Cargo.lock b/Cargo.lock index 8c42751..11411f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,10 +54,12 @@ dependencies = [ name = "alert-me" version = "0.1.0" dependencies = [ + "ciborium", "clap", "config", "entity", "futures", + "itertools", "migration", "reqwest", "rumqttc", @@ -550,6 +552,33 @@ dependencies = [ "windows-targets 0.52.5", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clap" version = "4.5.4" @@ -1109,6 +1138,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.12.3" diff --git a/app/Cargo.toml b/app/Cargo.toml index afa6808..43bb472 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -6,9 +6,11 @@ edition = "2021" [dependencies] entity = { path = "../entity" } migration = { path = "../migration" } +ciborium = "0.2.2" clap = { version = "4.5.4", features = ["derive"] } config = "0.14.0" futures = "0.3.30" +itertools = "0.12.1" reqwest = { version = "0.12.4", features = ["json", "rustls-tls"], default-features = false } rumqttc = "0.24.0" sea-orm = { workspace = true, features = [ "with-time", "sqlx-sqlite", "sqlx-postgres", "sqlx-mysql", "runtime-tokio-rustls", "macros" ] } diff --git a/app/src/data/alert.rs b/app/src/data/alert.rs new file mode 100644 index 0000000..c8926e5 --- /dev/null +++ b/app/src/data/alert.rs @@ -0,0 +1,77 @@ +use std::io::{self, Cursor}; + +use ciborium::into_writer; +use itertools::izip; +use serde::{Deserialize, Serialize}; +use time::PrimitiveDateTime; + +use crate::json::{self}; + +use super::{area::Area, image::Image, link::Link, text::Text}; + +pub type Cbor = Vec; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Alert { + pub id: String, + title: Text, + description: Text, + nation_wide: bool, + instructions: Vec, + sender: String, + publisher: Option, + links: Vec, + contact: Text, + event: String, + all_clear: bool, + severity: String, + publish_date: PrimitiveDateTime, + areas: Vec, + images: Vec, +} + +impl From for Alert { + fn from(value: json::alert::Alert) -> Self { + Self { + id: value.identifier.clone(), + title: value.title.into(), + description: value.description.into(), + nation_wide: value.nation_wide, + instructions: value.instructions.into_iter().map(|x| x.into()).collect(), + sender: value.sender, + publisher: value.publisher_name, + links: value.links.into_iter().map(|x| x.into()).collect(), + contact: value.contact.into(), + event: value.event, + all_clear: value.all_clear, + severity: value.severity, + publish_date: value.publish_date, + areas: value.areas.into_iter().map(|x| x.into()).collect(), + images: izip!( + &value.images, + &value.image_titles, + &value.image_descriptions + ) + .map(|(img, title, desc)| Image { + uri: img.uri.clone(), + digest: img.digest.clone(), + title: title.title.clone().into(), + alt_text: title.alt_text.clone(), + description: desc.description.clone().into(), + }) + .collect(), + } + } +} + +impl TryFrom for Cbor { + type Error = ciborium::ser::Error; + + fn try_from(value: Alert) -> Result { + let mut buffer = Vec::new(); + let writer = Cursor::new(&mut buffer); + into_writer(&value, writer)?; + + Ok(buffer) + } +} diff --git a/app/src/data/area.rs b/app/src/data/area.rs new file mode 100644 index 0000000..1c3d5c0 --- /dev/null +++ b/app/src/data/area.rs @@ -0,0 +1,24 @@ +use serde::{Deserialize, Serialize}; + +use crate::json; + +use super::{circle::Circle, polygon::Polygon, text::Text}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Area { + pub description: Text, + pub regions: Vec, + pub polygons: Vec, + pub circles: Vec, +} + +impl From for Area { + fn from(value: json::area::Area) -> Self { + Self { + description: value.description.into(), + regions: value.regions.into_iter().map(|x| x.region).collect(), + polygons: value.polygons.into_iter().map(|x| x.into()).collect(), + circles: value.circles.into_iter().map(|x| x.into()).collect(), + } + } +} diff --git a/app/src/data/circle.rs b/app/src/data/circle.rs new file mode 100644 index 0000000..101981d --- /dev/null +++ b/app/src/data/circle.rs @@ -0,0 +1,20 @@ +use serde::{Deserialize, Serialize}; + +use crate::json; + +use super::coordinate::Coordinate; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Circle { + pub center_position: Coordinate, + radius: String, +} + +impl From for Circle { + fn from(value: json::circle::Circle) -> Self { + Self { + center_position: value.center_position.into(), + radius: value.radius, + } + } +} diff --git a/app/src/data/coordinate.rs b/app/src/data/coordinate.rs new file mode 100644 index 0000000..232fdd2 --- /dev/null +++ b/app/src/data/coordinate.rs @@ -0,0 +1,9 @@ +use crate::json; + +pub type Coordinate = (String, String); + +impl From for Coordinate { + fn from(value: json::coordinate::Coordinate) -> Self { + (value.zero, value.one) + } +} diff --git a/app/src/data/image.rs b/app/src/data/image.rs new file mode 100644 index 0000000..4d475c7 --- /dev/null +++ b/app/src/data/image.rs @@ -0,0 +1,12 @@ +use serde::{Deserialize, Serialize}; + +use super::text::Text; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Image { + pub uri: String, + pub digest: String, + pub title: Text, + pub alt_text: String, + pub description: Text, +} diff --git a/app/src/data/link.rs b/app/src/data/link.rs new file mode 100644 index 0000000..fe6722f --- /dev/null +++ b/app/src/data/link.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; + +use crate::json; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Link { + pub href: String, + pub text: String, +} + +impl From for Link { + fn from(value: json::link::Link) -> Self { + Self { + href: value.href, + text: value.text, + } + } +} diff --git a/app/src/data/polygon.rs b/app/src/data/polygon.rs new file mode 100644 index 0000000..e22be41 --- /dev/null +++ b/app/src/data/polygon.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; + +use crate::json; + +use super::coordinate::Coordinate; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Polygon { + pub coordinates: Vec, +} + +impl From for Polygon { + fn from(value: json::polygon::Polygon) -> Self { + Self { + coordinates: value.coordinates.into_iter().map(|x| x.into()).collect(), + } + } +} diff --git a/app/src/data/text.rs b/app/src/data/text.rs new file mode 100644 index 0000000..a869e20 --- /dev/null +++ b/app/src/data/text.rs @@ -0,0 +1,45 @@ +use serde::{Deserialize, Serialize}; + +use crate::json::{self, contact::Contact, description::Description, title::Title}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Text { + pub origin: Option, + pub translated: String, +} + +impl From for Text { + fn from(value: Title) -> Self { + Self { + origin: value.title_gtrans_origin, + translated: value.title, + } + } +} + +impl From<Description> for Text { + fn from(value: Description) -> Self { + Self { + origin: value.description_gtrans_origin, + translated: value.description, + } + } +} + +impl From<json::text::Text> for Text { + fn from(value: json::text::Text) -> Self { + Self { + origin: value.text_origin, + translated: value.text, + } + } +} + +impl From<Contact> for Text { + fn from(value: Contact) -> Self { + Self { + origin: value.contact_origin, + translated: value.contact, + } + } +} diff --git a/app/src/lib.rs b/app/src/lib.rs index 159dc25..46c997f 100644 --- a/app/src/lib.rs +++ b/app/src/lib.rs @@ -1,6 +1,7 @@ use std::time::Duration; use config::Config; +use data::alert::Cbor; use datastore::rdbms::Db; use error::Error; use futures::{stream, StreamExt}; @@ -12,6 +13,16 @@ use crate::json::alert::Alert; pub mod alert_swiss; pub mod cli; pub mod config; +pub mod data { + pub mod alert; + pub mod area; + pub mod circle; + pub mod coordinate; + pub mod image; + pub mod link; + pub mod polygon; + pub mod text; +} pub mod error; pub mod datastore { pub mod rdbms; @@ -80,8 +91,18 @@ pub async fn run(config: Config) -> Result<(), Error> { interval.tick().await; let new_alerts = handle_alerts(db).await?; - if let Err(e) = mqtt.publish("chello".as_bytes()).await { - error!("failed pusblishing to mqtt: {e:?}") - } + stream::iter(new_alerts) + .for_each(|alert| async { + let alert: data::alert::Alert = alert.into(); + match <Cbor as TryFrom<data::alert::Alert>>::try_from(alert) { + Ok(cbor) => { + if let Err(e) = mqtt.publish(&cbor).await { + error!("failed pusblishing to mqtt: {e:?}") + } + } + Err(e) => error!("failed to serialize to cbor: {e:?}"), + }; + }) + .await; } }