MS Office 100% Compatibility - Phase 1 Implementation
- Add rust_xlsxwriter for Excel export with formatting support
- Add docx-rs for Word document import/export with HTML conversion
- Add PPTX export support with slides, shapes, and text elements
- Refactor sheet module into 7 files (types, formulas, handlers, etc)
- Refactor docs module into 6 files (types, handlers, storage, etc)
- Refactor slides module into 6 files (types, handlers, storage, etc)
- Fix collaboration modules (borrow issues, rand compatibility)
- Add ooxmlsdk dependency for future Office 2021 features
- Fix type mismatches in slides storage
- Update security protection API router type
Features:
- Excel: Read xlsx/xlsm/xls, write xlsx with styles
- Word: Read/write docx with formatting preservation
- PowerPoint: Write pptx with slides, shapes, text
- Real-time collaboration via WebSocket (already working)
- Theme-aware UI with --sentient-* CSS variables
2026-01-11 09:56:15 -03:00
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 ::{
2026-01-11 12:13:10 -03:00
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 ,
MS Office 100% Compatibility - Phase 1 Implementation
- Add rust_xlsxwriter for Excel export with formatting support
- Add docx-rs for Word document import/export with HTML conversion
- Add PPTX export support with slides, shapes, and text elements
- Refactor sheet module into 7 files (types, formulas, handlers, etc)
- Refactor docs module into 6 files (types, handlers, storage, etc)
- Refactor slides module into 6 files (types, handlers, storage, etc)
- Fix collaboration modules (borrow issues, rand compatibility)
- Add ooxmlsdk dependency for future Office 2021 features
- Fix type mismatches in slides storage
- Update security protection API router type
Features:
- Excel: Read xlsx/xlsm/xls, write xlsx with styles
- Word: Read/write docx with formatting preservation
- PowerPoint: Write pptx with slides, shapes, text
- Real-time collaboration via WebSocket (already working)
- Theme-aware UI with --sentient-* CSS variables
2026-01-11 09:56:15 -03:00
} ;
use crate ::slides ::utils ::export_to_html ;
use axum ::{
extract ::{ Path , Query , State } ,
http ::StatusCode ,
response ::IntoResponse ,
Json ,
} ;
use chrono ::Utc ;
use log ::error ;
2026-01-11 12:13:10 -03:00
use std ::collections ::HashMap ;
use std ::sync ::{ Arc , LazyLock , RwLock } ;
MS Office 100% Compatibility - Phase 1 Implementation
- Add rust_xlsxwriter for Excel export with formatting support
- Add docx-rs for Word document import/export with HTML conversion
- Add PPTX export support with slides, shapes, and text elements
- Refactor sheet module into 7 files (types, formulas, handlers, etc)
- Refactor docs module into 6 files (types, handlers, storage, etc)
- Refactor slides module into 6 files (types, handlers, storage, etc)
- Fix collaboration modules (borrow issues, rand compatibility)
- Add ooxmlsdk dependency for future Office 2021 features
- Fix type mismatches in slides storage
- Update security protection API router type
Features:
- Excel: Read xlsx/xlsm/xls, write xlsx with styles
- Word: Read/write docx with formatting preservation
- PowerPoint: Write pptx with slides, shapes, text
- Real-time collaboration via WebSocket (already working)
- Theme-aware UI with --sentient-* CSS variables
2026-01-11 09:56:15 -03:00
use uuid ::Uuid ;
pub async fn handle_slides_ai (
State ( _state ) : State < Arc < AppState > > ,
Json ( req ) : Json < SlidesAiRequest > ,
) -> 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 < Arc < AppState > > ,
) -> Result < Json < Presentation > , ( StatusCode , Json < serde_json ::Value > ) > {
Ok ( Json ( create_new_presentation ( ) ) )
}
pub async fn handle_list_presentations (
State ( state ) : State < Arc < AppState > > ,
) -> Result < Json < Vec < PresentationMetadata > > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Query ( query ) : Query < SearchQuery > ,
) -> Result < Json < Vec < PresentationMetadata > > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Query ( query ) : Query < LoadQuery > ,
) -> Result < Json < Presentation > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Json ( req ) : Json < SavePresentationRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Json ( req ) : Json < LoadQuery > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Path ( presentation_id ) : Path < String > ,
) -> Result < Json < Presentation > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Json ( req ) : Json < AddSlideRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Json ( req ) : Json < DeleteSlideRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Json ( req ) : Json < DuplicateSlideRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Json ( req ) : Json < ReorderSlidesRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Json ( req ) : Json < UpdateSlideNotesRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Json ( req ) : Json < AddElementRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Json ( req ) : Json < UpdateElementRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Json ( req ) : Json < DeleteElementRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Json ( req ) : Json < ApplyThemeRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Json ( req ) : Json < ExportRequest > ,
) -> Result < impl IntoResponse , ( StatusCode , Json < serde_json ::Value > ) > {
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 " } ) ) ,
) ) ,
}
}
2026-01-11 12:13:10 -03:00
static CURSORS : LazyLock < RwLock < HashMap < String , Vec < CollaborationCursor > > > > =
LazyLock ::new ( | | RwLock ::new ( HashMap ::new ( ) ) ) ;
static SELECTIONS : LazyLock < RwLock < HashMap < String , Vec < CollaborationSelection > > > > =
LazyLock ::new ( | | RwLock ::new ( HashMap ::new ( ) ) ) ;
static PRESENTER_SESSIONS : LazyLock < RwLock < HashMap < String , PresenterSession > > > =
LazyLock ::new ( | | RwLock ::new ( HashMap ::new ( ) ) ) ;
pub async fn handle_update_cursor (
State ( _state ) : State < Arc < AppState > > ,
Json ( req ) : Json < UpdateCursorRequest > ,
) -> Result < Json < serde_json ::Value > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Json ( req ) : Json < UpdateSelectionRequest > ,
) -> Result < Json < serde_json ::Value > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Query ( params ) : Query < HashMap < String , String > > ,
) -> Result < Json < ListCursorsResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Query ( params ) : Query < HashMap < String , String > > ,
) -> Result < Json < ListSelectionsResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Json ( req ) : Json < SetTransitionRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Json ( req ) : Json < ApplyTransitionToAllRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Json ( req ) : Json < RemoveTransitionRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Json ( req ) : Json < AddMediaRequest > ,
) -> Result < Json < serde_json ::Value > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Json ( req ) : Json < UpdateMediaRequest > ,
) -> Result < Json < serde_json ::Value > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Json ( req ) : Json < DeleteMediaRequest > ,
) -> Result < Json < serde_json ::Value > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Query ( params ) : Query < HashMap < String , String > > ,
) -> Result < Json < ListMediaResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Json ( req ) : Json < StartPresenterRequest > ,
) -> Result < Json < PresenterSessionResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Json ( req ) : Json < UpdatePresenterRequest > ,
) -> Result < Json < PresenterSessionResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Json ( req ) : Json < EndPresenterRequest > ,
) -> Result < Json < serde_json ::Value > , ( StatusCode , Json < serde_json ::Value > ) > {
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 < Arc < AppState > > ,
Query ( params ) : Query < HashMap < String , String > > ,
) -> Result < Json < PresenterNotesResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
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 ,
} ) )
}