added get_one, get_popular datasource methods, added respective routes
This commit is contained in:
parent
9bd2cf373a
commit
e3e81f6685
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.
|
||||||
.into()
|
.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();
|
.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())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user