From dc469c431f3c40a06e2fb08ecf0908a61dfccf95 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 15 Jun 2023 15:26:46 +0100 Subject: [PATCH] refactor(api): [#181] Axum API, settings contex --- src/web/api/v1/contexts/settings/handlers.rs | 68 +++++++++++ src/web/api/v1/contexts/settings/mod.rs | 2 + src/web/api/v1/contexts/settings/routes.rs | 19 +++ src/web/api/v1/routes.rs | 5 +- tests/e2e/contexts/settings/contract.rs | 118 +++++++++++++++++++ 5 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 src/web/api/v1/contexts/settings/handlers.rs create mode 100644 src/web/api/v1/contexts/settings/routes.rs diff --git a/src/web/api/v1/contexts/settings/handlers.rs b/src/web/api/v1/contexts/settings/handlers.rs new file mode 100644 index 00000000..17144c63 --- /dev/null +++ b/src/web/api/v1/contexts/settings/handlers.rs @@ -0,0 +1,68 @@ +//! 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 crate::common::AppData; +use crate::config::{ConfigurationPublic, TorrustBackend}; +use crate::errors::ServiceError; +use crate::web::api::v1::extractors::bearer_token::Extract; +use crate::web::api::v1::responses::{self, OkResponse}; + +/// Get all settings. +/// +/// # Errors +/// +/// This function will return an error if the user does not have permission to +/// view all the settings. +#[allow(clippy::unused_async)] +pub async fn get_all_handler( + State(app_data): State>, + Extract(maybe_bearer_token): Extract, +) -> Result>, ServiceError> { + let user_id = app_data.auth.get_user_id_from_bearer_token(&maybe_bearer_token).await?; + + let all_settings = app_data.settings_service.get_all(&user_id).await?; + + Ok(Json(responses::OkResponse { data: all_settings })) +} + +/// Get public Settings. +#[allow(clippy::unused_async)] +pub async fn get_public_handler(State(app_data): State>) -> Json> { + let public_settings = app_data.settings_service.get_public().await; + + Json(responses::OkResponse { data: public_settings }) +} + +/// Get website name. +#[allow(clippy::unused_async)] +pub async fn get_site_name_handler(State(app_data): State>) -> Json> { + let site_name = app_data.settings_service.get_site_name().await; + + Json(responses::OkResponse { data: site_name }) +} + +/// Update all the settings. +/// +/// # Errors +/// +/// This function will return an error if: +/// +/// - The user does not have permission to update the settings. +/// - The settings could not be updated because they were loaded from env vars. +/// See +#[allow(clippy::unused_async)] +pub async fn update_handler( + State(app_data): State>, + Extract(maybe_bearer_token): Extract, + extract::Json(torrust_backend): extract::Json, +) -> Result>, ServiceError> { + let user_id = app_data.auth.get_user_id_from_bearer_token(&maybe_bearer_token).await?; + + let new_settings = app_data.settings_service.update_all(torrust_backend, &user_id).await?; + + Ok(Json(responses::OkResponse { data: new_settings })) +} diff --git a/src/web/api/v1/contexts/settings/mod.rs b/src/web/api/v1/contexts/settings/mod.rs index 70cd94b2..40f511f2 100644 --- a/src/web/api/v1/contexts/settings/mod.rs +++ b/src/web/api/v1/contexts/settings/mod.rs @@ -167,3 +167,5 @@ //! //! Refer to the [`ConfigurationPublic`](crate::config::ConfigurationPublic) //! struct for more information about the response attributes. +pub mod handlers; +pub mod routes; diff --git a/src/web/api/v1/contexts/settings/routes.rs b/src/web/api/v1/contexts/settings/routes.rs new file mode 100644 index 00000000..baffa4c2 --- /dev/null +++ b/src/web/api/v1/contexts/settings/routes.rs @@ -0,0 +1,19 @@ +//! API routes for the [`settings`](crate::web::api::v1::contexts::settings) API context. +//! +//! Refer to the [API endpoint documentation](crate::web::api::v1::contexts::settings). +use std::sync::Arc; + +use axum::routing::{get, post}; +use axum::Router; + +use super::handlers::{get_all_handler, get_public_handler, get_site_name_handler, update_handler}; +use crate::common::AppData; + +/// Routes for the [`category`](crate::web::api::v1::contexts::category) API context. +pub fn router(app_data: Arc) -> Router { + Router::new() + .route("/", get(get_all_handler).with_state(app_data.clone())) + .route("/name", get(get_site_name_handler).with_state(app_data.clone())) + .route("/public", get(get_public_handler).with_state(app_data.clone())) + .route("/", post(update_handler).with_state(app_data)) +} diff --git a/src/web/api/v1/routes.rs b/src/web/api/v1/routes.rs index 1b347d18..008722c9 100644 --- a/src/web/api/v1/routes.rs +++ b/src/web/api/v1/routes.rs @@ -6,7 +6,7 @@ use axum::Router; use super::contexts::about::handlers::about_page_handler; //use tower_http::cors::CorsLayer; -use super::contexts::{about, tag}; +use super::contexts::{about, settings, tag}; use super::contexts::{category, user}; use crate::common::AppData; @@ -22,7 +22,8 @@ pub fn router(app_data: Arc) -> Router { .nest("/about", about::routes::router(app_data.clone())) .nest("/category", category::routes::router(app_data.clone())) .nest("/tag", tag::routes::router_for_single_resources(app_data.clone())) - .nest("/tags", tag::routes::router_for_multiple_resources(app_data.clone())); + .nest("/tags", tag::routes::router_for_multiple_resources(app_data.clone())) + .nest("/settings", settings::routes::router(app_data.clone())); Router::new() .route("/", get(about_page_handler).with_state(app_data)) diff --git a/tests/e2e/contexts/settings/contract.rs b/tests/e2e/contexts/settings/contract.rs index 0802512a..174ae6c1 100644 --- a/tests/e2e/contexts/settings/contract.rs +++ b/tests/e2e/contexts/settings/contract.rs @@ -94,3 +94,121 @@ async fn it_should_allow_admins_to_update_all_the_settings() { } assert_eq!(response.status, 200); } + +mod with_axum_implementation { + use std::env; + + use torrust_index_backend::web::api; + + use crate::common::asserts::assert_json_ok; + use crate::common::client::Client; + use crate::common::contexts::settings::responses::{AllSettingsResponse, Public, PublicSettingsResponse, SiteNameResponse}; + use crate::e2e::config::ENV_VAR_E2E_EXCLUDE_AXUM_IMPL; + use crate::e2e::contexts::user::steps::new_logged_in_admin; + use crate::e2e::environment::TestEnv; + + #[tokio::test] + async fn it_should_allow_guests_to_get_the_public_settings() { + let mut env = TestEnv::new(); + env.start(api::Implementation::Axum).await; + + if env::var(ENV_VAR_E2E_EXCLUDE_AXUM_IMPL).is_ok() { + println!("Skipped"); + return; + } + + let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); + + let response = client.get_public_settings().await; + + let res: PublicSettingsResponse = serde_json::from_str(&response.body) + .unwrap_or_else(|_| panic!("response {:#?} should be a PublicSettingsResponse", response.body)); + + assert_eq!( + res.data, + Public { + website_name: env.server_settings().unwrap().website.name, + tracker_url: env.server_settings().unwrap().tracker.url, + tracker_mode: env.server_settings().unwrap().tracker.mode, + email_on_signup: env.server_settings().unwrap().auth.email_on_signup, + } + ); + + assert_json_ok(&response); + } + + #[tokio::test] + async fn it_should_allow_guests_to_get_the_site_name() { + let mut env = TestEnv::new(); + env.start(api::Implementation::Axum).await; + + if env::var(ENV_VAR_E2E_EXCLUDE_AXUM_IMPL).is_ok() { + println!("Skipped"); + return; + } + + let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); + + let response = client.get_site_name().await; + + let res: SiteNameResponse = serde_json::from_str(&response.body).unwrap(); + + assert_eq!(res.data, "Torrust"); + + assert_json_ok(&response); + } + + #[tokio::test] + async fn it_should_allow_admins_to_get_all_the_settings() { + let mut env = TestEnv::new(); + env.start(api::Implementation::Axum).await; + + if env::var(ENV_VAR_E2E_EXCLUDE_AXUM_IMPL).is_ok() { + println!("Skipped"); + return; + } + + let logged_in_admin = new_logged_in_admin(&env).await; + let client = Client::authenticated(&env.server_socket_addr().unwrap(), &logged_in_admin.token); + + let response = client.get_settings().await; + + let res: AllSettingsResponse = serde_json::from_str(&response.body).unwrap(); + + assert_eq!(res.data, env.server_settings().unwrap()); + + assert_json_ok(&response); + } + + #[tokio::test] + async fn it_should_allow_admins_to_update_all_the_settings() { + let mut env = TestEnv::new(); + env.start(api::Implementation::Axum).await; + + if env::var(ENV_VAR_E2E_EXCLUDE_AXUM_IMPL).is_ok() { + println!("Skipped"); + return; + } + + if !env.is_isolated() { + // This test can't be executed in a non-isolated environment because + // it will change the settings for all the other tests. + return; + } + + let logged_in_admin = new_logged_in_admin(&env).await; + let client = Client::authenticated(&env.server_socket_addr().unwrap(), &logged_in_admin.token); + + let mut new_settings = env.server_settings().unwrap(); + + new_settings.website.name = "UPDATED NAME".to_string(); + + let response = client.update_settings(&new_settings).await; + + let res: AllSettingsResponse = serde_json::from_str(&response.body).unwrap(); + + assert_eq!(res.data, new_settings); + + assert_json_ok(&response); + } +}