Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GET recipes #76

Merged
merged 2 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
36 changes: 36 additions & 0 deletions src/lib/recipe/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,23 @@ 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()))
NChitty marked this conversation as resolved.
Show resolved Hide resolved
.flatten()
.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 +116,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 +265,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))
}
}
17 changes: 17 additions & 0 deletions src/lib/services/recipes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,23 @@ use crate::recipe::{mapper, Recipe};
use crate::services::ApplicationContext;
use crate::Repository;

/// Lists all recipes from the database.
///
/// # Errors
///
/// This function converts the result of the database operation to a status code
/// wrapped in an error.
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
Loading