diff --git a/Cargo.lock b/Cargo.lock index 5c77a26..605c377 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -294,6 +294,7 @@ dependencies = [ "mdq", "mongod", "mongodb", + "reqwest", "rocket", "rocket_cors", "serde", @@ -387,6 +388,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -665,6 +676,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -965,6 +991,19 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -1333,6 +1372,23 @@ dependencies = [ "version_check", ] +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1383,6 +1439,50 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "overload" version = "0.1.1" @@ -1462,6 +1562,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + [[package]] name = "powerfmt" version = "0.2.0" @@ -1623,6 +1729,46 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "resolv-conf" version = "0.7.0" @@ -1846,6 +1992,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -1868,6 +2023,29 @@ dependencies = [ "untrusted", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "0.9.0" @@ -1940,6 +2118,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_with" version = "1.14.0" @@ -2150,6 +2340,33 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "take_mut" version = "0.2.2" @@ -2280,6 +2497,16 @@ dependencies = [ "syn 2.0.77", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.24.1" @@ -2606,6 +2833,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -2663,6 +2896,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.93" @@ -2692,6 +2937,16 @@ version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +[[package]] +name = "web-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpki-roots" version = "0.25.4" diff --git a/Cargo.toml b/Cargo.toml index 4917619..279e9a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,4 @@ uuid = { version = "1.8.0", features = ["v4"] } mongod = { git = "https://git.hydrar.de/jmarya/mongod" } env_logger = "0.11.5" walkdir = "2.5.0" +reqwest = { version = "0.11", features = ["json"] } \ No newline at end of file diff --git a/config.toml b/config.toml index a686620..2bd816d 100644 --- a/config.toml +++ b/config.toml @@ -1 +1,13 @@ +# Only these tokens are authorized to make requests allowed_tokens = ["password"] + +# Webhook Notifications +[webhook] +# Item Variants inventory goes below the minimum +item_below_minimum = "https://example.com" + +# Transaction gets added +transaction_added = "https://example.com" + +# Transaction gets consumed +transaction_consumed = "https://example.com" diff --git a/docs/Webhooks.md b/docs/Webhooks.md index 304b8d6..8835a63 100644 --- a/docs/Webhooks.md +++ b/docs/Webhooks.md @@ -1,3 +1,9 @@ # Webhooks Certain events can trigger webhooks. This allows you to independently build your automation pipeline. -#todo \ No newline at end of file +Webhooks are configured in the `config.toml` file under `[webhook]`. + +## Webhook Events +These events can trigger a webhook: +- Add a transaction +- Consume a transaction +- Item Variant amount goes below specified minimum diff --git a/src/config.rs b/src/config.rs index c3d59e1..5878568 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,12 +4,15 @@ use serde::Deserialize; pub struct Config { /// Allowed tokens for access pub allowed_tokens: Vec, + /// Webhook Config + pub webhook: Option, } impl Default for Config { fn default() -> Self { Self { allowed_tokens: Vec::new(), + webhook: None, } } } @@ -21,3 +24,17 @@ pub fn get_config() -> Config { Config::default() } + +#[derive(Debug, Clone, Deserialize)] +pub struct Webhook { + pub item_below_minimum: Option, + pub transaction_added: Option, + pub transaction_consumed: Option, +} + +impl Webhook { + pub async fn send(url: &str, data: &serde_json::Value) { + let client = reqwest::Client::new(); + client.post(url).json(data).send().await.unwrap(); + } +} diff --git a/src/routes/item/demand.rs b/src/routes/item/demand.rs index b6b81a4..79741b3 100644 --- a/src/routes/item/demand.rs +++ b/src/routes/item/demand.rs @@ -1,10 +1,12 @@ +use mongod::{Model, ToAPI}; use rocket::serde::json::Json; use rocket::{get, post, State}; use serde::Deserialize; use serde_json::json; use crate::check_auth; -use crate::config::Config; +use crate::config::{Config, Webhook}; +use crate::item::Item; use crate::routes::Token; use crate::variant::Variant; use crate::{ @@ -26,7 +28,7 @@ pub struct DemandForm { pub async fn demand_route(f: Json, t: Token, c: &State) -> FallibleApiResponse { check_auth!(t, c); - Variant::demand( + let transaction = Variant::demand( &f.uuid, f.price .clone() @@ -37,6 +39,33 @@ pub async fn demand_route(f: Json, t: Token, c: &State) -> F .await .ok_or_else(|| api_error("Demand failed"))?; + if let Some(hook) = &c.webhook { + if let Some(url) = &hook.transaction_consumed { + Webhook::send(url, &transaction.api().await).await; + } + + if let Some(url) = &hook.item_below_minimum { + let variant = Item::get(&transaction.item) + .await + .unwrap() + .variant(&transaction.variant) + .unwrap(); + let min_res = variant.is_below_min().await; + if min_res.0 { + Webhook::send( + url, + &json!({ + "item": transaction.item, + "variant": transaction.variant, + "minimum": variant.min, + "current_amount": variant.min.unwrap() - min_res.1 + }), + ) + .await; + } + } + } + Ok(json!({"ok": 1})) } diff --git a/src/routes/item/supply.rs b/src/routes/item/supply.rs index 8984f23..13073c8 100644 --- a/src/routes/item/supply.rs +++ b/src/routes/item/supply.rs @@ -1,10 +1,11 @@ +use mongod::ToAPI; use rocket::serde::json::Json; use rocket::{get, post, State}; use serde::Deserialize; use serde_json::json; use crate::check_auth; -use crate::config::Config; +use crate::config::{Config, Webhook}; use crate::routes::Token; use crate::{ db::ItemDB, @@ -38,7 +39,7 @@ pub async fn supply_route( .variant(&form.variant) .ok_or_else(variant_does_not_exist_error)?; - let transaction_id = variant + let transaction = variant .supply( form.price .clone() @@ -49,7 +50,13 @@ pub async fn supply_route( ) .await; - Ok(json!({"uuid": transaction_id})) + if let Some(hook) = &c.webhook { + if let Some(url) = &hook.transaction_added { + Webhook::send(url, &transaction.api().await).await; + } + } + + Ok(json!({"uuid": transaction._id})) } /// Returns a list of Transaction UUIDs for the Item Variant diff --git a/src/transaction.rs b/src/transaction.rs index 53c5f72..64e9bf7 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -77,7 +77,7 @@ impl Transaction { } /// Consumes the Item with `price` and `destination` - pub async fn consume(self, price: Price, destination: &str) { + pub async fn consume(self, price: Price, destination: &str) -> Self { self.change() .consumed(Some(Consumed { destination: destination.to_string(), @@ -86,7 +86,7 @@ impl Transaction { })) .update() .await - .unwrap(); + .unwrap() } pub async fn is_expired(&self) -> bool { diff --git a/src/variant.rs b/src/variant.rs index 7271b2c..b5c7dbc 100644 --- a/src/variant.rs +++ b/src/variant.rs @@ -125,11 +125,11 @@ impl Variant { ret } - pub async fn demand(uuid: &str, price: Price, destination: &str) -> Option<()> { + pub async fn demand(uuid: &str, price: Price, destination: &str) -> Option { // check if transaction exists let mut t = Transaction::get(uuid).await?; - t.consume(price, destination).await; - Some(()) + t = t.consume(price, destination).await; + Some(t) } /// Records a supply transaction in the database. @@ -147,12 +147,12 @@ impl Variant { price: Price, origin: Option<&str>, location: Option<&str>, - ) -> String { + ) -> Transaction { let t = Transaction::new(&self.item, &self.variant, price, origin, location).await; t.insert().await.unwrap(); - t._id + t } pub async fn get_all_transactions(&self) -> Vec {