65 lines
2.0 KiB
Rust

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<Json<UploadResponse>, (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()))
}