use crate::shared::state::AppState; use crate::slides::collaboration::broadcast_slide_change; use crate::slides::storage::{ create_new_presentation, create_slide_with_layout, delete_presentation_from_drive, get_current_user_id, list_presentations_from_drive, load_presentation_by_id, load_presentation_from_drive, save_presentation_to_drive, }; use crate::slides::types::{ AddElementRequest, AddMediaRequest, AddSlideRequest, ApplyThemeRequest, ApplyTransitionToAllRequest, CollaborationCursor, CollaborationSelection, DeleteElementRequest, DeleteMediaRequest, DeleteSlideRequest, DuplicateSlideRequest, EndPresenterRequest, ExportRequest, ListCursorsResponse, ListMediaResponse, ListSelectionsResponse, LoadQuery, Presentation, PresentationMetadata, PresenterNotesResponse, PresenterSession, PresenterSessionResponse, RemoveTransitionRequest, ReorderSlidesRequest, SavePresentationRequest, SaveResponse, SearchQuery, SetTransitionRequest, SlidesAiRequest, SlidesAiResponse, StartPresenterRequest, UpdateCursorRequest, UpdateElementRequest, UpdateMediaRequest, UpdatePresenterRequest, UpdateSelectionRequest, UpdateSlideNotesRequest, }; use crate::slides::utils::export_to_html; use axum::{ extract::{Path, Query, State}, http::StatusCode, response::IntoResponse, Json, }; use chrono::Utc; use log::error; use std::collections::HashMap; use std::sync::{Arc, LazyLock, RwLock}; use uuid::Uuid; pub async fn handle_slides_ai( State(_state): State>, Json(req): Json, ) -> impl IntoResponse { let command = req.command.to_lowercase(); let response = if command.contains("add") && command.contains("slide") { "I've added a new slide to your presentation." } else if command.contains("duplicate") { "I've duplicated the current slide." } else if command.contains("delete") || command.contains("remove") { "I've removed the slide from your presentation." } else if command.contains("text") || command.contains("title") { "I've added a text box to your slide. Click to edit." } else if command.contains("image") || command.contains("picture") { "I've added an image placeholder. Click to upload an image." } else if command.contains("shape") { "I've added a shape to your slide. You can resize and move it." } else if command.contains("chart") { "I've added a chart. Click to edit the data." } else if command.contains("table") { "I've added a table. Click cells to edit." } else if command.contains("theme") || command.contains("design") { "I can help you change the theme. Choose from the Design menu." } else if command.contains("animate") || command.contains("animation") { "I've added an animation to the selected element." } else if command.contains("transition") { "I've applied a transition effect to this slide." } else if command.contains("help") { "I can help you with:\n• Add/duplicate/delete slides\n• Insert text, images, shapes\n• Add charts and tables\n• Apply themes and animations\n• Set slide transitions" } else { "I understand you want help with your presentation. Try commands like 'add slide', 'insert image', 'add chart', or 'apply animation'." }; Json(SlidesAiResponse { response: response.to_string(), action: None, data: None, }) } pub async fn handle_new_presentation( State(_state): State>, ) -> Result, (StatusCode, Json)> { Ok(Json(create_new_presentation())) } pub async fn handle_list_presentations( State(state): State>, ) -> Result>, (StatusCode, Json)> { let user_id = get_current_user_id(); match list_presentations_from_drive(&state, &user_id).await { Ok(presentations) => Ok(Json(presentations)), Err(e) => { error!("Failed to list presentations: {}", e); Ok(Json(Vec::new())) } } } pub async fn handle_search_presentations( State(state): State>, Query(query): Query, ) -> Result>, (StatusCode, Json)> { let user_id = get_current_user_id(); let presentations = match list_presentations_from_drive(&state, &user_id).await { Ok(p) => p, Err(_) => Vec::new(), }; let filtered = if let Some(q) = query.q { let q_lower = q.to_lowercase(); presentations .into_iter() .filter(|p| p.name.to_lowercase().contains(&q_lower)) .collect() } else { presentations }; Ok(Json(filtered)) } pub async fn handle_load_presentation( State(state): State>, Query(query): Query, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); match load_presentation_from_drive(&state, &user_id, &query.id).await { Ok(presentation) => Ok(Json(presentation)), Err(e) => Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": e })), )), } } pub async fn handle_save_presentation( State(state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); let presentation_id = req.id.unwrap_or_else(|| Uuid::new_v4().to_string()); let presentation = Presentation { id: presentation_id.clone(), name: req.name, owner_id: user_id.clone(), slides: req.slides, theme: req.theme, created_at: Utc::now(), updated_at: Utc::now(), }; if let Err(e) = save_presentation_to_drive(&state, &user_id, &presentation).await { return Err(( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e })), )); } Ok(Json(SaveResponse { id: presentation_id, success: true, message: Some("Presentation saved successfully".to_string()), })) } pub async fn handle_delete_presentation( State(state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); if let Err(e) = delete_presentation_from_drive(&state, &user_id, &req.id).await { return Err(( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e })), )); } Ok(Json(SaveResponse { id: req.id.unwrap_or_default(), success: true, message: Some("Presentation deleted".to_string()), })) } pub async fn handle_get_presentation_by_id( State(state): State>, Path(presentation_id): Path, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); match load_presentation_by_id(&state, &user_id, &presentation_id).await { Ok(presentation) => Ok(Json(presentation)), Err(e) => Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": e })), )), } } pub async fn handle_add_slide( State(state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); let mut presentation = match load_presentation_by_id(&state, &user_id, &req.presentation_id).await { Ok(p) => p, Err(e) => { return Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": e })), )) } }; let new_slide = create_slide_with_layout(&req.layout, &presentation.theme); if let Some(position) = req.position { if position <= presentation.slides.len() { presentation.slides.insert(position, new_slide); } else { presentation.slides.push(new_slide); } } else { presentation.slides.push(new_slide); } presentation.updated_at = Utc::now(); if let Err(e) = save_presentation_to_drive(&state, &user_id, &presentation).await { return Err(( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e })), )); } Ok(Json(SaveResponse { id: req.presentation_id, success: true, message: Some("Slide added".to_string()), })) } pub async fn handle_delete_slide( State(state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); let mut presentation = match load_presentation_by_id(&state, &user_id, &req.presentation_id).await { Ok(p) => p, Err(e) => { return Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": e })), )) } }; if req.slide_index >= presentation.slides.len() { return Err(( StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "Invalid slide index" })), )); } presentation.slides.remove(req.slide_index); presentation.updated_at = Utc::now(); if let Err(e) = save_presentation_to_drive(&state, &user_id, &presentation).await { return Err(( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e })), )); } Ok(Json(SaveResponse { id: req.presentation_id, success: true, message: Some("Slide deleted".to_string()), })) } pub async fn handle_duplicate_slide( State(state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); let mut presentation = match load_presentation_by_id(&state, &user_id, &req.presentation_id).await { Ok(p) => p, Err(e) => { return Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": e })), )) } }; if req.slide_index >= presentation.slides.len() { return Err(( StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "Invalid slide index" })), )); } let mut duplicated = presentation.slides[req.slide_index].clone(); duplicated.id = Uuid::new_v4().to_string(); presentation.slides.insert(req.slide_index + 1, duplicated); presentation.updated_at = Utc::now(); if let Err(e) = save_presentation_to_drive(&state, &user_id, &presentation).await { return Err(( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e })), )); } Ok(Json(SaveResponse { id: req.presentation_id, success: true, message: Some("Slide duplicated".to_string()), })) } pub async fn handle_reorder_slides( State(state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); let mut presentation = match load_presentation_by_id(&state, &user_id, &req.presentation_id).await { Ok(p) => p, Err(e) => { return Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": e })), )) } }; if req.slide_order.len() != presentation.slides.len() { return Err(( StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "Invalid slide order" })), )); } let old_slides = presentation.slides.clone(); presentation.slides = req .slide_order .iter() .filter_map(|&idx| old_slides.get(idx).cloned()) .collect(); presentation.updated_at = Utc::now(); if let Err(e) = save_presentation_to_drive(&state, &user_id, &presentation).await { return Err(( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e })), )); } Ok(Json(SaveResponse { id: req.presentation_id, success: true, message: Some("Slides reordered".to_string()), })) } pub async fn handle_update_slide_notes( State(state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); let mut presentation = match load_presentation_by_id(&state, &user_id, &req.presentation_id).await { Ok(p) => p, Err(e) => { return Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": e })), )) } }; if req.slide_index >= presentation.slides.len() { return Err(( StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "Invalid slide index" })), )); } presentation.slides[req.slide_index].notes = Some(req.notes); presentation.updated_at = Utc::now(); if let Err(e) = save_presentation_to_drive(&state, &user_id, &presentation).await { return Err(( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e })), )); } Ok(Json(SaveResponse { id: req.presentation_id, success: true, message: Some("Slide notes updated".to_string()), })) } pub async fn handle_add_element( State(state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); let mut presentation = match load_presentation_by_id(&state, &user_id, &req.presentation_id).await { Ok(p) => p, Err(e) => { return Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": e })), )) } }; if req.slide_index >= presentation.slides.len() { return Err(( StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "Invalid slide index" })), )); } presentation.slides[req.slide_index].elements.push(req.element); presentation.updated_at = Utc::now(); if let Err(e) = save_presentation_to_drive(&state, &user_id, &presentation).await { return Err(( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e })), )); } broadcast_slide_change( &req.presentation_id, &user_id, "User", "element_added", Some(req.slide_index), None, None, ) .await; Ok(Json(SaveResponse { id: req.presentation_id, success: true, message: Some("Element added".to_string()), })) } pub async fn handle_update_element( State(state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); let mut presentation = match load_presentation_by_id(&state, &user_id, &req.presentation_id).await { Ok(p) => p, Err(e) => { return Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": e })), )) } }; if req.slide_index >= presentation.slides.len() { return Err(( StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "Invalid slide index" })), )); } let slide = &mut presentation.slides[req.slide_index]; if let Some(pos) = slide.elements.iter().position(|e| e.id == req.element.id) { slide.elements[pos] = req.element.clone(); } else { slide.elements.push(req.element.clone()); } presentation.updated_at = Utc::now(); if let Err(e) = save_presentation_to_drive(&state, &user_id, &presentation).await { return Err(( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e })), )); } broadcast_slide_change( &req.presentation_id, &user_id, "User", "element_updated", Some(req.slide_index), Some(&req.element.id), None, ) .await; Ok(Json(SaveResponse { id: req.presentation_id, success: true, message: Some("Element updated".to_string()), })) } pub async fn handle_delete_element( State(state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); let mut presentation = match load_presentation_by_id(&state, &user_id, &req.presentation_id).await { Ok(p) => p, Err(e) => { return Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": e })), )) } }; if req.slide_index >= presentation.slides.len() { return Err(( StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "Invalid slide index" })), )); } presentation.slides[req.slide_index] .elements .retain(|e| e.id != req.element_id); presentation.updated_at = Utc::now(); if let Err(e) = save_presentation_to_drive(&state, &user_id, &presentation).await { return Err(( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e })), )); } Ok(Json(SaveResponse { id: req.presentation_id, success: true, message: Some("Element deleted".to_string()), })) } pub async fn handle_apply_theme( State(state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); let mut presentation = match load_presentation_by_id(&state, &user_id, &req.presentation_id).await { Ok(p) => p, Err(e) => { return Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": e })), )) } }; presentation.theme = req.theme; presentation.updated_at = Utc::now(); if let Err(e) = save_presentation_to_drive(&state, &user_id, &presentation).await { return Err(( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e })), )); } Ok(Json(SaveResponse { id: req.presentation_id, success: true, message: Some("Theme applied".to_string()), })) } pub async fn handle_export_presentation( State(state): State>, Json(req): Json, ) -> Result)> { let user_id = get_current_user_id(); let presentation = match load_presentation_by_id(&state, &user_id, &req.id).await { Ok(p) => p, Err(e) => { return Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": e })), )) } }; match req.format.as_str() { "html" => { let html = export_to_html(&presentation); Ok(([(axum::http::header::CONTENT_TYPE, "text/html")], html)) } "json" => { let json = serde_json::to_string_pretty(&presentation).unwrap_or_default(); Ok(([(axum::http::header::CONTENT_TYPE, "application/json")], json)) } "pptx" => { Ok(( [( axum::http::header::CONTENT_TYPE, "application/vnd.openxmlformats-officedocument.presentationml.presentation", )], "PPTX export not yet implemented".to_string(), )) } _ => Err(( StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "Unsupported format" })), )), } } static CURSORS: LazyLock>>> = LazyLock::new(|| RwLock::new(HashMap::new())); static SELECTIONS: LazyLock>>> = LazyLock::new(|| RwLock::new(HashMap::new())); static PRESENTER_SESSIONS: LazyLock>> = LazyLock::new(|| RwLock::new(HashMap::new())); pub async fn handle_update_cursor( State(_state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); let cursor = CollaborationCursor { user_id: user_id.clone(), user_name: "User".to_string(), user_color: "#4285f4".to_string(), slide_index: req.slide_index, element_id: req.element_id, x: req.x, y: req.y, last_activity: Utc::now(), }; if let Ok(mut cursors) = CURSORS.write() { let presentation_cursors = cursors.entry(req.presentation_id.clone()).or_default(); presentation_cursors.retain(|c| c.user_id != user_id); presentation_cursors.push(cursor); } Ok(Json(serde_json::json!({ "success": true }))) } pub async fn handle_update_selection( State(_state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); let selection = CollaborationSelection { user_id: user_id.clone(), user_name: "User".to_string(), user_color: "#4285f4".to_string(), slide_index: req.slide_index, element_ids: req.element_ids, }; if let Ok(mut selections) = SELECTIONS.write() { let presentation_selections = selections.entry(req.presentation_id.clone()).or_default(); presentation_selections.retain(|s| s.user_id != user_id); presentation_selections.push(selection); } Ok(Json(serde_json::json!({ "success": true }))) } pub async fn handle_list_cursors( State(_state): State>, Query(params): Query>, ) -> Result, (StatusCode, Json)> { let presentation_id = params.get("presentation_id").cloned().unwrap_or_default(); let cursors = if let Ok(cursors_map) = CURSORS.read() { cursors_map.get(&presentation_id).cloned().unwrap_or_default() } else { vec![] }; Ok(Json(ListCursorsResponse { cursors })) } pub async fn handle_list_selections( State(_state): State>, Query(params): Query>, ) -> Result, (StatusCode, Json)> { let presentation_id = params.get("presentation_id").cloned().unwrap_or_default(); let selections = if let Ok(selections_map) = SELECTIONS.read() { selections_map.get(&presentation_id).cloned().unwrap_or_default() } else { vec![] }; Ok(Json(ListSelectionsResponse { selections })) } pub async fn handle_set_transition( State(state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); let mut presentation = match load_presentation_by_id(&state, &user_id, &req.presentation_id).await { Ok(p) => p, Err(e) => { return Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": e })), )) } }; if req.slide_index >= presentation.slides.len() { return Err(( StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "Invalid slide index" })), )); } presentation.slides[req.slide_index].transition_config = Some(req.transition); presentation.updated_at = Utc::now(); if let Err(e) = save_presentation_to_drive(&state, &user_id, &presentation).await { return Err(( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e })), )); } Ok(Json(SaveResponse { id: req.presentation_id, success: true, message: Some("Transition set".to_string()), })) } pub async fn handle_apply_transition_to_all( State(state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); let mut presentation = match load_presentation_by_id(&state, &user_id, &req.presentation_id).await { Ok(p) => p, Err(e) => { return Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": e })), )) } }; for slide in presentation.slides.iter_mut() { slide.transition_config = Some(req.transition.clone()); } presentation.updated_at = Utc::now(); if let Err(e) = save_presentation_to_drive(&state, &user_id, &presentation).await { return Err(( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e })), )); } Ok(Json(SaveResponse { id: req.presentation_id, success: true, message: Some("Transition applied to all slides".to_string()), })) } pub async fn handle_remove_transition( State(state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); let mut presentation = match load_presentation_by_id(&state, &user_id, &req.presentation_id).await { Ok(p) => p, Err(e) => { return Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": e })), )) } }; if req.slide_index >= presentation.slides.len() { return Err(( StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "Invalid slide index" })), )); } presentation.slides[req.slide_index].transition_config = None; presentation.slides[req.slide_index].transition = None; presentation.updated_at = Utc::now(); if let Err(e) = save_presentation_to_drive(&state, &user_id, &presentation).await { return Err(( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e })), )); } Ok(Json(SaveResponse { id: req.presentation_id, success: true, message: Some("Transition removed".to_string()), })) } pub async fn handle_add_media( State(state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); let mut presentation = match load_presentation_by_id(&state, &user_id, &req.presentation_id).await { Ok(p) => p, Err(e) => { return Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": e })), )) } }; if req.slide_index >= presentation.slides.len() { return Err(( StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "Invalid slide index" })), )); } let media_list = presentation.slides[req.slide_index].media.get_or_insert_with(Vec::new); media_list.push(req.media.clone()); presentation.updated_at = Utc::now(); if let Err(e) = save_presentation_to_drive(&state, &user_id, &presentation).await { return Err(( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e })), )); } Ok(Json(serde_json::json!({ "success": true, "media": req.media }))) } pub async fn handle_update_media( State(state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); let mut presentation = match load_presentation_by_id(&state, &user_id, &req.presentation_id).await { Ok(p) => p, Err(e) => { return Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": e })), )) } }; if req.slide_index >= presentation.slides.len() { return Err(( StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "Invalid slide index" })), )); } if let Some(media_list) = &mut presentation.slides[req.slide_index].media { for media in media_list.iter_mut() { if media.id == req.media_id { if let Some(autoplay) = req.autoplay { media.autoplay = autoplay; } if let Some(loop_playback) = req.loop_playback { media.loop_playback = loop_playback; } if let Some(muted) = req.muted { media.muted = muted; } if let Some(volume) = req.volume { media.volume = Some(volume); } if let Some(start_time) = req.start_time { media.start_time = Some(start_time); } if let Some(end_time) = req.end_time { media.end_time = Some(end_time); } break; } } } presentation.updated_at = Utc::now(); if let Err(e) = save_presentation_to_drive(&state, &user_id, &presentation).await { return Err(( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e })), )); } Ok(Json(serde_json::json!({ "success": true }))) } pub async fn handle_delete_media( State(state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); let mut presentation = match load_presentation_by_id(&state, &user_id, &req.presentation_id).await { Ok(p) => p, Err(e) => { return Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": e })), )) } }; if req.slide_index >= presentation.slides.len() { return Err(( StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "Invalid slide index" })), )); } if let Some(media_list) = &mut presentation.slides[req.slide_index].media { media_list.retain(|m| m.id != req.media_id); } presentation.updated_at = Utc::now(); if let Err(e) = save_presentation_to_drive(&state, &user_id, &presentation).await { return Err(( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e })), )); } Ok(Json(serde_json::json!({ "success": true }))) } pub async fn handle_list_media( State(state): State>, Query(params): Query>, ) -> Result, (StatusCode, Json)> { let presentation_id = params.get("presentation_id").cloned().unwrap_or_default(); let slide_index: usize = params.get("slide_index").and_then(|s| s.parse().ok()).unwrap_or(0); let user_id = get_current_user_id(); let presentation = match load_presentation_by_id(&state, &user_id, &presentation_id).await { Ok(p) => p, Err(e) => { return Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": e })), )) } }; if slide_index >= presentation.slides.len() { return Err(( StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "Invalid slide index" })), )); } let media = presentation.slides[slide_index].media.clone().unwrap_or_default(); Ok(Json(ListMediaResponse { media })) } pub async fn handle_start_presenter( State(state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); let _presentation = match load_presentation_by_id(&state, &user_id, &req.presentation_id).await { Ok(p) => p, Err(e) => { return Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": e })), )) } }; let session = PresenterSession { id: uuid::Uuid::new_v4().to_string(), presentation_id: req.presentation_id, current_slide: 0, started_at: Utc::now(), elapsed_time: Some(0), is_paused: false, settings: req.settings, }; if let Ok(mut sessions) = PRESENTER_SESSIONS.write() { sessions.insert(session.id.clone(), session.clone()); } Ok(Json(PresenterSessionResponse { session })) } pub async fn handle_update_presenter( State(_state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let session = if let Ok(mut sessions) = PRESENTER_SESSIONS.write() { if let Some(session) = sessions.get_mut(&req.session_id) { if let Some(current_slide) = req.current_slide { session.current_slide = current_slide; } if let Some(is_paused) = req.is_paused { session.is_paused = is_paused; } if let Some(settings) = req.settings { session.settings = settings; } session.clone() } else { return Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": "Session not found" })), )); } } else { return Err(( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "Failed to access sessions" })), )); }; Ok(Json(PresenterSessionResponse { session })) } pub async fn handle_end_presenter( State(_state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { if let Ok(mut sessions) = PRESENTER_SESSIONS.write() { sessions.remove(&req.session_id); } Ok(Json(serde_json::json!({ "success": true }))) } pub async fn handle_get_presenter_notes( State(state): State>, Query(params): Query>, ) -> Result, (StatusCode, Json)> { let presentation_id = params.get("presentation_id").cloned().unwrap_or_default(); let slide_index: usize = params.get("slide_index").and_then(|s| s.parse().ok()).unwrap_or(0); let user_id = get_current_user_id(); let presentation = match load_presentation_by_id(&state, &user_id, &presentation_id).await { Ok(p) => p, Err(e) => { return Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": e })), )) } }; if slide_index >= presentation.slides.len() { return Err(( StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "Invalid slide index" })), )); } let notes = presentation.slides[slide_index].notes.clone(); let next_slide_notes = if slide_index + 1 < presentation.slides.len() { presentation.slides[slide_index + 1].notes.clone() } else { None }; Ok(Json(PresenterNotesResponse { slide_index, notes, next_slide_notes, next_slide_thumbnail: None, })) }