use axum::{extract::Multipart, http::StatusCode, Json}; use serde::Serialize; use uuid::Uuid; use crate::middleware::auth::AuthUser; /// Response returned after a chat image upload succeeds. #[derive(Serialize)] pub struct UploadResponse { pub url: String, } /// Accept a multipart chat image upload and store it under `uploads/chat-images`. pub async fn upload_chat_image( _auth: AuthUser, mut multipart: Multipart, ) -> Result, (StatusCode, String)> { while let Some(field) = multipart .next_field() .await .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))? { let content_type = field.content_type().unwrap_or("").to_string(); let ext = match content_type.as_str() { "image/png" => "png", "image/jpeg" => "jpg", "image/gif" => "gif", "image/webp" => "webp", _ => { return Err(( StatusCode::BAD_REQUEST, "Only PNG, JPG, GIF, WebP images are allowed".into(), )) } }; let data = field .bytes() .await .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?; if data.len() > 5 * 1024 * 1024 { return Err((StatusCode::BAD_REQUEST, "Image must be under 5MB".into())); } let dir = "uploads/chat-images"; tokio::fs::create_dir_all(dir) .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; let filename = format!("{}.{}", Uuid::new_v4(), ext); let path = format!("{}/{}", dir, filename); tokio::fs::write(&path, &data) .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; return Ok(Json(UploadResponse { url: format!("/uploads/chat-images/{}", filename), })); } Err((StatusCode::BAD_REQUEST, "No image provided".into())) }