Skip to content

Commit

Permalink
GET recipes
Browse files Browse the repository at this point in the history
  • Loading branch information
NChitty committed Aug 27, 2024
1 parent 70d3ce0 commit eef3072
Show file tree
Hide file tree
Showing 8 changed files with 900 additions and 294 deletions.
1,108 changes: 820 additions & 288 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,24 @@
"watch": "tsc -w"
},
"devDependencies": {
"@playwright/test": "^1.45.1",
"@playwright/test": "^1.46.1",
"@types/jest": "^29.5.12",
"@types/node": "^20.14.10",
"@types/node": "^20.16.1",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"aws-cdk": "^2.147.3",
"aws-cdk": "^2.154.1",
"eslint": "^8.57.0",
"eslint-config-google": "^0.14.0",
"jest": "^29.7.0",
"ts-jest": "^29.1.5",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typescript": "~5.2.2"
},
"dependencies": {
"aws-cdk-lib": "^2.147.3",
"aws-cdk-lib": "^2.154.1",
"aws-cdk-local": "^2.18.0",
"cargo-lambda-cdk": "^0.0.22",
"cdk-pipelines-github": "^0.4.123",
"cdk-pipelines-github": "^0.4.124",
"constructs": "^10.3.0",
"source-map-support": "^0.5.21"
}
Expand Down
11 changes: 11 additions & 0 deletions playwright/tests/recipes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ test.describe('Happy Path', () => {
});
});

test('List Recipes', async ({ request }) => {
const response = await request.get('./recipes');

expect(response.ok()).toBeTruthy();
const json = await response.json();
expect(json).toContainEqual({
id: recipeUuid,
...createData,
});
});

test('Update Recipe', async ({ request }) => {
const response = await request.patch(`./recipes/${recipeUuid}`, { data: updateData });

Expand Down
1 change: 1 addition & 0 deletions src/bin/recipes/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub async fn recipes() -> Router {
let recipe_context = ApplicationContext { repo };

Router::new()
.route("/", get(recipes::list::<DynamoDbRecipe>))
.route("/", post(recipes::create::<DynamoDbRecipe>))
.route("/:id", get(recipes::read_one::<DynamoDbRecipe>))
.route("/:id", patch(recipes::update::<DynamoDbRecipe>))
Expand Down
13 changes: 13 additions & 0 deletions src/lib/aws_client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use aws_sdk_dynamodb::error::SdkError;
use aws_sdk_dynamodb::operation::delete_item::{DeleteItemError, DeleteItemOutput};
use aws_sdk_dynamodb::operation::get_item::{GetItemError, GetItemOutput};
use aws_sdk_dynamodb::operation::put_item::{PutItemError, PutItemOutput};
use aws_sdk_dynamodb::operation::scan::{ScanError, ScanOutput};
use aws_sdk_dynamodb::types::AttributeValue;
use aws_sdk_dynamodb::Client;
use axum::async_trait;
Expand All @@ -17,6 +18,7 @@ pub trait DynamoDbClient: Send + Sync {
table_name: &str,
key: HashMap<String, AttributeValue>,
) -> Result<GetItemOutput, GetItemError>;

async fn put_item(
&self,
table_name: &str,
Expand All @@ -28,6 +30,8 @@ pub trait DynamoDbClient: Send + Sync {
table_name: &str,
key: HashMap<String, AttributeValue>,
) -> Result<DeleteItemOutput, DeleteItemError>;

async fn scan(&self, table_name: &str) -> Result<ScanOutput, ScanError>;
}

#[derive(Clone)]
Expand Down Expand Up @@ -82,4 +86,13 @@ impl DynamoDbClient for DynamoDbClientImpl {
.await
.map_err(SdkError::into_service_error)
}

async fn scan(&self, table_name: &str) -> Result<ScanOutput, ScanError> {
self.0
.scan()
.table_name(table_name)
.send()
.await
.map_err(SdkError::into_service_error)
}
}
1 change: 1 addition & 0 deletions src/lib/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod services;
#[cfg_attr(test, mockall::automock)]
#[async_trait]
pub trait Repository<T: Send + Sync>: Send + Sync {
fn get_all(&self) -> impl Future<Output = Result<Vec<T>, StatusCode>> + Send;
fn find_by_id(&self, id: Uuid) -> impl Future<Output = Result<T, StatusCode>> + Send;
fn save(&self, item: &T) -> impl Future<Output = Result<(), StatusCode>> + Send;
fn delete_by_id(&self, id: Uuid) -> impl Future<Output = Result<(), StatusCode>> + Send;
Expand Down
37 changes: 37 additions & 0 deletions src/lib/recipe/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,24 @@ impl DynamoDbRecipe {
}

impl Repository<Recipe> for DynamoDbRecipe {
async fn get_all(&self) -> Result<Vec<Recipe>, StatusCode> {
let scan_result = self
.client
.scan(&self.table_name)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

let items: Vec<Recipe> = scan_result
.items()
.iter()
.map(|item| from_item(item.clone()))
.filter(Result::is_ok)
.map(Result::unwrap)
.collect();

Ok(items)
}

async fn find_by_id(&self, id: Uuid) -> Result<Recipe, StatusCode> {
let get_item_result = self
.client
Expand Down Expand Up @@ -99,6 +117,7 @@ mod test {
use aws_sdk_dynamodb::operation::delete_item::DeleteItemOutput;
use aws_sdk_dynamodb::operation::get_item::GetItemOutput;
use aws_sdk_dynamodb::operation::put_item::{PutItemError, PutItemOutput};
use aws_sdk_dynamodb::operation::scan::ScanOutput;
use aws_sdk_dynamodb::types::error::{
ConditionalCheckFailedException,
ResourceNotFoundException,
Expand Down Expand Up @@ -247,4 +266,22 @@ mod test {
_ => false,
}))
}

#[tokio::test]
async fn test_scan() {
let mut mock = DynamoDbClient::default();
let recipe = Recipe {
id: Uuid::nil(),
name: "Name".to_owned(),
};
let item = to_item(&recipe).unwrap();
mock.expect_scan()
.with(eq("recipes"))
.return_once(move |_| Ok(ScanOutput::builder().items(item).build()));

let repo = DynamoDbRecipe::mock(Arc::new(mock), "recipes");

let result = repo.get_all().await;
assert!(result.is_ok_and(|collection| collection.contains(&recipe) && collection.len() == 1))
}
}
11 changes: 11 additions & 0 deletions src/lib/services/recipes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@ use crate::recipe::{mapper, Recipe};
use crate::services::ApplicationContext;
use crate::Repository;

pub async fn list<T>(
State(state): State<ApplicationContext<T>>,
) -> Result<Json<Vec<Recipe>>, StatusCode>
where
T: Repository<Recipe>,
{
let recipes = state.repo.get_all().await?;

Ok(Json(recipes))
}

/// Attempts to create a recipe in the database.
///
/// # Errors
Expand Down

0 comments on commit eef3072

Please sign in to comment.