use serde::{Deserialize, Serialize}; use serde_json::Value; use tauri::State; use crate::db::connection::DbManager; use crate::domains::chapter::service; use crate::error::AppError; use crate::helpers::timestamp_in_seconds; use crate::shared::session::SessionState; use crate::shared::types::Lang; fn get_session(session: &State) -> Result<(String, Lang), AppError> { let session_guard = session.lock().map_err(|e| AppError::Internal(format!("Session lock failed: {}", e)))?; let user_id = session_guard.get_user_id().map_err(|e| AppError::Auth(e))?.to_string(); let lang = session_guard.lang; Ok((user_id, lang)) } // ─── Queries ────────────────────────────────────────── #[tauri::command] pub fn get_chapters(book_id: String, db: State, session: State) -> Result, AppError> { let (user_id, lang) = get_session(&session)?; let db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?; let conn = db_manager.get_connection(&user_id)?; service::get_all_chapters_from_a_book(conn, &user_id, &book_id, lang) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct GetWholeChapterData { pub id: String, pub version: i64, pub book_id: String, } #[tauri::command] pub fn get_whole_chapter(data: GetWholeChapterData, db: State, session: State) -> Result { let (user_id, lang) = get_session(&session)?; let db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?; let conn = db_manager.get_connection(&user_id)?; service::get_whole_chapter(conn, &user_id, &data.id, data.version, Some(&data.book_id), lang) } #[tauri::command] pub fn get_chapter_story(chapter_id: String, db: State, session: State) -> Result, AppError> { let (user_id, lang) = get_session(&session)?; let db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?; let conn = db_manager.get_connection(&user_id)?; service::get_chapter_story(conn, &user_id, &chapter_id, lang) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct GetCompanionData { pub chapter_id: String, pub version: i64, } #[tauri::command] pub fn get_companion_content(data: GetCompanionData, db: State, session: State) -> Result { let (user_id, lang) = get_session(&session)?; let db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?; let conn = db_manager.get_connection(&user_id)?; service::get_companion_content(conn, &user_id, &data.chapter_id, data.version, lang) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct GetChapterContentData { pub chapter_id: String, pub version: i64, } #[tauri::command] pub fn get_chapter_content(data: GetChapterContentData, db: State, session: State) -> Result { let (user_id, lang) = get_session(&session)?; let db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?; let conn = db_manager.get_connection(&user_id)?; service::get_chapter_content_by_version(conn, &user_id, &data.chapter_id, data.version, lang) } // ─── Mutations ──────────────────────────────────────── #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct SaveChapterContentData { pub chapter_id: String, pub version: i64, pub content: Value, pub total_word_count: i64, pub content_id: String, } #[tauri::command] pub fn save_chapter_content(data: SaveChapterContentData, db: State, session: State) -> Result { let (user_id, lang) = get_session(&session)?; let db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?; let conn = db_manager.get_connection(&user_id)?; let content_str = serde_json::to_string(&data.content).map_err(|e| AppError::Internal(format!("JSON serialize failed: {}", e)))?; service::save_chapter_content(conn, &user_id, &data.chapter_id, data.version, &content_str, data.total_word_count, timestamp_in_seconds(), lang) } #[tauri::command] pub fn get_last_chapter(book_id: String, db: State, session: State) -> Result, AppError> { let (user_id, lang) = get_session(&session)?; let db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?; let conn = db_manager.get_connection(&user_id)?; service::get_last_chapter(conn, &user_id, &book_id, lang) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct AddChapterData { pub book_id: String, pub title: String, pub chapter_order: i64, pub chapter_id: Option, } #[tauri::command] pub fn add_chapter(data: AddChapterData, db: State, session: State) -> Result { let (user_id, lang) = get_session(&session)?; let db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?; let conn = db_manager.get_connection(&user_id)?; service::add_chapter(conn, &user_id, &data.book_id, &data.title, 0, data.chapter_order, lang, data.chapter_id.as_deref()) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct RemoveChapterData { pub chapter_id: String, pub book_id: String, pub deleted_at: i64, } #[tauri::command] pub fn remove_chapter(data: RemoveChapterData, db: State, session: State) -> Result { let (user_id, lang) = get_session(&session)?; let db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?; let conn = db_manager.get_connection(&user_id)?; service::remove_chapter(conn, &user_id, &data.book_id, &data.chapter_id, data.deleted_at, lang) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct UpdateChapterData { pub chapter_id: String, pub title: String, pub chapter_order: i64, } #[tauri::command] pub fn update_chapter(data: UpdateChapterData, db: State, session: State) -> Result { let (user_id, lang) = get_session(&session)?; let db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?; let conn = db_manager.get_connection(&user_id)?; service::update_chapter(conn, &user_id, &data.chapter_id, &data.title, data.chapter_order, lang) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct AddChapterInfoData { pub chapter_id: String, pub act_id: i64, pub book_id: String, pub plot_id: Option, pub incident_id: Option, pub chapter_info_id: Option, } #[tauri::command] pub fn add_chapter_information(data: AddChapterInfoData, db: State, session: State) -> Result { let (user_id, lang) = get_session(&session)?; let db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?; let conn = db_manager.get_connection(&user_id)?; service::add_chapter_information(conn, &user_id, &data.chapter_id, data.act_id, &data.book_id, data.plot_id.as_deref(), data.incident_id.as_deref(), lang, data.chapter_info_id.as_deref()) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct RemoveChapterInfoData { pub chapter_info_id: String, pub book_id: String, pub deleted_at: i64, } #[tauri::command] pub fn remove_chapter_information(data: RemoveChapterInfoData, db: State, session: State) -> Result { let (user_id, lang) = get_session(&session)?; let db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?; let conn = db_manager.get_connection(&user_id)?; service::remove_chapter_information(conn, &user_id, &data.book_id, &data.chapter_info_id, data.deleted_at, lang) } // ─── Book Tags (aggregate) ─────────────────────────── #[derive(Serialize)] pub struct Tag { pub label: String, pub value: String, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct BookTags { pub characters: Vec, pub locations: Vec, pub objects: Vec, pub world_elements: Vec, } #[tauri::command] pub fn get_book_tags(book_id: String, db: State, session: State) -> Result { let (user_id, lang) = get_session(&session)?; let db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?; let conn = db_manager.get_connection(&user_id)?; let character_response = crate::domains::character::service::get_character_list(conn, &user_id, &book_id, lang)?; let character_tags: Vec = character_response.characters.into_iter().map(|character| Tag { label: character.name, value: character.id }).collect(); let location_elements = crate::domains::location::service::get_location_tags(conn, &user_id, &book_id, lang)?; let location_tags: Vec = location_elements.into_iter().map(|element| Tag { label: element.name, value: element.id }).collect(); let spell_response = crate::domains::spell::service::get_spell_list(conn, &user_id, &book_id, lang)?; let object_tags: Vec = spell_response.spells.into_iter().map(|spell| Tag { label: spell.name, value: spell.id }).collect(); let world_response = crate::domains::world::service::get_worlds(conn, &user_id, &book_id, lang)?; let mut world_tags: Vec = Vec::new(); for world in &world_response.worlds { for element_list in [&world.laws, &world.biomes, &world.issues, &world.customs, &world.kingdoms, &world.climate, &world.resources, &world.wildlife, &world.arts, &world.ethnic_groups, &world.social_classes, &world.important_characters] { for element in element_list { world_tags.push(Tag { label: element.name.clone(), value: element.id.clone() }); } } } Ok(BookTags { characters: character_tags, locations: location_tags, objects: object_tags, world_elements: world_tags }) }