use std::{
fs::{File, create_dir_all, remove_dir_all, remove_file},
io::Write,
path::Path as FsPath,
};
use axum::{
Router,
extract::DefaultBodyLimit,
response::{IntoResponse, Redirect},
routing::get,
};
use tower_http::limit::RequestBodyLimitLayer;
use crate::{
error::ImageEndpointError,
extractors::{Folder, FolderAndFileName},
helpers::create_final_image,
image_app_options::ImageAppOptions,
multipart::ImageMultipart,
};
use crate::error::Result;
#[allow(clippy::needless_pass_by_value)]
pub fn image_app(
image_app_options: ImageAppOptions,
) -> std::result::Result<Router, ImageEndpointError> {
image_app_options.create_image_folders()?;
let state = image_app_options.image_app_state();
let api_route = image_app_options.api_route();
let cache_layer = image_app_options.cache_layer()?;
let serve_dir = image_app_options.serve_dir()?;
Ok(Router::new()
.route(
&api_route,
get(get_image).post(post_image).delete(delete_image),
)
.layer(cache_layer)
.nest_service(&image_app_options.img_route, serve_dir)
.layer(DefaultBodyLimit::disable())
.layer(RequestBodyLimitLayer::new(
image_app_options.request_body_limit,
))
.with_state(state))
}
async fn post_image(folder: Folder, image_multipart: ImageMultipart) -> Result<impl IntoResponse> {
create_dir_all(folder.original_subfolder())?;
let file_path = &folder.original_subfolder_file_path(&image_multipart.image_name);
if FsPath::exists(file_path) {
Err(ImageEndpointError::ImageAlreadyFound)
} else {
let mut file = File::create(file_path)?;
file.write_all(&image_multipart.image)?;
Ok(())
}
}
async fn get_image(folder_and_filename: FolderAndFileName) -> Result<impl IntoResponse> {
let original_filename = folder_and_filename.original_filename();
let file_exists = folder_and_filename.does_original_file_exist();
if file_exists && folder_and_filename.no_options_set() {
let redirect_to = folder_and_filename.original_redirect_path();
Ok(Redirect::temporary(&redirect_to))
} else if file_exists {
let hash = folder_and_filename.hash();
let filepath = folder_and_filename.modified_filepath(&hash);
if !FsPath::exists(&filepath) {
create_final_image(
&original_filename,
filepath,
folder_and_filename.get_image_options(),
)?;
}
let redirect_to = folder_and_filename.modified_redirect_path(&hash);
Ok(Redirect::temporary(&redirect_to))
} else {
Err(ImageEndpointError::ImageNotFound(original_filename))
}
}
async fn delete_image(folder_and_filename: FolderAndFileName) -> Result<impl IntoResponse> {
let hash = folder_and_filename.hash();
let modified_folder = folder_and_filename.modified_folder(&hash);
if modified_folder.exists() {
remove_dir_all(&modified_folder)?;
}
let original_filename = folder_and_filename.original_filename();
remove_file(&original_filename)?;
Ok("")
}