2026-03-19 10:05:38 -04:00
|
|
|
use anyhow::Result;
|
|
|
|
|
use sqlx::{PgPool, query, query_as, query_scalar};
|
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
|
|
|
|
use crate::votes::model::BestSlugs;
|
2026-03-17 09:38:52 -04:00
|
|
|
|
2026-03-17 10:53:57 -04:00
|
|
|
use super::model::Vote;
|
2026-03-19 10:05:38 -04:00
|
|
|
|
|
|
|
|
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<i64> {
|
|
|
|
|
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<Vec<BestSlugs>> {
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|