From a57503f97e074aeafdab9e011a7b18be1ffb02a7 Mon Sep 17 00:00:00 2001 From: Nicholas Chitty Date: Mon, 15 Jan 2024 18:21:37 -0500 Subject: [PATCH] Feature/read recipe (#10) * Impl Default for Recipe * Simple construction * Base repository trait * Code organization + nested * With context * POSTGRESgit ap! LFG * formatting * Lints * Fmt --- lambda/Cargo.lock | 4 +++ lambda/Cargo.toml | 2 +- lambda/src/lib.rs | 10 ++++++ lambda/src/main.rs | 7 ++-- lambda/src/recipe/mod.rs | 12 ++++++- lambda/src/services/mod.rs | 29 +++++++++++++++ lambda/src/services/recipes.rs | 64 ++++++++++++++++++++++++++++++++++ 7 files changed, 123 insertions(+), 5 deletions(-) create mode 100644 lambda/src/lib.rs create mode 100644 lambda/src/services/mod.rs create mode 100644 lambda/src/services/recipes.rs diff --git a/lambda/Cargo.lock b/lambda/Cargo.lock index 766ec52..d9957ee 100644 --- a/lambda/Cargo.lock +++ b/lambda/Cargo.lock @@ -1517,6 +1517,7 @@ dependencies = [ "tokio-stream", "tracing", "url", + "uuid", ] [[package]] @@ -1598,6 +1599,7 @@ dependencies = [ "stringprep", "thiserror", "tracing", + "uuid", "whoami", ] @@ -1637,6 +1639,7 @@ dependencies = [ "stringprep", "thiserror", "tracing", + "uuid", "whoami", ] @@ -1661,6 +1664,7 @@ dependencies = [ "tracing", "url", "urlencoding", + "uuid", ] [[package]] diff --git a/lambda/Cargo.toml b/lambda/Cargo.toml index 644e7a8..24a9cf9 100644 --- a/lambda/Cargo.toml +++ b/lambda/Cargo.toml @@ -11,7 +11,7 @@ lambda_http = "0.9.0" lambda_runtime = "0.9.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.111" -sqlx = { version = "0.7.3", features = ["runtime-tokio", "postgres"] } +sqlx = { version = "0.7.3", features = ["runtime-tokio", "macros", "postgres", "uuid"] } tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/lambda/src/lib.rs b/lambda/src/lib.rs new file mode 100644 index 0000000..f78f6a9 --- /dev/null +++ b/lambda/src/lib.rs @@ -0,0 +1,10 @@ +use std::future::Future; + +use uuid::Uuid; + +pub mod recipe; +pub mod services; + +pub trait Repository: Send + Sync { + fn find_by_id(&self, id: Uuid) -> impl Future>; +} diff --git a/lambda/src/main.rs b/lambda/src/main.rs index 934d471..2bd1275 100644 --- a/lambda/src/main.rs +++ b/lambda/src/main.rs @@ -2,12 +2,11 @@ use axum::extract::Query; use axum::response::Json; use axum::routing::get; use axum::Router; +use lambda::services; use lambda_http::{run, Error}; use serde::Deserialize; use serde_json::{json, Value}; -mod recipe; - #[derive(Debug, Deserialize)] struct Root { name: String, @@ -30,9 +29,11 @@ async fn main() -> Result<(), Error> { .without_time() .init(); + let recipe_service = services::recipes().await; let app = Router::new() .route("/", get(root)) - .route("/ping", get(ping)); + .route("/ping", get(ping)) + .nest("/recipes", recipe_service); run(app).await } diff --git a/lambda/src/recipe/mod.rs b/lambda/src/recipe/mod.rs index 2955f95..6cf1394 100644 --- a/lambda/src/recipe/mod.rs +++ b/lambda/src/recipe/mod.rs @@ -1,8 +1,18 @@ use serde::{Deserialize, Serialize}; +use sqlx::FromRow; use uuid::Uuid; -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, FromRow, Serialize)] pub struct Recipe { id: Uuid, name: String, } + +impl Default for Recipe { + fn default() -> Self { + Self { + id: Uuid::nil(), + name: "Basic Recipe".to_owned(), + } + } +} diff --git a/lambda/src/services/mod.rs b/lambda/src/services/mod.rs new file mode 100644 index 0000000..ea0733f --- /dev/null +++ b/lambda/src/services/mod.rs @@ -0,0 +1,29 @@ +use axum::routing::get; +use axum::Router; + +use crate::services::recipes::PostgresRecipeRepository; + +mod recipes; + +#[derive(Clone)] +struct ApplicationContext { + pub repo: T, +} + +pub async fn recipes() -> Router { + let db_username = std::env::var("DB_USERNAME").unwrap_or_else(|_| "postgres".to_string()); + let db_password = std::env::var("DB_PASSWORD").unwrap_or_else(|_| "password1234".to_string()); + let db_host = std::env::var("DB_HOST").unwrap_or_else(|_| "db".to_string()); + let db_port = std::env::var("DB_PORT").unwrap_or_else(|_| "5432".to_string()); + let db_name = std::env::var("DB_NAME").unwrap_or_else(|_| "meal-planner".to_string()); + let db_connection_str = + format!("postgresql://{db_username}:{db_password}@{db_host}:{db_port}/{db_name}"); + + let recipe_context = ApplicationContext { + repo: PostgresRecipeRepository::new(&db_connection_str).await, + }; + + Router::new() + .route("/:id", get(recipes::read_one::)) + .with_state(recipe_context) +} diff --git a/lambda/src/services/recipes.rs b/lambda/src/services/recipes.rs new file mode 100644 index 0000000..02ca912 --- /dev/null +++ b/lambda/src/services/recipes.rs @@ -0,0 +1,64 @@ +use std::time::Duration; + +use axum::extract::{Path, State}; +use axum::http::StatusCode; +use axum::Json; +use sqlx::postgres::PgPoolOptions; +use sqlx::{Error, PgPool}; +use uuid::Uuid; + +use crate::recipe::Recipe; +use crate::services::ApplicationContext; +use crate::Repository; + +#[derive(Clone)] +pub(super) struct TestRecipeRepository {} + +#[derive(Clone)] +pub(super) struct PostgresRecipeRepository { + pool: PgPool, +} + +impl PostgresRecipeRepository { + pub(super) async fn new(url: &String) -> Self { + let pool: PgPool = PgPoolOptions::new() + .max_connections(5) + .acquire_timeout(Duration::from_secs(3)) + .connect(&url) + .await + .expect("Can\'t connect to database"); + + Self { pool } + } +} + +impl Repository for PostgresRecipeRepository { + async fn find_by_id(&self, id: Uuid) -> Option { + let result: Result = sqlx::query_as("SELECT * FROM recipes WHERE id = $1") + .bind(id) + .fetch_one(&self.pool) + .await; + + match result { + Ok(recipe) => Some(recipe), + Err(err) => { + dbg!(err); + None + }, + } + } +} + +pub(super) async fn read_one( + State(state): State>, + Path(id): Path, +) -> Result, StatusCode> +where + T: Repository, +{ + if let Some(recipe) = state.repo.find_by_id(id).await { + return Ok(Json(recipe)); + } + + Err(StatusCode::NOT_FOUND) +}