diff --git a/src-tauri/src/domains/act/service.rs b/src-tauri/src/domains/act/service.rs index 30c3f54..f85350a 100644 --- a/src-tauri/src/domains/act/service.rs +++ b/src-tauri/src/domains/act/service.rs @@ -7,7 +7,9 @@ use crate::domains::act::repo; use crate::domains::chapter::repo as chapter_repo; use crate::domains::chapter::service as chapter_service; use crate::domains::incident::repo as incident_repo; +use crate::domains::incident::service as incident_service; use crate::domains::plotpoint::repo as plotpoint_repo; +use crate::domains::plotpoint::service as plotpoint_service; use crate::error::AppResult; use crate::helpers::{create_unique_id, timestamp_in_seconds}; use crate::shared::types::Lang; @@ -50,38 +52,8 @@ pub struct SyncedActSummary { pub last_update: i64, } -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct IncidentProps { - pub incident_id: String, - pub title: String, - pub summary: String, - pub chapters: Option>, -} - -pub struct IncidentStory { - pub incident_title: String, - pub incident_summary: String, - pub chapter_summary: String, - pub chapter_goal: String, -} - -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct PlotPointProps { - pub plot_point_id: String, - pub title: String, - pub summary: String, - pub linked_incident_id: Option, - pub chapters: Option>, -} - -pub struct PlotPointStory { - pub plot_title: String, - pub plot_summary: String, - pub chapter_summary: String, - pub chapter_goal: String, -} +pub use incident_service::{IncidentProps, IncidentStory}; +pub use plotpoint_service::{PlotPointProps, PlotPointStory}; #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -138,97 +110,6 @@ fn get_all_chapter_from_acts(conn: &Connection, user_id: &str, book_id: &str, la Ok(act_chapters) } -/// Retrieves all incidents for a specific book with their associated chapters. -/// Decrypts incident titles and summaries using the user's encryption key. -/// * `conn` - Database connection -/// * `user_id` - The unique identifier of the user -/// * `book_id` - The unique identifier of the book -/// * `act_chapters` - Array of chapters from acts to associate with incidents -/// * `lang` - The language for error messages ("fr" or "en") -/// Returns a list of incident properties with decrypted data. -fn get_incidents(conn: &Connection, user_id: &str, book_id: &str, act_chapters: &[ActChapter], lang: Lang) -> AppResult> { - let incident_query_results: Vec = incident_repo::fetch_all_incitent_incidents(conn, user_id, book_id, lang)?; - let user_key: String = get_user_encryption_key(user_id)?; - let mut incidents: Vec = Vec::new(); - - if incident_query_results.is_empty() { - return Ok(incidents); - } - - for incident_record in &incident_query_results { - let mut associated_chapters: Vec = Vec::new(); - for chapter in act_chapters { - if chapter.incident_id.as_deref() == Some(&incident_record.incident_id) { - associated_chapters.push(ActChapter { - chapter_info_id: chapter.chapter_info_id, - chapter_id: chapter.chapter_id.clone(), - title: chapter.title.clone(), - chapter_order: chapter.chapter_order, - act_id: chapter.act_id, - incident_id: chapter.incident_id.clone(), - plot_point_id: chapter.plot_point_id.clone(), - summary: chapter.summary.clone(), - goal: chapter.goal.clone(), - }); - } - } - incidents.push(IncidentProps { - incident_id: incident_record.incident_id.clone(), - title: if incident_record.title.is_empty() { String::new() } else { decrypt_data_with_user_key(&incident_record.title, &user_key)? }, - summary: if incident_record.summary.is_empty() { String::new() } else { decrypt_data_with_user_key(&incident_record.summary, &user_key)? }, - chapters: Some(associated_chapters), - }); - } - - Ok(incidents) -} - -/// Retrieves all plot points for a specific book with their associated chapters. -/// Decrypts plot point titles and summaries using the user's encryption key. -/// * `conn` - Database connection -/// * `user_id` - The unique identifier of the user -/// * `book_id` - The unique identifier of the book -/// * `act_chapters` - Array of act chapters to associate with plot points -/// * `lang` - The language for error messages ("fr" or "en") -/// Returns a list of plot point properties with their associated chapters. -fn get_plot_points(conn: &Connection, user_id: &str, book_id: &str, act_chapters: &[ActChapter], lang: Lang) -> AppResult> { - let plot_point_query_results: Vec = plotpoint_repo::fetch_all_plot_points(conn, user_id, book_id, lang)?; - let user_encryption_key: String = get_user_encryption_key(user_id)?; - let mut plot_points: Vec = Vec::new(); - - if plot_point_query_results.is_empty() { - return Ok(plot_points); - } - - for plot_point_row in &plot_point_query_results { - let mut associated_chapters: Vec = Vec::new(); - for chapter in act_chapters { - if chapter.plot_point_id.as_deref() == Some(&plot_point_row.plot_point_id) { - associated_chapters.push(ActChapter { - chapter_info_id: chapter.chapter_info_id, - chapter_id: chapter.chapter_id.clone(), - title: chapter.title.clone(), - chapter_order: chapter.chapter_order, - act_id: chapter.act_id, - incident_id: chapter.incident_id.clone(), - plot_point_id: chapter.plot_point_id.clone(), - summary: chapter.summary.clone(), - goal: chapter.goal.clone(), - }); - } - } - plot_points.push(PlotPointProps { - plot_point_id: plot_point_row.plot_point_id.clone(), - title: if plot_point_row.title.is_empty() { String::new() } else { decrypt_data_with_user_key(&plot_point_row.title, &user_encryption_key)? }, - summary: if plot_point_row.summary.is_empty() { String::new() } else { decrypt_data_with_user_key(&plot_point_row.summary, &user_encryption_key)? }, - linked_incident_id: plot_point_row.linked_incident_id.clone(), - chapters: Some(associated_chapters), - }); - } - - Ok(plot_points) -} - /// Updates chapter information for multiple chapters including summary and goal. /// Encrypts summaries and goals before storing in the database. /// * `conn` - Database connection @@ -261,8 +142,8 @@ pub fn get_acts_data(conn: &Connection, user_id: &str, book_id: &str, lang: Lang let user_encryption_key: String = get_user_encryption_key(user_id)?; let act_chapters: Vec = get_all_chapter_from_acts(conn, user_id, book_id, lang)?; let act_queries: Vec = repo::fetch_all_acts(conn, user_id, book_id, lang)?; - let book_incidents: Vec = get_incidents(conn, user_id, book_id, &act_chapters, lang)?; - let book_plot_points: Vec = get_plot_points(conn, user_id, book_id, &act_chapters, lang)?; + let book_incidents: Vec = incident_service::get_incitents_incidents(conn, user_id, book_id, &act_chapters, lang)?; + let book_plot_points: Vec = plotpoint_service::get_plot_points(conn, user_id, book_id, &act_chapters, lang)?; let mut acts: Vec = Vec::new(); diff --git a/src-tauri/src/domains/character/repo.rs b/src-tauri/src/domains/character/repo.rs index decc153..ebc20ce 100644 --- a/src-tauri/src/domains/character/repo.rs +++ b/src-tauri/src/domains/character/repo.rs @@ -82,30 +82,6 @@ pub struct AttributeResult { pub attribute_value: String, } -pub struct CompleteCharacterResult { - pub character_id: String, - pub first_name: String, - pub last_name: String, - pub nickname: Option, - pub age: Option, - pub gender: Option, - pub species: Option, - pub nationality: Option, - pub status: Option, - pub category: String, - pub title: String, - pub role: String, - pub biography: String, - pub history: String, - pub speech_pattern: Option, - pub catchphrase: Option, - pub residence: Option, - pub notes: Option, - pub color: Option, - pub attribute_name: String, - pub attribute_value: String, -} - pub struct CharacterData { pub first_name: String, pub last_name: Option, @@ -346,54 +322,6 @@ pub fn fetch_attributes(conn: &Connection, character_id: &str, user_id: &str, la /// * `book_id` - The unique identifier of the book /// * `tags` - An optional array of character IDs to filter by /// * `lang` - The language for error messages ("fr" or "en") -/// Returns an array of complete character results with attributes. -pub fn fetch_complete_characters(conn: &Connection, user_id: &str, book_id: &str, tags: &[String], lang: Lang) -> AppResult> { - let mut query = "SELECT charac.character_id, first_name, last_name, nickname, age, gender, species, nationality, status, category, title, role, biography, history, speech_pattern, catchphrase, residence, notes, color, attribute_name, attribute_value FROM book_characters AS charac LEFT JOIN book_characters_attributes AS attr ON charac.character_id=attr.character_id WHERE charac.user_id=?1 AND charac.book_id=?2".to_string(); - let mut param_values: Vec> = Vec::new(); - param_values.push(Box::new(user_id.to_string())); - param_values.push(Box::new(book_id.to_string())); - - if !tags.is_empty() { - let placeholders: String = tags.iter().enumerate().map(|(index, _)| format!("?{}", index + 3)).collect::>().join(","); - query += &format!(" AND charac.character_id IN ({})", placeholders); - for tag in tags { - param_values.push(Box::new(tag.clone())); - } - } - - let param_refs: Vec<&dyn rusqlite::types::ToSql> = param_values.iter().map(|param| param.as_ref()).collect(); - - let mut statement = conn - .prepare(&query) - .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les personnages complets.".to_string() } else { "Unable to retrieve complete characters.".to_string() }))?; - - let characters = statement - .query_map(param_refs.as_slice(), |query_row| { - Ok(CompleteCharacterResult { - character_id: query_row.get(0)?, first_name: query_row.get(1)?, - last_name: query_row.get(2)?, nickname: query_row.get(3)?, - age: query_row.get(4)?, gender: query_row.get(5)?, - species: query_row.get(6)?, nationality: query_row.get(7)?, - status: query_row.get(8)?, category: query_row.get(9)?, - title: query_row.get(10)?, role: query_row.get(11)?, - biography: query_row.get(12)?, history: query_row.get(13)?, - speech_pattern: query_row.get(14)?, catchphrase: query_row.get(15)?, - residence: query_row.get(16)?, notes: query_row.get(17)?, - color: query_row.get(18)?, attribute_name: query_row.get(19)?, - attribute_value: query_row.get(20)?, - }) - }) - .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les personnages complets.".to_string() } else { "Unable to retrieve complete characters.".to_string() }))? - .collect::, _>>() - .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les personnages complets.".to_string() } else { "Unable to retrieve complete characters.".to_string() }))?; - - if characters.is_empty() { - return Err(AppError::NotFound(if lang == Lang::Fr { "Aucun personnage complet trouvé.".to_string() } else { "No complete characters found.".to_string() })); - } - - Ok(characters) -} - /// Updates an existing character attribute. /// * `conn` - Database connection /// * `user_id` - The unique identifier of the user diff --git a/src-tauri/src/domains/character/service.rs b/src-tauri/src/domains/character/service.rs index 7ee2ccd..4f8dcbd 100644 --- a/src-tauri/src/domains/character/service.rs +++ b/src-tauri/src/domains/character/service.rs @@ -94,47 +94,6 @@ pub struct CharacterListResponse { pub enabled: bool, } -pub struct CompleteCharacterProps { - pub id: Option, - pub name: String, - pub last_name: String, - pub nickname: String, - pub age: Option, - pub gender: String, - pub species: String, - pub nationality: String, - pub status: String, - pub title: String, - pub category: String, - pub image: Option, - pub role: String, - pub biography: String, - pub history: String, - pub speech_pattern: String, - pub catchphrase: String, - pub residence: String, - pub notes: String, - pub color: String, - pub physical: Vec, - pub psychological: Vec, - pub relations: Vec, - pub skills: Vec, - pub weaknesses: Vec, - pub strengths: Vec, - pub goals: Vec, - pub motivations: Vec, - pub arc: Vec, - pub secrets: Vec, - pub fears: Vec, - pub flaws: Vec, - pub beliefs: Vec, - pub conflicts: Vec, - pub quotes: Vec, - pub distinguishing_marks: Vec, - pub items: Vec, - pub affiliations: Vec, -} - #[derive(Serialize)] pub struct Attribute { pub id: String, @@ -410,247 +369,3 @@ pub fn get_attributes(conn: &Connection, character_id: &str, user_id: &str, lang Ok(character_attributes) } -/// Retrieves complete character data including all attributes for multiple characters. -/// Used for exporting or displaying full character profiles. -/// * `conn` - Database connection -/// * `user_id` - The unique identifier of the user -/// * `book_id` - The unique identifier of the book -/// * `characters` - An array of character IDs to retrieve -/// * `lang` - The language code for localization -/// Returns an array of complete character objects with all their attributes. -pub fn get_complete_character_list(conn: &Connection, user_id: &str, book_id: &str, characters: &[String], lang: Lang) -> AppResult> { - let encrypted_character_list: Vec = match repo::fetch_complete_characters(conn, user_id, book_id, characters, lang) { - Ok(result) => result, - Err(_) => return Ok(vec![]), - }; - - if encrypted_character_list.is_empty() { - return Ok(vec![]); - } - - let user_key: String = get_user_encryption_key(user_id)?; - let mut complete_characters_map: HashMap = HashMap::new(); - - for encrypted_character in &encrypted_character_list { - if encrypted_character.character_id.is_empty() { - continue; - } - - if !complete_characters_map.contains_key(&encrypted_character.character_id) { - let decrypted_character = CompleteCharacterProps { - id: None, - name: if encrypted_character.first_name.is_empty() { String::new() } else { decrypt_data_with_user_key(&encrypted_character.first_name, &user_key)? }, - last_name: if encrypted_character.last_name.is_empty() { String::new() } else { decrypt_data_with_user_key(&encrypted_character.last_name, &user_key)? }, - nickname: if let Some(ref value) = encrypted_character.nickname { decrypt_data_with_user_key(value, &user_key)? } else { String::new() }, - age: if let Some(ref value) = encrypted_character.age { Some(decrypt_data_with_user_key(value, &user_key)?.parse::().unwrap_or(0)) } else { None }, - gender: if let Some(ref value) = encrypted_character.gender { decrypt_data_with_user_key(value, &user_key)? } else { String::new() }, - species: if let Some(ref value) = encrypted_character.species { decrypt_data_with_user_key(value, &user_key)? } else { String::new() }, - nationality: if let Some(ref value) = encrypted_character.nationality { decrypt_data_with_user_key(value, &user_key)? } else { String::new() }, - status: if let Some(ref value) = encrypted_character.status { decrypt_data_with_user_key(value, &user_key)? } else { "alive".to_string() }, - title: if encrypted_character.title.is_empty() { String::new() } else { decrypt_data_with_user_key(&encrypted_character.title, &user_key)? }, - category: if encrypted_character.category.is_empty() { String::new() } else { decrypt_data_with_user_key(&encrypted_character.category, &user_key)? }, - image: None, - role: if encrypted_character.role.is_empty() { String::new() } else { decrypt_data_with_user_key(&encrypted_character.role, &user_key)? }, - biography: if encrypted_character.biography.is_empty() { String::new() } else { decrypt_data_with_user_key(&encrypted_character.biography, &user_key)? }, - history: if encrypted_character.history.is_empty() { String::new() } else { decrypt_data_with_user_key(&encrypted_character.history, &user_key)? }, - speech_pattern: if let Some(ref value) = encrypted_character.speech_pattern { decrypt_data_with_user_key(value, &user_key)? } else { String::new() }, - catchphrase: if let Some(ref value) = encrypted_character.catchphrase { decrypt_data_with_user_key(value, &user_key)? } else { String::new() }, - residence: if let Some(ref value) = encrypted_character.residence { decrypt_data_with_user_key(value, &user_key)? } else { String::new() }, - notes: if let Some(ref value) = encrypted_character.notes { decrypt_data_with_user_key(value, &user_key)? } else { String::new() }, - color: if let Some(ref value) = encrypted_character.color { decrypt_data_with_user_key(value, &user_key)? } else { String::new() }, - physical: vec![], - psychological: vec![], - relations: vec![], - skills: vec![], - weaknesses: vec![], - strengths: vec![], - goals: vec![], - motivations: vec![], - arc: vec![], - secrets: vec![], - fears: vec![], - flaws: vec![], - beliefs: vec![], - conflicts: vec![], - quotes: vec![], - distinguishing_marks: vec![], - items: vec![], - affiliations: vec![], - }; - complete_characters_map.insert(encrypted_character.character_id.clone(), decrypted_character); - } - - let character_entry: &mut CompleteCharacterProps = match complete_characters_map.get_mut(&encrypted_character.character_id) { - Some(entry) => entry, - None => continue, - }; - - if encrypted_character.attribute_name.is_empty() { - continue; - } - - let decrypted_attribute_name: String = decrypt_data_with_user_key(&encrypted_character.attribute_name, &user_key)?; - let decrypted_attribute_value: String = if encrypted_character.attribute_value.is_empty() { String::new() } else { decrypt_data_with_user_key(&encrypted_character.attribute_value, &user_key)? }; - - let attribute = Attribute { id: String::new(), name: decrypted_attribute_value }; - - match decrypted_attribute_name.as_str() { - "physical" => character_entry.physical.push(attribute), - "psychological" => character_entry.psychological.push(attribute), - "relations" => character_entry.relations.push(attribute), - "skills" => character_entry.skills.push(attribute), - "weaknesses" => character_entry.weaknesses.push(attribute), - "strengths" => character_entry.strengths.push(attribute), - "goals" => character_entry.goals.push(attribute), - "motivations" => character_entry.motivations.push(attribute), - "arc" => character_entry.arc.push(attribute), - "secrets" => character_entry.secrets.push(attribute), - "fears" => character_entry.fears.push(attribute), - "flaws" => character_entry.flaws.push(attribute), - "beliefs" => character_entry.beliefs.push(attribute), - "conflicts" => character_entry.conflicts.push(attribute), - "quotes" => character_entry.quotes.push(attribute), - "distinguishingMarks" => character_entry.distinguishing_marks.push(attribute), - "items" => character_entry.items.push(attribute), - "affiliations" => character_entry.affiliations.push(attribute), - _ => {} - } - } - - Ok(complete_characters_map.into_values().collect()) -} - -/// Generates a formatted vCard-style string representation of characters. -/// Useful for AI context or text-based exports. -/// * `characters` - An array of complete character objects to format -/// Returns a formatted string containing all character information. -pub fn character_v_card(characters: &[CompleteCharacterProps]) -> String { - let mut unique_characters_map: HashMap = HashMap::new(); - - for character in characters { - let character_identifier: String = if !character.name.is_empty() { - character.name.clone() - } else if let Some(ref id) = character.id { - id.clone() - } else { - "unknown".to_string() - }; - - if !unique_characters_map.contains_key(&character_identifier) { - unique_characters_map.insert(character_identifier.clone(), CompleteCharacterProps { - id: None, - name: character.name.clone(), - last_name: character.last_name.clone(), - nickname: character.nickname.clone(), - age: character.age, - gender: character.gender.clone(), - species: character.species.clone(), - nationality: character.nationality.clone(), - status: character.status.clone(), - title: character.title.clone(), - category: character.category.clone(), - image: None, - role: character.role.clone(), - biography: character.biography.clone(), - history: character.history.clone(), - speech_pattern: character.speech_pattern.clone(), - catchphrase: character.catchphrase.clone(), - residence: character.residence.clone(), - notes: character.notes.clone(), - color: character.color.clone(), - physical: vec![], - psychological: vec![], - relations: vec![], - skills: vec![], - weaknesses: vec![], - strengths: vec![], - goals: vec![], - motivations: vec![], - arc: vec![], - secrets: vec![], - fears: vec![], - flaws: vec![], - beliefs: vec![], - conflicts: vec![], - quotes: vec![], - distinguishing_marks: vec![], - items: vec![], - affiliations: vec![], - }); - } - - let aggregated_character_data: &mut CompleteCharacterProps = unique_characters_map.get_mut(&character_identifier).unwrap(); - - for attribute in &character.physical { aggregated_character_data.physical.push(Attribute { id: attribute.id.clone(), name: attribute.name.clone() }); } - for attribute in &character.psychological { aggregated_character_data.psychological.push(Attribute { id: attribute.id.clone(), name: attribute.name.clone() }); } - for attribute in &character.relations { aggregated_character_data.relations.push(Attribute { id: attribute.id.clone(), name: attribute.name.clone() }); } - for attribute in &character.skills { aggregated_character_data.skills.push(Attribute { id: attribute.id.clone(), name: attribute.name.clone() }); } - for attribute in &character.weaknesses { aggregated_character_data.weaknesses.push(Attribute { id: attribute.id.clone(), name: attribute.name.clone() }); } - for attribute in &character.strengths { aggregated_character_data.strengths.push(Attribute { id: attribute.id.clone(), name: attribute.name.clone() }); } - for attribute in &character.goals { aggregated_character_data.goals.push(Attribute { id: attribute.id.clone(), name: attribute.name.clone() }); } - for attribute in &character.motivations { aggregated_character_data.motivations.push(Attribute { id: attribute.id.clone(), name: attribute.name.clone() }); } - for attribute in &character.arc { aggregated_character_data.arc.push(Attribute { id: attribute.id.clone(), name: attribute.name.clone() }); } - for attribute in &character.secrets { aggregated_character_data.secrets.push(Attribute { id: attribute.id.clone(), name: attribute.name.clone() }); } - for attribute in &character.fears { aggregated_character_data.fears.push(Attribute { id: attribute.id.clone(), name: attribute.name.clone() }); } - for attribute in &character.flaws { aggregated_character_data.flaws.push(Attribute { id: attribute.id.clone(), name: attribute.name.clone() }); } - for attribute in &character.beliefs { aggregated_character_data.beliefs.push(Attribute { id: attribute.id.clone(), name: attribute.name.clone() }); } - for attribute in &character.conflicts { aggregated_character_data.conflicts.push(Attribute { id: attribute.id.clone(), name: attribute.name.clone() }); } - for attribute in &character.quotes { aggregated_character_data.quotes.push(Attribute { id: attribute.id.clone(), name: attribute.name.clone() }); } - for attribute in &character.distinguishing_marks { aggregated_character_data.distinguishing_marks.push(Attribute { id: attribute.id.clone(), name: attribute.name.clone() }); } - for attribute in &character.items { aggregated_character_data.items.push(Attribute { id: attribute.id.clone(), name: attribute.name.clone() }); } - for attribute in &character.affiliations { aggregated_character_data.affiliations.push(Attribute { id: attribute.id.clone(), name: attribute.name.clone() }); } - } - - let formatted_characters_description: String = unique_characters_map.values().map(|character| { - let mut character_description_lines: Vec = Vec::new(); - - let full_name: String = [&character.name, &character.last_name].iter().filter(|name| !name.is_empty()).map(|name| name.as_str()).collect::>().join(" "); - if !full_name.is_empty() { - character_description_lines.push(format!("Nom : {}", full_name)); - } - - let simple_properties: Vec<(&str, &str)> = vec![ - ("Category", &character.category), - ("Title", &character.title), - ("Role", &character.role), - ("Biography", &character.biography), - ("History", &character.history), - ]; - for (property_label, property_value) in simple_properties { - if !property_value.is_empty() { - character_description_lines.push(format!("{} : {}", property_label, property_value)); - } - } - - let array_properties: Vec<(&str, &Vec)> = vec![ - ("Physical", &character.physical), - ("Psychological", &character.psychological), - ("Relations", &character.relations), - ("Skills", &character.skills), - ("Weaknesses", &character.weaknesses), - ("Strengths", &character.strengths), - ("Goals", &character.goals), - ("Motivations", &character.motivations), - ("Arc", &character.arc), - ("Secrets", &character.secrets), - ("Fears", &character.fears), - ("Flaws", &character.flaws), - ("Beliefs", &character.beliefs), - ("Conflicts", &character.conflicts), - ("Quotes", &character.quotes), - ("DistinguishingMarks", &character.distinguishing_marks), - ("Items", &character.items), - ("Affiliations", &character.affiliations), - ]; - for (capitalized_property_key, attribute_values) in array_properties { - if !attribute_values.is_empty() { - let formatted_attribute_values: String = attribute_values.iter().map(|attribute_item| attribute_item.name.as_str()).collect::>().join(", "); - character_description_lines.push(format!("{} : {}", capitalized_property_key, formatted_attribute_values)); - } - } - - character_description_lines.join("\n") - }).collect::>().join("\n\n"); - - formatted_characters_description -} diff --git a/src-tauri/src/domains/cover/service.rs b/src-tauri/src/domains/cover/service.rs index 36db9ea..e69de29 100644 --- a/src-tauri/src/domains/cover/service.rs +++ b/src-tauri/src/domains/cover/service.rs @@ -1,53 +0,0 @@ -use std::fs; -use std::path::Path; - -use base64::{engine::general_purpose::STANDARD as BASE64, Engine}; -use rusqlite::Connection; - -use crate::crypto::encryption::decrypt_data_with_user_key; -use crate::crypto::key_manager::get_user_encryption_key; -use crate::domains::book::repo; -use crate::error::AppResult; -use crate::helpers::timestamp_in_seconds; -use crate::shared::types::Lang; - -/// Retrieves and decrypts the cover picture for a specific book. -/// Returns the decrypted cover image data, or an empty string if not found. -pub fn get_cover_picture(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult { - let cover_query: repo::BookCoverQuery = repo::fetch_book_cover(conn, user_id, book_id, lang)?; - if !cover_query.cover_image.is_empty() { - let user_encryption_key: String = get_user_encryption_key(user_id)?; - decrypt_data_with_user_key(&cover_query.cover_image, &user_encryption_key) - } else { - Ok(String::new()) - } -} - -/// Deletes the cover picture association for a specific book. -/// Clears the cover image reference in the database. -/// Returns true if the cover was successfully deleted, false otherwise. -pub fn delete_cover_picture(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult { - let _existing_cover_name: String = get_cover_picture(conn, user_id, book_id, lang)?; - let last_update: i64 = timestamp_in_seconds(); - repo::update_book_cover(conn, book_id, "", user_id, last_update, lang) -} - -/// Retrieves and decrypts a picture file, returning it as a base64-encoded string. -/// Returns the base64-encoded image data, or an empty string if the image cannot be read. -pub fn get_picture(_user_id: &str, user_key: &str, image: &str, _lang: Lang) -> String { - if image.is_empty() { - return String::new(); - } - match try_get_picture(user_key, image) { - Ok(base64_data) => base64_data, - Err(_) => String::new(), - } -} - -fn try_get_picture(user_key: &str, image: &str) -> AppResult { - let decrypted_file_name: String = decrypt_data_with_user_key(image, user_key)?; - let user_directory: &Path = Path::new(&decrypted_file_name); - let file_data: Vec = fs::read(user_directory) - .map_err(|error| crate::error::AppError::Internal(error.to_string()))?; - Ok(BASE64.encode(&file_data)) -} diff --git a/src-tauri/src/domains/incident/service.rs b/src-tauri/src/domains/incident/service.rs index a97a1ad..ee5fa57 100644 --- a/src-tauri/src/domains/incident/service.rs +++ b/src-tauri/src/domains/incident/service.rs @@ -1,8 +1,9 @@ use rusqlite::Connection; +use serde::{Serialize, Deserialize}; use crate::crypto::encryption::{encrypt_data_with_user_key, decrypt_data_with_user_key, hash_element}; use crate::crypto::key_manager::get_user_encryption_key; -use crate::domains::chapter::repo::ActChapterQuery; +use crate::domains::act::service::ActChapter; use crate::domains::incident::repo; use crate::domains::tombstone::repo as tombstone_repo; use crate::error::AppResult; @@ -24,12 +25,13 @@ pub struct SyncedIncident { pub last_update: i64, } -/// Represents the properties of an incident with its associated chapters. +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct IncidentProps { pub incident_id: String, pub title: String, pub summary: String, - pub chapters: Vec, + pub chapters: Option>, } /// Creates a new incident for a book. @@ -63,7 +65,7 @@ pub fn add_new_incident( /// Returns an array of incident properties with decrypted data. pub fn get_incitents_incidents( conn: &Connection, user_id: &str, book_id: &str, - act_chapters: &[ActChapterQuery], lang: Lang, + act_chapters: &[ActChapter], lang: Lang, ) -> AppResult> { let incident_query_results: Vec = repo::fetch_all_incitent_incidents(conn, user_id, book_id, lang)?; let user_key: String = get_user_encryption_key(user_id)?; @@ -71,11 +73,11 @@ pub fn get_incitents_incidents( if !incident_query_results.is_empty() { for incident_record in &incident_query_results { - let mut associated_chapters: Vec = Vec::new(); + let mut associated_chapters: Vec = Vec::new(); for chapter in act_chapters { if let Some(ref incident_id) = chapter.incident_id { if incident_id == &incident_record.incident_id { - associated_chapters.push(ActChapterQuery { + associated_chapters.push(ActChapter { chapter_info_id: chapter.chapter_info_id, chapter_id: chapter.chapter_id.clone(), title: chapter.title.clone(), @@ -95,7 +97,7 @@ pub fn get_incitents_incidents( incident_id: incident_record.incident_id.clone(), title: decrypted_title, summary: decrypted_summary, - chapters: associated_chapters, + chapters: Some(associated_chapters), }); } } diff --git a/src-tauri/src/domains/plotpoint/service.rs b/src-tauri/src/domains/plotpoint/service.rs index 3e54b2a..7f22912 100644 --- a/src-tauri/src/domains/plotpoint/service.rs +++ b/src-tauri/src/domains/plotpoint/service.rs @@ -1,8 +1,9 @@ use rusqlite::Connection; +use serde::{Serialize, Deserialize}; use crate::crypto::encryption::{decrypt_data_with_user_key, encrypt_data_with_user_key, hash_element}; use crate::crypto::key_manager::get_user_encryption_key; -use crate::domains::chapter::repo::ActChapterQuery; +use crate::domains::act::service::ActChapter; use crate::domains::plotpoint::repo; use crate::domains::tombstone::repo as tombstone_repo; use crate::error::AppResult; @@ -17,13 +18,14 @@ pub struct PlotPointStory { pub chapter_goal: String, } -/// Represents a plot point with its properties and associated chapters. +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct PlotPointProps { pub plot_point_id: String, pub title: String, pub summary: String, pub linked_incident_id: Option, - pub chapters: Vec, + pub chapters: Option>, } /// Represents a synced plot point with minimal information. @@ -44,7 +46,7 @@ pub struct SyncedPlotPoint { /// Errors if the plot points cannot be retrieved or decrypted. pub fn get_plot_points( conn: &Connection, user_id: &str, book_id: &str, - act_chapters: &[ActChapterQuery], lang: Lang, + act_chapters: &[ActChapter], lang: Lang, ) -> AppResult> { let plot_point_query_results: Vec = repo::fetch_all_plot_points(conn, user_id, book_id, lang)?; let user_encryption_key: String = get_user_encryption_key(user_id)?; @@ -52,11 +54,11 @@ pub fn get_plot_points( if !plot_point_query_results.is_empty() { for plot_point_row in &plot_point_query_results { - let mut associated_chapters: Vec = Vec::new(); + let mut associated_chapters: Vec = Vec::new(); for chapter in act_chapters { if chapter.plot_point_id.as_deref() == Some(&plot_point_row.plot_point_id) { - associated_chapters.push(ActChapterQuery { + associated_chapters.push(ActChapter { chapter_info_id: chapter.chapter_info_id, chapter_id: chapter.chapter_id.clone(), title: chapter.title.clone(), @@ -78,7 +80,7 @@ pub fn get_plot_points( title: decrypted_title, summary: decrypted_summary, linked_incident_id: plot_point_row.linked_incident_id.clone(), - chapters: associated_chapters, + chapters: Some(associated_chapters), }); } } diff --git a/src-tauri/src/domains/series_location/repo.rs b/src-tauri/src/domains/series_location/repo.rs index e221e22..e323211 100644 --- a/src-tauri/src/domains/series_location/repo.rs +++ b/src-tauri/src/domains/series_location/repo.rs @@ -207,18 +207,6 @@ pub fn delete_sub_element(conn: &Connection, user_id: &str, sub_element_id: &str Ok(delete_result > 0) } -/// Updates a location's name. -pub fn update_location(conn: &Connection, user_id: &str, location_id: &str, encrypted_name: &str, original_name: &str, last_update: i64, lang: Lang) -> AppResult { - let update_result = conn - .execute( - "UPDATE series_locations SET loc_name = ?1, loc_original_name = ?2, last_update = ?3 WHERE loc_id = ?4 AND user_id = ?5", - params![encrypted_name, original_name, last_update, location_id, user_id], - ) - .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour le lieu.".to_string() } else { "Unable to update location.".to_string() }))?; - - Ok(update_result > 0) -} - /// Fetches all locations for a series for sync. pub fn fetch_series_locations_table(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult> { let mut statement = conn @@ -239,48 +227,6 @@ pub fn fetch_series_locations_table(conn: &Connection, user_id: &str, series_id: Ok(locations) } -/// Fetches all elements for a location for sync. -pub fn fetch_series_location_elements_table(conn: &Connection, user_id: &str, location_id: &str, lang: Lang) -> AppResult> { - let mut statement = conn - .prepare("SELECT element_id, location_id, user_id, element_name, original_name, element_description, last_update FROM series_location_elements WHERE location_id = ?1 AND user_id = ?2") - .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les éléments de lieu pour sync.".to_string() } else { "Unable to retrieve location elements for sync.".to_string() }))?; - - let elements = statement - .query_map(params![location_id, user_id], |query_row| { - Ok(SeriesLocationElementsTableResult { - element_id: query_row.get(0)?, location_id: query_row.get(1)?, user_id: query_row.get(2)?, - element_name: query_row.get(3)?, original_name: query_row.get(4)?, - element_description: query_row.get(5)?, last_update: query_row.get(6)?, - }) - }) - .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les éléments de lieu pour sync.".to_string() } else { "Unable to retrieve location elements for sync.".to_string() }))? - .collect::, _>>() - .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les éléments de lieu pour sync.".to_string() } else { "Unable to retrieve location elements for sync.".to_string() }))?; - - Ok(elements) -} - -/// Fetches all sub-elements for an element for sync. -pub fn fetch_series_location_sub_elements_table(conn: &Connection, user_id: &str, element_id: &str, lang: Lang) -> AppResult> { - let mut statement = conn - .prepare("SELECT sub_element_id, element_id, user_id, sub_elem_name, original_name, sub_elem_description, last_update FROM series_location_sub_elements WHERE element_id = ?1 AND user_id = ?2") - .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sous-éléments pour sync.".to_string() } else { "Unable to retrieve sub-elements for sync.".to_string() }))?; - - let sub_elements = statement - .query_map(params![element_id, user_id], |query_row| { - Ok(SeriesLocationSubElementsTableResult { - sub_element_id: query_row.get(0)?, element_id: query_row.get(1)?, user_id: query_row.get(2)?, - sub_elem_name: query_row.get(3)?, original_name: query_row.get(4)?, - sub_elem_description: query_row.get(5)?, last_update: query_row.get(6)?, - }) - }) - .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sous-éléments pour sync.".to_string() } else { "Unable to retrieve sub-elements for sync.".to_string() }))? - .collect::, _>>() - .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sous-éléments pour sync.".to_string() } else { "Unable to retrieve sub-elements for sync.".to_string() }))?; - - Ok(sub_elements) -} - /// Fetches all series locations for a user for sync comparison. pub fn fetch_synced_series_locations(conn: &Connection, user_id: &str, lang: Lang) -> AppResult> { let mut statement = conn @@ -332,68 +278,6 @@ pub fn fetch_synced_series_location_sub_elements(conn: &Connection, user_id: &st Ok(sub_elements) } -/// Fetches a complete location by ID for sync. -pub fn fetch_complete_location_by_id(conn: &Connection, location_id: &str, lang: Lang) -> AppResult> { - let mut statement = conn - .prepare("SELECT loc_id, series_id, user_id, loc_name, loc_original_name, last_update FROM series_locations WHERE loc_id = ?1") - .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le lieu complet.".to_string() } else { "Unable to retrieve complete location.".to_string() }))?; - - let locations = statement - .query_map(params![location_id], |query_row| { - Ok(SeriesLocationsTableResult { - loc_id: query_row.get(0)?, series_id: query_row.get(1)?, user_id: query_row.get(2)?, - loc_name: query_row.get(3)?, loc_original_name: query_row.get(4)?, last_update: query_row.get(5)?, - }) - }) - .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le lieu complet.".to_string() } else { "Unable to retrieve complete location.".to_string() }))? - .collect::, _>>() - .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le lieu complet.".to_string() } else { "Unable to retrieve complete location.".to_string() }))?; - - Ok(locations) -} - -/// Fetches a complete location element by ID for sync. -pub fn fetch_complete_location_element_by_id(conn: &Connection, element_id: &str, lang: Lang) -> AppResult> { - let mut statement = conn - .prepare("SELECT element_id, location_id, user_id, element_name, original_name, element_description, last_update FROM series_location_elements WHERE element_id = ?1") - .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer l'élément de lieu complet.".to_string() } else { "Unable to retrieve complete location element.".to_string() }))?; - - let elements = statement - .query_map(params![element_id], |query_row| { - Ok(SeriesLocationElementsTableResult { - element_id: query_row.get(0)?, location_id: query_row.get(1)?, user_id: query_row.get(2)?, - element_name: query_row.get(3)?, original_name: query_row.get(4)?, - element_description: query_row.get(5)?, last_update: query_row.get(6)?, - }) - }) - .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer l'élément de lieu complet.".to_string() } else { "Unable to retrieve complete location element.".to_string() }))? - .collect::, _>>() - .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer l'élément de lieu complet.".to_string() } else { "Unable to retrieve complete location element.".to_string() }))?; - - Ok(elements) -} - -/// Fetches a complete location sub-element by ID for sync. -pub fn fetch_complete_location_sub_element_by_id(conn: &Connection, sub_element_id: &str, lang: Lang) -> AppResult> { - let mut statement = conn - .prepare("SELECT sub_element_id, element_id, user_id, sub_elem_name, original_name, sub_elem_description, last_update FROM series_location_sub_elements WHERE sub_element_id = ?1") - .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le sous-élément complet.".to_string() } else { "Unable to retrieve complete sub-element.".to_string() }))?; - - let sub_elements = statement - .query_map(params![sub_element_id], |query_row| { - Ok(SeriesLocationSubElementsTableResult { - sub_element_id: query_row.get(0)?, element_id: query_row.get(1)?, user_id: query_row.get(2)?, - sub_elem_name: query_row.get(3)?, original_name: query_row.get(4)?, - sub_elem_description: query_row.get(5)?, last_update: query_row.get(6)?, - }) - }) - .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le sous-élément complet.".to_string() } else { "Unable to retrieve complete sub-element.".to_string() }))? - .collect::, _>>() - .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le sous-élément complet.".to_string() } else { "Unable to retrieve complete sub-element.".to_string() }))?; - - Ok(sub_elements) -} - /// Checks if a location exists. pub fn is_location_exist(conn: &Connection, user_id: &str, location_id: &str, lang: Lang) -> AppResult { let exists: bool = conn