file upload functionality

This commit is contained in:
blek 2023-10-01 18:40:37 +10:00
parent aae3ccfc2b
commit d31e3429f1
Signed by: blek
GPG Key ID: 14546221E3595D0C
8 changed files with 43 additions and 20 deletions

View File

@ -8,6 +8,7 @@ services:
bfile: bfile:
volumes: volumes:
- './filed:/opt/code' - './filed:/opt/code'
- './volatile/files:/opt/user_uploads'
caddy: caddy:
image: caddy:alpine image: caddy:alpine
volumes: volumes:

View File

@ -8,5 +8,5 @@ use crate::env::Env;
pub fn redis_conn(env: Env) -> Result<Client, RedisError> { pub fn redis_conn(env: Env) -> Result<Client, RedisError> {
log::info!("Connecting to redis DB on {}", env.redis.host); log::info!("Connecting to redis DB on {}", env.redis.host);
redis::Client::open(format!("redis://{}:{}/", env.redis.host, env.redis.port)) redis::Client::open(format!("redis://:{}@{}:{}/", env.redis.pass, env.redis.host, env.redis.port))
} }

View File

@ -7,7 +7,8 @@ use crate::env::Env;
use super::File; use super::File;
pub struct FileFinder { #[derive(Debug, Clone)]
pub struct FileManager {
conn: Client, conn: Client,
env: Env env: Env
} }
@ -17,9 +18,9 @@ pub enum LookupKind {
ByHash ByHash
} }
impl FileFinder { impl FileManager {
pub fn new(conn: Client, env: Env) -> FileFinder { pub fn new(conn: Client, env: Env) -> FileManager {
FileFinder { conn, env } FileManager { conn, env }
} }
fn find(self: &Self, key: String) -> Result<Option<File>, Box<dyn Error>> { fn find(self: &Self, key: String) -> Result<Option<File>, Box<dyn Error>> {
let mut conn = self.conn.get_connection()?; let mut conn = self.conn.get_connection()?;

View File

@ -1,4 +1,3 @@
// this whole crate is just a boilerplate
#![allow(unused)] #![allow(unused)]
use std::{sync::Arc, error::Error, ops::Add}; use std::{sync::Arc, error::Error, ops::Add};

View File

@ -3,7 +3,7 @@
forms.rs - All the forms forms.rs - All the forms
*/ */
use std::collections::HashMap; use std::{collections::HashMap, string::FromUtf8Error};
use askama::Template; use askama::Template;
use warp::{Filter, reply::Reply, reject::Rejection, filters::multipart::FormData, http::StatusCode}; use warp::{Filter, reply::Reply, reject::Rejection, filters::multipart::FormData, http::StatusCode};
@ -11,7 +11,7 @@ use futures_util::TryStreamExt;
use bytes::BufMut; use bytes::BufMut;
use serde::Serialize; use serde::Serialize;
use crate::files::File; use crate::files::{File, lookup::LookupKind};
use super::{state::SharedState, pages::BadActionReq, rejection::HttpReject}; use super::{state::SharedState, pages::BadActionReq, rejection::HttpReject};
@ -20,8 +20,13 @@ struct FormElement {
data: Vec<u8>, data: Vec<u8>,
mime: String mime: String
} }
impl FormElement {
pub fn as_str_or_reject(self: &Self) -> Result<String, Rejection> {
Ok(String::from_utf8(self.data.clone()).map_err(|err| warp::reject::custom(HttpReject::FromUtf8Error(err)))?)
}
}
pub async fn upload(form: FormData, _state: SharedState) -> Result<Box<dyn Reply>, Rejection> { pub async fn upload(form: FormData, state: SharedState) -> Result<Box<dyn Reply>, Rejection> {
let params: HashMap<String, FormElement> = form.and_then(|mut field| async move { let params: HashMap<String, FormElement> = form.and_then(|mut field| async move {
let mut bytes: Vec<u8> = vec![]; let mut bytes: Vec<u8> = vec![];
@ -60,24 +65,37 @@ pub async fn upload(form: FormData, _state: SharedState) -> Result<Box<dyn Reply
let delmode = params.get("delmode").unwrap(); let delmode = params.get("delmode").unwrap();
let named = params.get("named"); let named = params.get("named");
let filename = params.get("filename").unwrap(); let filename = params.get("filename").unwrap();
let mut is_named = named.is_none();
if named.is_some() {
is_named = named.unwrap().as_str_or_reject()? == "on";
}
let file = File::create( let file = File::create(
data.data.clone(), data.data.clone(),
data.mime.clone(), data.mime.clone(),
match named { match named {
Some(named) => { Some(named) => {
if String::from_utf8(named.data.clone()) if named.as_str_or_reject()?
.map_err(|err| warp::reject::custom(HttpReject::FromUtf8Error(err)))?
.to_string() == "on" { .to_string() == "on" {
Some(String::from_utf8(filename.data.clone()).map_err(|err| warp::reject::custom(HttpReject::FromUtf8Error(err)))?) Some(filename.as_str_or_reject()?)
} else { } else {
None None
} }
}, },
None => None None => None
}, },
_state.env.clone() state.env.clone()
).await; ).await
.map_err(|err| warp::reject::custom(HttpReject::StringError(err.to_string())))?;
state.file_mgr.save(&file, {
if is_named {
LookupKind::ByName
} else {
LookupKind::ByHash
}
}).map_err(|err| warp::reject::custom(HttpReject::StringError(err.to_string())))?;
Ok(Box::new(warp::reply::json(&params))) Ok(Box::new(warp::reply::json(&params)))
} }

View File

@ -6,7 +6,7 @@ use std::env::current_dir;
use warp::{Filter, reply::Reply, reject::Rejection}; use warp::{Filter, reply::Reply, reject::Rejection};
use crate::env::Env; use crate::{env::Env, files::lookup::FileManager};
mod pages; mod pages;
mod forms; mod forms;
@ -31,9 +31,11 @@ pub async fn serve(env: Env) {
log::info!("Listening on {}", env.listen.to_string()); log::info!("Listening on {}", env.listen.to_string());
let redis_cli = crate::db::redis_conn(env.clone()).unwrap();
let state = SharedState { let state = SharedState {
redis_cli: crate::db::redis_conn(env.clone()).unwrap(), redis_cli: redis_cli.clone(),
env: env.clone() env: env.clone(),
file_mgr: FileManager::new(redis_cli, env.clone())
}; };
warp::serve(routes(state)).run(env.listen).await; warp::serve(routes(state)).run(env.listen).await;

View File

@ -5,6 +5,7 @@ use std::string::FromUtf8Error;
pub enum HttpReject { pub enum HttpReject {
WarpError(warp::Error), WarpError(warp::Error),
AskamaError(askama::Error), AskamaError(askama::Error),
FromUtf8Error(FromUtf8Error) FromUtf8Error(FromUtf8Error),
StringError(String)
} }
impl warp::reject::Reject for HttpReject {} impl warp::reject::Reject for HttpReject {}

View File

@ -1,10 +1,11 @@
use redis::Client; use redis::Client;
use crate::env::Env; use crate::{env::Env, files::lookup::FileManager};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SharedState { pub struct SharedState {
pub redis_cli: Client, pub redis_cli: Client,
pub env: Env pub env: Env,
pub file_mgr: FileManager
} }