181 lines
5.6 KiB
Rust
181 lines
5.6 KiB
Rust
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<RwLock<post_manager::PostManager>>,
|
|
update_lock: Arc<Mutex<()>>,
|
|
}
|
|
|
|
#[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<AppState>) -> 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<AppState>, Path(page): Path<String>) -> 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<AppState>,
|
|
Path(post_name): Path<String>,
|
|
) -> 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<AppState>) -> 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<String, String> {
|
|
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<String, String> {
|
|
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
|
|
}
|