use anyhow::Result; use sqlx::{PgPool, query, query_as, query_scalar}; use uuid::Uuid; use crate::votes::model::BestSlugs; use super::model::Vote; pub async fn insert_new_vote(vote: &Vote, db: &PgPool) -> Result<()> { query("insert into votes (slug, voter_id) values ($1, $2)") .bind(vote.slug.clone()) .bind(vote.voter_id) .execute(db) .await?; Ok(()) } pub async fn get_vote_count_for_slug(slug: &str, db: &PgPool) -> Result { let count: i64 = query_scalar("select count(*) from votes where slug=$1") .bind(slug) .fetch_one(db) .await?; Ok(count) } pub async fn get_top_n_slugs(n: i64, db: &PgPool) -> Result> { if n > 0 { let top_slugs = query_as::<_, BestSlugs>( r#"select slug, count(*) AS vote_count from votes group by slug order by vote_count desc limit $1 "#, ) .bind(n) .fetch_all(db) .await?; Ok(top_slugs) } else { Ok(vec![]) } } pub async fn delete_vote(slug: &str, voter_id: &Uuid, db: &PgPool) -> Result<()> { query("delete from votes where slug=$1 and voter_id=$2") .bind(slug) .bind(voter_id) .execute(db) .await?; Ok(()) } #[cfg(test)] mod postgres_tests { use sqlx::PgPool; use uuid::Uuid; use crate::{ test_helpers::db::test_pool, votes::{ model::Vote, repository::{delete_vote, get_top_n_slugs, get_vote_count_for_slug, insert_new_vote}, }, }; async fn cleanup(db: &PgPool) { for vote in test_votes() { delete_vote(&vote.slug, &vote.voter_id, db).await.unwrap() } } fn test_votes() -> [Vote; 9] { [ Vote { slug: "blog_post1".into(), voter_id: Uuid::from_u128(0x1), created_at: chrono::Utc::now(), }, Vote { slug: "blog_post1".into(), voter_id: Uuid::from_u128(0x2), created_at: chrono::Utc::now(), }, Vote { slug: "blog_post2".into(), voter_id: Uuid::from_u128(0x3), created_at: chrono::Utc::now(), }, Vote { slug: "blog_post2".into(), voter_id: Uuid::from_u128(0x4), created_at: chrono::Utc::now(), }, Vote { slug: "blog_post3".into(), voter_id: Uuid::from_u128(0x5), created_at: chrono::Utc::now(), }, Vote { slug: "blog_post3".into(), voter_id: Uuid::from_u128(0x6), created_at: chrono::Utc::now(), }, Vote { slug: "blog_post1".into(), voter_id: Uuid::from_u128(0x7), created_at: chrono::Utc::now(), }, Vote { slug: "blog_post1".into(), voter_id: Uuid::from_u128(0x8), created_at: chrono::Utc::now(), }, Vote { slug: "blog_post3".into(), voter_id: Uuid::from_u128(0x9), created_at: chrono::Utc::now(), }, ] } #[tokio::test] #[ignore] pub async fn postgres_tests() { let db = test_pool().await; cleanup(&db).await; let votes = test_votes(); for vote in votes.iter() { insert_new_vote(vote, &db) .await .expect("Insertions to db failed"); } assert_eq!(get_vote_count_for_slug("blog_post1", &db).await.unwrap(), 4); assert_eq!(get_vote_count_for_slug("blog_post2", &db).await.unwrap(), 2); assert_eq!(get_vote_count_for_slug("blog_post3", &db).await.unwrap(), 3); let top_2 = get_top_n_slugs(2, &db).await.unwrap(); assert_eq!(top_2[0].slug, "blog_post1"); assert_eq!(top_2[1].slug, "blog_post3"); delete_vote(&votes[4].slug, &votes[4].voter_id, &db) .await .unwrap(); delete_vote(&votes[5].slug, &votes[5].voter_id, &db) .await .unwrap(); assert_eq!(get_vote_count_for_slug("blog_post1", &db).await.unwrap(), 4); assert_eq!(get_vote_count_for_slug("blog_post2", &db).await.unwrap(), 2); assert_eq!(get_vote_count_for_slug("blog_post3", &db).await.unwrap(), 1); let top_2 = get_top_n_slugs(2, &db).await.unwrap(); assert_eq!(top_2[0].slug, "blog_post1"); assert_eq!(top_2[1].slug, "blog_post2"); cleanup(&db).await; } }