Files
bhs/src/main.rs
icsboyx d6c465466a
All checks were successful
Rust CI / build-test (push) Successful in 1m22s
feat: add statistics API and dashboard for asset metrics
- Implemented `/api/stats` endpoint to return JSON metrics including active assets, total uploads, storage usage, and recent activity.
- Created `stats.html` page to display real-time statistics with auto-refresh functionality.
- Enhanced asset logging to include uploader IP and detailed event information for uploads and deletions.
- Updated asset model to store uploader IP for audit purposes.
- Improved logging functionality to ensure log directory exists before writing.
- Refactored asset creation and management to support new features and logging.
2026-01-09 20:59:24 +01:00

112 lines
3.3 KiB
Rust

mod api;
mod data_mgt;
mod logs;
use actix_files::NamedFile;
use actix_web::{
App, HttpRequest, HttpResponse, HttpServer, get, route,
web::{self},
};
use serde_json::Value;
use std::{env, fs, path::PathBuf, sync::LazyLock};
pub static HTML_DIR: &str = "data/html/";
pub static LOG_DIR: &str = "data/logs/";
pub static DATA_STORAGE: &str = "data/storage/";
pub static BIND_ADDR: LazyLock<String> = LazyLock::new(|| match env::var("BIND_ADDR") {
Ok(addr) => {
println!("Binding to address: {}", addr);
addr.parse().unwrap_or("127.0.0.1".to_string())
}
Err(_) => {
println!("Binding to default address: 0.0.0.0");
"0.0.0.0".to_string()
}
});
pub static BIND_PORT: LazyLock<u16> = LazyLock::new(|| match env::var("BIND_PORT") {
Ok(port_str) => {
println!("Binding to port: {}", port_str);
port_str.parse().unwrap_or(8080)
}
Err(_) => {
println!("Binding to default port: 8080");
8080
}
});
pub static STATIC_PAGES: LazyLock<Vec<String>> = LazyLock::new(|| {
fs::read_dir(HTML_DIR)
.unwrap()
.filter_map(|entry| entry.ok().and_then(|e| e.file_name().to_str().map(|s| s.to_string())))
.collect()
});
use crate::{
api::{api_get_asset, api_stats, api_upload},
logs::log_to_file,
};
#[get("/")]
async fn index(reg: HttpRequest) -> actix_web::Result<NamedFile> {
let now = std::time::Instant::now();
let path: PathBuf = PathBuf::from(HTML_DIR.to_string() + "index.html");
log_to_file(&reg, now);
Ok(NamedFile::open(path)?)
}
#[get("/bhs/{id}")]
async fn view_asset(req: HttpRequest) -> actix_web::Result<NamedFile> {
let now = std::time::Instant::now();
let path: PathBuf = PathBuf::from(HTML_DIR.to_string() + "view.html");
log_to_file(&req, now);
Ok(NamedFile::open(path)?)
}
#[route("/{tail:.*}", method = "GET", method = "POST")]
async fn catch_all(req: HttpRequest, _payload: Option<web::Json<Value>>) -> actix_web::Result<HttpResponse> {
let now = std::time::Instant::now();
let response = match req.uri().path() {
path if STATIC_PAGES.contains(&path[1..].into()) => {
let file_path = HTML_DIR.to_string() + path;
Ok(NamedFile::open(file_path)?.into_response(&req))
}
_ => Ok(HttpResponse::NotFound().body("Not Found")),
};
log_to_file(&req, now);
response
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let _ = fs::create_dir_all(DATA_STORAGE);
let _ = fs::create_dir_all(LOG_DIR);
println!("Starting server at http://{}:{}/", *BIND_ADDR, *BIND_PORT);
tokio::spawn(async {
let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(60));
loop {
interval.tick().await;
if let Err(e) = data_mgt::clear_assets().await {
eprintln!("Error clearing assets: {}", e);
}
}
});
HttpServer::new(|| {
App::new()
.app_data(web::JsonConfig::default().limit(1024 * 1024 * 3))
.service(index)
.service(view_asset)
.service(api_get_asset)
.service(api_upload)
.service(api_stats)
.service(catch_all)
})
.bind((BIND_ADDR.clone(), *BIND_PORT))?
.run()
.await
}