file uploads

This commit is contained in:
2025-07-13 07:17:40 +02:00
parent 11105c074f
commit 548dd7e9ef
14 changed files with 451 additions and 13 deletions

27
backend/Cargo.lock generated
View File

@@ -116,6 +116,7 @@ dependencies = [
"matchit",
"memchr",
"mime",
"multer",
"percent-encoding",
"pin-project-lite",
"rustversion",
@@ -345,6 +346,15 @@ dependencies = [
"serde",
]
[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
[[package]]
name = "env_filter"
version = "0.1.3"
@@ -958,6 +968,23 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "multer"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b"
dependencies = [
"bytes",
"encoding_rs",
"futures-util",
"http",
"httparse",
"memchr",
"mime",
"spin",
"version_check",
]
[[package]]
name = "num-bigint-dig"
version = "0.8.4"

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
axum = { version = "0.8.4", features = ["query"] }
axum = { version = "0.8.4", features = ["multipart", "query"] }
dotenv = "0.15.0"
env_logger = "0.11.8"
log = "0.4.27"

View File

@@ -2,16 +2,33 @@ mod create_directory;
mod delete_directory;
mod get_warren_path;
mod list_warrens;
mod upload_files;
use axum::routing::{delete, get, post};
use axum::{
extract::DefaultBodyLimit,
routing::{delete, get, post},
};
use crate::server::Router;
pub(super) fn router() -> Router {
Router::new()
.route("/", get(list_warrens::route))
.route("/{warren_id}", get(get_warren_path::route))
.route("/{warren_id}/{*rest}", get(get_warren_path::route))
.route("/{warren_id}/{*rest}", post(create_directory::route))
.route("/{warren_id}/{*rest}", delete(delete_directory::route))
.route("/{warren_id}/get", get(get_warren_path::route))
.route("/{warren_id}/get/{*rest}", get(get_warren_path::route))
.route("/{warren_id}/create/{*rest}", post(create_directory::route))
.route(
"/{warren_id}/delete/{*rest}",
delete(delete_directory::route),
)
.route(
"/{warren_id}/upload",
// 536870912 bytes = 0.5GB
post(upload_files::route).route_layer(DefaultBodyLimit::max(536870912)),
)
.route(
"/{warren_id}/upload/{*rest}",
// 536870912 bytes = 0.5GB
post(upload_files::route).route_layer(DefaultBodyLimit::max(536870912)),
)
}

View File

@@ -0,0 +1,34 @@
use axum::extract::{Multipart, Path, State};
use serde::Deserialize;
use uuid::Uuid;
use crate::{Result, api::AppState, warrens::Warren};
#[derive(Deserialize)]
pub(super) struct UploadPath {
warren_id: Uuid,
rest: Option<String>,
}
pub(super) async fn route(
State(state): State<AppState>,
Path(UploadPath { warren_id, rest }): Path<UploadPath>,
mut multipart: Multipart,
) -> Result<()> {
let warren = Warren::get(state.pool(), &warren_id).await?;
while let Ok(Some(field)) = multipart.next_field().await {
if field.name().is_none_or(|name| name != "files") {
continue;
};
let file_name = field.file_name().map(str::to_owned).unwrap();
let data = field.bytes().await.unwrap();
warren
.upload(state.serve_dir(), rest.as_deref(), &file_name, &data)
.await?;
}
Ok(())
}

View File

@@ -1,9 +1,24 @@
use std::path::Path;
use tokio::fs;
use tokio::{fs, io::AsyncWriteExt};
use crate::Result;
pub async fn write_file<P>(path: P, data: &[u8]) -> Result<()>
where
P: AsRef<Path>,
{
let mut file = fs::OpenOptions::new()
.write(true)
.create(true)
.open(path)
.await?;
file.write_all(data).await?;
Ok(())
}
pub async fn delete_file<P>(path: P) -> Result<()>
where
P: AsRef<Path>,

View File

@@ -8,7 +8,9 @@ use uuid::Uuid;
use crate::{
Result,
fs::{DirectoryEntry, FileType, create_dir, delete_dir, delete_file, get_dir_entries},
fs::{
DirectoryEntry, FileType, create_dir, delete_dir, delete_file, get_dir_entries, write_file,
},
};
#[derive(Debug, Clone, Serialize, FromRow)]
@@ -64,6 +66,20 @@ impl Warren {
FileType::Directory => delete_dir(path).await,
}
}
pub async fn upload(
&self,
serve_path: &str,
rest_path: Option<&str>,
file_name: &str,
data: &[u8],
) -> Result<()> {
let rest = format!("{}/{file_name}", rest_path.unwrap_or(""));
let path = build_path(serve_path, &self.path, Some(&rest));
write_file(path, data).await
}
}
fn build_path(serve_path: &str, warren_path: &str, rest_path: Option<&str>) -> PathBuf {
@@ -72,7 +88,7 @@ fn build_path(serve_path: &str, warren_path: &str, rest_path: Option<&str>) -> P
final_path.push(warren_path.strip_prefix("/").unwrap_or(warren_path));
if let Some(ref rest) = rest_path {
final_path.push(rest);
final_path.push(rest.strip_prefix("/").unwrap_or(rest));
}
final_path