change: change get from json cache file to multipart

Signed-off-by: Pakin <pakin.t@forth.co.th>
This commit is contained in:
Pakin 2026-04-27 15:47:11 +07:00
parent d19dab7561
commit 5bb2a6c192
3 changed files with 187 additions and 24 deletions

51
Cargo.lock generated
View file

@ -247,9 +247,9 @@ dependencies = [
[[package]]
name = "axum"
version = "0.8.8"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
dependencies = [
"axum-core",
"bytes",
@ -297,6 +297,29 @@ dependencies = [
"tracing",
]
[[package]]
name = "axum-extra"
version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be44683b41ccb9ab2d23a5230015c9c3c55be97a25e4428366de8873103f7970"
dependencies = [
"axum",
"axum-core",
"bytes",
"fastrand",
"futures-core",
"futures-util",
"http",
"http-body",
"http-body-util",
"mime",
"multer",
"pin-project-lite",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "axum-macros"
version = "0.5.0"
@ -1667,6 +1690,23 @@ dependencies = [
"pxfm",
]
[[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 = "multimap"
version = "0.10.1"
@ -2614,6 +2654,12 @@ dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
@ -2695,6 +2741,7 @@ version = "0.1.0"
dependencies = [
"async-compression",
"axum",
"axum-extra",
"axum-macros",
"bb8",
"bb8-redis",

View file

@ -7,6 +7,7 @@ edition = "2024"
async-compression = { version = "0.4.39", features = ["tokio", "brotli"] }
axum = "0.8.7"
axum-macros = "0.5.0"
axum-extra = { version = "0.12.6", features = ["multipart"] }
bb8 = "0.9.1"
bb8-redis = "0.26.0"
brotli = "8.0.2"

View file

@ -8,6 +8,7 @@ use axum::{
response::{IntoResponse, Response},
routing::{get, post},
};
use axum_extra::extract::multipart::Multipart;
use axum_macros::debug_handler;
use bb8::{Pool, PooledConnection};
use bb8_redis::RedisConnectionManager;
@ -378,37 +379,148 @@ struct Signature {
#[debug_handler]
async fn commit_handler(
State(state): State<AppState>,
// request body
Json(payload): Json<CommitBody>,
// request body as multipart/form-data
mut payload: Multipart,
) -> impl IntoResponse {
let mut content = match fetch_content_from_redis(state.redis.clone(), &payload.patch_key).await
{
Ok(c) => c,
Err(e) => {
// Extract multipart fields
let mut path: Option<String> = None;
let mut signature_username: Option<String> = None;
let mut signature_email: Option<String> = None;
let mut message: Option<String> = None;
let mut file_bytes: Option<Vec<u8>> = None;
// Process each field in the multipart payload
while let Ok(Some(field)) = payload.next_field().await {
let name = field.name().unwrap_or("").to_string();
match name.as_str() {
"path" => {
path = Some(match field.text().await {
Ok(t) => t,
Err(e) => {
return (
axum::http::StatusCode::BAD_REQUEST,
Json(json!({"error": format!("Failed to read path field: {}", e)})),
);
}
});
}
"signature_username" => {
signature_username = Some(match field.text().await {
Ok(t) => t,
Err(e) => {
return (
axum::http::StatusCode::BAD_REQUEST,
Json(
json!({"error": format!("Failed to read signature_username field: {}", e)}),
),
);
}
});
}
"signature_email" => {
signature_email = Some(match field.text().await {
Ok(t) => t,
Err(e) => {
return (
axum::http::StatusCode::BAD_REQUEST,
Json(
json!({"error": format!("Failed to read signature_email field: {}", e)}),
),
);
}
});
}
"message" => {
message = Some(match field.text().await {
Ok(t) => t,
Err(e) => {
return (
axum::http::StatusCode::BAD_REQUEST,
Json(json!({"error": format!("Failed to read message field: {}", e)})),
);
}
});
}
"file" => {
file_bytes = Some(match field.bytes().await {
Ok(b) => b.to_vec(),
Err(e) => {
return (
axum::http::StatusCode::BAD_REQUEST,
Json(json!({"error": format!("Failed to read file field: {}", e)})),
);
}
});
}
_ => {
// Ignore unknown fields
}
}
}
// Validate required fields
let path = match path {
Some(p) => p,
None => {
return (
axum::http::StatusCode::BAD_REQUEST,
Json(json!({"error": e})),
Json(json!({"error": "Missing required field: path"})),
);
}
};
let is_patch_file = content.starts_with("patch");
// do apply patch first
if is_patch_file {
content = apply_patch_to_file(state.redis.clone(), &payload.path, &mut content).await;
}
let signature_username = match signature_username {
Some(su) => su,
None => {
return (
axum::http::StatusCode::BAD_REQUEST,
Json(json!({"error": "Missing required field: signature_username"})),
);
}
};
let signature_email = match signature_email {
Some(se) => se,
None => {
return (
axum::http::StatusCode::BAD_REQUEST,
Json(json!({"error": "Missing required field: signature_email"})),
);
}
};
let file_bytes = match file_bytes {
Some(fb) => fb,
None => {
return (
axum::http::StatusCode::BAD_REQUEST,
Json(json!({"error": "Missing required field: file"})),
);
}
};
// Create signature
let signature = Signature {
username: signature_username,
email: signature_email,
};
// Get branch name from config
let branch = state
.clone()
.get_config("GIT_REPO_BRANCH_NAME")
.map(|x| x.to_string())
.unwrap_or("master".to_string());
// Commit the file content directly from multipart upload
let commit_oid = match commit_file_content(
state.clone().repo,
&payload.path,
&content.as_bytes(),
payload.signature,
&payload.message.unwrap_or("update: from api".to_string()),
state
.clone()
.get_config("GIT_REPO_BRANCH_NAME")
.map(|x| x.to_string())
.unwrap_or("master".to_string()),
&path,
&file_bytes,
signature,
&message.unwrap_or("update: from api".to_string()),
branch,
)
.await
{
@ -425,7 +537,10 @@ async fn commit_handler(
let redis_pre_lock = state.redis.clone();
{
if let Ok(mut rl) = redis_pre_lock.get().await {
let _ = rl.rpush(format!("{}.history", payload.path), payload.patch_key);
let _ = rl.rpush(
format!("{}.history", path),
format!("commit-{}", commit_oid),
);
}
}