Implement all API according to swagger spec #27

Merged
blek merged 16 commits from delete-upload-api-methods into 0.2-dev 2023-12-14 11:56:46 +01:00
5 changed files with 181 additions and 7 deletions
Showing only changes of commit c298a188bf - Show all commits

View File

@ -1,7 +1,7 @@
use warp::{reply::Reply, reject::Rejection, Filter}; use warp::{reply::Reply, reject::Rejection, Filter};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use self::files::{get_all::get_all_f, delete::delete_f}; use self::files::{get_all::get_all_f, delete::delete_f, upload::upload_f};
use super::state::SharedState; use super::state::SharedState;
@ -25,5 +25,6 @@ pub fn get_routes(state: SharedState) -> impl Filter<Extract = impl Reply, Error
.and(warp::path::end()) .and(warp::path::end())
.map(api_root) .map(api_root)
.or(get_all_f(state.clone())) .or(get_all_f(state.clone()))
.or(delete_f(state)) .or(delete_f(state.clone()))
.or(upload_f(state))
} }

View File

@ -16,6 +16,39 @@ fn check_api_enabled(state: &SharedState) -> Result<(), WithStatus<Json>> {
Ok(()) Ok(())
} }
fn is_api_pass(state: &SharedState) -> bool {
if let Some(keys) = state.config.api.apikeys.clone() {
keys.len() != 0
} else {
false
}
}
fn check_api_pass(state: &SharedState, key: String) -> Result<(), WithStatus<Json>> {
let mut valid = {
if let Some(keys) = state.config.api.apikeys.clone() {
keys.iter().find(|x| (**x) == key).is_some()
} else {
false
}
};
if key.len() == 0 {
valid = false
}
if valid {
Ok(())
} else {
Err(
warp::reply::with_status(
json(&ErrorMessage::new(Error::APIPasswordDenied)),
StatusCode::FORBIDDEN
)
)
}
}
fn function_disabled_err() -> WithStatus<Json> { fn function_disabled_err() -> WithStatus<Json> {
warp::reply::with_status( warp::reply::with_status(
json(&ErrorMessage::new(Error::APIFunctionDisabled)), json(&ErrorMessage::new(Error::APIFunctionDisabled)),
@ -25,3 +58,4 @@ fn function_disabled_err() -> WithStatus<Json> {
pub mod get_all; pub mod get_all;
pub mod delete; pub mod delete;
pub mod upload;

View File

@ -0,0 +1,137 @@
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
use warp::{reply::{Reply, with_status, json}, http::StatusCode, reject::Rejection, Filter, filters::multipart::FormData};
use crate::web::{state::SharedState, forms::FormElement, rejection::HttpReject, api::types::{ErrorMessage, Error}};
use super::{is_api_pass, check_api_pass};
#[derive(Serialize, Deserialize)]
struct UploadAPIMetadata {
sha512: String,
name: Option<String>,
pass: Option<String>
}
struct UploadAPIPayload {
file: Vec<u8>,
instance_pass: Option<String>,
metadata: UploadAPIMetadata
}
impl Default for UploadAPIPayload {
fn default() -> Self {
Self {
file: vec![],
instance_pass: None,
metadata: UploadAPIMetadata {
sha512: "".into(),
name: None,
pass: None
}
}
}
}
impl UploadAPIPayload {
pub fn from_form(data: HashMap<String, FormElement>) -> Option<UploadAPIPayload> {
let mut out = Self::default();
let file = data.get("file");
let instance_pass = data.get("instance_pass");
let metadata = data.get("metadata");
let mut fields_set = false;
// required fields
if let Some(file) = file {
if let Some(metadata) = metadata {
if let Some(metadata) = metadata.as_atr_or_none() {
out.file = file.data.clone();
if let Ok(metadata) = serde_json::from_str(&metadata) {
out.metadata = metadata;
}
fields_set = true;
}
}
}
// optional ones
if let Some(pass) = instance_pass {
if let Some(pass) = pass.as_atr_or_none() {
out.instance_pass = Some(pass);
}
}
if ! fields_set {
None
} else {
Some(out)
}
}
}
pub async fn upload(state: SharedState, data: FormData) -> Result<Box<dyn Reply>, Rejection> {
let data = FormElement::from_formdata(data)
.await;
if let Err(err) = data {
return Ok(
Box::new(
with_status(
json(
&ErrorMessage {
error: Error::APIError,
details: Some(
format!("Error while parsing payload: {err}")
)
}
),
StatusCode::BAD_REQUEST
)
)
)
}
let data = data.unwrap(); // this is guaranteed to be `Ok` at this point
let payload = UploadAPIPayload::from_form(data);
if let Some(payload) = payload {
if is_api_pass(&state) {
if let Err(res) = check_api_pass(
&state,
match payload.instance_pass {
Some(x) => x,
None => "".into()
}
) {
return Ok(Box::new(res))
}
}
}
Ok(
Box::new(
with_status(
json(&ErrorMessage {
error: Error::APIError,
details: Some("Request payload invalid".into())
}),
StatusCode::BAD_REQUEST
)
)
)
}
pub fn upload_f(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
warp::path!("api" / "files" / "upload")
.and(warp::post())
.map(move || state.clone())
.and(warp::multipart::form())
.and_then(upload)
}

View File

@ -4,7 +4,8 @@ use serde::{Serialize, Deserialize};
pub enum Error { pub enum Error {
APIDisabled, APIDisabled,
APIFunctionDisabled, APIFunctionDisabled,
APIError APIError,
APIPasswordDenied
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -19,7 +20,8 @@ impl ErrorMessage {
details: match error { details: match error {
Error::APIDisabled => Some("API is disabled by the administrator. Please contact them for further details".into()), Error::APIDisabled => Some("API is disabled by the administrator. Please contact them for further details".into()),
Error::APIFunctionDisabled => Some("This API function is disabled by the administrator. Please contact them for further details.".into()), Error::APIFunctionDisabled => Some("This API function is disabled by the administrator. Please contact them for further details.".into()),
Error::APIError => Some("An error has occured while executing the API request".into()) Error::APIError => Some("An error has occured while executing the API request".into()),
Error::APIPasswordDenied => Some("API password authorization has been denied.".into())
}, },
error, error,
} }

View File

@ -20,10 +20,10 @@ use state::SharedState;
pub fn routes(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { pub fn routes(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
static_dir!("static") static_dir!("static")
.or(curlapi::get_routes(state.clone())) .or(curlapi::get_routes(state.clone()))
.or(pages::get_routes(state.clone()))
.or(forms::get_routes(state.clone())) .or(forms::get_routes(state.clone()))
.or(api::get_routes(state.clone())) .or(api::get_routes(state.clone()))
.or(uploaded::get_uploaded(state)) .or(uploaded::get_uploaded(state.clone()))
.or(pages::get_routes(state))
} }
/* /*