#!/bin/bash echo "🔄 UPDATING EVENTS HANDLER (CORRECT VERSION)" echo "=============================================" # Step 1: Restore from backup if needed if [ -f src/handlers/events.rs.backup.* ]; then echo "📦 Restoring from backup first..." cp src/handlers/events.rs.backup.* src/handlers/events.rs echo "✅ Restored" fi # Step 2: Add the correct imports echo "📝 Adding correct imports..." sed -i '/^use uuid::Uuid;$/a\ \ // New imports for WebP and multipart support\ use axum::extract::Multipart;\ use crate::utils::images::convert_to_webp;\ use tokio::fs;\ use chrono::{DateTime, Utc};' src/handlers/events.rs echo "✅ Imports added" # Step 3: Create the CORRECT submit function echo "🔧 Creating CORRECT submit function..." cat > /tmp/correct_submit_function.rs << 'EOF' pub async fn submit( State(state): State, mut multipart: Multipart, ) -> Result>> { // Initialize the request struct with ACTUAL fields let mut req = SubmitEventRequest { title: String::new(), description: String::new(), start_time: Utc::now(), // Temporary default end_time: Utc::now(), // Temporary default location: String::new(), location_url: None, category: String::new(), is_featured: None, recurring_type: None, bulletin_week: String::new(), submitter_email: None, }; // Track image paths (we'll save these separately to DB) let mut image_path: Option = None; let mut thumbnail_path: Option = None; // Extract form fields and files while let Some(field) = multipart.next_field().await.map_err(|e| { ApiError::ValidationError(format!("Failed to read multipart field: {}", e)) })? { let name = field.name().unwrap_or("").to_string(); match name.as_str() { "title" => { req.title = field.text().await.map_err(|e| { ApiError::ValidationError(format!("Invalid title: {}", e)) })?; }, "description" => { req.description = field.text().await.map_err(|e| { ApiError::ValidationError(format!("Invalid description: {}", e)) })?; }, "start_time" => { let time_str = field.text().await.map_err(|e| { ApiError::ValidationError(format!("Invalid start_time: {}", e)) })?; // Parse as NaiveDateTime first, then convert to UTC let naive_dt = chrono::NaiveDateTime::parse_from_str(&time_str, "%Y-%m-%dT%H:%M") .map_err(|e| ApiError::ValidationError(format!("Invalid start_time format: {}", e)))?; req.start_time = DateTime::from_utc(naive_dt, Utc); }, "end_time" => { let time_str = field.text().await.map_err(|e| { ApiError::ValidationError(format!("Invalid end_time: {}", e)) })?; let naive_dt = chrono::NaiveDateTime::parse_from_str(&time_str, "%Y-%m-%dT%H:%M") .map_err(|e| ApiError::ValidationError(format!("Invalid end_time format: {}", e)))?; req.end_time = DateTime::from_utc(naive_dt, Utc); }, "location" => { req.location = field.text().await.map_err(|e| { ApiError::ValidationError(format!("Invalid location: {}", e)) })?; }, "category" => { req.category = field.text().await.map_err(|e| { ApiError::ValidationError(format!("Invalid category: {}", e)) })?; }, "location_url" => { let url = field.text().await.map_err(|e| { ApiError::ValidationError(format!("Invalid location_url: {}", e)) })?; if !url.is_empty() { req.location_url = Some(url); } }, "reoccuring" => { // Note: form uses "reoccuring" but model uses "recurring_type" let recurring = field.text().await.map_err(|e| { ApiError::ValidationError(format!("Invalid recurring: {}", e)) })?; if !recurring.is_empty() { req.recurring_type = Some(recurring); } }, "submitter_email" => { let email = field.text().await.map_err(|e| { ApiError::ValidationError(format!("Invalid submitter_email: {}", e)) })?; if !email.is_empty() { req.submitter_email = Some(email); } }, "bulletin_week" => { req.bulletin_week = field.text().await.map_err(|e| { ApiError::ValidationError(format!("Invalid bulletin_week: {}", e)) })?; }, "image" => { let image_data = field.bytes().await.map_err(|e| { ApiError::ValidationError(format!("Failed to read image: {}", e)) })?; if !image_data.is_empty() { // Save original immediately let uuid = Uuid::new_v4(); let original_path = format!("uploads/events/original_{}.jpg", uuid); // Ensure directory exists fs::create_dir_all("uploads/events").await.map_err(|e| { ApiError::FileError(e) })?; fs::write(&original_path, &image_data).await.map_err(|e| { ApiError::FileError(e) })?; // Set original path immediately image_path = Some(original_path.clone()); // Convert to WebP in background (user doesn't wait) let pool = state.pool.clone(); tokio::spawn(async move { if let Ok(webp_data) = convert_to_webp(&image_data).await { let webp_path = format!("uploads/events/{}.webp", uuid); if fs::write(&webp_path, webp_data).await.is_ok() { // Update database with WebP path (using actual column name "image") let _ = sqlx::query!( "UPDATE pending_events SET image = $1 WHERE image = $2", webp_path, original_path ).execute(&pool).await; // Delete original file let _ = fs::remove_file(&original_path).await; } } }); } }, "thumbnail" => { let thumb_data = field.bytes().await.map_err(|e| { ApiError::ValidationError(format!("Failed to read thumbnail: {}", e)) })?; if !thumb_data.is_empty() { let uuid = Uuid::new_v4(); let original_path = format!("uploads/events/thumb_original_{}.jpg", uuid); fs::create_dir_all("uploads/events").await.map_err(|e| { ApiError::FileError(e) })?; fs::write(&original_path, &thumb_data).await.map_err(|e| { ApiError::FileError(e) })?; thumbnail_path = Some(original_path.clone()); // Convert thumbnail to WebP in background let pool = state.pool.clone(); tokio::spawn(async move { if let Ok(webp_data) = convert_to_webp(&thumb_data).await { let webp_path = format!("uploads/events/thumb_{}.webp", uuid); if fs::write(&webp_path, webp_data).await.is_ok() { let _ = sqlx::query!( "UPDATE pending_events SET thumbnail = $1 WHERE thumbnail = $2", webp_path, original_path ).execute(&pool).await; let _ = fs::remove_file(&original_path).await; } } }); } }, _ => { // Ignore unknown fields let _ = field.bytes().await; } } } // Validate required fields if req.title.is_empty() { return Err(ApiError::ValidationError("Title is required".to_string())); } if req.description.is_empty() { return Err(ApiError::ValidationError("Description is required".to_string())); } if req.location.is_empty() { return Err(ApiError::ValidationError("Location is required".to_string())); } if req.category.is_empty() { return Err(ApiError::ValidationError("Category is required".to_string())); } if req.bulletin_week.is_empty() { req.bulletin_week = "current".to_string(); // Default value } // Submit to database first let mut pending_event = db::events::submit_for_approval(&state.pool, req).await?; // Update with image paths if we have them if let Some(img_path) = image_path { sqlx::query!( "UPDATE pending_events SET image = $1 WHERE id = $2", img_path, pending_event.id ).execute(&state.pool).await.map_err(ApiError::DatabaseError)?; } if let Some(thumb_path) = thumbnail_path { sqlx::query!( "UPDATE pending_events SET thumbnail = $1 WHERE id = $2", thumb_path, pending_event.id ).execute(&state.pool).await.map_err(ApiError::DatabaseError)?; } // Send email notification to admin (existing logic) let mailer = state.mailer.clone(); let event_for_email = pending_event.clone(); tokio::spawn(async move { if let Err(e) = mailer.send_event_submission_notification(&event_for_email).await { tracing::error!("Failed to send email: {:?}", e); } else { tracing::info!("Email sent for event: {}", event_for_email.title); } }); Ok(Json(ApiResponse { success: true, data: Some(pending_event), message: Some("Event submitted successfully! Images are being optimized in the background.".to_string()), })) } EOF # Step 4: Replace the old submit function with the CORRECT one echo "🔄 Replacing submit function..." # Find the line numbers of the current submit function start_line=$(grep -n "^pub async fn submit(" src/handlers/events.rs | cut -d: -f1) end_line=$(awk "NR>$start_line && /^}/ {print NR; exit}" src/handlers/events.rs) if [ -n "$start_line" ] && [ -n "$end_line" ]; then # Create a temporary file with everything except the old submit function head -n $((start_line - 1)) src/handlers/events.rs > /tmp/events_before.rs tail -n +$((end_line + 1)) src/handlers/events.rs > /tmp/events_after.rs # Combine: before + new function + after cat /tmp/events_before.rs /tmp/correct_submit_function.rs /tmp/events_after.rs > src/handlers/events.rs echo "✅ Submit function replaced successfully" else echo "❌ Could not find submit function boundaries" exit 1 fi # Step 5: Clean up temp files rm -f /tmp/correct_submit_function.rs /tmp/events_before.rs /tmp/events_after.rs # Step 6: Build to check for errors echo "🔨 Building to verify changes..." if cargo build; then echo "✅ Build successful!" echo "" echo "🎉 EVENTS HANDLER UPDATED SUCCESSFULLY!" echo "======================================" echo "✅ Uses ACTUAL model fields" echo "✅ Proper DateTime handling" echo "✅ Correct ApiError variants" echo "✅ Real database columns" echo "✅ Background WebP conversion" echo "✅ No hallucinated bullshit" echo "" echo "🚀 Ready to handle multipart form submissions with WebP conversion!" else echo "❌ Build failed - check errors above" exit 1 fi