
Complete church management system with bulletin management, media processing, live streaming integration, and web interface. Includes authentication, email notifications, database migrations, and comprehensive test suite.
311 lines
12 KiB
Bash
Executable file
311 lines
12 KiB
Bash
Executable file
#!/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<AppState>,
|
|
mut multipart: Multipart,
|
|
) -> Result<Json<ApiResponse<PendingEvent>>> {
|
|
// 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<String> = None;
|
|
let mut thumbnail_path: Option<String> = 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
|