Add rollback api method
This commit is contained in:
Generated
+39
-1
@@ -69,6 +69,7 @@ dependencies = [
|
||||
"matchit",
|
||||
"memchr",
|
||||
"mime",
|
||||
"multer",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"serde_core",
|
||||
@@ -286,6 +287,15 @@ version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
|
||||
|
||||
[[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 = "equivalent"
|
||||
version = "1.0.2"
|
||||
@@ -551,7 +561,6 @@ dependencies = [
|
||||
"axum",
|
||||
"criterion",
|
||||
"futures-util",
|
||||
"http-body-util",
|
||||
"parking_lot",
|
||||
"rand",
|
||||
"tokio",
|
||||
@@ -615,6 +624,23 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[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-traits"
|
||||
version = "0.2.19"
|
||||
@@ -955,6 +981,12 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
@@ -1100,6 +1132,12 @@ version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
|
||||
+1
-2
@@ -4,13 +4,12 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
axum = { version = "0.8.9" }
|
||||
axum = { version = "0.8.9", features = ["multipart"] }
|
||||
tokio = { version = "1.53.1", features = ["full"] }
|
||||
tokio-util = { version = "0.7.19", features = ["io"] }
|
||||
rand = "0.10.2"
|
||||
tower-http = {version = "0.7.0", features = ["cors", "trace"]}
|
||||
parking_lot = "0.12.5"
|
||||
http-body-util = "0.1.3"
|
||||
futures-util = "0.3.32"
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
+55
-16
@@ -1,19 +1,20 @@
|
||||
use axum::body::Body;
|
||||
use axum::extract::{DefaultBodyLimit, Path, State};
|
||||
use axum::extract::{DefaultBodyLimit, Multipart, Path, State};
|
||||
use axum::http::Method;
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum::routing::post;
|
||||
use axum::{routing::get, Router};
|
||||
use futures_util::TryStreamExt;
|
||||
use http_body_util::BodyExt;
|
||||
use parking_lot::RwLock;
|
||||
use rand::distr::{Alphanumeric, SampleString};
|
||||
use rand::rng;
|
||||
use std::os::unix::prelude::MetadataExt;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio_util::io::StreamReader;
|
||||
use axum::http::header::CONTENT_TYPE;
|
||||
use tokio::fs::File;
|
||||
use tokio_util::io::{ReaderStream, StreamReader};
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
|
||||
#[tokio::main]
|
||||
@@ -26,6 +27,7 @@ async fn main() {
|
||||
|
||||
let app = Router::new()
|
||||
.route("/generate", get(generate_id))
|
||||
.route("/upload/stream/{id}", post(upload_file_stream))
|
||||
.route("/upload/{id}", post(upload_file))
|
||||
.route("/download/{id}", get(download_file))
|
||||
.route("/{id}/version", get(version))
|
||||
@@ -33,7 +35,6 @@ async fn main() {
|
||||
.layer(cors)
|
||||
.with_state(Arc::new(RwLock::new(ApiState {
|
||||
work_directory: get_dir(),
|
||||
temp_directory: PathBuf::from("/tmp"),
|
||||
})));
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:8089").await.unwrap();
|
||||
@@ -42,28 +43,27 @@ async fn main() {
|
||||
|
||||
struct ApiState {
|
||||
work_directory: PathBuf,
|
||||
temp_directory: PathBuf,
|
||||
}
|
||||
|
||||
async fn generate_id() -> String {
|
||||
Alphanumeric.sample_string(&mut rng(), 24).to_uppercase()
|
||||
}
|
||||
|
||||
async fn upload_file(
|
||||
async fn upload_file_stream(
|
||||
State(state): State<Arc<RwLock<ApiState>>>,
|
||||
Path(id): Path<String>,
|
||||
body: Body,
|
||||
) -> Result<impl IntoResponse, StatusCode> {
|
||||
if !validate_id(id.as_str()) {
|
||||
return Err(StatusCode::FORBIDDEN);
|
||||
return Err(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
let temp_path;
|
||||
let store_path;
|
||||
{
|
||||
let state = state.read();
|
||||
temp_path = state.temp_directory.join(id.as_str());
|
||||
store_path = state.work_directory.join(id.as_str());
|
||||
temp_path = state.work_directory.join(format!("{id}.tmp"));
|
||||
}
|
||||
|
||||
let mut temp_file = tokio::fs::OpenOptions::new()
|
||||
@@ -83,23 +83,62 @@ async fn upload_file(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn download_file(Path(id): Path<String>) -> Result<Vec<u8>, StatusCode> {
|
||||
async fn upload_file(
|
||||
Path(id): Path<String>,
|
||||
mut multipart: Multipart,
|
||||
) -> Result<impl IntoResponse, StatusCode> {
|
||||
if !validate_id(id.as_str()) {
|
||||
return Err(StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
let file = multipart.next_field().await;
|
||||
|
||||
if let Ok(Some(file)) = file {
|
||||
let data = file
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
let mut path = get_dir();
|
||||
path.push(id.clone());
|
||||
if !path.exists() {
|
||||
File::create(path.clone())
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
}
|
||||
|
||||
tokio::fs::write(path.clone(), data)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
let file = File::open(path).await.unwrap();
|
||||
Ok(file.metadata().await.unwrap().mtime().to_string())
|
||||
} else {
|
||||
Err(StatusCode::BAD_REQUEST)
|
||||
}
|
||||
}
|
||||
|
||||
async fn download_file(Path(id): Path<String>) -> Result<impl IntoResponse, StatusCode> {
|
||||
if !validate_id(id.as_str()) {
|
||||
return Err(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
let mut path = get_dir();
|
||||
path.push(id.clone());
|
||||
if let Ok(data) = tokio::fs::read(path).await {
|
||||
Ok(data)
|
||||
} else {
|
||||
Err(StatusCode::NOT_FOUND)
|
||||
}
|
||||
|
||||
let file = tokio::fs::File::open(path).await.map_err(|_| StatusCode::NOT_FOUND)?;
|
||||
let stream = ReaderStream::new(file);
|
||||
let body = Body::from_stream(stream);
|
||||
let response = Response::builder()
|
||||
.header(CONTENT_TYPE, "application/octet-stream") // Or detect mime type
|
||||
.status(StatusCode::OK).body(body)
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
async fn version(Path(id): Path<String>) -> Result<impl IntoResponse, StatusCode> {
|
||||
if !validate_id(id.as_str()) {
|
||||
return Err(StatusCode::BAD_REQUEST);
|
||||
return Err(StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
let dir = get_dir().join(id);
|
||||
|
||||
Reference in New Issue
Block a user