chore: added image compression and metadata stripping 88ed124c
Steve · 2026-04-04 14:22 3 file(s) · +26 −3
Cargo.lock +1 −0
652 652
 "axum",
653 653
 "base64",
654 654
 "dotenvy",
655 +
 "image",
655 656
 "nanoid",
656 657
 "rand 0.8.5",
657 658
 "reqwest 0.12.28",
apps/cellar/Cargo.toml +1 −0
25 25
askama_web = { version = "0.15", features = ["axum-0.8"] }
26 26
reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false }
27 27
base64 = "0.22"
28 +
image = "0.25"
apps/cellar/src/server.rs +24 −3
1 1
use askama::Template;
2 +
use image::ImageDecoder;
2 3
use askama_web::WebTemplate;
3 4
use axum::{
4 5
    extract::{DefaultBodyLimit, Multipart, Path, Query, State},
424 425
    }
425 426
}
426 427
428 +
// --- Image processing ---
429 +
430 +
fn process_image(data: &[u8]) -> Result<Vec<u8>, String> {
431 +
    let reader = image::ImageReader::new(std::io::Cursor::new(data))
432 +
        .with_guessed_format()
433 +
        .map_err(|e| format!("Failed to read image: {}", e))?;
434 +
    let mut decoder = reader
435 +
        .into_decoder()
436 +
        .map_err(|e| format!("Failed to create decoder: {}", e))?;
437 +
    let orientation = decoder.orientation().unwrap_or(image::metadata::Orientation::NoTransforms);
438 +
    let mut img = image::DynamicImage::from_decoder(decoder)
439 +
        .map_err(|e| format!("Failed to decode image: {}", e))?;
440 +
    img.apply_orientation(orientation);
441 +
    let mut output = Vec::new();
442 +
    let encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut output, 75);
443 +
    img.write_with_encoder(encoder)
444 +
        .map_err(|e| format!("JPEG encoding failed: {}", e))?;
445 +
    Ok(output)
446 +
}
447 +
427 448
// --- Multipart parsing ---
428 449
429 450
struct WineFormData {
459 480
        let field_name = field.name().unwrap_or("").to_string();
460 481
        match field_name.as_str() {
461 482
            "image" => {
462 -
                let content_type = field.content_type().unwrap_or("application/octet-stream").to_string();
463 483
                let bytes = field.bytes().await.map_err(|e| format!("Failed to read image: {}", e))?;
464 484
                if !bytes.is_empty() {
465 -
                    image = Some(bytes.to_vec());
466 -
                    image_mime = Some(content_type);
485 +
                    let processed = process_image(&bytes)?;
486 +
                    image = Some(processed);
487 +
                    image_mime = Some("image/jpeg".to_string());
467 488
                }
468 489
            }
469 490
            "name" => name = field.text().await.unwrap_or_default(),