mod git_tracker; mod post_manager; mod template_engine; use axum::{ extract::{Path, State}, http::StatusCode, response::{Html, IntoResponse, Response}, routing::get, Router, }; use clap::Parser; use std::sync::Arc; use tokio::process::Command; use tokio::sync::{Mutex, RwLock}; use tower_http::services::ServeDir; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { /// Disable caching of markdown rendering #[arg(long)] no_cache: bool, } #[derive(Clone)] struct AppState { post_manager: Arc>, update_lock: Arc>, } #[tokio::main] async fn main() { let args = Args::parse(); let post_manager = Arc::new(RwLock::new( post_manager::PostManager::new(".", args.no_cache).expect("Failed to initialize post manager"), )); let app_state = AppState { post_manager: post_manager.clone(), update_lock: Arc::new(Mutex::new(())), }; if args.no_cache { println!("Running with caching disabled"); } let p_router = Router::new() .route("/:post_name", get(post_handler)) .fallback_service(ServeDir::new("posts")); let app = Router::new() .route("/", get(index_handler)) .route("/update", get(update_handler)) .route("/:page", get(all_handler)) .nest("/p", p_router) .with_state(app_state); let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") .await .expect("Failed to bind to address"); println!("Server running on http://127.0.0.1:3000"); axum::serve(listener, app) .await .expect("Failed to start server"); } async fn index_handler(State(state): State) -> Response { match render_template("index.html", &state).await { Ok(html) => Html(html).into_response(), Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e).into_response(), } } async fn all_handler(State(state): State, Path(page): Path) -> Response { if page.contains("..") { return (StatusCode::NOT_FOUND, "Invalid path").into_response(); } match render_template(&format!("page_{}.html", page), &state).await { Ok(html) => Html(html).into_response(), Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e).into_response(), } } async fn post_handler( State(state): State, Path(post_name): Path, ) -> Response { if post_name.contains("..") { return (StatusCode::NOT_FOUND, "Invalid path").into_response(); } let manager = state.post_manager.read().await; match manager.get_post(&post_name).await { Some(_post) => { drop(manager); match render_post_template(&state, post_name).await { Ok(html) => Html(html).into_response(), Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e).into_response(), } } None => (StatusCode::NOT_FOUND, "Post not found").into_response(), } } async fn update_handler(State(state): State) -> impl IntoResponse { // Acquire lock to prevent concurrent updates let _lock = state.update_lock.lock().await; // Run git pull --autostash let output = Command::new("git") .arg("pull") .arg("--autostash") .output() .await; match output { Ok(output) => { if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); eprintln!("Git pull failed: {}", stderr); return ( StatusCode::INTERNAL_SERVER_ERROR, format!("Git pull failed: {}", stderr), ) .into_response(); } let stdout = String::from_utf8_lossy(&output.stdout); println!("Git pull output: {}", stdout); // Refresh posts let mut manager = state.post_manager.write().await; match manager.refresh_posts() { Ok(_) => { println!("Successfully refreshed log pages"); (StatusCode::OK, "Update successful: pulled changes and refreshed log pages") .into_response() } Err(e) => { eprintln!("Failed to refresh posts: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to refresh posts: {}", e), ) .into_response() } } } Err(e) => { eprintln!("Failed to execute git pull: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to execute git pull: {}", e), ) .into_response() } } } async fn render_template(template_name: &str, state: &AppState) -> Result { let template_path = format!("templates/{}", template_name); let template_content = std::fs::read_to_string(&template_path) .map_err(|e| format!("Failed to read template: {}", e))?; let manager = state.post_manager.read().await; template_engine::render_template(&template_content, &*manager, None).await } async fn render_post_template(state: &AppState, post_name: String) -> Result { let template_content = std::fs::read_to_string("templates/post.html") .map_err(|e| format!("Failed to read post template: {}", e))?; let manager = state.post_manager.read().await; template_engine::render_template(&template_content, &*manager, Some(&post_name)).await }