diff --git a/README.md b/README.md index 9b66880..b7e7192 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,14 @@ To get set up: ## running & config -At runtime the environment variable `DATABASE_URL` must be set (e.g. `postgres://localhost`). -A folder named `files` needs to be created next to the application. +| environment variable | default value | +| -------------------- | --------------------- | +| DATABASE_URL | postresql://localhost | +| SERVER_URL | http://loalhost:8000 | +| FILES_DIR | ./files | +| UPLOAD_MAX_BYTES | 8388608 (8MiB) | +| BIND_ADDRESS | 0.0.0.0:8000 | Other things are not configurable yet. -- The application listens on port 8000 -- The server url is `http://localhost:8000/` -- The upload limit is 8MiB - The maximum filename length is 255 -- The uploaded files are stored in the `files` directory diff --git a/src/deleter.rs b/src/deleter.rs index 90042e9..bda13f0 100644 --- a/src/deleter.rs +++ b/src/deleter.rs @@ -3,7 +3,7 @@ use chrono::{prelude::*, Duration}; use futures::future::FutureExt; use sqlx::postgres::PgPool; -pub(crate) async fn delete_old_files(receiver: Receiver<()>, db: PgPool) { +pub(crate) async fn delete_old_files(receiver: Receiver<()>, db: PgPool, files_dir: PathBuf) { loop { wait_for_file_expiry(&receiver, &db).await; let now = Local::now().naive_local(); @@ -13,7 +13,8 @@ pub(crate) async fn delete_old_files(receiver: Receiver<()>, db: PgPool) { .await .unwrap(); for expired_file in expired_files { - let path = PathBuf::from(&format!("files/{}", expired_file.file_id)); + let mut path = files_dir.clone(); + path.push(&expired_file.file_id); if path.exists().await { log::info!("delete file {}", expired_file.file_id); fs::remove_file(&path).await.expect("could not delete file"); diff --git a/src/main.rs b/src/main.rs index c608c8d..3866fd4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,9 +30,11 @@ async fn upload( payload: Multipart, db: web::Data, sender: web::Data>, + config: web::Data, ) -> Result { let file_id = format!("{:x?}", rand::random::()); - let filename = PathBuf::from(format!("files/{}", file_id)); + let mut filename = config.files_dir.clone(); + filename.push(&file_id); let (original_name, valid_till, kind) = match multipart::parse_multipart(payload, &file_id, &filename).await { @@ -72,8 +74,10 @@ async fn upload( .finish()) } -async fn uploaded(id: web::Path) -> Result { - let upload_html = UPLOAD_HTML.replace("{id}", id.as_ref()); +async fn uploaded(id: web::Path, config: web::Data) -> Result { + let upload_html = UPLOAD_HTML + .replace("{id}", id.as_ref()) + .replace("{server}", &config.server_url); Ok(HttpResponse::Ok() .content_type("text/html") .body(upload_html)) @@ -83,6 +87,7 @@ async fn download( req: HttpRequest, id: web::Path, db: web::Data, + config: web::Data, ) -> Result { let row = sqlx::query!( "SELECT file_id, file_name, kind from files WHERE file_id = $1", @@ -91,7 +96,8 @@ async fn download( .fetch_one(db.as_ref()) .await .map_err(|_| error::ErrorNotFound("could not find file"))?; - let path: PathBuf = PathBuf::from(format!("files/{}", row.file_id)); + let mut path = config.files_dir.clone(); + path.push(&row.file_id); if row.kind == FileKind::TEXT.to_string() { let content = fs::read_to_string(path).await?; @@ -110,7 +116,7 @@ async fn download( async fn setup_db() -> PgPool { let pool = PgPool::builder() .max_size(5) - .build(&env::var("DATABASE_URL").expect("DATABASE_URL environement variable not set")) + .build(&env::var("DATABASE_URL").unwrap_or_else(|_| "postgresql://localhost".to_owned())) .await .expect("could not create db pool"); @@ -122,6 +128,12 @@ async fn setup_db() -> PgPool { pool } +#[derive(Clone)] +struct Config { + server_url: String, + files_dir: PathBuf, +} + #[actix_rt::main] async fn main() -> std::io::Result<()> { std::env::set_var("RUST_LOG", "warn,datatrash=info,actix_web=info"); @@ -131,25 +143,42 @@ async fn main() -> std::io::Result<()> { log::info!("omnomnom"); + let config = Config { + server_url: env::var("SERVER_URL").unwrap_or_else(|_| "http://localhost:8000".to_owned()), + files_dir: PathBuf::from(env::var("FILES_DIR").unwrap_or_else(|_| "./files".to_owned())), + }; + let (send, recv) = async_std::sync::channel::<()>(1); - task::spawn(deleter::delete_old_files(recv, pool.clone())); + task::spawn(deleter::delete_old_files( + recv, + pool.clone(), + config.files_dir.clone(), + )); let db = web::Data::new(pool); let send = web::Data::new(send); + let max_bytes: usize = env::var("UPLOAD_MAX_BYTES") + .ok() + .and_then(|variable| variable.parse().ok()) + .unwrap_or(8_388_608); + let bind_address = env::var("BIND_ADDRESS").unwrap_or_else(|_| "0.0.0.0:8000".to_owned()); - HttpServer::new(move || { - App::new() - .wrap(middleware::Logger::default()) - .app_data(db.clone()) - .app_data(send.clone()) - .app_data(Bytes::configure(|cfg| cfg.limit(8_388_608))) - .service(web::resource("/").route(web::get().to(index))) - .service(web::resource("/upload").route(web::post().to(upload))) - .service(web::resource("/upload/{id}").route(web::get().to(uploaded))) - .service(web::resource("/file/{id}").route(web::get().to(download))) - .service(Files::new("/static", "static").disable_content_disposition()) + HttpServer::new({ + move || { + App::new() + .wrap(middleware::Logger::default()) + .app_data(db.clone()) + .app_data(send.clone()) + .app_data(Bytes::configure(|cfg| cfg.limit(max_bytes))) + .data(config.clone()) + .service(web::resource("/").route(web::get().to(index))) + .service(web::resource("/upload").route(web::post().to(upload))) + .service(web::resource("/upload/{id}").route(web::get().to(uploaded))) + .service(web::resource("/file/{id}").route(web::get().to(download))) + .service(Files::new("/static", "static").disable_content_disposition()) + } }) - .bind("0.0.0.0:8000")? + .bind(bind_address)? .run() .await } diff --git a/template/upload.html b/template/upload.html index 5923e9f..bacfd6c 100644 --- a/template/upload.html +++ b/template/upload.html @@ -10,7 +10,7 @@

datatrash

Uploaded - + http://localhost:8000/files/{id}