mirror of https://github.com/ChillFish8/lust.git
291 lines
8.9 KiB
Rust
291 lines
8.9 KiB
Rust
use base64::{decode, encode};
|
|
use gotham::handler::HandlerResult;
|
|
use gotham::hyper::http::StatusCode;
|
|
use gotham::hyper::{body, Body};
|
|
use gotham::state::{FromState, State};
|
|
use log::{debug, error};
|
|
|
|
use crate::cache::CACHE_STATE;
|
|
use crate::configure::StateConfig;
|
|
use crate::context::{FilesListPayload, FilterType, OrderBy};
|
|
use crate::image::{
|
|
delete_image,
|
|
get_image,
|
|
process_new_image,
|
|
ImageGet,
|
|
ImageRemove,
|
|
ImageUpload,
|
|
ImageUploaded,
|
|
};
|
|
use crate::response::{empty_response, image_response, json_response};
|
|
use crate::storage::StorageBackend;
|
|
use crate::traits::ImageStore;
|
|
use crate::PathExtractor;
|
|
|
|
macro_rules! from_body {
|
|
( $e:expr ) => {{
|
|
let res = body::to_bytes(Body::take_from(&mut $e)).await;
|
|
let bod = match res {
|
|
Ok(bod) => bod,
|
|
Err(e) => {
|
|
error!("failed to read data from body {:?}", &e);
|
|
return Ok((
|
|
$e,
|
|
json_response(
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
Some(json!({
|
|
"message": format!("encountered exception: {:?}", e)
|
|
})),
|
|
),
|
|
));
|
|
}
|
|
};
|
|
|
|
match serde_json::from_slice(bod.as_ref()) {
|
|
Ok(v) => v,
|
|
Err(e) => {
|
|
return Ok((
|
|
$e,
|
|
json_response(
|
|
StatusCode::UNPROCESSABLE_ENTITY,
|
|
Some(json!({
|
|
"message":
|
|
format!(
|
|
"failed to deserialize POST body due to the following error: {:?}",
|
|
e
|
|
)
|
|
})),
|
|
),
|
|
))
|
|
}
|
|
}
|
|
}};
|
|
}
|
|
|
|
/// Gets a given image from the storage backend with the given
|
|
/// preset and format if it does not already exist in cache.
|
|
///
|
|
/// This endpoint can return any of the following status codes:
|
|
///
|
|
/// 404:
|
|
/// The image does not exist, NOTE: This endpoint will **always**
|
|
/// return a 404 if an unexpected error was encountered rather than
|
|
/// raising an error to the requester, instead it will be logged in
|
|
/// the console.
|
|
///
|
|
/// 200:
|
|
/// The image was successfully fetched and sent as the response.
|
|
///
|
|
/// TODO:
|
|
/// Likely performance issues could become apparent at higher
|
|
/// concurrency due to the Mutex on the LRU cache, although this
|
|
/// is probably insignificant compared to the time spent on IO.
|
|
pub async fn get_file(mut state: State) -> HandlerResult {
|
|
let path_vars = PathExtractor::take_from(&mut state);
|
|
let params = ImageGet::take_from(&mut state);
|
|
let config = StateConfig::take_from(&mut state);
|
|
|
|
let file_id = path_vars.file_id;
|
|
let category = path_vars.category.unwrap_or_else(|| "default".to_string());
|
|
|
|
let format = params
|
|
.format
|
|
.unwrap_or_else(|| config.0.default_serving_format.clone());
|
|
|
|
let mut preset = params
|
|
.preset
|
|
.unwrap_or_else(|| config.0.default_serving_preset.clone());
|
|
|
|
if preset != "original" {
|
|
// We dont want to necessarily error if you give an invalid
|
|
// preset, but we dont want to attempt something that doesnt
|
|
// exist.
|
|
if !config.0.size_presets.contains_key(&preset) {
|
|
preset = "original".into();
|
|
}
|
|
}
|
|
|
|
let cache = CACHE_STATE.get().expect("not initialised");
|
|
let img = if let Some(cached) = cache.get(file_id, preset.clone(), format) {
|
|
debug!(
|
|
"using cached version of image for file_id: {}, preset: {}, format: {:?}",
|
|
file_id, &preset, format,
|
|
);
|
|
Some(cached)
|
|
} else {
|
|
debug!(
|
|
"using backend version of image for file_id: {}, preset: {}, format: {:?}",
|
|
file_id, &preset, format,
|
|
);
|
|
if let Some(data) = get_image(&mut state, file_id, preset.clone(), &category, format).await
|
|
{
|
|
cache.set(file_id, preset, format, data.clone());
|
|
Some(data)
|
|
} else {
|
|
None
|
|
}
|
|
};
|
|
|
|
match img {
|
|
None => Ok((state, empty_response(StatusCode::NOT_FOUND))),
|
|
Some(data) => {
|
|
if params.encode.unwrap_or(false) {
|
|
let encoded = encode(data.as_ref());
|
|
return Ok((
|
|
state,
|
|
json_response(
|
|
StatusCode::OK,
|
|
Some(json!({
|
|
"image": encoded,
|
|
})),
|
|
),
|
|
));
|
|
}
|
|
Ok((state, image_response(format, data)))
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Handles a POST request for adding a image to the store.
|
|
///
|
|
/// The image payload must be in JSON format and be base64 encoded in
|
|
/// the standard specification.
|
|
///
|
|
/// E.g.
|
|
/// ```json
|
|
/// {
|
|
/// "format": "png",
|
|
/// "data": "...data ensues..."
|
|
/// }
|
|
/// ```
|
|
pub async fn add_file(mut state: State) -> HandlerResult {
|
|
let upload: ImageUpload = from_body!(state);
|
|
|
|
let format = upload.format;
|
|
let data = match decode(upload.data) {
|
|
Ok(d) => d,
|
|
Err(_) => {
|
|
return Ok((
|
|
state,
|
|
json_response(
|
|
StatusCode::UNPROCESSABLE_ENTITY,
|
|
Some(json!({
|
|
"message": "data is not encoded in base64 format correctly",
|
|
})),
|
|
),
|
|
))
|
|
},
|
|
};
|
|
|
|
let category = upload.category.unwrap_or_else(|| "default".to_string());
|
|
|
|
let (file_id, formats) = match process_new_image(&mut state, &category, format, data).await {
|
|
Ok(v) => v,
|
|
Err(e) => {
|
|
return Ok((
|
|
state,
|
|
json_response(
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
Some(json!({
|
|
"message": format!("failed to process image: {:?}", e),
|
|
})),
|
|
),
|
|
));
|
|
},
|
|
};
|
|
|
|
let resp = ImageUploaded {
|
|
file_id,
|
|
formats,
|
|
category,
|
|
};
|
|
|
|
let resp = serde_json::to_value(resp).expect("failed to serialize uploaded stats");
|
|
|
|
Ok((state, json_response(StatusCode::OK, Some(resp))))
|
|
}
|
|
|
|
/// Handles removing a image from the store.
|
|
///
|
|
/// This removes the image from both the database backend and
|
|
/// the cache if it exists in there.
|
|
///
|
|
/// This only requires the UUID of the image no other information
|
|
/// is needed.
|
|
///
|
|
/// Note on semantics:
|
|
/// This endpoint does not check if the image exists or not,
|
|
/// it simply tries to remove it if it exists otherwise ignores it.
|
|
///
|
|
/// For that reason this will always return 200 if no exceptions
|
|
/// happened at the time.
|
|
///
|
|
/// This endpoint can return any of the following responses:
|
|
///
|
|
/// 500:
|
|
/// The server could not complete the request due to a unexpected
|
|
/// exception, this is typically only possible via the transaction
|
|
/// on the database backend failing.
|
|
///
|
|
/// 200:
|
|
/// The image has been removed successfully.
|
|
pub async fn remove_file(mut state: State) -> HandlerResult {
|
|
let params = ImageRemove::take_from(&mut state);
|
|
|
|
if let Err(e) = delete_image(&mut state, params.file_id).await {
|
|
return Ok((
|
|
state,
|
|
json_response(
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
Some(json!({
|
|
"message": format!(
|
|
"failed to delete image with id: {} due to the following exception: {:?}",
|
|
params.file_id,
|
|
e
|
|
)
|
|
})),
|
|
),
|
|
));
|
|
};
|
|
|
|
Ok((
|
|
state,
|
|
json_response(
|
|
StatusCode::OK,
|
|
Some(json!({
|
|
"message": "file deleted if exists",
|
|
"file_id": params.file_id.to_string()
|
|
})),
|
|
),
|
|
))
|
|
}
|
|
|
|
pub async fn list_files(mut state: State) -> HandlerResult {
|
|
let payload: FilesListPayload = from_body!(state);
|
|
let storage = StorageBackend::take_from(&mut state);
|
|
|
|
let filter = payload.filter.unwrap_or_else(|| FilterType::All);
|
|
let sort = payload.order.unwrap_or_else(|| OrderBy::CreationDate);
|
|
let page = payload.page.unwrap_or_else(|| 1usize);
|
|
|
|
let (status, payload) = match storage.list_entities(filter.clone(), sort, page).await {
|
|
Ok(results) => (
|
|
StatusCode::OK,
|
|
Some(json!({
|
|
"page": page,
|
|
"filtered_by": filter,
|
|
"ordered_by": sort,
|
|
"results": results,
|
|
})),
|
|
),
|
|
Err(e) => (
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
Some(json!({
|
|
"message": format!("failed to fetch results for page due to error: {:?}", e)
|
|
})),
|
|
),
|
|
};
|
|
|
|
Ok((state, json_response(status, payload)))
|
|
}
|