added get_one, get_popular datasource methods, added respective routes
This commit is contained in:
		
							
								
								
									
										1
									
								
								backend/public/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								backend/public/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,2 +1,3 @@ | |||||||
| target/ | target/ | ||||||
| .env | .env | ||||||
|  | debug.log | ||||||
|   | |||||||
							
								
								
									
										17
									
								
								backend/public/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										17
									
								
								backend/public/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1057,6 +1057,7 @@ dependencies = [ | |||||||
|  "serde_json", |  "serde_json", | ||||||
|  "sqlx", |  "sqlx", | ||||||
|  "tokio", |  "tokio", | ||||||
|  |  "tower-http", | ||||||
|  "tracing", |  "tracing", | ||||||
|  "tracing-subscriber", |  "tracing-subscriber", | ||||||
| ] | ] | ||||||
| @@ -1803,6 +1804,22 @@ dependencies = [ | |||||||
|  "tracing", |  "tracing", | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "tower-http" | ||||||
|  | version = "0.6.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" | ||||||
|  | dependencies = [ | ||||||
|  |  "bitflags", | ||||||
|  |  "bytes", | ||||||
|  |  "http", | ||||||
|  |  "http-body", | ||||||
|  |  "pin-project-lite", | ||||||
|  |  "tower-layer", | ||||||
|  |  "tower-service", | ||||||
|  |  "tracing", | ||||||
|  | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "tower-layer" | name = "tower-layer" | ||||||
| version = "0.3.3" | version = "0.3.3" | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ authors = ["Wyatt J. Miller <wyatt@wyattjmiller.com"] | |||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| axum = "0.7.6" | axum = "0.7.6" | ||||||
|  | tower-http = { version = "0.6.1", features = ["trace"] } | ||||||
| tokio = { version = "1.40.0", features = ["full"] } | tokio = { version = "1.40.0", features = ["full"] } | ||||||
| sqlx = { version = "0.8.2", features = [ | sqlx = { version = "0.8.2", features = [ | ||||||
|   "runtime-tokio-rustls", |   "runtime-tokio-rustls", | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | use axum::routing::trace; | ||||||
| use sqlx::{PgPool, Pool, Postgres}; | use sqlx::{PgPool, Pool, Postgres}; | ||||||
|  |  | ||||||
| use crate::routes::posts::Post; | use crate::routes::posts::Post; | ||||||
| @@ -5,11 +6,20 @@ use crate::routes::posts::Post; | |||||||
| pub struct PostsDatasource; | pub struct PostsDatasource; | ||||||
| impl PostsDatasource { | impl PostsDatasource { | ||||||
|     pub async fn get_all(pool: &Pool<Postgres>) -> Result<Vec<Post>, sqlx::Error> { |     pub async fn get_all(pool: &Pool<Postgres>) -> Result<Vec<Post>, sqlx::Error> { | ||||||
|         sqlx::query_as::<_, Post>( |         sqlx::query_as!(Post, "SELECT p.post_id, a.first_name, a.last_name, p.title, p.body, p.created_at FROM posts p LEFT JOIN authors a ON a.author_id = p.author_id WHERE p.deleted_at IS NULL ORDER BY p.created_at DESC LIMIT 10") | ||||||
|             "SELECT post_id, title, body, created_at FROM posts ORDER BY created_at DESC LIMIT 10", |         .fetch_all(pool) | ||||||
|         ) |         .await | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub async fn get_one(pool: &Pool<Postgres>, post_id: i32) -> Result<Post, sqlx::Error> { | ||||||
|  |         sqlx::query_as!(Post, "SELECT p.post_id, a.first_name, a.last_name, p.title, p.body, p.created_at FROM posts p LEFT JOIN authors a ON a.author_id = p.author_id WHERE p.deleted_at IS NULL AND p.post_id = $1 ORDER BY p.created_at DESC", post_id) | ||||||
|  |         .fetch_one(pool) | ||||||
|  |         .await | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub async fn get_popular(pool: &Pool<Postgres>) -> Result<Vec<Post>, sqlx::Error> { | ||||||
|  |         sqlx::query_as!(Post, "SELECT p.post_id, a.first_name, a.last_name, p.title, p.body, p.created_at FROM posts p LEFT JOIN authors a ON a.author_id = p.author_id LEFT JOIN comments c ON p.post_id = c.post_id WHERE p.deleted_at IS NULL GROUP BY p.post_id, a.first_name, a.last_name ORDER BY p.created_at DESC LIMIT 3") | ||||||
|         .fetch_all(pool) |         .fetch_all(pool) | ||||||
|         .await |         .await | ||||||
|     } |     } | ||||||
|     pub async fn get_one(pool: PgPool, post_id: i32) {} |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,10 +1,13 @@ | |||||||
| use axum::Router; | use axum::Router; | ||||||
| use config::config; | use config::config; | ||||||
| use sqlx::{postgres::PgPoolOptions, PgPool}; | use sqlx::{postgres::PgPoolOptions, PgPool}; | ||||||
|  | use std::fs::File; | ||||||
|  | use std::sync::Arc; | ||||||
| use std::time::Duration; | use std::time::Duration; | ||||||
| use tokio::net::TcpListener; | use tokio::net::TcpListener; | ||||||
| use tokio::signal; | use tokio::signal; | ||||||
| use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; | use tower_http::trace::{self, TraceLayer}; | ||||||
|  | use tracing_subscriber::{filter, layer::SubscriberExt, prelude::*, util::SubscriberInitExt}; | ||||||
|  |  | ||||||
| mod config; | mod config; | ||||||
| mod datasources; | mod datasources; | ||||||
| @@ -16,22 +19,57 @@ pub struct AppState { | |||||||
|  |  | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() { | async fn main() { | ||||||
|  |     // setting up configuration | ||||||
|     let _ = config(); |     let _ = config(); | ||||||
|  |  | ||||||
|  |     // setting up logging | ||||||
|  |     let stdout_log = tracing_subscriber::fmt::layer().pretty(); | ||||||
|  |     let file = File::create("debug.log"); | ||||||
|  |     let file = match file { | ||||||
|  |         Ok(file) => file, | ||||||
|  |         Err(error) => panic!("Error: {:?}", error), | ||||||
|  |     }; | ||||||
|  |     let debug_log = tracing_subscriber::fmt::layer().with_writer(Arc::new(file)); | ||||||
|  |     let metrics_layer = filter::LevelFilter::INFO; | ||||||
|     tracing_subscriber::registry() |     tracing_subscriber::registry() | ||||||
|         .with( |         .with( | ||||||
|             tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| { |             stdout_log | ||||||
|                 format!( |                 // Add an `INFO` filter to the stdout logging layer | ||||||
|                     "{}=debug,tower_http=debug,axum=trace", |                 .with_filter(filter::LevelFilter::INFO) | ||||||
|                     env!("CARGO_CRATE_NAME") |                 // Combine the filtered `stdout_log` layer with the | ||||||
|  |                 // `debug_log` layer, producing a new `Layered` layer. | ||||||
|  |                 .and_then(debug_log) | ||||||
|  |                 // Add a filter to *both* layers that rejects spans and | ||||||
|  |                 // events whose targets start with `metrics`. | ||||||
|  |                 .with_filter(filter::filter_fn(|metadata| { | ||||||
|  |                     !metadata.target().starts_with("metrics") | ||||||
|  |                 })), | ||||||
|         ) |         ) | ||||||
|                 .into() |         .with( | ||||||
|             }), |             // Add a filter to the metrics label that *only* enables | ||||||
|  |             // events whose targets start with `metrics`. | ||||||
|  |             metrics_layer.with_filter(filter::filter_fn(|metadata| { | ||||||
|  |                 metadata.target().starts_with("metrics") | ||||||
|  |             })), | ||||||
|         ) |         ) | ||||||
|         .with(tracing_subscriber::fmt::layer()) |  | ||||||
|         .init(); |         .init(); | ||||||
|  |  | ||||||
|     let db_connection_str = std::env::var("DATABASE_URL").unwrap(); |     // tracing_subscriber::registry() | ||||||
|     // .unwrap_or_else(|_| "postgres://postgres:password@localhost".to_string()); |     //     .with( | ||||||
|  |     //         tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| { | ||||||
|  |     //             format!( | ||||||
|  |     //                 "{}=debug,tower_http=debug,axum=trace", | ||||||
|  |     //                 env!("CARGO_CRATE_NAME") | ||||||
|  |     //             ) | ||||||
|  |     //             .into() | ||||||
|  |     //         }), | ||||||
|  |     //     ) | ||||||
|  |     //     .with(tracing_subscriber::fmt::layer()) | ||||||
|  |     //     .init(); | ||||||
|  |  | ||||||
|  |     // grabbing the database url from our env variables | ||||||
|  |     let db_connection_str = std::env::var("DATABASE_URL") | ||||||
|  |         .unwrap_or_else(|_| "postgres://postgres:password@localhost".to_string()); | ||||||
|  |  | ||||||
|     // set up connection pool |     // set up connection pool | ||||||
|     let pool = PgPoolOptions::new() |     let pool = PgPoolOptions::new() | ||||||
| @@ -46,7 +84,12 @@ async fn main() { | |||||||
|     // build our application with some routes |     // build our application with some routes | ||||||
|     let app = Router::new() |     let app = Router::new() | ||||||
|         .nest("/", routes::root::RootRoute::routes()) |         .nest("/", routes::root::RootRoute::routes()) | ||||||
|         .nest("/posts", routes::posts::PostsRoute::routes(&app_state)); |         .nest("/posts", routes::posts::PostsRoute::routes(&app_state)) | ||||||
|  |         .layer( | ||||||
|  |             TraceLayer::new_for_http() | ||||||
|  |                 .make_span_with(trace::DefaultMakeSpan::new().level(tracing::Level::INFO)) | ||||||
|  |                 .on_response(trace::DefaultOnResponse::new().level(tracing::Level::INFO)), | ||||||
|  |         ); | ||||||
|     // .nest( |     // .nest( | ||||||
|     //     "/comments", |     //     "/comments", | ||||||
|     //     routes::comments::CommentsRoute::routes(&app_state), |     //     routes::comments::CommentsRoute::routes(&app_state), | ||||||
|   | |||||||
| @@ -1,22 +1,29 @@ | |||||||
| use crate::{datasources::posts::PostsDatasource, AppState}; | use crate::{datasources::posts::PostsDatasource, AppState}; | ||||||
| use axum::{ | use axum::{ | ||||||
|     extract::State, |     extract::{Path, State}, | ||||||
|     http::StatusCode, |     http::StatusCode, | ||||||
|     response::{IntoResponse, Response}, |     response::IntoResponse, | ||||||
|     routing::get, |     routing::get, | ||||||
|     Json, Router, |     Json, Router, | ||||||
| }; | }; | ||||||
| use chrono::Utc; | use chrono::Utc; | ||||||
| use serde::{Serialize, Serializer}; | use serde::{Deserialize, Serialize, Serializer}; | ||||||
| use sqlx::{PgPool, Pool, Postgres}; | use sqlx::{Pool, Postgres}; | ||||||
|  |  | ||||||
| #[derive(sqlx::FromRow, Serialize)] | #[derive(sqlx::FromRow, Serialize, Debug)] | ||||||
| pub struct Post { | pub struct Post { | ||||||
|     pub post_id: i32, |     pub post_id: i32, | ||||||
|  |     pub first_name: Option<String>, | ||||||
|  |     pub last_name: Option<String>, | ||||||
|     pub title: String, |     pub title: String, | ||||||
|     pub body: String, |     pub body: String, | ||||||
|     #[serde(serialize_with = "serialize_datetime")] |     #[serde(serialize_with = "serialize_datetime")] | ||||||
|     pub created_at: chrono::DateTime<Utc>, |     pub created_at: Option<chrono::DateTime<Utc>>, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Deserialize)] | ||||||
|  | pub struct PostGetOneParams { | ||||||
|  |     pub id: i32, | ||||||
| } | } | ||||||
|  |  | ||||||
| pub struct PostsRoute; | pub struct PostsRoute; | ||||||
| @@ -25,7 +32,8 @@ impl PostsRoute { | |||||||
|         // add more post routes here! |         // add more post routes here! | ||||||
|         Router::new() |         Router::new() | ||||||
|             .route("/all", get(PostsRoute::get_all)) |             .route("/all", get(PostsRoute::get_all)) | ||||||
|             // .route("/:id", get(PostsRoute::get_one)) |             .route("/:id", get(PostsRoute::get_one)) | ||||||
|  |             .route("/popular", get(PostsRoute::get_popular_posts)) | ||||||
|             .with_state(app_state.db.clone()) |             .with_state(app_state.db.clone()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -38,21 +46,34 @@ impl PostsRoute { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     // get one post |     // get one post | ||||||
|     // async fn get_one(State(pool): State<PgPool>) -> Json<()> { |     async fn get_one( | ||||||
|     //     let results = PostsDatasource::get_one(pool).await; |         State(pool): State<Pool<Postgres>>, | ||||||
|     //     Json {} |         Path(params): Path<PostGetOneParams>, | ||||||
|     // } |     ) -> impl IntoResponse { | ||||||
|  |         match PostsDatasource::get_one(&pool, params.id).await { | ||||||
|     // get the top three posts with the highest view count |             Ok(post) => Ok(Json(post)), | ||||||
|     // async fn get_popular_posts(State(pool): State<PgPool>) {} |             Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, e.to_string())), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // get the top three posts with the most comments |     // get the top three posts with the most comments | ||||||
|  |     async fn get_popular_posts(State(pool): State<Pool<Postgres>>) -> impl IntoResponse { | ||||||
|  |         match PostsDatasource::get_popular(&pool).await { | ||||||
|  |             Ok(posts) => Ok(Json(posts)), | ||||||
|  |             Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, e.to_string())), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // get the top three posts with the highest view count | ||||||
|     // async fn get_hot_posts(State(pool): State<PgPool>) {} |     // async fn get_hot_posts(State(pool): State<PgPool>) {} | ||||||
| } | } | ||||||
|  |  | ||||||
| fn serialize_datetime<S>(date: &chrono::DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error> | fn serialize_datetime<S>( | ||||||
|  |     date: &Option<chrono::DateTime<Utc>>, | ||||||
|  |     serializer: S, | ||||||
|  | ) -> Result<S::Ok, S::Error> | ||||||
| where | where | ||||||
|     S: Serializer, |     S: Serializer, | ||||||
| { | { | ||||||
|     serializer.serialize_str(&date.to_rfc3339()) |     serializer.serialize_str(&date.unwrap().to_rfc3339()) | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user