Skip to content

Commit a58b5d7

Browse files
committed
test(api): add web api
1 parent d168a97 commit a58b5d7

5 files changed

Lines changed: 145 additions & 0 deletions

File tree

src/webapi/mod.rs

Whitespace-only changes.

test_api/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Test API
2+
I recently testet how I could implement the Web API,
3+
but was not motivated to implement this in the project.
4+
5+
I would be happy if you can make the API and send a pull request.

test_api/handlers.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use axum::{Json, extract::Query, http::StatusCode, response::IntoResponse};
2+
use serde::Deserialize;
3+
use crate::webapi::stats;
4+
5+
#[derive(Deserialize)]
6+
pub struct SearchParams {
7+
pub query: Option<String>,
8+
pub limit: Option<i64>,
9+
}
10+
11+
#[derive(Deserialize)]
12+
pub struct DeleteParams {
13+
pub id: i32,
14+
}
15+
16+
#[derive(Deserialize)]
17+
pub struct ScanParams {
18+
pub generate: bool,
19+
pub amount: Option<i32>,
20+
pub cidr: Option<String>,
21+
}
22+
23+
pub async fn get_stats() -> impl IntoResponse {
24+
Json(stats::fetch_stats().await)
25+
}
26+
27+
pub async fn search_servers(Query(params): Query<SearchParams>) -> impl IntoResponse {
28+
StatusCode::NOT_IMPLEMENTED
29+
}
30+
31+
pub async fn delete_server(Query(params): Query<DeleteParams>) -> impl IntoResponse {
32+
format!("Deleting server with ID: {}", params.id)
33+
}
34+
35+
pub async fn start_scan(
36+
Query(params): Query<ScanParams>,
37+
body: String
38+
) -> impl IntoResponse {
39+
if params.generate {
40+
(StatusCode::ACCEPTED, "Generation scan started".to_string())
41+
} else {
42+
let ip_count = body.lines().count();
43+
(StatusCode::ACCEPTED, format!("Scanning {} provided IPs", ip_count))
44+
}
45+
}

test_api/mod.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use std::net::SocketAddr;
2+
use axum::Router;
3+
use axum::routing::get;
4+
use crate::manager::TaskManager;
5+
use crate::logger;
6+
use crate::logger::DefaultColor;
7+
use colored_text::Colorize;
8+
use tokio_util::sync::CancellationToken;
9+
10+
pub mod handlers;
11+
mod stats;
12+
13+
pub async fn start(port: u16) {
14+
let _ = TaskManager::spawn("WebAPI", move |cancel_token| async move {
15+
let api_v1 = Router::new()
16+
.route("/stats", get(handlers::get_stats))
17+
.route("/server/search", get(handlers::search_servers))
18+
.route("/server", delete(handlers::delete_server))
19+
.route("/scan", post(handlers::start_scan));
20+
21+
let app = Router::new().nest("/api/v1", api_v1);
22+
23+
let addr = SocketAddr::from(([0, 0, 0, 0], port));
24+
25+
logger::info(
26+
format!(
27+
"API starting on {}{}",
28+
"http://".hex(DefaultColor::Highlight.hex()),
29+
addr.to_string().hex(DefaultColor::Highlight.hex())
30+
)
31+
).prefix("WebAPI").send().await;
32+
33+
let listener = match tokio::net::TcpListener::bind(addr).await {
34+
Ok(l) => l,
35+
Err(e) => {
36+
logger::error(format!(
37+
"Failed to bind API port {}: {}",
38+
port.hex(DefaultColor::Highlight.hex()),
39+
e.to_string().hex(DefaultColor::Highlight.hex())
40+
)).prefix("WebAPI").send().await;
41+
return;
42+
}
43+
};
44+
45+
if let Err(e) = axum::serve(listener, app)
46+
.with_graceful_shutdown(shutdown_signal(cancel_token))
47+
.await
48+
{
49+
logger::error(format!(
50+
"API Server Error: {}",
51+
e.to_string().hex(DefaultColor::Highlight.hex())
52+
)).prefix("WebAPI").send().await;
53+
}
54+
}).await;
55+
}
56+
57+
async fn shutdown_signal(token: CancellationToken) {
58+
token.cancelled().await;
59+
logger::warning("API received shutdown signal, closing connections...".into())
60+
.prefix("WebAPI").send().await;
61+
}

test_api/stats.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use serde::Serialize;
2+
use crate::database::server;
3+
4+
#[derive(Serialize)]
5+
pub struct StatsResponse {
6+
pub total_servers: i64,
7+
pub history_entries: i64,
8+
pub player_data_points: i64,
9+
pub system: SystemInfo,
10+
}
11+
12+
#[derive(Serialize)]
13+
pub struct SystemInfo {
14+
pub version: String,
15+
pub cpu_arch: String,
16+
pub os: String,
17+
}
18+
19+
pub async fn fetch_stats() -> StatsResponse {
20+
let total_servers = server::get_total_servers().await.unwrap_or(-1);
21+
let history_entries = server::get_total_history().await.unwrap_or(-1);
22+
let player_data_points = server::get_total_player_history().await.unwrap_or(-1);
23+
24+
StatsResponse {
25+
total_servers,
26+
history_entries,
27+
player_data_points,
28+
system: SystemInfo {
29+
version: env!("CARGO_PKG_VERSION").to_string(),
30+
cpu_arch: std::env::consts::ARCH.to_string(),
31+
os: std::env::consts::OS.to_string(),
32+
},
33+
}
34+
}

0 commit comments

Comments
 (0)