use axum::extract::{Path, State}; use axum::{ routing::{get, post}, Json, Router, }; use serde::{Deserialize, Serialize}; use std::net::SocketAddr; use std::sync::{Arc, Mutex}; #[derive(Debug, Clone, Serialize, Deserialize)] struct Page { slug: String, votes: i32, } #[derive(Debug, Clone, Serialize, Deserialize)] struct VoteRecord { pages: Vec, } #[derive(Clone)] struct AppState { record: Arc>, } async fn view_votes(State(state): State, Path(slug): Path) -> Json { tracing::info!("Requesting votes for {slug}"); for page in &state.record.lock().unwrap().pages { if page.slug == slug { return Json(page.clone()); } } return Json(Page { slug, votes: 0 }); } async fn leaderboard_submit_score(State(state): State, Path(slug): Path) { tracing::info!("Submitting vote for {slug}"); let mut found = false; for mut page in &mut state.record.lock().unwrap().pages { if page.slug == slug { page.votes += 1; found = true; } } if !found { state .record .lock() .unwrap() .pages .push(Page { slug, votes: 1 }); } } #[tokio::main] async fn main() { tracing_subscriber::fmt::init(); let initial_state = if let Ok(data) = std::fs::read_to_string("votes.json") { AppState { record: Arc::new(Mutex::new( serde_json::from_str(&data).expect("Failed to parse"), )), } } else { AppState { record: Arc::new(Mutex::new(VoteRecord { pages: vec![] })), } }; let app = Router::new() .route("/votes/submit/:slug", post(leaderboard_submit_score)) .route("/votes/view/:slug", get(view_votes)) .with_state(initial_state.clone()); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); tracing::info!("Listening on {}", addr); axum::Server::bind(&addr) .serve(app.into_make_service()) .await .unwrap(); let state_clone = initial_state.clone(); let st = state_clone.record.lock().unwrap(); serde_json::to_writer( &std::fs::File::create("votes.json").unwrap(), &st.to_owned(), ) .expect("TODO: panic message"); }