diff --git a/src/web/api/v1/contexts/proxy/handlers.rs b/src/web/api/v1/contexts/proxy/handlers.rs new file mode 100644 index 00000000..d31e112a --- /dev/null +++ b/src/web/api/v1/contexts/proxy/handlers.rs @@ -0,0 +1,47 @@ +//! API handlers for the the [`proxy`](crate::web::api::v1::contexts::proxy) API +//! context. +use std::sync::Arc; + +use axum::extract::{Path, State}; +use axum::response::Response; + +use super::responses::png_image; +use crate::cache::image::manager::Error; +use crate::common::AppData; +use crate::ui::proxy::map_error_to_image; +use crate::web::api::v1::extractors::bearer_token::Extract; + +/// Get the remote image. It uses the cached image if available. +#[allow(clippy::unused_async)] +pub async fn get_proxy_image_handler( + State(app_data): State>, + Extract(maybe_bearer_token): Extract, + Path(url): Path, +) -> Response { + if maybe_bearer_token.is_none() { + return png_image(map_error_to_image(&Error::Unauthenticated)); + } + + let Ok(user_id) = app_data.auth.get_user_id_from_bearer_token(&maybe_bearer_token).await else { return png_image(map_error_to_image(&Error::Unauthenticated)) }; + + // code-review: Handling status codes in the frontend other tan OK is quite a pain. + // Return OK for now. + + // todo: it also work for other image types but we are always returning the + // same content type: `image/png`. If we only support PNG images we should + // change the documentation and return an error for other image types. + + // Get image URL from URL path parameter. + let image_url = urlencoding::decode(&url).unwrap_or_default().into_owned(); + + match app_data.proxy_service.get_image_by_url(&image_url, &user_id).await { + Ok(image_bytes) => { + // Returns the cached image. + png_image(image_bytes) + } + Err(e) => { + // Returns an error image. + png_image(map_error_to_image(&e)) + } + } +} diff --git a/src/web/api/v1/contexts/proxy/mod.rs b/src/web/api/v1/contexts/proxy/mod.rs index 29eb0879..c63397a4 100644 --- a/src/web/api/v1/contexts/proxy/mod.rs +++ b/src/web/api/v1/contexts/proxy/mod.rs @@ -41,3 +41,6 @@ //! --output mandelbrotset.jpg \ //! http://0.0.0.0:3000/v1/proxy/image/https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fcommons%2Fthumb%2F2%2F21%2FMandel_zoom_00_mandelbrot_set.jpg%2F1280px-Mandel_zoom_00_mandelbrot_set.jpg //! ``` +pub mod handlers; +pub mod responses; +pub mod routes; diff --git a/src/web/api/v1/contexts/proxy/responses.rs b/src/web/api/v1/contexts/proxy/responses.rs new file mode 100644 index 00000000..1ce9730c --- /dev/null +++ b/src/web/api/v1/contexts/proxy/responses.rs @@ -0,0 +1,8 @@ +use axum::response::{IntoResponse, Response}; +use bytes::Bytes; +use hyper::{header, StatusCode}; + +#[must_use] +pub fn png_image(bytes: Bytes) -> Response { + (StatusCode::OK, [(header::CONTENT_TYPE, "image/png")], bytes).into_response() +} diff --git a/src/web/api/v1/contexts/proxy/routes.rs b/src/web/api/v1/contexts/proxy/routes.rs new file mode 100644 index 00000000..e6bd7bef --- /dev/null +++ b/src/web/api/v1/contexts/proxy/routes.rs @@ -0,0 +1,15 @@ +//! API routes for the [`proxy`](crate::web::api::v1::contexts::proxy) API context. +//! +//! Refer to the [API endpoint documentation](crate::web::api::v1::contexts::proxy). +use std::sync::Arc; + +use axum::routing::get; +use axum::Router; + +use super::handlers::get_proxy_image_handler; +use crate::common::AppData; + +/// Routes for the [`about`](crate::web::api::v1::contexts::about) API context. +pub fn router(app_data: Arc) -> Router { + Router::new().route("/image/:url", get(get_proxy_image_handler).with_state(app_data)) +} diff --git a/src/web/api/v1/routes.rs b/src/web/api/v1/routes.rs index 2667bf1b..f012d68c 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, settings, tag, torrent}; +use super::contexts::{about, proxy, settings, tag, torrent}; use super::contexts::{category, user}; use crate::common::AppData; @@ -25,7 +25,8 @@ pub fn router(app_data: Arc) -> Router { .nest("/tags", tag::routes::router_for_multiple_resources(app_data.clone())) .nest("/settings", settings::routes::router(app_data.clone())) .nest("/torrent", torrent::routes::router_for_single_resources(app_data.clone())) - .nest("/torrents", torrent::routes::router_for_multiple_resources(app_data.clone())); + .nest("/torrents", torrent::routes::router_for_multiple_resources(app_data.clone())) + .nest("/proxy", proxy::routes::router(app_data.clone())); Router::new() .route("/", get(about_page_handler).with_state(app_data)) diff --git a/tests/e2e/contexts/mod.rs b/tests/e2e/contexts/mod.rs index fa791e5f..797781be 100644 --- a/tests/e2e/contexts/mod.rs +++ b/tests/e2e/contexts/mod.rs @@ -1,5 +1,6 @@ pub mod about; pub mod category; +pub mod proxy; pub mod root; pub mod settings; pub mod tag; diff --git a/tests/e2e/contexts/proxy/contract.rs b/tests/e2e/contexts/proxy/contract.rs new file mode 100644 index 00000000..46c8b8a9 --- /dev/null +++ b/tests/e2e/contexts/proxy/contract.rs @@ -0,0 +1,6 @@ +//! API contract for `proxy` context. + +mod with_axum_implementation { + + // todo +} diff --git a/tests/e2e/contexts/proxy/mod.rs b/tests/e2e/contexts/proxy/mod.rs new file mode 100644 index 00000000..2943dbb5 --- /dev/null +++ b/tests/e2e/contexts/proxy/mod.rs @@ -0,0 +1 @@ +pub mod contract;