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 ::sheet ::collaboration ::broadcast_sheet_change ;
2026-01-11 12:22:14 -03:00
use crate ::sheet ::export ::{ export_to_csv , export_to_html , export_to_json , export_to_markdown , export_to_ods , export_to_xlsx } ;
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 ::sheet ::formulas ::evaluate_formula ;
use crate ::sheet ::storage ::{
2026-01-11 12:22:14 -03:00
create_new_spreadsheet , delete_sheet_from_drive , get_current_user_id , import_spreadsheet_bytes ,
list_sheets_from_drive , load_sheet_by_id , load_sheet_from_drive , parse_csv_to_worksheets ,
parse_excel_to_worksheets , save_sheet_to_drive ,
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 ::sheet ::types ::{
2026-01-11 12:13:10 -03:00
AddCommentRequest , AddExternalLinkRequest , AddNoteRequest , ArrayFormula , ArrayFormulaRequest ,
CellComment , CellData , CellUpdateRequest , ChartConfig , ChartOptions , ChartPosition ,
ChartRequest , ClearFilterRequest , CommentReply , CommentWithLocation , ConditionalFormatRequest ,
ConditionalFormatRule , CreateNamedRangeRequest , DataValidationRequest , DeleteArrayFormulaRequest ,
DeleteChartRequest , DeleteCommentRequest , DeleteNamedRangeRequest , ExportRequest , ExternalLink ,
FilterConfig , FilterRequest , FormatRequest , FormulaRequest , FormulaResult , FreezePanesRequest ,
ListCommentsRequest , ListCommentsResponse , ListExternalLinksResponse , ListNamedRangesRequest ,
ListNamedRangesResponse , LoadFromDriveRequest , LoadQuery , LockCellsRequest , MergeCellsRequest ,
MergedCell , NamedRange , ProtectSheetRequest , RefreshExternalLinkRequest , RemoveExternalLinkRequest ,
ReplyCommentRequest , ResolveCommentRequest , SaveRequest , SaveResponse , SearchQuery , ShareRequest ,
SheetAiRequest , SheetAiResponse , SheetProtection , SortRequest , Spreadsheet , SpreadsheetMetadata ,
UnprotectSheetRequest , UpdateNamedRangeRequest , ValidateCellRequest , ValidationResult ,
ValidationRule , Worksheet ,
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 axum ::{
extract ::{ Path , Query , State } ,
http ::StatusCode ,
response ::IntoResponse ,
Json ,
} ;
use chrono ::Utc ;
use log ::error ;
use std ::collections ::HashMap ;
use std ::sync ::Arc ;
use uuid ::Uuid ;
pub async fn handle_sheet_ai (
State ( _state ) : State < Arc < AppState > > ,
Json ( req ) : Json < SheetAiRequest > ,
) -> impl IntoResponse {
let command = req . command . to_lowercase ( ) ;
let response = if command . contains ( " sum " ) {
" I can help you sum values. Select a range and use the SUM formula, or I've added a SUM formula below your selection. "
} else if command . contains ( " average " ) | | command . contains ( " avg " ) {
" I can calculate averages. Select a range and use the AVERAGE formula. "
} else if command . contains ( " chart " ) {
" To create a chart, select your data range first, then choose the chart type from the Chart menu. "
} else if command . contains ( " sort " ) {
" I can sort your data. Select the range you want to sort, then specify ascending or descending order. "
} else if command . contains ( " format " ) | | command . contains ( " currency " ) | | command . contains ( " percent " ) {
" I've applied the formatting to your selected cells. "
} else if command . contains ( " bold " ) | | command . contains ( " italic " ) {
" I've applied the text formatting to your selected cells. "
} else if command . contains ( " filter " ) {
" I've enabled filtering on your data. Use the dropdown arrows in the header row to filter. "
} else if command . contains ( " freeze " ) {
" I've frozen the specified rows/columns so they stay visible when scrolling. "
} else if command . contains ( " merge " ) {
" I've merged the selected cells into one. "
} else if command . contains ( " clear " ) {
" I've cleared the content from the selected cells. "
} else if command . contains ( " help " ) {
" I can help you with: \n • Sum/Average columns \n • Format as currency or percent \n • Bold/Italic formatting \n • Sort data \n • Create charts \n • Filter data \n • Freeze panes \n • Merge cells "
} else {
" I understand you want help with your spreadsheet. Try commands like 'sum column B', 'format as currency', 'sort ascending', or 'create a chart'. "
} ;
Json ( SheetAiResponse {
response : response . to_string ( ) ,
action : None ,
data : None ,
} )
}
pub async fn handle_new_sheet (
State ( _state ) : State < Arc < AppState > > ,
) -> Result < Json < Spreadsheet > , ( StatusCode , Json < serde_json ::Value > ) > {
Ok ( Json ( create_new_spreadsheet ( ) ) )
}
pub async fn handle_list_sheets (
State ( state ) : State < Arc < AppState > > ,
) -> Result < Json < Vec < SpreadsheetMetadata > > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
match list_sheets_from_drive ( & state , & user_id ) . await {
Ok ( sheets ) = > Ok ( Json ( sheets ) ) ,
Err ( e ) = > {
error! ( " Failed to list sheets: {} " , e ) ;
Ok ( Json ( Vec ::new ( ) ) )
}
}
}
pub async fn handle_search_sheets (
State ( state ) : State < Arc < AppState > > ,
Query ( query ) : Query < SearchQuery > ,
) -> Result < Json < Vec < SpreadsheetMetadata > > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let sheets = match list_sheets_from_drive ( & state , & user_id ) . await {
Ok ( s ) = > s ,
Err ( _ ) = > Vec ::new ( ) ,
} ;
let filtered = if let Some ( q ) = query . q {
let q_lower = q . to_lowercase ( ) ;
sheets
. into_iter ( )
. filter ( | s | s . name . to_lowercase ( ) . contains ( & q_lower ) )
. collect ( )
} else {
sheets
} ;
Ok ( Json ( filtered ) )
}
pub async fn handle_load_sheet (
State ( state ) : State < Arc < AppState > > ,
Query ( query ) : Query < LoadQuery > ,
) -> Result < Json < Spreadsheet > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
match load_sheet_from_drive ( & state , & user_id , & query . id ) . await {
Ok ( sheet ) = > Ok ( Json ( sheet ) ) ,
Err ( e ) = > Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ,
}
}
pub async fn handle_load_from_drive (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < LoadFromDriveRequest > ,
) -> Result < Json < Spreadsheet > , ( StatusCode , Json < serde_json ::Value > ) > {
let drive = state . drive . as_ref ( ) . ok_or_else ( | | {
(
StatusCode ::SERVICE_UNAVAILABLE ,
Json ( serde_json ::json! ( { " error " : " Drive not available " } ) ) ,
)
} ) ? ;
let result = drive
. get_object ( )
. bucket ( & req . bucket )
. key ( & req . path )
. send ( )
. await
. map_err ( | e | {
(
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : format ! ( " File not found: {e} " ) } ) ) ,
)
} ) ? ;
let bytes = result
. body
. collect ( )
. await
. map_err ( | e | {
(
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : format ! ( " Failed to read file: {e} " ) } ) ) ,
)
} ) ?
. into_bytes ( ) ;
let ext = req . path . rsplit ( '.' ) . next ( ) . unwrap_or ( " " ) . to_lowercase ( ) ;
let file_name = req . path . rsplit ( '/' ) . next ( ) . unwrap_or ( " Spreadsheet " ) ;
let sheet_name = file_name
. rsplit ( '.' )
. last ( )
. unwrap_or ( " Spreadsheet " )
. to_string ( ) ;
let worksheets = match ext . as_str ( ) {
" csv " | " tsv " = > {
let delimiter = if ext = = " tsv " { b '\t' } else { b ',' } ;
parse_csv_to_worksheets ( & bytes , delimiter , & sheet_name ) . map_err ( | e | {
(
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
)
} ) ?
}
" xlsx " | " xls " | " ods " | " xlsb " | " xlsm " = > {
parse_excel_to_worksheets ( & bytes , & ext ) . map_err ( | e | {
(
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
)
} ) ?
}
_ = > {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : format ! ( " Unsupported format: .{ext} " ) } ) ) ,
) ) ;
}
} ;
let user_id = get_current_user_id ( ) ;
let sheet = Spreadsheet {
id : Uuid ::new_v4 ( ) . to_string ( ) ,
name : sheet_name ,
owner_id : user_id ,
worksheets ,
created_at : Utc ::now ( ) ,
updated_at : Utc ::now ( ) ,
} ;
Ok ( Json ( sheet ) )
}
pub async fn handle_save_sheet (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < SaveRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let sheet_id = req . id . unwrap_or_else ( | | Uuid ::new_v4 ( ) . to_string ( ) ) ;
let sheet = Spreadsheet {
id : sheet_id . clone ( ) ,
name : req . name ,
owner_id : user_id . clone ( ) ,
worksheets : req . worksheets ,
created_at : Utc ::now ( ) ,
updated_at : Utc ::now ( ) ,
} ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : sheet_id ,
success : true ,
message : Some ( " Sheet saved successfully " . to_string ( ) ) ,
} ) )
}
pub async fn handle_delete_sheet (
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_sheet_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 ( " Sheet deleted " . to_string ( ) ) ,
} ) )
}
pub async fn handle_update_cell (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < CellUpdateRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let worksheet = & mut sheet . worksheets [ req . worksheet_index ] ;
let key = format! ( " {} , {} " , req . row , req . col ) ;
let ( value , formula ) = if req . value . starts_with ( '=' ) {
let result = evaluate_formula ( & req . value , worksheet ) ;
( Some ( result . value ) , Some ( req . value . clone ( ) ) )
} else {
( Some ( req . value . clone ( ) ) , None )
} ;
let cell = worksheet . data . entry ( key ) . or_insert_with ( | | CellData {
value : None ,
formula : None ,
style : None ,
format : None ,
note : None ,
} ) ;
cell . value = value ;
cell . formula = formula ;
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
broadcast_sheet_change (
& req . sheet_id ,
& user_id ,
" User " ,
req . row ,
req . col ,
& req . value ,
req . worksheet_index ,
)
. await ;
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Cell updated " . to_string ( ) ) ,
} ) )
}
pub async fn handle_format_cells (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < FormatRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let worksheet = & mut sheet . worksheets [ req . worksheet_index ] ;
for row in req . start_row ..= req . end_row {
for col in req . start_col ..= req . end_col {
let key = format! ( " {} , {} " , row , col ) ;
let cell = worksheet . data . entry ( key ) . or_insert_with ( | | CellData {
value : None ,
formula : None ,
style : None ,
format : None ,
note : None ,
} ) ;
cell . style = Some ( req . style . clone ( ) ) ;
}
}
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Format applied " . to_string ( ) ) ,
} ) )
}
pub async fn handle_evaluate_formula (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < FormulaRequest > ,
) -> Result < Json < FormulaResult > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( _ ) = > {
return Ok ( Json ( evaluate_formula (
& req . formula ,
& Worksheet {
name : " temp " . to_string ( ) ,
data : HashMap ::new ( ) ,
column_widths : None ,
row_heights : None ,
frozen_rows : None ,
frozen_cols : None ,
merged_cells : None ,
filters : None ,
hidden_rows : None ,
validations : None ,
conditional_formats : None ,
charts : None ,
} ,
) ) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let result = evaluate_formula ( & req . formula , & sheet . worksheets [ req . worksheet_index ] ) ;
Ok ( Json ( result ) )
}
pub async fn handle_export_sheet (
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 sheet = match load_sheet_by_id ( & state , & user_id , & req . id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
match req . format . as_str ( ) {
" csv " = > {
let csv = export_to_csv ( & sheet ) ;
Ok ( ( [ ( axum ::http ::header ::CONTENT_TYPE , " text/csv " ) ] , csv ) )
}
" xlsx " = > {
let xlsx = export_to_xlsx ( & sheet ) . map_err ( | e | {
(
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
)
} ) ? ;
Ok ( (
[ (
axum ::http ::header ::CONTENT_TYPE ,
" application/vnd.openxmlformats-officedocument.spreadsheetml.sheet " ,
) ] ,
xlsx ,
) )
}
" json " = > {
let json = export_to_json ( & sheet ) ;
Ok ( ( [ ( axum ::http ::header ::CONTENT_TYPE , " application/json " ) ] , json ) )
}
2026-01-11 12:22:14 -03:00
" html " = > {
let html = export_to_html ( & sheet ) ;
Ok ( ( [ ( axum ::http ::header ::CONTENT_TYPE , " text/html " ) ] , html ) )
}
" ods " = > {
let ods = export_to_ods ( & sheet ) . map_err ( | e | {
(
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
)
} ) ? ;
Ok ( (
[ (
axum ::http ::header ::CONTENT_TYPE ,
" application/vnd.oasis.opendocument.spreadsheet " ,
) ] ,
ods ,
) )
}
" md " | " markdown " = > {
let md = export_to_markdown ( & sheet ) ;
Ok ( ( [ ( axum ::http ::header ::CONTENT_TYPE , " text/markdown " ) ] , md ) )
}
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
_ = > Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Unsupported format " } ) ) ,
) ) ,
}
}
pub async fn handle_share_sheet (
Json ( req ) : Json < ShareRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( format! ( " Shared with {} as {} " , req . email , req . permission ) ) ,
} ) )
}
pub async fn handle_get_sheet_by_id (
State ( state ) : State < Arc < AppState > > ,
Path ( sheet_id ) : Path < String > ,
) -> Result < Json < Spreadsheet > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
match load_sheet_by_id ( & state , & user_id , & sheet_id ) . await {
Ok ( sheet ) = > Ok ( Json ( sheet ) ) ,
Err ( e ) = > Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ,
}
}
pub async fn handle_merge_cells (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < MergeCellsRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let worksheet = & mut sheet . worksheets [ req . worksheet_index ] ;
let merged = MergedCell {
start_row : req . start_row ,
start_col : req . start_col ,
end_row : req . end_row ,
end_col : req . end_col ,
} ;
let merged_cells = worksheet . merged_cells . get_or_insert_with ( Vec ::new ) ;
merged_cells . push ( merged ) ;
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Cells merged " . to_string ( ) ) ,
} ) )
}
pub async fn handle_unmerge_cells (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < MergeCellsRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let worksheet = & mut sheet . worksheets [ req . worksheet_index ] ;
if let Some ( ref mut merged_cells ) = worksheet . merged_cells {
merged_cells . retain ( | m | {
! ( m . start_row = = req . start_row
& & m . start_col = = req . start_col
& & m . end_row = = req . end_row
& & m . end_col = = req . end_col )
} ) ;
}
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Cells unmerged " . to_string ( ) ) ,
} ) )
}
pub async fn handle_freeze_panes (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < FreezePanesRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let worksheet = & mut sheet . worksheets [ req . worksheet_index ] ;
worksheet . frozen_rows = Some ( req . frozen_rows ) ;
worksheet . frozen_cols = Some ( req . frozen_cols ) ;
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Panes frozen " . to_string ( ) ) ,
} ) )
}
pub async fn handle_sort_range (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < SortRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let worksheet = & mut sheet . worksheets [ req . worksheet_index ] ;
let mut rows : Vec < Vec < Option < CellData > > > = Vec ::new ( ) ;
for row in req . start_row ..= req . end_row {
let mut row_data = Vec ::new ( ) ;
for col in req . start_col ..= req . end_col {
let key = format! ( " {} , {} " , row , col ) ;
row_data . push ( worksheet . data . get ( & key ) . cloned ( ) ) ;
}
rows . push ( row_data ) ;
}
let sort_col_idx = ( req . sort_col - req . start_col ) as usize ;
rows . sort_by ( | a , b | {
let val_a = a
. get ( sort_col_idx )
. and_then ( | c | c . as_ref ( ) )
. and_then ( | c | c . value . clone ( ) )
. unwrap_or_default ( ) ;
let val_b = b
. get ( sort_col_idx )
. and_then ( | c | c . as_ref ( ) )
. and_then ( | c | c . value . clone ( ) )
. unwrap_or_default ( ) ;
let num_a = val_a . parse ::< f64 > ( ) . ok ( ) ;
let num_b = val_b . parse ::< f64 > ( ) . ok ( ) ;
let cmp = match ( num_a , num_b ) {
( Some ( na ) , Some ( nb ) ) = > na . partial_cmp ( & nb ) . unwrap_or ( std ::cmp ::Ordering ::Equal ) ,
_ = > val_a . cmp ( & val_b ) ,
} ;
if req . ascending {
cmp
} else {
cmp . reverse ( )
}
} ) ;
for ( row_offset , row_data ) in rows . iter ( ) . enumerate ( ) {
for ( col_offset , cell ) in row_data . iter ( ) . enumerate ( ) {
let key = format! (
" {},{} " ,
req . start_row + row_offset as u32 ,
req . start_col + col_offset as u32
) ;
if let Some ( c ) = cell {
worksheet . data . insert ( key , c . clone ( ) ) ;
} else {
worksheet . data . remove ( & key ) ;
}
}
}
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Range sorted " . to_string ( ) ) ,
} ) )
}
pub async fn handle_filter_data (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < FilterRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let worksheet = & mut sheet . worksheets [ req . worksheet_index ] ;
let filters = worksheet . filters . get_or_insert_with ( HashMap ::new ) ;
filters . insert (
req . col ,
FilterConfig {
filter_type : req . filter_type ,
values : req . values ,
condition : req . condition ,
value1 : req . value1 ,
value2 : req . value2 ,
} ,
) ;
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Filter applied " . to_string ( ) ) ,
} ) )
}
pub async fn handle_clear_filter (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < ClearFilterRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let worksheet = & mut sheet . worksheets [ req . worksheet_index ] ;
if let Some ( ref mut filters ) = worksheet . filters {
if let Some ( col ) = req . col {
filters . remove ( & col ) ;
} else {
filters . clear ( ) ;
}
}
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Filter cleared " . to_string ( ) ) ,
} ) )
}
pub async fn handle_create_chart (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < ChartRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let worksheet = & mut sheet . worksheets [ req . worksheet_index ] ;
let chart = ChartConfig {
id : Uuid ::new_v4 ( ) . to_string ( ) ,
chart_type : req . chart_type ,
title : req . title . unwrap_or_else ( | | " Chart " . to_string ( ) ) ,
data_range : req . data_range ,
label_range : req . label_range . unwrap_or_default ( ) ,
position : req . position . unwrap_or ( ChartPosition {
row : 0 ,
col : 5 ,
width : 400 ,
height : 300 ,
} ) ,
options : ChartOptions ::default ( ) ,
datasets : vec ! [ ] ,
labels : vec ! [ ] ,
} ;
let charts = worksheet . charts . get_or_insert_with ( Vec ::new ) ;
charts . push ( chart ) ;
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Chart created " . to_string ( ) ) ,
} ) )
}
pub async fn handle_delete_chart (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < DeleteChartRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let worksheet = & mut sheet . worksheets [ req . worksheet_index ] ;
if let Some ( ref mut charts ) = worksheet . charts {
charts . retain ( | c | c . id ! = req . chart_id ) ;
}
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Chart deleted " . to_string ( ) ) ,
} ) )
}
pub async fn handle_conditional_format (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < ConditionalFormatRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let worksheet = & mut sheet . worksheets [ req . worksheet_index ] ;
let rule = ConditionalFormatRule {
id : Uuid ::new_v4 ( ) . to_string ( ) ,
start_row : req . start_row ,
start_col : req . start_col ,
end_row : req . end_row ,
end_col : req . end_col ,
rule_type : req . rule_type ,
condition : req . condition ,
style : req . style ,
priority : 1 ,
} ;
let formats = worksheet . conditional_formats . get_or_insert_with ( Vec ::new ) ;
formats . push ( rule ) ;
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Conditional format applied " . to_string ( ) ) ,
} ) )
}
pub async fn handle_data_validation (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < DataValidationRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let worksheet = & mut sheet . worksheets [ req . worksheet_index ] ;
let validations = worksheet . validations . get_or_insert_with ( HashMap ::new ) ;
for row in req . start_row ..= req . end_row {
for col in req . start_col ..= req . end_col {
let key = format! ( " {} , {} " , row , col ) ;
validations . insert (
key ,
ValidationRule {
validation_type : req . validation_type . clone ( ) ,
operator : req . operator . clone ( ) ,
value1 : req . value1 . clone ( ) ,
value2 : req . value2 . clone ( ) ,
allowed_values : req . allowed_values . clone ( ) ,
error_title : None ,
error_message : req . error_message . clone ( ) ,
input_title : None ,
input_message : None ,
} ,
) ;
}
}
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Data validation applied " . to_string ( ) ) ,
} ) )
}
pub async fn handle_validate_cell (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < ValidateCellRequest > ,
) -> Result < Json < ValidationResult > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let worksheet = & sheet . worksheets [ req . worksheet_index ] ;
let key = format! ( " {} , {} " , req . row , req . col ) ;
if let Some ( ref validations ) = worksheet . validations {
if let Some ( rule ) = validations . get ( & key ) {
let result = validate_value ( & req . value , rule ) ;
return Ok ( Json ( result ) ) ;
}
}
Ok ( Json ( ValidationResult {
valid : true ,
error_message : None ,
} ) )
}
fn validate_value ( value : & str , rule : & ValidationRule ) -> ValidationResult {
let valid = match rule . validation_type . as_str ( ) {
" number " = > value . parse ::< f64 > ( ) . is_ok ( ) ,
" integer " = > value . parse ::< i64 > ( ) . is_ok ( ) ,
" list " = > rule
. allowed_values
. as_ref ( )
. map ( | v | v . contains ( & value . to_string ( ) ) )
. unwrap_or ( true ) ,
" date " = > chrono ::NaiveDate ::parse_from_str ( value , " %Y-%m-%d " ) . is_ok ( ) ,
" text_length " = > {
let len = value . len ( ) ;
let min = rule . value1 . as_ref ( ) . and_then ( | v | v . parse ::< usize > ( ) . ok ( ) ) . unwrap_or ( 0 ) ;
let max = rule . value2 . as_ref ( ) . and_then ( | v | v . parse ::< usize > ( ) . ok ( ) ) . unwrap_or ( usize ::MAX ) ;
len > = min & & len < = max
}
_ = > true ,
} ;
ValidationResult {
valid ,
error_message : if valid {
None
} else {
rule . error_message . clone ( ) . or_else ( | | Some ( " Invalid value " . to_string ( ) ) )
} ,
}
}
pub async fn handle_add_note (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < AddNoteRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let worksheet = & mut sheet . worksheets [ req . worksheet_index ] ;
let key = format! ( " {} , {} " , req . row , req . col ) ;
let cell = worksheet . data . entry ( key ) . or_insert_with ( | | CellData {
value : None ,
formula : None ,
style : None ,
format : None ,
note : None ,
} ) ;
cell . note = Some ( req . note ) ;
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Note added " . to_string ( ) ) ,
} ) )
}
pub async fn handle_import_sheet (
2026-01-11 12:22:14 -03:00
State ( state ) : State < Arc < AppState > > ,
mut multipart : axum ::extract ::Multipart ,
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
) -> Result < Json < Spreadsheet > , ( StatusCode , Json < serde_json ::Value > ) > {
2026-01-11 12:22:14 -03:00
let mut file_bytes : Option < Vec < u8 > > = None ;
let mut filename = " import.xlsx " . to_string ( ) ;
while let Ok ( Some ( field ) ) = multipart . next_field ( ) . await {
if field . name ( ) = = Some ( " file " ) {
filename = field . file_name ( ) . unwrap_or ( " import.xlsx " ) . to_string ( ) ;
if let Ok ( bytes ) = field . bytes ( ) . await {
file_bytes = Some ( bytes . to_vec ( ) ) ;
}
}
}
let bytes = file_bytes . ok_or_else ( | | {
(
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " No file uploaded " } ) ) ,
)
} ) ? ;
let mut sheet = import_spreadsheet_bytes ( & bytes , & filename ) . map_err ( | e | {
(
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
)
} ) ? ;
let user_id = get_current_user_id ( ) ;
sheet . owner_id = user_id . clone ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( sheet ) )
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
}
2026-01-11 12:13:10 -03:00
pub async fn handle_add_comment (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < AddCommentRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let worksheet = & mut sheet . worksheets [ req . worksheet_index ] ;
let key = format! ( " {} , {} " , req . row , req . col ) ;
let comment = CellComment {
id : Uuid ::new_v4 ( ) . to_string ( ) ,
author_id : user_id . clone ( ) ,
author_name : " User " . to_string ( ) ,
content : req . content ,
created_at : Utc ::now ( ) ,
updated_at : Utc ::now ( ) ,
replies : vec ! [ ] ,
resolved : false ,
} ;
let comments = worksheet . comments . get_or_insert_with ( HashMap ::new ) ;
comments . insert ( key . clone ( ) , comment ) ;
let cell = worksheet . data . entry ( key ) . or_insert_with ( | | CellData {
value : None ,
formula : None ,
style : None ,
format : None ,
note : None ,
locked : None ,
has_comment : None ,
array_formula_id : None ,
} ) ;
cell . has_comment = Some ( true ) ;
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Comment added " . to_string ( ) ) ,
} ) )
}
pub async fn handle_reply_comment (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < ReplyCommentRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let worksheet = & mut sheet . worksheets [ req . worksheet_index ] ;
let key = format! ( " {} , {} " , req . row , req . col ) ;
if let Some ( comments ) = & mut worksheet . comments {
if let Some ( comment ) = comments . get_mut ( & key ) {
if comment . id = = req . comment_id {
let reply = CommentReply {
id : Uuid ::new_v4 ( ) . to_string ( ) ,
author_id : user_id . clone ( ) ,
author_name : " User " . to_string ( ) ,
content : req . content ,
created_at : Utc ::now ( ) ,
} ;
comment . replies . push ( reply ) ;
comment . updated_at = Utc ::now ( ) ;
}
}
}
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Reply added " . to_string ( ) ) ,
} ) )
}
pub async fn handle_resolve_comment (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < ResolveCommentRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let worksheet = & mut sheet . worksheets [ req . worksheet_index ] ;
let key = format! ( " {} , {} " , req . row , req . col ) ;
if let Some ( comments ) = & mut worksheet . comments {
if let Some ( comment ) = comments . get_mut ( & key ) {
if comment . id = = req . comment_id {
comment . resolved = req . resolved ;
comment . updated_at = Utc ::now ( ) ;
}
}
}
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Comment resolved " . to_string ( ) ) ,
} ) )
}
pub async fn handle_delete_comment (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < DeleteCommentRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let worksheet = & mut sheet . worksheets [ req . worksheet_index ] ;
let key = format! ( " {} , {} " , req . row , req . col ) ;
if let Some ( comments ) = & mut worksheet . comments {
comments . remove ( & key ) ;
}
if let Some ( cell ) = worksheet . data . get_mut ( & key ) {
cell . has_comment = Some ( false ) ;
}
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Comment deleted " . to_string ( ) ) ,
} ) )
}
pub async fn handle_list_comments (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < ListCommentsRequest > ,
) -> Result < Json < ListCommentsResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let worksheet = & sheet . worksheets [ req . worksheet_index ] ;
let mut comments_list = vec! [ ] ;
if let Some ( comments ) = & worksheet . comments {
for ( key , comment ) in comments {
let parts : Vec < & str > = key . split ( ',' ) . collect ( ) ;
if parts . len ( ) = = 2 {
if let ( Ok ( row ) , Ok ( col ) ) = ( parts [ 0 ] . parse ::< u32 > ( ) , parts [ 1 ] . parse ::< u32 > ( ) ) {
comments_list . push ( CommentWithLocation {
row ,
col ,
comment : comment . clone ( ) ,
} ) ;
}
}
}
}
Ok ( Json ( ListCommentsResponse { comments : comments_list } ) )
}
pub async fn handle_protect_sheet (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < ProtectSheetRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let mut protection = req . protection ;
if let Some ( password ) = req . password {
protection . password_hash = Some ( format! ( " {:x} " , md5 ::compute ( password . as_bytes ( ) ) ) ) ;
}
sheet . worksheets [ req . worksheet_index ] . protection = Some ( protection ) ;
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Sheet protected " . to_string ( ) ) ,
} ) )
}
pub async fn handle_unprotect_sheet (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < UnprotectSheetRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let worksheet = & mut sheet . worksheets [ req . worksheet_index ] ;
if let Some ( protection ) = & worksheet . protection {
if let Some ( hash ) = & protection . password_hash {
if let Some ( password ) = & req . password {
let provided_hash = format! ( " {:x} " , md5 ::compute ( password . as_bytes ( ) ) ) ;
if & provided_hash ! = hash {
return Err ( (
StatusCode ::UNAUTHORIZED ,
Json ( serde_json ::json! ( { " error " : " Invalid password " } ) ) ,
) ) ;
}
} else {
return Err ( (
StatusCode ::UNAUTHORIZED ,
Json ( serde_json ::json! ( { " error " : " Password required " } ) ) ,
) ) ;
}
}
}
worksheet . protection = None ;
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Sheet unprotected " . to_string ( ) ) ,
} ) )
}
pub async fn handle_lock_cells (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < LockCellsRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let worksheet = & mut sheet . worksheets [ req . worksheet_index ] ;
for row in req . start_row ..= req . end_row {
for col in req . start_col ..= req . end_col {
let key = format! ( " {row} , {col} " ) ;
let cell = worksheet . data . entry ( key ) . or_insert_with ( | | CellData {
value : None ,
formula : None ,
style : None ,
format : None ,
note : None ,
locked : None ,
has_comment : None ,
array_formula_id : None ,
} ) ;
cell . locked = Some ( req . locked ) ;
}
}
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( if req . locked { " Cells locked " } else { " Cells unlocked " } . to_string ( ) ) ,
} ) )
}
pub async fn handle_add_external_link (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < AddExternalLinkRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
let link = ExternalLink {
id : Uuid ::new_v4 ( ) . to_string ( ) ,
source_path : req . source_path ,
link_type : req . link_type ,
target_sheet : req . target_sheet ,
target_range : req . target_range ,
status : " active " . to_string ( ) ,
last_updated : Utc ::now ( ) ,
} ;
let links = sheet . external_links . get_or_insert_with ( Vec ::new ) ;
links . push ( link ) ;
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " External link added " . to_string ( ) ) ,
} ) )
}
pub async fn handle_refresh_external_link (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < RefreshExternalLinkRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if let Some ( links ) = & mut sheet . external_links {
for link in links . iter_mut ( ) {
if link . id = = req . link_id {
link . last_updated = Utc ::now ( ) ;
link . status = " refreshed " . to_string ( ) ;
}
}
}
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " External link refreshed " . to_string ( ) ) ,
} ) )
}
pub async fn handle_remove_external_link (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < RemoveExternalLinkRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if let Some ( links ) = & mut sheet . external_links {
links . retain ( | link | link . id ! = req . link_id ) ;
}
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " External link removed " . to_string ( ) ) ,
} ) )
}
pub async fn handle_list_external_links (
State ( state ) : State < Arc < AppState > > ,
Query ( params ) : Query < HashMap < String , String > > ,
) -> Result < Json < ListExternalLinksResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let sheet_id = params . get ( " sheet_id " ) . cloned ( ) . unwrap_or_default ( ) ;
let user_id = get_current_user_id ( ) ;
let sheet = match load_sheet_by_id ( & state , & user_id , & sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
let links = sheet . external_links . unwrap_or_default ( ) ;
Ok ( Json ( ListExternalLinksResponse { links } ) )
}
pub async fn handle_array_formula (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < ArrayFormulaRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let array_formula_id = Uuid ::new_v4 ( ) . to_string ( ) ;
let array_formula = ArrayFormula {
id : array_formula_id . clone ( ) ,
formula : req . formula . clone ( ) ,
start_row : req . start_row ,
start_col : req . start_col ,
end_row : req . end_row ,
end_col : req . end_col ,
is_dynamic : req . formula . starts_with ( '=' ) & & req . formula . contains ( '#' ) ,
} ;
let worksheet = & mut sheet . worksheets [ req . worksheet_index ] ;
let array_formulas = worksheet . array_formulas . get_or_insert_with ( Vec ::new ) ;
array_formulas . push ( array_formula ) ;
for row in req . start_row ..= req . end_row {
for col in req . start_col ..= req . end_col {
let key = format! ( " {row} , {col} " ) ;
let cell = worksheet . data . entry ( key ) . or_insert_with ( | | CellData {
value : None ,
formula : None ,
style : None ,
format : None ,
note : None ,
locked : None ,
has_comment : None ,
array_formula_id : None ,
} ) ;
cell . array_formula_id = Some ( array_formula_id . clone ( ) ) ;
if row = = req . start_row & & col = = req . start_col {
cell . formula = Some ( format! ( " {{ {} }} " , req . formula ) ) ;
}
}
}
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Array formula created " . to_string ( ) ) ,
} ) )
}
pub async fn handle_delete_array_formula (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < DeleteArrayFormulaRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if req . worksheet_index > = sheet . worksheets . len ( ) {
return Err ( (
StatusCode ::BAD_REQUEST ,
Json ( serde_json ::json! ( { " error " : " Invalid worksheet index " } ) ) ,
) ) ;
}
let worksheet = & mut sheet . worksheets [ req . worksheet_index ] ;
if let Some ( array_formulas ) = & mut worksheet . array_formulas {
array_formulas . retain ( | af | af . id ! = req . array_formula_id ) ;
}
for cell in worksheet . data . values_mut ( ) {
if cell . array_formula_id . as_ref ( ) = = Some ( & req . array_formula_id ) {
cell . array_formula_id = None ;
cell . formula = None ;
}
}
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Array formula deleted " . to_string ( ) ) ,
} ) )
}
pub async fn handle_create_named_range (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < CreateNamedRangeRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
let named_range = NamedRange {
id : Uuid ::new_v4 ( ) . to_string ( ) ,
name : req . name ,
scope : req . scope ,
worksheet_index : req . worksheet_index ,
start_row : req . start_row ,
start_col : req . start_col ,
end_row : req . end_row ,
end_col : req . end_col ,
comment : req . comment ,
} ;
let named_ranges = sheet . named_ranges . get_or_insert_with ( Vec ::new ) ;
named_ranges . push ( named_range ) ;
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Named range created " . to_string ( ) ) ,
} ) )
}
pub async fn handle_update_named_range (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < UpdateNamedRangeRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if let Some ( named_ranges ) = & mut sheet . named_ranges {
for range in named_ranges . iter_mut ( ) {
if range . id = = req . range_id {
if let Some ( name ) = req . name {
range . name = name ;
}
if let Some ( start_row ) = req . start_row {
range . start_row = start_row ;
}
if let Some ( start_col ) = req . start_col {
range . start_col = start_col ;
}
if let Some ( end_row ) = req . end_row {
range . end_row = end_row ;
}
if let Some ( end_col ) = req . end_col {
range . end_col = end_col ;
}
if let Some ( comment ) = req . comment {
range . comment = Some ( comment ) ;
}
}
}
}
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Named range updated " . to_string ( ) ) ,
} ) )
}
pub async fn handle_delete_named_range (
State ( state ) : State < Arc < AppState > > ,
Json ( req ) : Json < DeleteNamedRangeRequest > ,
) -> Result < Json < SaveResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let user_id = get_current_user_id ( ) ;
let mut sheet = match load_sheet_by_id ( & state , & user_id , & req . sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
if let Some ( named_ranges ) = & mut sheet . named_ranges {
named_ranges . retain ( | r | r . id ! = req . range_id ) ;
}
sheet . updated_at = Utc ::now ( ) ;
if let Err ( e ) = save_sheet_to_drive ( & state , & user_id , & sheet ) . await {
return Err ( (
StatusCode ::INTERNAL_SERVER_ERROR ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) ) ;
}
Ok ( Json ( SaveResponse {
id : req . sheet_id ,
success : true ,
message : Some ( " Named range deleted " . to_string ( ) ) ,
} ) )
}
pub async fn handle_list_named_ranges (
State ( state ) : State < Arc < AppState > > ,
Query ( params ) : Query < HashMap < String , String > > ,
) -> Result < Json < ListNamedRangesResponse > , ( StatusCode , Json < serde_json ::Value > ) > {
let sheet_id = params . get ( " sheet_id " ) . cloned ( ) . unwrap_or_default ( ) ;
let user_id = get_current_user_id ( ) ;
let sheet = match load_sheet_by_id ( & state , & user_id , & sheet_id ) . await {
Ok ( s ) = > s ,
Err ( e ) = > {
return Err ( (
StatusCode ::NOT_FOUND ,
Json ( serde_json ::json! ( { " error " : e } ) ) ,
) )
}
} ;
let ranges = sheet . named_ranges . unwrap_or_default ( ) ;
Ok ( Json ( ListNamedRangesResponse { ranges } ) )
}