diff --git a/backend/src/api/mod.rs b/backend/src/api/mod.rs index f3ff8e0..eae722d 100644 --- a/backend/src/api/mod.rs +++ b/backend/src/api/mod.rs @@ -1,17 +1,18 @@ -use std::collections::HashMap; - -use axum::{routing::get, Router}; - use crate::AppState; +use axum::{routing::get, Router}; +use std::collections::HashMap; pub mod test; -type PlainParams = HashMap; +type AnyParams = HashMap; pub fn router() -> Router { Router::::new() .route("/test/timeout", get(test::timeout)) .route("/test/lorem", get(test::lorem)) .route("/test/subreddit-info", get(test::subreddit_info)) - .route("/test/subreddit-comments", get(test::subreddit_comments)) + .route("/test/post-comments", get(test::post_comments)) + .route("/test/user-info", get(test::user_info)) + .route("/test/subreddit-posts", get(test::subreddit_posts)) + .route("/test/user-posts", get(test::user_posts)) } diff --git a/backend/src/api/test.rs b/backend/src/api/test.rs index dd9e6ff..bea0e06 100644 --- a/backend/src/api/test.rs +++ b/backend/src/api/test.rs @@ -5,24 +5,18 @@ use axum::{ Json, }; use lipsum::lipsum; -use log::{info, warn}; +use log::info; use log_derive::logfn; use reqwest::StatusCode; use serde_json::{json, Value}; use crate::{ app_error::AppError, - reddit::{ - model::{ - listing::KindContainer, - subreddit_info::{RedditSubredditInfo, RedditSubredditInfoRaw}, - }, - RedditRequest, - }, + reddit::{model::listing::KindContainer, RedditRequest}, AppState, }; -use super::PlainParams; +use super::AnyParams; /// Returns after a specified delay. #[utoipa::path( @@ -37,7 +31,7 @@ use super::PlainParams; ) )] #[logfn(err = "ERROR", fmt = "'timeout' failed: {:?}")] -pub async fn timeout(Query(params): Query) -> Result, AppError> { +pub async fn timeout(Query(params): Query) -> Result, AppError> { let t = params .get("t") .ok_or_else(|| AppError::new(StatusCode::BAD_REQUEST, "Missing `t` parameter"))? @@ -65,7 +59,7 @@ pub async fn timeout(Query(params): Query) -> Result, A ) )] #[logfn(err = "ERROR", fmt = "'lorem' failed: {:?}")] -pub async fn lorem(Query(params): Query) -> Result, AppError> { +pub async fn lorem(Query(params): Query) -> Result, AppError> { let words = params .get("words") .unwrap_or(&"50".to_string()) @@ -87,22 +81,22 @@ pub async fn lorem(Query(params): Query) -> Result, App pub async fn subreddit_info( State(mut state): State, - Query(params): Query, -) -> Result, AppError> { + Query(params): Query, +) -> Result, AppError> { let subreddit = params .get("r") .ok_or_else(|| AppError::new(StatusCode::BAD_REQUEST, "Missing `subreddit` parameter"))?; let req = RedditRequest::SubredditInfo(subreddit.into()); let json = state.reddit.fetch_raw(req).await?; - let info = serde_json::from_value::(json).unwrap(); + let info = serde_json::from_value::(json).unwrap(); - Ok(Json(info.data)) + Ok(Json(info)) } -pub async fn subreddit_comments( +pub async fn post_comments( State(mut state): State, - Query(params): Query, + Query(params): Query, ) -> Result>, AppError> { let r = params .get("r") @@ -127,3 +121,49 @@ pub async fn subreddit_comments( info!("Data parsed successfully. Took {:?}", elapsed); Ok(Json(val)) } + +pub async fn user_info( + State(mut state): State, + Query(params): Query, +) -> Result, AppError> { + let user = params + .get("u") + .ok_or_else(|| AppError::new(StatusCode::BAD_REQUEST, "Missing `u` parameter"))?; + let req = RedditRequest::UserInfo(user.into()); + + let json = state.reddit.fetch_raw(req).await?; + + let info = serde_json::from_value::(json).unwrap(); + + Ok(Json(info)) +} + +pub async fn subreddit_posts( + State(mut state): State, + Query(params): Query, +) -> Result, AppError> { + let subreddit = params + .get("r") + .ok_or_else(|| AppError::new(StatusCode::BAD_REQUEST, "Missing `r` parameter"))?; + let req = RedditRequest::SubredditPosts(subreddit.into()); + let json = state.reddit.fetch_raw(req).await?; + + let info = serde_json::from_value::(json).unwrap(); + + Ok(Json(info)) +} + +pub async fn user_posts( + State(mut state): State, + Query(params): Query, +) -> Result, AppError> { + let user = params + .get("u") + .ok_or_else(|| AppError::new(StatusCode::BAD_REQUEST, "Missing `u` parameter"))?; + let req = RedditRequest::UserPosts(user.into()); + let json = state.reddit.fetch_raw(req).await?; + + let info = serde_json::from_value(json).unwrap(); + + Ok(Json(info)) +} diff --git a/backend/src/reddit/mod.rs b/backend/src/reddit/mod.rs index 8d9c36d..49ef4de 100644 --- a/backend/src/reddit/mod.rs +++ b/backend/src/reddit/mod.rs @@ -177,9 +177,29 @@ impl RedditConnection { .build()?; let start = SystemTime::now(); - let res = self.http.execute(req).await?.json().await?; + let res = self.http.execute(req).await?; let elapsed = SystemTime::now().duration_since(start).unwrap(); + + let ratelimit_headers = const { + [ + "x-ratelimit-remaining", + "x-ratelimit-reset", + "x-ratelimit-used", + ] + }; + let header_log = &res + .headers() + .iter() + .filter(|h| ratelimit_headers.contains(&h.0.as_str())) + .fold(String::from("\n"), |acc, (k, v)| { + let s = format!("{}: {}\n", k, v.to_str().unwrap()); + acc + s.as_str() + }); + + info!("Headers: {}", header_log.trim_end()); info!("Data fetched successfully. Took {:?}", elapsed); - Ok(res) + + let json = res.json().await?; + Ok(json) } } diff --git a/backend/src/reddit/model/comment.rs b/backend/src/reddit/model/comment.rs index 5f9d861..d8142ac 100644 --- a/backend/src/reddit/model/comment.rs +++ b/backend/src/reddit/model/comment.rs @@ -39,7 +39,9 @@ pub struct RedditComment { /// UNIX timestamp of the comment creation created_utc: f32, /// Depth of the comment in the thread. 0 is the top-level comment, 1 is a reply to the top-level comment, etc. - depth: u32, + /// + /// When fetching posts/comments from a user, this field is always `None`. + depth: Option, /// Upvotes - downvotes score: i64, } diff --git a/backend/src/reddit/model/listing.rs b/backend/src/reddit/model/listing.rs index 2e48706..4b23b4a 100644 --- a/backend/src/reddit/model/listing.rs +++ b/backend/src/reddit/model/listing.rs @@ -1,4 +1,4 @@ -use super::{comment::RedditComment, post::RedditPost}; +use super::{comment::RedditComment, post::RedditPost, subreddit_info::RedditSubredditInfo, user::RedditUser}; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -15,19 +15,19 @@ pub enum KindContainer { T1(Box), // Comment #[serde(rename = "t2")] - T2(Value), // Account TODO + T2(Box), // Account TODO #[serde(rename = "t3")] T3(Box), // Link/Post #[serde(rename = "t4")] - T4(Value), // Message TODO + T4(Box), // Message TODO #[serde(rename = "t5")] - T5(Value), // Subreddit TODO + T5(Box), // Subreddit Info #[serde(rename = "t6")] - T6(Value), // Award TODO + T6(Box), // Award TODO } /// List of IDs of items to fetch to get the ones that didn't fit in the first response. diff --git a/backend/src/reddit/model/subreddit_info.rs b/backend/src/reddit/model/subreddit_info.rs index 1c71a32..c1e6f95 100644 --- a/backend/src/reddit/model/subreddit_info.rs +++ b/backend/src/reddit/model/subreddit_info.rs @@ -2,12 +2,6 @@ use derive_getters::Getters; use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RedditSubredditInfoRaw { - pub kind: String, - pub data: RedditSubredditInfo -} - /// Reddit subreddit data /// https://www.reddit.com/r/Polska/about.json #[derive(Getters, Debug, PartialEq, Clone, Serialize, Deserialize)] diff --git a/backend/src/reddit/reddit_error.rs b/backend/src/reddit/reddit_error.rs index 9b424b4..b762c83 100644 --- a/backend/src/reddit/reddit_error.rs +++ b/backend/src/reddit/reddit_error.rs @@ -13,15 +13,6 @@ pub enum RedditError { #[error("Domain '{0}' is not a valid Reddit API domain")] InvalidDomain(String), - #[error("Failed to read credentials file. It should be placed in the same dir as Cargo.toml")] - CredentialsNotFound, - - #[error("Malformed .reddit-credentials.json file. Please make sure it's valid JSON")] - MalformedCredentials, - - #[error("Credentials file contains no credentials in the array")] - NoClientsInCredentials, - #[error("HTTP Error: `{0}`")] HttpError(#[from] reqwest::Error),