Add route for getting most popular posts
This commit is contained in:
parent
6979e73307
commit
3ee97b5d43
4 changed files with 84 additions and 8 deletions
|
|
@ -18,8 +18,7 @@ impl Env {
|
|||
.map(|s| s.trim().to_string())
|
||||
.collect();
|
||||
|
||||
let port = env::var("PORT")
|
||||
.unwrap_or_else(|_| "3000".to_string());
|
||||
let port = env::var("PORT").unwrap_or_else(|_| "3000".to_string());
|
||||
|
||||
Env {
|
||||
postgres_connection_string,
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ async fn main() {
|
|||
|
||||
let state = AppState::new().await;
|
||||
let port = &state.env.port;
|
||||
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}")).await.unwrap();
|
||||
tracing::info!("listening on {}", listener.local_addr().unwrap());
|
||||
axum::serve(listener, upvoters::app(state))
|
||||
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}"))
|
||||
.await
|
||||
.unwrap();
|
||||
tracing::info!("listening on {}", listener.local_addr().unwrap());
|
||||
axum::serve(listener, upvoters::app(state)).await.unwrap();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
use axum::{
|
||||
Json, Router,
|
||||
extract::{Path, State},
|
||||
extract::{Path, Query, State},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
routing::{delete, get, post},
|
||||
};
|
||||
use axum_extra::extract::{CookieJar, cookie::Cookie};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{error, info};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
state::AppState,
|
||||
votes::{
|
||||
repository::{delete_vote, insert_new_vote},
|
||||
repository::{delete_vote, get_top_n_slugs, insert_new_vote},
|
||||
service::get_votes_and_voted,
|
||||
},
|
||||
};
|
||||
|
|
@ -22,6 +23,7 @@ pub fn router() -> Router<AppState> {
|
|||
.route("/posts/{slug}/vote", post(upvote_handler))
|
||||
.route("/posts/{slug}/vote", delete(delete_vote_handler))
|
||||
.route("/posts/{slug}/votes", get(get_votes_and_voted_handler))
|
||||
.route("/posts/best", get(get_best_handler))
|
||||
}
|
||||
|
||||
fn get_or_init_voter_id(jar: CookieJar) -> (CookieJar, Uuid) {
|
||||
|
|
@ -110,6 +112,30 @@ async fn get_votes_and_voted_handler(
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct BestVotesQuery {
|
||||
top: usize,
|
||||
}
|
||||
|
||||
async fn get_best_handler(
|
||||
jar: CookieJar,
|
||||
State(state): State<AppState>,
|
||||
query: Query<BestVotesQuery>,
|
||||
) -> impl IntoResponse {
|
||||
match get_top_n_slugs(query.top as i64, &state.db).await {
|
||||
Ok(top_slugs) => {
|
||||
info!(n=%query.top, "fetched most popular slugs successfully");
|
||||
(StatusCode::OK, jar, Json(top_slugs)).into_response()
|
||||
}
|
||||
Err(err) => {
|
||||
error!(
|
||||
error = %err,
|
||||
"failed to fetch popular slugs"
|
||||
);
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, jar, "Failed to upvote").into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use axum::{
|
||||
|
|
@ -333,4 +359,54 @@ mod tests {
|
|||
|
||||
delete_votes_for_slug(slug, &db).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_best_returns_top_slugs_limited_by_query() {
|
||||
let db = test_pool().await;
|
||||
let slugs = [
|
||||
"get_best_handler_top_post_1",
|
||||
"get_best_handler_top_post_2",
|
||||
"get_best_handler_top_post_3",
|
||||
];
|
||||
|
||||
for slug in slugs {
|
||||
delete_votes_for_slug(slug, &db).await.unwrap();
|
||||
}
|
||||
|
||||
for voter_id in [
|
||||
Uuid::from_u128(0x100),
|
||||
Uuid::from_u128(0x101),
|
||||
Uuid::from_u128(0x102),
|
||||
] {
|
||||
insert_new_vote(slugs[0], &voter_id, &db).await.unwrap();
|
||||
}
|
||||
for voter_id in [Uuid::from_u128(0x200), Uuid::from_u128(0x201)] {
|
||||
insert_new_vote(slugs[1], &voter_id, &db).await.unwrap();
|
||||
}
|
||||
insert_new_vote(slugs[2], &Uuid::from_u128(0x300), &db)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let app = crate::app(test_state(db.clone()));
|
||||
let request = Request::builder()
|
||||
.method("GET")
|
||||
.uri("/posts/best?top=2")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let response = app.oneshot(request).await.unwrap();
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
|
||||
let json: Value = serde_json::from_slice(&body).unwrap();
|
||||
assert_eq!(json.as_array().unwrap().len(), 2);
|
||||
assert_eq!(json[0]["slug"], slugs[0]);
|
||||
assert_eq!(json[0]["vote_count"], 3);
|
||||
assert_eq!(json[1]["slug"], slugs[1]);
|
||||
assert_eq!(json[1]["vote_count"], 2);
|
||||
|
||||
for slug in slugs {
|
||||
delete_votes_for_slug(slug, &db).await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::prelude::FromRow;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
|
@ -11,7 +12,7 @@ pub struct Vote {
|
|||
}
|
||||
|
||||
/// Struct representing the best slug posts
|
||||
#[derive(Debug, FromRow)]
|
||||
#[derive(Debug, FromRow, Serialize, Deserialize)]
|
||||
pub struct BestSlugs {
|
||||
pub slug: String,
|
||||
pub vote_count: i64,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue