2026-02-26 08:31:48 -05:00
|
|
|
use std::error::Error;
|
2026-02-23 09:57:22 -05:00
|
|
|
|
2026-02-26 16:30:51 -05:00
|
|
|
use crate::settings::get_settings;
|
2026-02-26 12:19:58 -05:00
|
|
|
use axum::extract::Request;
|
|
|
|
|
use axum::http::StatusCode;
|
|
|
|
|
use axum::middleware::Next;
|
|
|
|
|
use axum::response::Response;
|
2026-02-26 10:08:41 -05:00
|
|
|
use axum::routing::{delete, get, put};
|
2026-02-27 12:28:16 -05:00
|
|
|
use axum::{Router, http};
|
2026-02-26 16:30:51 -05:00
|
|
|
use log::{debug, error, info};
|
2026-02-27 12:28:16 -05:00
|
|
|
use tower::ServiceBuilder;
|
|
|
|
|
use tower_http::cors::{Any, CorsLayer};
|
2026-02-17 09:34:22 -05:00
|
|
|
use tower_http::services::ServeDir;
|
2026-03-03 12:20:57 -05:00
|
|
|
use axum::Json;
|
2026-02-17 09:34:22 -05:00
|
|
|
|
2026-02-26 08:31:48 -05:00
|
|
|
pub mod devices;
|
|
|
|
|
pub mod notifications;
|
|
|
|
|
pub mod utils;
|
2026-02-25 15:58:32 -05:00
|
|
|
|
2026-02-23 09:57:22 -05:00
|
|
|
pub async fn serve() -> Result<(), Box<dyn Error>> {
|
2026-02-17 09:34:22 -05:00
|
|
|
info!("Starting web server");
|
|
|
|
|
let static_files = ServeDir::new("./web");
|
|
|
|
|
|
2026-02-27 12:28:16 -05:00
|
|
|
// Allow all origins and headers for API
|
|
|
|
|
let cors_layer = CorsLayer::new()
|
|
|
|
|
.allow_origin(Any)
|
|
|
|
|
.allow_headers([http::header::AUTHORIZATION, http::header::CONTENT_TYPE]);
|
|
|
|
|
|
2026-02-17 09:34:22 -05:00
|
|
|
let router = Router::new()
|
2026-03-03 12:20:57 -05:00
|
|
|
.route("/api/test", get(test_api))
|
2026-02-26 10:08:41 -05:00
|
|
|
.route("/api/devices", get(devices::list))
|
|
|
|
|
.route("/api/devices", put(devices::register))
|
|
|
|
|
.route("/api/devices/{mac_address}", delete(devices::unregister))
|
|
|
|
|
.route("/api/devices/{mac_address}", get(devices::read))
|
|
|
|
|
.route("/api/notifications", get(notifications::list))
|
|
|
|
|
.route("/api/notifications/{id}", get(notifications::read))
|
2026-02-23 19:45:19 -05:00
|
|
|
.route(
|
|
|
|
|
"/api/notifications/{id}/read_without_flagging",
|
2026-02-26 10:08:41 -05:00
|
|
|
get(notifications::read_without_flagging),
|
2026-02-23 19:45:19 -05:00
|
|
|
)
|
2026-02-26 12:19:58 -05:00
|
|
|
.route_layer(axum::middleware::from_fn(auth))
|
2026-02-27 12:28:16 -05:00
|
|
|
.layer(ServiceBuilder::new().layer(cors_layer))
|
2026-02-26 12:19:58 -05:00
|
|
|
.route(
|
|
|
|
|
"/",
|
|
|
|
|
get(|| async { "Go to /web for the UI or to /api for the better UI." }),
|
|
|
|
|
)
|
2026-02-17 09:34:22 -05:00
|
|
|
.nest_service("/web", static_files);
|
2026-02-26 16:30:51 -05:00
|
|
|
|
|
|
|
|
let web_server_host_and_port = format!(
|
|
|
|
|
"{}:{}",
|
|
|
|
|
get_settings().web_server.ip_address,
|
|
|
|
|
get_settings().web_server.port
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
info!("Web server starting at http://{}", web_server_host_and_port);
|
2026-02-17 09:34:22 -05:00
|
|
|
// Start the server
|
2026-02-26 16:30:51 -05:00
|
|
|
let listener = tokio::net::TcpListener::bind(web_server_host_and_port).await?;
|
2026-02-23 09:57:22 -05:00
|
|
|
|
|
|
|
|
debug!("Web server bound to IP and port");
|
|
|
|
|
|
|
|
|
|
axum::serve(listener, router).await?;
|
2026-02-17 09:34:22 -05:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2026-02-26 12:19:58 -05:00
|
|
|
|
2026-03-03 12:20:57 -05:00
|
|
|
async fn test_api() -> Result<Json<String>, StatusCode> {
|
|
|
|
|
Ok(Json("OOTT_API_OK".to_string()))
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-26 12:19:58 -05:00
|
|
|
async fn auth(request: Request, next: Next) -> Result<Response, StatusCode> {
|
|
|
|
|
let auth_header = request
|
|
|
|
|
.headers()
|
|
|
|
|
.get(axum::http::header::AUTHORIZATION)
|
|
|
|
|
.and_then(|header| header.to_str().ok());
|
|
|
|
|
|
|
|
|
|
let auth_header = match auth_header {
|
|
|
|
|
Some(value) => value,
|
|
|
|
|
None => return Err(StatusCode::UNAUTHORIZED),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut valid_header = "Bearer ".to_string();
|
2026-02-26 16:30:51 -05:00
|
|
|
let api_key = get_settings().web_server.api_key.as_str();
|
|
|
|
|
if api_key == "CHANGE_ME" {
|
|
|
|
|
error!("The configured api_key is still 'CHANGE_ME', please change it.");
|
|
|
|
|
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
valid_header.push_str(api_key);
|
2026-02-26 12:19:58 -05:00
|
|
|
|
|
|
|
|
if auth_header == valid_header {
|
|
|
|
|
Ok(next.run(request).await)
|
|
|
|
|
} else {
|
|
|
|
|
Err(StatusCode::UNAUTHORIZED)
|
|
|
|
|
}
|
|
|
|
|
}
|