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

Axum API: category context #195

Merged
merged 4 commits into from
Jun 15, 2023
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
9 changes: 9 additions & 0 deletions src/web/api/v1/contexts/category/forms.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
pub struct AddCategoryForm {
pub name: String,
pub icon: Option<String>,
}

pub type DeleteCategoryForm = AddCategoryForm;
85 changes: 85 additions & 0 deletions src/web/api/v1/contexts/category/handlers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//! API handlers for the the [`category`](crate::web::api::v1::contexts::category) API
//! context.
use std::sync::Arc;

use axum::extract::{self, State};
use axum::response::Json;

use super::forms::{AddCategoryForm, DeleteCategoryForm};
use super::responses::{added_category, deleted_category};
use crate::common::AppData;
use crate::databases::database::{self, Category};
use crate::errors::ServiceError;
use crate::web::api::v1::extractors::bearer_token::Extract;
use crate::web::api::v1::responses::{self, OkResponse};

/// It handles the request to get all the categories.
///
/// It returns:
///
/// - `200` response with a json containing the category list [`Vec<Category>`](crate::databases::database::Category).
/// - Other error status codes if there is a database error.
///
/// Refer to the [API endpoint documentation](crate::web::api::v1::contexts::category)
/// for more information about this endpoint.
///
/// # Errors
///
/// It returns an error if there is a database error.
#[allow(clippy::unused_async)]
pub async fn get_all_handler(
State(app_data): State<Arc<AppData>>,
) -> Result<Json<responses::OkResponse<Vec<Category>>>, database::Error> {
match app_data.category_repository.get_all().await {
Ok(categories) => Ok(Json(responses::OkResponse { data: categories })),
Err(error) => Err(error),
}
}

/// It adds a new category.
///
/// # Errors
///
/// It returns an error if:
///
/// - The user does not have permissions to create a new category.
/// - There is a database error.
#[allow(clippy::unused_async)]
pub async fn add_handler(
State(app_data): State<Arc<AppData>>,
Extract(maybe_bearer_token): Extract,
extract::Json(category_form): extract::Json<AddCategoryForm>,
) -> Result<Json<OkResponse<String>>, ServiceError> {
let user_id = app_data.auth.get_user_id_from_bearer_token(&maybe_bearer_token).await?;

match app_data.category_service.add_category(&category_form.name, &user_id).await {
Ok(_) => Ok(added_category(&category_form.name)),
Err(error) => Err(error),
}
}

/// It deletes a category.
///
/// # Errors
///
/// It returns an error if:
///
/// - The user does not have permissions to delete category.
/// - There is a database error.
#[allow(clippy::unused_async)]
pub async fn delete_handler(
State(app_data): State<Arc<AppData>>,
Extract(maybe_bearer_token): Extract,
extract::Json(category_form): extract::Json<DeleteCategoryForm>,
) -> Result<Json<OkResponse<String>>, ServiceError> {
// code-review: why do we need to send the whole category object to delete it?
// And we should use the ID instead of the name, because the name could change
// or we could add support for multiple languages.

let user_id = app_data.auth.get_user_id_from_bearer_token(&maybe_bearer_token).await?;

match app_data.category_service.delete_category(&category_form.name, &user_id).await {
Ok(_) => Ok(deleted_category(&category_form.name)),
Err(error) => Err(error),
}
}
4 changes: 4 additions & 0 deletions src/web/api/v1/contexts/category/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,7 @@
//! Refer to [`OkResponse`](crate::models::response::OkResponse<T>) for more
//! information about the response attributes. The response contains only the
//! name of the deleted category.
pub mod forms;
pub mod handlers;
pub mod responses;
pub mod routes;
19 changes: 19 additions & 0 deletions src/web/api/v1/contexts/category/responses.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//! API responses for the the [`category`](crate::web::api::v1::contexts::category) API
//! context.
use axum::Json;

use crate::web::api::v1::responses::OkResponse;

/// Response after successfully creating a new category.
pub fn added_category(category_name: &str) -> Json<OkResponse<String>> {
Json(OkResponse {
data: category_name.to_string(),
})
}

/// Response after successfully deleting a new category.
pub fn deleted_category(category_name: &str) -> Json<OkResponse<String>> {
Json(OkResponse {
data: category_name.to_string(),
})
}
18 changes: 18 additions & 0 deletions src/web/api/v1/contexts/category/routes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//! API routes for the [`category`](crate::web::api::v1::contexts::category) API context.
//!
//! Refer to the [API endpoint documentation](crate::web::api::v1::contexts::category).
use std::sync::Arc;

use axum::routing::{delete, get, post};
use axum::Router;

use super::handlers::{add_handler, delete_handler, get_all_handler};
use crate::common::AppData;

/// Routes for the [`category`](crate::web::api::v1::contexts::category) API context.
pub fn router(app_data: Arc<AppData>) -> Router {
Router::new()
.route("/", get(get_all_handler).with_state(app_data.clone()))
.route("/", post(add_handler).with_state(app_data.clone()))
.route("/", delete(delete_handler).with_state(app_data))
}
13 changes: 7 additions & 6 deletions src/web/api/v1/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,23 @@ use std::sync::Arc;
use axum::Router;

//use tower_http::cors::CorsLayer;
use super::contexts::{about, user};
use super::contexts::about;
use super::contexts::{category, user};
use crate::common::AppData;

/// Add all API routes to the router.
#[allow(clippy::needless_pass_by_value)]
pub fn router(app_data: Arc<AppData>) -> Router {
let user_routes = user::routes::router(app_data.clone());
let about_routes = about::routes::router(app_data);
let api_routes = Router::new()
.nest("/user", user::routes::router(app_data.clone()))
.nest("/about", about::routes::router(app_data.clone()))
.nest("/category", category::routes::router(app_data));

let api_routes = Router::new().nest("/user", user_routes).nest("/about", about_routes);
Router::new().nest("/v1", api_routes)

// For development purposes only.
// It allows calling the API on a different port. For example
// API: http://localhost:3000/v1
// Webapp: http://localhost:8080
//Router::new().nest("/v1", api_routes).layer(CorsLayer::permissive())

Router::new().nest("/v1", api_routes)
}
21 changes: 21 additions & 0 deletions tests/common/contexts/category/asserts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use crate::common::asserts::assert_json_ok;
use crate::common::contexts::category::responses::{AddedCategoryResponse, DeletedCategoryResponse};
use crate::common::responses::TextResponse;

pub fn assert_added_category_response(response: &TextResponse, category_name: &str) {
let added_category_response: AddedCategoryResponse = serde_json::from_str(&response.body)
.unwrap_or_else(|_| panic!("response {:#?} should be a AddedCategoryResponse", response.body));

assert_eq!(added_category_response.data, category_name);

assert_json_ok(response);
}

pub fn assert_deleted_category_response(response: &TextResponse, category_name: &str) {
let deleted_category_response: DeletedCategoryResponse = serde_json::from_str(&response.body)
.unwrap_or_else(|_| panic!("response {:#?} should be a DeletedCategoryResponse", response.body));

assert_eq!(deleted_category_response.data, category_name);

assert_json_ok(response);
}
1 change: 1 addition & 0 deletions tests/common/contexts/category/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod asserts;
pub mod fixtures;
pub mod forms;
pub mod responses;
5 changes: 5 additions & 0 deletions tests/common/contexts/category/responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ pub struct AddedCategoryResponse {
pub data: String,
}

#[derive(Deserialize)]
pub struct DeletedCategoryResponse {
pub data: String,
}

#[derive(Deserialize, Debug)]
pub struct ListResponse {
pub data: Vec<ListItem>,
Expand Down
Loading