diff --git a/backend/public/.gitignore b/backend/public/.gitignore index 14ee500..6d63e9a 100644 --- a/backend/public/.gitignore +++ b/backend/public/.gitignore @@ -1,2 +1,3 @@ target/ .env +debug.log diff --git a/backend/public/Cargo.lock b/backend/public/Cargo.lock index 3702047..2eee503 100644 --- a/backend/public/Cargo.lock +++ b/backend/public/Cargo.lock @@ -1057,6 +1057,7 @@ dependencies = [ "serde_json", "sqlx", "tokio", + "tower-http", "tracing", "tracing-subscriber", ] @@ -1803,6 +1804,22 @@ dependencies = [ "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]] name = "tower-layer" version = "0.3.3" diff --git a/backend/public/Cargo.toml b/backend/public/Cargo.toml index 4f9b011..f9ff84a 100644 --- a/backend/public/Cargo.toml +++ b/backend/public/Cargo.toml @@ -8,6 +8,7 @@ authors = ["Wyatt J. Miller ) -> Result, sqlx::Error> { - sqlx::query_as::<_, Post>( - "SELECT post_id, title, body, created_at FROM posts ORDER BY created_at DESC LIMIT 10", - ) + 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") + .fetch_all(pool) + .await + } + + pub async fn get_one(pool: &Pool, post_id: i32) -> Result { + 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) -> Result, 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) .await } - pub async fn get_one(pool: PgPool, post_id: i32) {} } diff --git a/backend/public/src/main.rs b/backend/public/src/main.rs index 0905b60..d5e331f 100644 --- a/backend/public/src/main.rs +++ b/backend/public/src/main.rs @@ -1,10 +1,13 @@ use axum::Router; use config::config; use sqlx::{postgres::PgPoolOptions, PgPool}; +use std::fs::File; +use std::sync::Arc; use std::time::Duration; use tokio::net::TcpListener; 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 datasources; @@ -16,22 +19,57 @@ pub struct AppState { #[tokio::main] async fn main() { + // setting up configuration 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() .with( - tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| { - format!( - "{}=debug,tower_http=debug,axum=trace", - env!("CARGO_CRATE_NAME") - ) - .into() - }), + stdout_log + // Add an `INFO` filter to the stdout logging layer + .with_filter(filter::LevelFilter::INFO) + // 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") + })), + ) + .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(); - let db_connection_str = std::env::var("DATABASE_URL").unwrap(); - // .unwrap_or_else(|_| "postgres://postgres:password@localhost".to_string()); + // tracing_subscriber::registry() + // .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 let pool = PgPoolOptions::new() @@ -46,7 +84,12 @@ async fn main() { // build our application with some routes let app = Router::new() .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( // "/comments", // routes::comments::CommentsRoute::routes(&app_state), diff --git a/backend/public/src/routes/posts.rs b/backend/public/src/routes/posts.rs index 6eabe07..488252b 100644 --- a/backend/public/src/routes/posts.rs +++ b/backend/public/src/routes/posts.rs @@ -1,22 +1,29 @@ use crate::{datasources::posts::PostsDatasource, AppState}; use axum::{ - extract::State, + extract::{Path, State}, http::StatusCode, - response::{IntoResponse, Response}, + response::IntoResponse, routing::get, Json, Router, }; use chrono::Utc; -use serde::{Serialize, Serializer}; -use sqlx::{PgPool, Pool, Postgres}; +use serde::{Deserialize, Serialize, Serializer}; +use sqlx::{Pool, Postgres}; -#[derive(sqlx::FromRow, Serialize)] +#[derive(sqlx::FromRow, Serialize, Debug)] pub struct Post { pub post_id: i32, + pub first_name: Option, + pub last_name: Option, pub title: String, pub body: String, #[serde(serialize_with = "serialize_datetime")] - pub created_at: chrono::DateTime, + pub created_at: Option>, +} + +#[derive(Deserialize)] +pub struct PostGetOneParams { + pub id: i32, } pub struct PostsRoute; @@ -25,7 +32,8 @@ impl PostsRoute { // add more post routes here! Router::new() .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()) } @@ -38,21 +46,34 @@ impl PostsRoute { } // get one post - // async fn get_one(State(pool): State) -> Json<()> { - // let results = PostsDatasource::get_one(pool).await; - // Json {} - // } - - // get the top three posts with the highest view count - // async fn get_popular_posts(State(pool): State) {} + async fn get_one( + State(pool): State>, + Path(params): Path, + ) -> impl IntoResponse { + match PostsDatasource::get_one(&pool, params.id).await { + Ok(post) => Ok(Json(post)), + Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, e.to_string())), + } + } // get the top three posts with the most comments + async fn get_popular_posts(State(pool): State>) -> 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) {} } -fn serialize_datetime(date: &chrono::DateTime, serializer: S) -> Result +fn serialize_datetime( + date: &Option>, + serializer: S, +) -> Result where S: Serializer, { - serializer.serialize_str(&date.to_rfc3339()) + serializer.serialize_str(&date.unwrap().to_rfc3339()) }