Skip to content

Commit

Permalink
Feature/read recipe (#10)
Browse files Browse the repository at this point in the history
* Impl Default for Recipe

* Simple construction

* Base repository trait

* Code organization + nested

* With context

* POSTGRESgit ap! LFG

* formatting

* Lints

* Fmt
  • Loading branch information
NChitty authored Jan 15, 2024
1 parent b6779bd commit a57503f
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 5 deletions.
4 changes: 4 additions & 0 deletions lambda/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lambda/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
10 changes: 10 additions & 0 deletions lambda/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use std::future::Future;

use uuid::Uuid;

pub mod recipe;
pub mod services;

pub trait Repository<T>: Send + Sync {
fn find_by_id(&self, id: Uuid) -> impl Future<Output = Option<T>>;
}
7 changes: 4 additions & 3 deletions lambda/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
}
12 changes: 11 additions & 1 deletion lambda/src/recipe/mod.rs
Original file line number Diff line number Diff line change
@@ -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(),
}
}
}
29 changes: 29 additions & 0 deletions lambda/src/services/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use axum::routing::get;
use axum::Router;

use crate::services::recipes::PostgresRecipeRepository;

mod recipes;

#[derive(Clone)]
struct ApplicationContext<T> {
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::<PostgresRecipeRepository>))
.with_state(recipe_context)
}
64 changes: 64 additions & 0 deletions lambda/src/services/recipes.rs
Original file line number Diff line number Diff line change
@@ -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<Recipe> for PostgresRecipeRepository {
async fn find_by_id(&self, id: Uuid) -> Option<Recipe> {
let result: Result<Recipe, Error> = 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<T>(
State(state): State<ApplicationContext<T>>,
Path(id): Path<Uuid>,
) -> Result<Json<Recipe>, StatusCode>
where
T: Repository<Recipe>,
{
if let Some(recipe) = state.repo.find_by_id(id).await {
return Ok(Json(recipe));
}

Err(StatusCode::NOT_FOUND)
}

0 comments on commit a57503f

Please sign in to comment.