/* * Copyright (C) 2025 Jonni Liljamo * * This file is licensed under GPL-3.0-or-later, see NOTICE and LICENSE for * more information. */ use std::sync::Arc; use axum::{ Json, Router, response::IntoResponse, routing::{get, post}, }; use clap::Parser; use reqwest::StatusCode; use serde::Deserialize; use tokio::net::TcpListener; use tower_http::trace::TraceLayer; use tracing::{error, info}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; mod config; use config::Config; mod state; use state::State; mod service; #[derive(Parser)] #[command(version)] struct Args { /// Config file location #[arg(short, long, default_value = "./canwa.toml")] config: String, } #[tokio::main] async fn main() { tracing_subscriber::registry() .with( tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| { format!( "{}=debug,tower_http=debug,axum::rejection=trace", env!("CARGO_CRATE_NAME") ) .into() }), ) .with(tracing_subscriber::fmt::layer()) .init(); let args: Args = Args::parse(); let config: Config = Config::from_path(&args.config).await.unwrap(); let state: Arc = Arc::new(State::from_config(&config).unwrap()); let router = Router::new() .route("/", get(|| async { "canwa" })) .route( "/message", post({ let shared_state = Arc::clone(&state); move |body| message(shared_state, body) }), ) .layer(TraceLayer::new_for_http()) .with_state(state); let addr = format!("{}:{}", config.interface, config.port); info!(msg="serving http", %addr); let listener = TcpListener::bind(addr).await.unwrap(); axum::serve(listener, router).await.unwrap(); } #[derive(Deserialize)] struct MessageForm { token: String, title: String, message: String, } async fn message(state: Arc, Json(message): Json) -> impl IntoResponse { let notifier = match state .notifiers .iter() .find(|(_k, v)| v.token == message.token) { Some(n) => n, None => return (StatusCode::UNAUTHORIZED, "unauthorized"), }; info!(msg = "message", notifier = notifier.0); for (_k, v) in state .services .iter() .filter(|(k, _v)| notifier.1.services.contains(k)) { match v.send(&message.title, &message.message).await { Ok(_) => {} Err(err) => { error!(msg = "message sending failed", ?err); return (StatusCode::INTERNAL_SERVER_ERROR, "failed to send message"); } } } (StatusCode::OK, "") }