feat: allow requesting to register endpoints
This commit is contained in:
parent
07dfe2d9bc
commit
5adf192e5d
6 changed files with 178 additions and 44 deletions
101
Cargo.lock
generated
101
Cargo.lock
generated
|
|
@ -38,12 +38,78 @@ version = "1.0.98"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.7.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"sync_wrapper",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.75"
|
||||
|
|
@ -73,9 +139,9 @@ checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
|||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.17.0"
|
||||
version = "3.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||
checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
|
|
@ -85,9 +151,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.25"
|
||||
version = "1.2.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951"
|
||||
checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
|
@ -114,6 +180,7 @@ dependencies = [
|
|||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
|
@ -336,6 +403,12 @@ version = "1.10.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.6.0"
|
||||
|
|
@ -348,6 +421,7 @@ dependencies = [
|
|||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
|
|
@ -599,6 +673,12 @@ version = "0.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
|
|
@ -636,6 +716,7 @@ name = "noisebell"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"chrono",
|
||||
"futures",
|
||||
"reqwest",
|
||||
|
|
@ -1037,6 +1118,16 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_path_to_error"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
|
|
@ -1299,6 +1390,7 @@ dependencies = [
|
|||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1337,6 +1429,7 @@ version = "0.1.41"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
tokio = { version = "1.36", features = ["full"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
rppal = "0.22"
|
||||
reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features=false}
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
chrono = "0.4"
|
||||
futures = "0.3.31"
|
||||
tracing-appender = "0.2.3"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
futures = "0.3"
|
||||
tracing-appender = "0.2"
|
||||
axum = "0.7"
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ echo "Building for Raspberry Pi..."
|
|||
cross build --release --target aarch64-unknown-linux-gnu
|
||||
|
||||
echo "Copying to Raspberry Pi..."
|
||||
scp target/aarch64-unknown-linux-gnu/release/noisebell noisebridge@noisebell.local:~/
|
||||
scp endpoints.json noisebridge@noisebell.local:/home/noisebridge/endpoints.json
|
||||
scp target/aarch64-unknown-linux-gnu/release/noisebell noisebridge@noisebell.local:~/noisebell/
|
||||
scp endpoints.json noisebridge@noisebell.local:/home/noisebridge/noisebell/endpoints.json
|
||||
|
||||
echo "Setting permissions"
|
||||
ssh noisebridge@noisebell.local "chmod +x ~/noisebell "
|
||||
ssh noisebridge@noisebell.local "chmod +x ~/noisebell/noisebell"
|
||||
|
||||
echo "Deployment complete!"
|
||||
|
|
|
|||
|
|
@ -1,16 +1,4 @@
|
|||
{
|
||||
"endpoints": [
|
||||
{
|
||||
"url": "http://localhost:8080/webhook",
|
||||
"description": "Local development endpoint"
|
||||
},
|
||||
{
|
||||
"url": "https://api.example.com/notifications",
|
||||
"description": "Production notification service"
|
||||
},
|
||||
{
|
||||
"url": "https://webhook.site/your-unique-id",
|
||||
"description": "Webhook testing service"
|
||||
}
|
||||
]
|
||||
}
|
||||
50
src/main.rs
50
src/main.rs
|
|
@ -3,8 +3,15 @@ mod webhook;
|
|||
|
||||
use std::time::Duration;
|
||||
use std::fs;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use axum::{
|
||||
routing::post,
|
||||
Router,
|
||||
Json,
|
||||
extract::State,
|
||||
};
|
||||
use tracing::{error, info};
|
||||
use tracing_appender::rolling::{RollingFileAppender, Rotation};
|
||||
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
|
@ -12,8 +19,10 @@ use tracing_subscriber::filter::LevelFilter;
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
info!("creating logs directory");
|
||||
fs::create_dir_all("logs")?;
|
||||
|
||||
info!("initializing logging");
|
||||
let file_appender = RollingFileAppender::builder()
|
||||
.rotation(Rotation::DAILY)
|
||||
.filename_prefix("noisebell")
|
||||
|
|
@ -35,23 +44,26 @@ async fn main() -> Result<()> {
|
|||
.with(fmt::Layer::default().with_writer(non_blocking))
|
||||
.init();
|
||||
|
||||
info!("Starting noisebell...");
|
||||
|
||||
const DEFAULT_GPIO_PIN: u8 = 17;
|
||||
const DEFAULT_WEBHOOK_RETRIES: u32 = 3;
|
||||
const DEFAULT_SERVER_PORT: u16 = 8080;
|
||||
|
||||
let gpio_pin = std::env::var("GPIO_PIN")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(DEFAULT_GPIO_PIN);
|
||||
info!("initializing webhook notifier");
|
||||
let webhook_notifier = Arc::new(webhook::WebhookNotifier::new(DEFAULT_WEBHOOK_RETRIES)?);
|
||||
|
||||
let webhook_retries = std::env::var("WEBHOOK_RETRIES")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(DEFAULT_WEBHOOK_RETRIES);
|
||||
info!("initializing gpio monitor");
|
||||
let mut gpio_monitor = gpio::GpioMonitor::new(DEFAULT_GPIO_PIN, Duration::from_millis(100))?;
|
||||
|
||||
let webhook_notifier = webhook::WebhookNotifier::new(webhook_retries)?;
|
||||
let mut gpio_monitor = gpio::GpioMonitor::new(gpio_pin, Duration::from_millis(100))?;
|
||||
let app = Router::new()
|
||||
.route("/endpoints", post(add_endpoint))
|
||||
.with_state(webhook_notifier.clone());
|
||||
|
||||
let server_addr = format!("127.0.0.1:{}", DEFAULT_SERVER_PORT);
|
||||
info!("Starting API server on http://{}", server_addr);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(&server_addr).await?;
|
||||
axum::serve(listener, app.into_make_service())
|
||||
.await?;
|
||||
|
||||
let callback = move |event: gpio::CircuitEvent| {
|
||||
info!("Circuit state changed: {:?}", event);
|
||||
|
|
@ -63,7 +75,7 @@ async fn main() -> Result<()> {
|
|||
});
|
||||
};
|
||||
|
||||
info!("starting gpio_monitor");
|
||||
info!("starting GPIO monitor");
|
||||
|
||||
if let Err(e) = gpio_monitor.monitor(callback).await {
|
||||
error!("GPIO monitoring error: {}", e);
|
||||
|
|
@ -71,3 +83,15 @@ async fn main() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn add_endpoint(
|
||||
State(notifier): State<Arc<webhook::WebhookNotifier>>,
|
||||
Json(endpoint): Json<webhook::Endpoint>,
|
||||
) -> Result<(), axum::http::StatusCode> {
|
||||
notifier.add_endpoint(endpoint)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("Failed to add endpoint: {}", e);
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,19 +2,21 @@ use anyhow::Result;
|
|||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::{error, info};
|
||||
use std::time::Duration;
|
||||
use futures::future::join_all;
|
||||
|
||||
use crate::gpio::CircuitEvent;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
struct Endpoint {
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct Endpoint {
|
||||
url: String,
|
||||
description: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct EndpointsConfig {
|
||||
endpoints: Vec<Endpoint>,
|
||||
}
|
||||
|
|
@ -29,22 +31,47 @@ struct WebhookPayload {
|
|||
#[derive(Clone)]
|
||||
pub struct WebhookNotifier {
|
||||
client: Client,
|
||||
endpoints: Vec<Endpoint>,
|
||||
endpoints: Arc<RwLock<Vec<Endpoint>>>,
|
||||
max_retries: u32,
|
||||
}
|
||||
|
||||
impl WebhookNotifier {
|
||||
pub fn new(max_retries: u32) -> Result<Self> {
|
||||
let config = fs::read_to_string("endpoints.json")?;
|
||||
let endpoints_config: EndpointsConfig = serde_json::from_str(&config)?;
|
||||
let endpoints = if let Ok(config) = fs::read_to_string("endpoints.json") {
|
||||
let endpoints_config: EndpointsConfig = serde_json::from_str(&config)?;
|
||||
endpoints_config.endpoints
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
client: Client::new(),
|
||||
endpoints: endpoints_config.endpoints,
|
||||
endpoints: Arc::new(RwLock::new(endpoints)),
|
||||
max_retries,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn add_endpoint(&self, endpoint: Endpoint) -> Result<()> {
|
||||
let mut endpoints = self.endpoints.write().await;
|
||||
|
||||
endpoints.retain(|e| e.description != endpoint.description);
|
||||
|
||||
endpoints.push(endpoint);
|
||||
|
||||
self.save_endpoints().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn save_endpoints(&self) -> Result<()> {
|
||||
let endpoints = self.endpoints.read().await;
|
||||
let config = EndpointsConfig {
|
||||
endpoints: endpoints.clone(),
|
||||
};
|
||||
fs::write("endpoints.json", serde_json::to_string_pretty(&config)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_webhook(&self, endpoint: &Endpoint, payload: &WebhookPayload) -> Result<()> {
|
||||
match self.client
|
||||
.post(&endpoint.url)
|
||||
|
|
@ -106,7 +133,8 @@ impl WebhookNotifier {
|
|||
new_state: state.to_string(),
|
||||
};
|
||||
|
||||
let webhook_futures: Vec<_> = self.endpoints.iter()
|
||||
let endpoints = self.endpoints.read().await;
|
||||
let webhook_futures: Vec<_> = endpoints.iter()
|
||||
.map(|endpoint| {
|
||||
info!("Sending webhook to {}: {}", endpoint.description, serde_json::to_string(&payload).unwrap());
|
||||
self.send_webhook_with_retries(endpoint, &payload)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue