Initial blog setup
This commit is contained in:
180
src/main.rs
Normal file
180
src/main.rs
Normal file
@@ -0,0 +1,180 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user