Step 1: Add image processing dependency
Update Cargo.toml:
tokio = { version = "1.44.1", features = ["fs", "io-util"] }
uuid = { version = "1.16.0", features = ["v4"] }
image = { version = "0.25.5", default-features = false, features = ["jpeg", "png", "webp", "gif"] }
Step 2: Create reusable compression helper
In common.rs, add compress_uploaded_image:
Input: raw bytes + filename + max width/height + quality
Behavior:
decode image
resize if larger than target
if alpha channel -> save PNG
else -> save JPEG with quality
keep SVG/GIF as original
Use signature like:
pub fn compress_uploaded_image(
raw: &[u8],
original_filename: &str,
max_width: u32,
max_height: u32,
jpeg_quality: u8,
) -> Result<(Vec<u8>, &'static str)>
full fun code
pub fn compress_uploaded_image(
raw: &[u8],
original_filename: &str,
max_width: u32,
max_height: u32,
jpeg_quality: u8,
) -> Result<(Vec<u8>, &'static str)> {
let original_ext = sanitize_ext(original_filename);
// Keep formats that are not handled by raster re-encode.
if original_ext == ".svg" || original_ext == ".gif" {
return Ok((raw.to_vec(), original_ext));
}
let decoded = match image::load_from_memory(raw) {
Ok(img) => img,
Err(_) => {
// Keep original if decode fails instead of breaking upload.
return Ok((raw.to_vec(), original_ext));
}
};
let (w, h) = decoded.dimensions();
let resized = if w > max_width || h > max_height {
decoded.resize(max_width, max_height, FilterType::Lanczos3)
} else {
decoded
};
let has_alpha = matches!(
resized.color(),
ColorType::La8 | ColorType::La16 | ColorType::Rgba8 | ColorType::Rgba16 | ColorType::Rgba32F
);
let mut out = Vec::new();
if has_alpha {
let rgba = resized.to_rgba8();
let (rw, rh) = rgba.dimensions();
PngEncoder::new(&mut out)
.write_image(rgba.as_raw(), rw, rh, ColorType::Rgba8.into())
.map_err(error::ErrorInternalServerError)?;
Ok((out, ".png"))
} else {
let rgb = resized.to_rgb8();
JpegEncoder::new_with_quality(&mut out, jpeg_quality)
.encode_image(&image::DynamicImage::ImageRgb8(rgb))
.map_err(error::ErrorInternalServerError)?;
Ok((out, ".jpg"))
}
Step 3: Apply in Brand Model upload handler
In brand_model.rs:
use super::common::{compress_uploaded_image, ensure_login_user_id, ApiResponse};
Inside upload_brand_model_images_handler, read multipart into memory, compress, then write compressed bytes:
let mut raw = Vec::new();
while let Some(chunk) = field.next().await {
let data = chunk.map_err(error::ErrorBadRequest)?;
raw.extend_from_slice(&data);
}
if raw.is_empty() {
continue;
}
let (compressed, ext) = compress_uploaded_image(&raw, &file_name, 1280, 1280, 76)?;
let stored_name = format!(
"vehicle_image_{}_{}{}",
chrono::Utc::now().format("%Y%m%d%H%M%S"),
Uuid::new_v4().simple(),
ext
);
let full_path = format!("uploads/brand_models/{}", stored_name);
let mut file = File::create(&full_path)
.await
.map_err(error::ErrorInternalServerError)?;
file.write_all(&compressed)
.await
.map_err(error::ErrorInternalServerError)?;
saved_files.push(stored_name);
if saved_files.len() >= 3 {
break;
}
full function code
pub async fn upload_brand_model_images_handler(
mut payload: Multipart,
session: Session,
) -> Result<HttpResponse> {
ensure_login_user_id(&session)?;
let mut saved_files: Vec<String> = Vec::new();
while let Some(item) = payload.next().await {
let mut field = item.map_err(error::ErrorBadRequest)?;
let file_name = field
.content_disposition()
.and_then(|cd| cd.get_filename())
.unwrap_or("upload.bin")
.to_string();
let mut raw = Vec::new();
while let Some(chunk) = field.next().await {
let data = chunk.map_err(error::ErrorBadRequest)?;
raw.extend_from_slice(&data);
}
if raw.is_empty() {
continue;
}
let (compressed, ext) = compress_uploaded_image(&raw, &file_name, 1280, 1280, 76)?;
let stored_name = format!(
"vehicle_image_{}_{}{}",
chrono::Utc::now().format("%Y%m%d%H%M%S"),
Uuid::new_v4().simple(),
ext
);
let full_path = format!("uploads/brand_models/{}", stored_name);
let mut file = File::create(&full_path)
.await
.map_err(error::ErrorInternalServerError)?;
file.write_all(&compressed)
.await
.map_err(error::ErrorInternalServerError)?;
saved_files.push(stored_name);
if saved_files.len() >= 3 {
break;
}
}
let response = ApiResponse {
message: "Images uploaded".to_string(),
data: saved_files,
};
Ok(HttpResponse::Ok().json(response))
}
Step 4: Apply in Vehicle partner image upload handler
In vehicle.rs:
use super::common::{compress_uploaded_image, ensure_login_user_id, ApiResponse};
In upload_vehicle_partner_image_handler:
let mut raw = Vec::new();
while let Some(chunk) = field.next().await {
let data = chunk.map_err(error::ErrorBadRequest)?;
raw.extend_from_slice(&data);
}
if raw.is_empty() {
continue;
}
let (compressed, ext) = compress_uploaded_image(&raw, &file_name, 1280, 1280, 76)?;
let timestamp = chrono::Utc::now().format("%Y%m%d%H%M%S");
let rand = Uuid::new_v4().simple().to_string();
let stored_name = format!("vehicle_image_{}_{}{}", timestamp, rand, ext);
let full_path = format!("uploads/brand_models/{}", stored_name);
let mut file = File::create(&full_path)
.await
.map_err(error::ErrorInternalServerError)?;
file.write_all(&compressed)
.await
.map_err(error::ErrorInternalServerError)?;
partner_image = Some(stored_name);
full function
pub async fn upload_vehicle_partner_image_handler(
mut payload: Multipart,
session: Session,
) -> Result<HttpResponse> {
ensure_login_user_id(&session)?;
let mut partner_image: Option<String> = None;
while let Some(item) = payload.next().await {
let mut field = item.map_err(error::ErrorBadRequest)?;
let field_name = field
.content_disposition()
.and_then(|cd| cd.get_name())
.unwrap_or("")
.to_string();
if field_name != "partner_image" {
continue;
}
let file_name = field
.content_disposition()
.and_then(|cd| cd.get_filename())
.unwrap_or("upload.bin")
.to_string();
let mut raw = Vec::new();
while let Some(chunk) = field.next().await {
let data = chunk.map_err(error::ErrorBadRequest)?;
raw.extend_from_slice(&data);
}
if raw.is_empty() {
continue;
}
let (compressed, ext) = compress_uploaded_image(&raw, &file_name, 1280, 1280, 76)?;
let timestamp = chrono::Utc::now().format("%Y%m%d%H%M%S");
let rand = Uuid::new_v4().simple().to_string();
let stored_name = format!("vehicle_image_{}_{}{}", timestamp, rand, ext);
// Store partner image with admin vehicle images in same folder.
let full_path = format!("uploads/brand_models/{}", stored_name);
let mut file = File::create(&full_path)
.await
.map_err(error::ErrorInternalServerError)?;
file.write_all(&compressed)
.await
.map_err(error::ErrorInternalServerError)?;
partner_image = Some(stored_name);
break;
}
#[derive(serde::Serialize)]
struct UploadedPartnerImage {
partner_image: Option<String>,
}
let response = ApiResponse {
message: "Partner vehicle image uploaded".to_string(),
data: UploadedPartnerImage { partner_image },
};
Ok(HttpResponse::Ok().json(response))
}
Step 5: Why this helps
Smaller files on disk
Faster image serving from /uploads
Better frontend rendering speed on list/gallery pages
Consistent optimized size for high-volume image CRUD
Top comments (0)