Inital Commit
This commit is contained in:
commit
a309e824be
29 changed files with 2856 additions and 0 deletions
154
server/src/main.rs
Normal file
154
server/src/main.rs
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
use axum::{
|
||||
body::Body,
|
||||
extract::{Path, State},
|
||||
http::{header, StatusCode, Uri},
|
||||
response::{IntoResponse, Response},
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use axum_server::tls_rustls::RustlsConfig;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::PathBuf;
|
||||
use tokio::fs;
|
||||
use tower_http::services::ServeDir;
|
||||
use tracing::{info, warn};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
website_root: PathBuf,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let website_root = PathBuf::from("../website");
|
||||
let state = AppState {
|
||||
website_root: website_root.clone(),
|
||||
};
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(serve_index))
|
||||
.route("/post/{file}", get(serve_post_page))
|
||||
.route("/api/backdrops.json", get(serve_backdrops))
|
||||
.route("/api/team.json", get(serve_team))
|
||||
.route("/api/posts.json", get(serve_posts))
|
||||
.route("/api/post/{file}", get(serve_post_md))
|
||||
.nest_service("/assets", ServeDir::new(website_root.join("assets")))
|
||||
.nest_service("/posts", ServeDir::new(website_root.join("posts")))
|
||||
.fallback(get(serve_static))
|
||||
.with_state(state.clone());
|
||||
|
||||
// HTTP on port 80
|
||||
let http_app = app.clone();
|
||||
let http_handle = tokio::spawn(async move {
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], 80));
|
||||
info!("HTTP server listening on {}", addr);
|
||||
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||
axum::serve(listener, http_app).await.unwrap();
|
||||
});
|
||||
|
||||
// HTTPS on port 443
|
||||
let certs_dir = website_root.join("certs");
|
||||
let cert_path = certs_dir.join("cert.pem");
|
||||
let key_path = certs_dir.join("key.pem");
|
||||
|
||||
if cert_path.exists() && key_path.exists() {
|
||||
let tls_config = RustlsConfig::from_pem_file(cert_path, key_path)
|
||||
.await
|
||||
.expect("Failed to load TLS certificates");
|
||||
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], 443));
|
||||
info!("HTTPS server listening on {}", addr);
|
||||
|
||||
axum_server::bind_rustls(addr, tls_config)
|
||||
.serve(app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
} else {
|
||||
warn!("TLS certificates not found, running HTTP only on port 8080");
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], 8080));
|
||||
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
}
|
||||
|
||||
http_handle.await.unwrap();
|
||||
}
|
||||
|
||||
async fn serve_index(State(state): State<AppState>) -> impl IntoResponse {
|
||||
serve_file(state.website_root.join("index.html")).await
|
||||
}
|
||||
|
||||
async fn serve_post_page(
|
||||
State(state): State<AppState>,
|
||||
Path(_file): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
serve_file(state.website_root.join("post.html")).await
|
||||
}
|
||||
|
||||
async fn serve_backdrops(State(state): State<AppState>) -> impl IntoResponse {
|
||||
serve_file(state.website_root.join("api/backdrops.json")).await
|
||||
}
|
||||
|
||||
async fn serve_team(State(state): State<AppState>) -> impl IntoResponse {
|
||||
serve_file(state.website_root.join("api/team.json")).await
|
||||
}
|
||||
|
||||
async fn serve_posts(State(state): State<AppState>) -> impl IntoResponse {
|
||||
serve_file(state.website_root.join("api/posts.json")).await
|
||||
}
|
||||
|
||||
async fn serve_post_md(
|
||||
State(state): State<AppState>,
|
||||
Path(file): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
let path = state.website_root.join("posts").join(&file);
|
||||
if path.exists() {
|
||||
let content = fs::read_to_string(path).await.unwrap_or_default();
|
||||
Response::builder()
|
||||
.header(header::CONTENT_TYPE, "text/markdown; charset=utf-8")
|
||||
.body(Body::from(content))
|
||||
.unwrap()
|
||||
} else {
|
||||
StatusCode::NOT_FOUND.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
async fn serve_static(uri: Uri, State(state): State<AppState>) -> impl IntoResponse {
|
||||
let path = uri.path().trim_start_matches('/');
|
||||
let file_path = state.website_root.join(path);
|
||||
|
||||
if file_path.exists() && file_path.is_file() {
|
||||
serve_file(file_path).await
|
||||
} else {
|
||||
StatusCode::NOT_FOUND.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
async fn serve_file(path: PathBuf) -> Response {
|
||||
if !path.exists() {
|
||||
return StatusCode::NOT_FOUND.into_response();
|
||||
}
|
||||
|
||||
let content = match fs::read(&path).await {
|
||||
Ok(c) => c,
|
||||
Err(_) => return StatusCode::INTERNAL_SERVER_ERROR.into_response(),
|
||||
};
|
||||
|
||||
let mime = match path.extension().and_then(|e| e.to_str()) {
|
||||
Some("html") => "text/html",
|
||||
Some("css") => "text/css",
|
||||
Some("js") => "application/javascript",
|
||||
Some("json") => "application/json",
|
||||
Some("png") => "image/png",
|
||||
Some("jpg") | Some("jpeg") => "image/jpeg",
|
||||
Some("mp4") => "video/mp4",
|
||||
Some("md") => "text/markdown; charset=utf-8",
|
||||
_ => "application/octet-stream",
|
||||
};
|
||||
|
||||
Response::builder()
|
||||
.header(header::CONTENT_TYPE, mime)
|
||||
.body(Body::from(content))
|
||||
.unwrap()
|
||||
}
|
||||
Loading…
Reference in a new issue