vote.rs (1928B)
1 use askama::Template; 2 use axum::{ 3 extract::{Path, State}, 4 response::Response, 5 }; 6 use axum_extra::extract::cookie::CookieJar; 7 8 use crate::database::{TARGET_TYPE_COMMENT, TARGET_TYPE_STORY}; 9 use crate::error::AppError; 10 use crate::sse; 11 use crate::state::AppState; 12 use crate::templates::VoteButtonTemplate; 13 14 fn render_vote_response( 15 target_type: &str, 16 target_id: i64, 17 score: i64, 18 voted: bool, 19 ) -> Result<Response, AppError> { 20 let template = VoteButtonTemplate { 21 target_type, 22 target_id, 23 score, 24 voted, 25 }; 26 let button_html = template.render()?; 27 28 let score_class = if target_type == "comment" { "comment-score" } else { "story-score" }; 29 let score_html = format!( 30 "<span class=\"{score_class}\" id=\"score-{target_type}-{target_id}\">{score} point{}</span>", 31 if score == 1 { "" } else { "s" } 32 ); 33 34 Ok(sse::patch_multiple(&[&button_html, &score_html])) 35 } 36 37 /// Vote on a story (Datastar endpoint) 38 pub async fn vote_story( 39 State(state): State<AppState>, 40 jar: CookieJar, 41 Path(story_id): Path<i64>, 42 ) -> Result<Response, AppError> { 43 let user_id = state.get_user_id(&jar).await 44 .ok_or_else(|| AppError::Unauthorized("Login required to vote".to_string()))?; 45 46 let (voted, new_score) = state.db().toggle_vote(user_id, TARGET_TYPE_STORY, story_id).await?; 47 48 render_vote_response("story", story_id, new_score, voted) 49 } 50 51 /// Vote on a comment (Datastar endpoint) 52 pub async fn vote_comment( 53 State(state): State<AppState>, 54 jar: CookieJar, 55 Path(comment_id): Path<i64>, 56 ) -> Result<Response, AppError> { 57 let user_id = state.get_user_id(&jar).await 58 .ok_or_else(|| AppError::Unauthorized("Login required to vote".to_string()))?; 59 60 let (voted, new_score) = state.db().toggle_vote(user_id, TARGET_TYPE_COMMENT, comment_id).await?; 61 62 render_vote_response("comment", comment_id, new_score, voted) 63 }