use crate::core::shared::state::AppState; use crate::docs::storage::{get_current_user_id, load_document_from_drive, save_document}; use crate::docs::types::{GenerateTocRequest, TableOfContents, TocEntry, TocResponse, UpdateTocRequest}; use crate::docs::utils::strip_html; use axum::{ extract::State, http::StatusCode, Json, }; use chrono::Utc; use std::sync::Arc; pub async fn handle_generate_toc( State(state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); let mut doc = match load_document_from_drive(&state, &user_id, &req.doc_id).await { Ok(Some(d)) => d, Ok(None) => { return Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": "Document not found" })), )) } Err(e) => { return Err(( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e })), )) } }; let mut entries = Vec::new(); let content = &doc.content; for level in 1..=req.max_level { let tag = format!(""); let end_tag = format!(""); let mut search_pos = 0; while let Some(start) = content[search_pos..].find(&tag) { let abs_start = search_pos + start; if let Some(end) = content[abs_start..].find(&end_tag) { let text_start = abs_start + tag.len(); let text_end = abs_start + end; let text = strip_html(&content[text_start..text_end]); entries.push(TocEntry { id: uuid::Uuid::new_v4().to_string(), text, level, page_number: None, position: abs_start, }); search_pos = text_end + end_tag.len(); } else { break; } } } entries.sort_by_key(|e| e.position); let toc = TableOfContents { id: uuid::Uuid::new_v4().to_string(), title: "Table of Contents".to_string(), entries, max_level: req.max_level, show_page_numbers: req.show_page_numbers, use_hyperlinks: req.use_hyperlinks, }; doc.toc = Some(toc.clone()); doc.updated_at = Utc::now(); if let Err(e) = save_document(&state, &user_id, &doc).await { return Err(( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e })), )); } Ok(Json(TocResponse { toc })) } pub async fn handle_update_toc( State(state): State>, Json(req): Json, ) -> Result, (StatusCode, Json)> { let user_id = get_current_user_id(); let doc = match load_document_from_drive(&state, &user_id, &req.doc_id).await { Ok(Some(d)) => d, Ok(None) => { return Err(( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": "Document not found" })), )) } Err(e) => { return Err(( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e })), )) } }; let existing_toc = doc.toc.unwrap_or_else(|| TableOfContents { id: uuid::Uuid::new_v4().to_string(), title: "Table of Contents".to_string(), entries: vec![], max_level: 3, show_page_numbers: true, use_hyperlinks: true, }); let gen_req = GenerateTocRequest { doc_id: req.doc_id, max_level: existing_toc.max_level, show_page_numbers: existing_toc.show_page_numbers, use_hyperlinks: existing_toc.use_hyperlinks, }; handle_generate_toc(State(state), Json(gen_req)).await }