Add vote_exists and additional delete/get vote count handler

This commit is contained in:
Alex Selimov 2026-03-19 14:43:39 -04:00
parent f81b29c5e9
commit c77e58f21b
4 changed files with 90 additions and 10 deletions

View file

@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Deserialize)] #[derive(Serialize, Deserialize)]
pub struct CreateVoteRequest {} pub struct VotesAndDidVote {
pub vote_count: i64,
#[derive(Serialize)] pub voted: bool,
pub struct VoteResponse {} }

View file

@ -1,17 +1,26 @@
use axum::{ use axum::{
Router, Json, Router,
extract::{Path, State}, extract::{Path, State},
http::StatusCode, http::StatusCode,
response::IntoResponse, response::IntoResponse,
routing::post, routing::{delete, get, post},
}; };
use axum_extra::extract::{CookieJar, cookie::Cookie}; use axum_extra::extract::{CookieJar, cookie::Cookie};
use uuid::Uuid; use uuid::Uuid;
use crate::{state::AppState, votes::repository::insert_new_vote}; use crate::{
state::AppState,
votes::{
repository::{delete_vote, insert_new_vote},
service::get_votes_and_voted,
},
};
pub fn router() -> Router<AppState> { pub fn router() -> Router<AppState> {
Router::new().route("/posts/{slug}/vote", post(upvote_handler)) Router::new()
.route("/posts/{slug}/vote", post(upvote_handler))
.route("/posts/{slug}/vote", delete(downvote_handler))
.route("/posts/{slug}/votes", get(get_votes_and_voted_handler))
} }
fn get_or_init_voter_id(jar: CookieJar) -> (CookieJar, Uuid) { fn get_or_init_voter_id(jar: CookieJar) -> (CookieJar, Uuid) {
@ -51,6 +60,37 @@ async fn upvote_handler(
} }
} }
async fn downvote_handler(
jar: CookieJar,
Path(slug): Path<String>,
State(state): State<AppState>,
) -> impl IntoResponse {
let (jar, voter_id) = get_or_init_voter_id(jar);
match delete_vote(&slug, &voter_id, &state.db).await {
Ok(()) => (StatusCode::OK, jar, "Successfully upvoted"),
Err(err) => {
println!("{err}");
(StatusCode::INTERNAL_SERVER_ERROR, jar, "Failed to upvote")
}
}
}
async fn get_votes_and_voted_handler(
jar: CookieJar,
Path(slug): Path<String>,
State(state): State<AppState>,
) -> impl IntoResponse {
let (jar, voter_id) = get_or_init_voter_id(jar);
match get_votes_and_voted(&slug, &voter_id, &state.db).await {
Ok(votes_and_voted) => (StatusCode::OK, jar, Json(votes_and_voted)).into_response(),
Err(err) => {
println!("{err}");
(StatusCode::INTERNAL_SERVER_ERROR, jar, "Failed to upvote").into_response()
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use axum::{ use axum::{

View file

@ -2,3 +2,4 @@ pub mod dto;
pub mod handlers; pub mod handlers;
pub mod model; pub mod model;
pub mod repository; pub mod repository;
pub mod service;

View file

@ -4,6 +4,14 @@ use uuid::Uuid;
use crate::votes::model::BestSlugs; use crate::votes::model::BestSlugs;
pub async fn vote_exists(slug: &str, voter_id: &Uuid, db: &PgPool) -> Result<bool> {
let count: i64 = query_scalar("select count(*) from votes where slug=$1 and voter_id=$2")
.bind(slug)
.bind(voter_id)
.fetch_one(db)
.await?;
Ok(count > 0)
}
pub async fn insert_new_vote(slug: &str, voter_id: &Uuid, db: &PgPool) -> Result<()> { pub async fn insert_new_vote(slug: &str, voter_id: &Uuid, db: &PgPool) -> Result<()> {
query( query(
r#"insert into votes (slug, voter_id) r#"insert into votes (slug, voter_id)
@ -61,7 +69,7 @@ mod postgres_tests {
use crate::{ use crate::{
test_helpers::db::test_pool, test_helpers::db::test_pool,
votes::repository::{ votes::repository::{
delete_vote, get_top_n_slugs, get_vote_count_for_slug, insert_new_vote, delete_vote, get_top_n_slugs, get_vote_count_for_slug, insert_new_vote, vote_exists,
}, },
}; };
@ -230,4 +238,35 @@ mod postgres_tests {
cleanup(&db, &votes).await; cleanup(&db, &votes).await;
} }
#[tokio::test]
pub async fn vote_exists_test() {
let db = test_pool().await;
let votes = vec![
(
"vote_exists_test_blog_post1".to_string(),
Uuid::from_u128(0x1),
),
(
"vote_exists_test_blog_post2".to_string(),
Uuid::from_u128(0x2),
),
(
"vote_exists_test_blog_post3".to_string(),
Uuid::from_u128(0x3),
),
];
cleanup(&db, &votes).await;
insert_new_vote(&votes[0].0, &votes[0].1, &db)
.await
.unwrap();
insert_new_vote(&votes[1].0, &votes[1].1, &db)
.await
.unwrap();
assert!(vote_exists(&votes[0].0, &votes[0].1, &db).await.unwrap());
assert!(vote_exists(&votes[1].0, &votes[1].1, &db).await.unwrap());
assert!(!vote_exists(&votes[2].0, &votes[2].1, &db).await.unwrap());
}
} }