Skip to content

Commit

Permalink
* Add the endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
lloydmeta committed Oct 30, 2024
1 parent 784013c commit 61b3a56
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 31 deletions.
92 changes: 92 additions & 0 deletions server/src/api/responses.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,98 @@
use serde::*;

use crate::infra::image_manipulation;

#[derive(Serialize)]
pub struct Standard {
pub message: String,
}

#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
pub struct MetadataResponse {
pub source: Source,
pub operations: Vec<Operation>,
}

impl MetadataResponse {
pub fn build(url: &str, ops: &image_manipulation::Operations) -> Self {
let operations = ops
.0
.iter()
.map(|op| match *op {
image_manipulation::Operation::Resize { width, height } => Operation {
r#type: "resize".to_string(),
width: Some(width),
height: Some(height),
},
image_manipulation::Operation::FlipHorizontally => Operation {
r#type: "flip_horizontally".to_string(),
width: None,
height: None,
},
image_manipulation::Operation::FlipVertically => Operation {
r#type: "flip_vertically".to_string(),
width: None,
height: None,
},
})
.collect();

MetadataResponse {
source: Source {
url: url.to_string(),
},
operations,
}
}
}

#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
pub struct Source {
pub url: String,
}

#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
pub struct Operation {
pub r#type: String,
pub width: Option<u32>,
pub height: Option<u32>,
}

#[cfg(test)]
mod tests {
use crate::infra::image_caching::ImageResize;

use super::*;

#[test]
fn test_metadata_response_build() {
let domain = image_manipulation::Operations::build(&Some(ImageResize {
target_width: -100,
target_height: -300,
}));
let result = MetadataResponse::build("http://beachape.com/images/lol.png", &domain);
let expected = MetadataResponse {
source: Source {
url: "http://beachape.com/images/lol.png".to_string(),
},
operations: vec![
Operation {
r#type: "resize".to_string(),
width: Some(100),
height: Some(300),
},
Operation {
r#type: "flip_horizontally".to_string(),
width: None,
height: None,
},
Operation {
r#type: "flip_vertically".to_string(),
width: None,
height: None,
},
],
};
assert_eq!(expected, result)
}
}
46 changes: 30 additions & 16 deletions server/src/api/routing/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ use responses::Standard;
use tower_http::catch_panic::CatchPanicLayer;

use crate::api::requests::{ImageResizePathParam, Signature};
use crate::api::responses;
use crate::api::responses::{self, MetadataResponse};
use crate::infra::components::AppComponents;
use crate::infra::config::AuthenticationSettings;
use crate::infra::errors::AppError;
use crate::infra::image_caching::{
ImageCacher, ImageFetchRequest, ImageFetchedCacheRequest, ImageResizeRequest,
ImageResizedCacheRequest,
};
use crate::infra::image_manipulation::{Operations, OperationsRunner, SingletonOperationsRunner};

use miniaturs_shared::signature::{ensure_signature_is_valid_for_path_and_query, SignatureError};

Expand All @@ -39,6 +40,7 @@ pub fn create_router(app_components: AppComponents) -> Router {
.route("/", get(root))
.route("/health", get(health_check))
.route("/:signature/:resized_image/*image_url", get(resize))
.route("/:signature/meta/:resized_image/*image_url", get(metadata))
.fallback(handle_404)
.layer(CatchPanicLayer::custom(handle_panic))
.with_state(app_components)
Expand All @@ -60,10 +62,11 @@ async fn resize(
&uri,
signature,
)?;
let operations = Operations::build(&Some(resized_image.into()));
let processed_image_request = {
ImageResizeRequest {
requested_image_url: image_url.clone(),
resize_target: resized_image.into(),
operations,
}
};
let maybe_cached_resized_image = app_components
Expand Down Expand Up @@ -137,23 +140,16 @@ async fn resize(
let format = reader_with_format
.format()
.ok_or(AppError::UnableToDetermineFormat)?;
let mut dynamic_image = reader_with_format.decode()?;

dynamic_image = dynamic_image.resize(
resized_image.target_width as u32,
resized_image.target_height as u32,
image::imageops::FilterType::Gaussian,
);

if resized_image.target_width < 0 {
dynamic_image = dynamic_image.fliph();
}
if resized_image.target_height < 0 {
dynamic_image = dynamic_image.flipv();
}
let image = SingletonOperationsRunner
.run(
reader_with_format.decode()?,
&processed_image_request.operations,
)
.await;

let mut cursor = Cursor::new(Vec::new());
dynamic_image.write_to(&mut cursor, format)?;
image.write_to(&mut cursor, format)?;
let written_bytes = cursor.into_inner();

let cache_image_req = ImageResizedCacheRequest {
Expand Down Expand Up @@ -181,6 +177,24 @@ async fn resize(
}
}

async fn metadata(
State(app_components): State<AppComponents>,
uri: Uri,
Path((signature, resized_image, image_url)): Path<(Signature, ImageResizePathParam, String)>,
) -> Result<Response, AppError> {
ensure_signature_is_valid(
&app_components.config.authentication_settings,
&uri,
signature,
)?;

let operations = Operations::build(&Some(resized_image.into()));
let metadata = MetadataResponse::build(&image_url, &operations);
let mut response_headers = HeaderMap::new();
response_headers.insert(CACHE_CONTROL, CACHE_CONTROL_HEADER_VALUE);
Ok((StatusCode::OK, response_headers, Json(metadata)).into_response())
}

/// Example on how to return status codes and data from an Axum function
async fn health_check() -> (StatusCode, Json<Standard>) {
let health = true;
Expand Down
25 changes: 13 additions & 12 deletions server/src/infra/image_caching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ use sha256;

use crate::api::requests::ImageResizePathParam;

use super::image_manipulation::Operations;

#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)]
pub struct ImageResizeRequest {
pub requested_image_url: String,
pub resize_target: ImageResize,
pub operations: Operations,
}

#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Copy)]
Expand Down Expand Up @@ -274,10 +276,10 @@ mod tests {
fn test_cache_key() -> TestResult {
let req = ImageResizeRequest {
requested_image_url: "https://beachape.com/images/something.png".to_string(),
resize_target: ImageResize {
operations: Operations::build(&Some(ImageResize {
target_width: 100,
target_height: 100,
},
})),
};
let key = req.cache_key();
assert!(key.0.len() < 1024);
Expand All @@ -289,10 +291,10 @@ mod tests {
let req = ImageResizedCacheRequest {
request: ImageResizeRequest {
requested_image_url: "https://beachape.com/images/something.png".to_string(),
resize_target: ImageResize {
operations: Operations::build(&Some(ImageResize {
target_width: 100,
target_height: 200,
},
})),
},
content_type: "image/png".to_string(),
};
Expand All @@ -317,10 +319,10 @@ mod tests {
let req = ImageResizeRequest {
requested_image_url: "https://beachape.com/images/something_that_does_not_exist.png"
.to_string(),
resize_target: ImageResize {
operations: Operations::build(&Some(ImageResize {
target_width: 100,
target_height: 100,
},
})),
};
let retrieved = s3_image_cacher.get(&req).await;
assert!(retrieved.is_none());
Expand All @@ -338,10 +340,10 @@ mod tests {

let req = ImageResizeRequest {
requested_image_url: "https://beachape.com/images/something.png".to_string(),
resize_target: ImageResize {
operations: Operations::build(&Some(ImageResize {
target_width: 100,
target_height: 100,
},
})),
};
let content = b"testcontent";
let image_set_req = ImageResizedCacheRequest {
Expand All @@ -368,11 +370,10 @@ mod tests {
};
let req = ImageResizeRequest {
requested_image_url: "https://beachape.com/images/something_else.png".to_string(),

resize_target: ImageResize {
operations: Operations::build(&Some(ImageResize {
target_width: 300,
target_height: 500,
},
})),
};
let content = b"testcontent";
let image_set_req = ImageResizedCacheRequest {
Expand Down
4 changes: 2 additions & 2 deletions server/src/infra/image_manipulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ use serde::{Deserialize, Serialize};

use super::image_caching::ImageResize;

#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Copy)]
pub enum Operation {
Resize { width: u32, height: u32 },
FlipHorizontally,
FlipVertically,
}

#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
pub struct Operations(pub Vec<Operation>);

impl Operations {
Expand Down
Loading

0 comments on commit 61b3a56

Please sign in to comment.