use rusqlite::{params, Connection}; use crate::error::{AppError, AppResult}; use crate::shared::types::Lang; pub struct BookCharactersTable { pub character_id: String, pub book_id: String, pub user_id: String, pub first_name: String, pub last_name: Option, pub nickname: Option, pub age: Option, pub gender: Option, pub species: Option, pub nationality: Option, pub status: Option, pub category: String, pub title: Option, pub image: Option, pub role: Option, pub biography: Option, pub history: Option, pub speech_pattern: Option, pub catchphrase: Option, pub residence: Option, pub notes: Option, pub color: Option, pub last_update: i64, } pub struct SyncedCharacterResult { pub character_id: String, pub book_id: String, pub first_name: String, pub last_update: i64, } pub struct SyncedCharacterAttributeResult { pub attr_id: String, pub character_id: String, pub attribute_name: String, pub last_update: i64, } pub struct BookCharactersAttributesTable { pub attr_id: String, pub character_id: String, pub user_id: String, pub attribute_name: String, pub attribute_value: String, pub last_update: i64, } pub struct CharacterResult { 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 title: String, pub category: String, pub image: 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 series_character_id: Option, } pub struct AttributeResult { pub attr_id: String, pub attribute_name: String, pub attribute_value: String, } pub struct CharacterData { pub first_name: String, pub last_name: Option, pub nickname: Option, pub age: Option, pub gender: Option, pub species: Option, pub nationality: Option, pub status: Option, pub title: Option, pub category: Option, pub image: Option, pub role: Option, pub biography: Option, pub history: Option, pub speech_pattern: Option, pub catchphrase: Option, pub residence: Option, pub notes: Option, pub color: Option, } pub struct SyncCharacterData { pub first_name: String, pub last_name: Option, pub nickname: Option, pub age: Option, pub gender: Option, pub species: Option, pub nationality: Option, pub status: Option, pub category: String, pub title: Option, pub image: Option, pub role: Option, pub biography: Option, pub history: Option, pub speech_pattern: Option, pub catchphrase: Option, pub residence: Option, pub notes: Option, pub color: Option, } /// Fetches all characters for a specific book and user. /// * `conn` - Database connection /// * `user_id` - The unique identifier of the user /// * `book_id` - The unique identifier of the book /// * `lang` - The language for error messages ("fr" or "en") /// Returns an array of character results. pub fn fetch_characters(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult> { let mut statement = conn .prepare("SELECT character_id, first_name, last_name, nickname, age, gender, species, nationality, status, title, category, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, series_character_id FROM book_characters WHERE book_id=?1 AND user_id=?2") .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les personnages.".to_string() } else { "Unable to retrieve characters.".to_string() }))?; let characters = statement .query_map(params![book_id, user_id], |query_row| { Ok(CharacterResult { 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)?, title: query_row.get(9)?, category: query_row.get(10)?, image: query_row.get(11)?, role: query_row.get(12)?, biography: query_row.get(13)?, history: query_row.get(14)?, speech_pattern: query_row.get(15)?, catchphrase: query_row.get(16)?, residence: query_row.get(17)?, notes: query_row.get(18)?, color: query_row.get(19)?, series_character_id: query_row.get(20)?, }) }) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les personnages.".to_string() } else { "Unable to retrieve characters.".to_string() }))? .collect::, _>>() .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les personnages.".to_string() } else { "Unable to retrieve characters.".to_string() }))?; Ok(characters) } /// Adds a new character to the database. /// * `conn` - Database connection /// * `user_id` - The unique identifier of the user /// * `character_id` - The unique identifier for the new character /// * `character_data` - Object containing all encrypted character fields /// * `book_id` - The unique identifier of the book /// * `lang` - The language for error messages ("fr" or "en") /// * `series_character_id` - Optional series character identifier /// Returns the character ID if successful. pub fn add_new_character( conn: &Connection, user_id: &str, character_id: &str, character_data: &CharacterData, book_id: &str, lang: Lang, series_character_id: Option<&str>, last_update: i64, ) -> AppResult { let insert_result = if let Some(series_id) = series_character_id { conn.execute( "INSERT INTO book_characters (character_id, book_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status, category, title, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, series_character_id, last_update) VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15,?16,?17,?18,?19,?20,?21,?22,?23,?24)", params![character_id, book_id, user_id, character_data.first_name, character_data.last_name, character_data.nickname, character_data.age, character_data.gender, character_data.species, character_data.nationality, character_data.status, character_data.category, character_data.title, character_data.image, character_data.role, character_data.biography, character_data.history, character_data.speech_pattern, character_data.catchphrase, character_data.residence, character_data.notes, character_data.color, series_id, last_update], ) } else { conn.execute( "INSERT INTO book_characters (character_id, book_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status, category, title, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update) VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15,?16,?17,?18,?19,?20,?21,?22,?23)", params![character_id, book_id, user_id, character_data.first_name, character_data.last_name, character_data.nickname, character_data.age, character_data.gender, character_data.species, character_data.nationality, character_data.status, character_data.category, character_data.title, character_data.image, character_data.role, character_data.biography, character_data.history, character_data.speech_pattern, character_data.catchphrase, character_data.residence, character_data.notes, character_data.color, last_update], ) }; let insert_result = insert_result .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'ajouter le personnage.".to_string() } else { "Unable to add character.".to_string() }))?; if insert_result > 0 { Ok(character_id.to_string()) } else { Err(AppError::Internal(if lang == Lang::Fr { "Une erreur s'est produite lors de l'ajout du personnage.".to_string() } else { "Error adding character.".to_string() })) } } /// Inserts a new attribute for a character. /// * `conn` - Database connection /// * `attribute_id` - The unique identifier for the new attribute /// * `character_id` - The unique identifier of the character /// * `user_id` - The unique identifier of the user /// * `attribute_type` - The attribute name/type /// * `name` - The attribute value /// * `last_update` - The timestamp of the last update /// * `lang` - The language for error messages ("fr" or "en") /// Returns the attribute ID if successful. pub fn insert_attribute( conn: &Connection, attribute_id: &str, character_id: &str, user_id: &str, attribute_type: &str, name: &str, last_update: i64, lang: Lang, ) -> AppResult { let insert_result = conn .execute( "INSERT INTO book_characters_attributes (attr_id, character_id, user_id, attribute_name, attribute_value, last_update) VALUES (?1,?2,?3,?4,?5,?6)", params![attribute_id, character_id, user_id, attribute_type, name, last_update], ) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'ajouter l'attribut.".to_string() } else { "Unable to add attribute.".to_string() }))?; if insert_result > 0 { Ok(attribute_id.to_string()) } else { Err(AppError::Internal(if lang == Lang::Fr { "Une erreur s'est produite lors de l'ajout de l'attribut.".to_string() } else { "Error adding attribute.".to_string() })) } } /// Updates an existing character's information. /// * `conn` - Database connection /// * `user_id` - The unique identifier of the user /// * `id` - The unique identifier of the character to update /// * `character_data` - Object containing all encrypted character fields /// * `last_update` - The timestamp of the last update /// * `lang` - The language for error messages ("fr" or "en") /// * `series_character_id` - Optional series character identifier /// Returns true if the update was successful, false otherwise. pub fn update_character( conn: &Connection, user_id: &str, id: &str, character_data: &CharacterData, last_update: i64, lang: Lang, series_character_id: Option<&str>, ) -> AppResult { let update_result = if let Some(series_id) = series_character_id { conn.execute( "UPDATE book_characters SET first_name=?1, last_name=?2, nickname=?3, age=?4, gender=?5, species=?6, nationality=?7, status=?8, title=?9, category=?10, image=?11, role=?12, biography=?13, history=?14, speech_pattern=?15, catchphrase=?16, residence=?17, notes=?18, color=?19, series_character_id=?20, last_update=?21 WHERE character_id=?22 AND user_id=?23", params![character_data.first_name, character_data.last_name, character_data.nickname, character_data.age, character_data.gender, character_data.species, character_data.nationality, character_data.status, character_data.title, character_data.category, character_data.image, character_data.role, character_data.biography, character_data.history, character_data.speech_pattern, character_data.catchphrase, character_data.residence, character_data.notes, character_data.color, series_id, last_update, id, user_id], ) } else { conn.execute( "UPDATE book_characters SET first_name=?1, last_name=?2, nickname=?3, age=?4, gender=?5, species=?6, nationality=?7, status=?8, title=?9, category=?10, image=?11, role=?12, biography=?13, history=?14, speech_pattern=?15, catchphrase=?16, residence=?17, notes=?18, color=?19, last_update=?20 WHERE character_id=?21 AND user_id=?22", params![character_data.first_name, character_data.last_name, character_data.nickname, character_data.age, character_data.gender, character_data.species, character_data.nationality, character_data.status, character_data.title, character_data.category, character_data.image, character_data.role, character_data.biography, character_data.history, character_data.speech_pattern, character_data.catchphrase, character_data.residence, character_data.notes, character_data.color, last_update, id, user_id], ) }; let update_result = update_result .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour le personnage.".to_string() } else { "Unable to update character.".to_string() }))?; Ok(update_result > 0) } /// Deletes a character and all its related data (attributes) from the database. /// * `conn` - Database connection /// * `user_id` - The unique identifier of the user /// * `character_id` - The unique identifier of the character to delete /// * `lang` - The language for error messages ("fr" or "en") /// Returns true if the deletion was successful, false otherwise. pub fn delete_character(conn: &Connection, user_id: &str, character_id: &str, lang: Lang) -> AppResult { conn.execute( "DELETE FROM book_characters_attributes WHERE character_id=?1 AND user_id=?2", params![character_id, user_id], ) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de supprimer le personnage.".to_string() } else { "Unable to delete character.".to_string() }))?; let delete_result = conn .execute( "DELETE FROM book_characters WHERE character_id=?1 AND user_id=?2", params![character_id, user_id], ) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de supprimer le personnage.".to_string() } else { "Unable to delete character.".to_string() }))?; Ok(delete_result > 0) } /// Deletes a character attribute from the database. /// * `conn` - Database connection /// * `user_id` - The unique identifier of the user /// * `attribute_id` - The unique identifier of the attribute to delete /// * `lang` - The language for error messages ("fr" or "en") /// Returns true if the deletion was successful, false otherwise. pub fn delete_attribute(conn: &Connection, user_id: &str, attribute_id: &str, lang: Lang) -> AppResult { let delete_result = conn .execute( "DELETE FROM book_characters_attributes WHERE attr_id=?1 AND user_id=?2", params![attribute_id, user_id], ) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de supprimer l'attribut.".to_string() } else { "Unable to delete attribute.".to_string() }))?; Ok(delete_result > 0) } /// Fetches all attributes for a specific character. /// * `conn` - Database connection /// * `character_id` - The unique identifier of the character /// * `user_id` - The unique identifier of the user /// * `lang` - The language for error messages ("fr" or "en") /// Returns an array of attribute results. pub fn fetch_attributes(conn: &Connection, character_id: &str, user_id: &str, lang: Lang) -> AppResult> { let mut statement = conn .prepare("SELECT attr_id, attribute_name, attribute_value FROM book_characters_attributes WHERE character_id=?1 AND user_id=?2") .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les attributs.".to_string() } else { "Unable to retrieve attributes.".to_string() }))?; let attributes = statement .query_map(params![character_id, user_id], |query_row| { Ok(AttributeResult { attr_id: query_row.get(0)?, attribute_name: query_row.get(1)?, attribute_value: query_row.get(2)? }) }) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les attributs.".to_string() } else { "Unable to retrieve attributes.".to_string() }))? .collect::, _>>() .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les attributs.".to_string() } else { "Unable to retrieve attributes.".to_string() }))?; Ok(attributes) } /// Fetches complete character information including attributes, optionally filtered by character IDs. /// * `conn` - Database connection /// * `user_id` - The unique identifier of the user /// * `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") /// Updates an existing character attribute. /// * `conn` - Database connection /// * `user_id` - The unique identifier of the user /// * `character_attribute_id` - The unique identifier of the attribute to update /// * `attribute_name` - The new attribute name /// * `attribute_value` - The new attribute value /// * `last_update` - The timestamp of the last update /// * `lang` - The language for error messages ("fr" or "en") /// Returns true if the update was successful, false otherwise. pub fn update_character_attribute( conn: &Connection, user_id: &str, character_attribute_id: &str, attribute_name: &str, attribute_value: &str, last_update: i64, lang: Lang, ) -> AppResult { let update_result = conn .execute( "UPDATE book_characters_attributes SET attribute_name=?1, attribute_value=?2, last_update=?3 WHERE attr_id=?4 AND user_id=?5", params![attribute_name, attribute_value, last_update, character_attribute_id, user_id], ) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour l'attribut du personnage.".to_string() } else { "Unable to update character attribute.".to_string() }))?; Ok(update_result > 0) } /// Checks if a character exists in the database. /// * `conn` - Database connection /// * `user_id` - The unique identifier of the user /// * `character_id` - The unique identifier of the character to check /// * `lang` - The language for error messages ("fr" or "en") /// Returns true if the character exists, false otherwise. pub fn is_character_exist(conn: &Connection, user_id: &str, character_id: &str, lang: Lang) -> AppResult { let mut statement = conn .prepare("SELECT 1 FROM book_characters WHERE character_id=?1 AND user_id=?2") .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence du personnage.".to_string() } else { "Unable to check character existence.".to_string() }))?; let exists = statement .query_row(params![character_id, user_id], |_query_row| Ok(true)) .unwrap_or(false); Ok(exists) } /// Checks if a character attribute exists in the database. /// * `conn` - Database connection /// * `user_id` - The unique identifier of the user /// * `character_attribute_id` - The unique identifier of the attribute to check /// * `lang` - The language for error messages ("fr" or "en") /// Returns true if the attribute exists, false otherwise. pub fn is_character_attribute_exist(conn: &Connection, user_id: &str, character_attribute_id: &str, lang: Lang) -> AppResult { let mut statement = conn .prepare("SELECT 1 FROM book_characters_attributes WHERE attr_id=?1 AND user_id=?2") .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence de l'attribut du personnage.".to_string() } else { "Unable to check character attribute existence.".to_string() }))?; let exists = statement .query_row(params![character_attribute_id, user_id], |_query_row| Ok(true)) .unwrap_or(false); Ok(exists) } /// Fetches all characters for a specific book. /// * `conn` - Database connection /// * `user_id` - The unique identifier of the user /// * `book_id` - The unique identifier of the book /// * `lang` - The language for error messages ("fr" or "en") /// Returns an array of book characters. pub fn fetch_book_characters(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult> { let mut statement = conn .prepare("SELECT character_id, book_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status, category, title, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update FROM book_characters WHERE user_id=?1 AND book_id=?2") .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les personnages.".to_string() } else { "Unable to retrieve characters.".to_string() }))?; let characters = statement .query_map(params![user_id, book_id], |query_row| { Ok(BookCharactersTable { character_id: query_row.get(0)?, book_id: query_row.get(1)?, user_id: query_row.get(2)?, first_name: query_row.get(3)?, last_name: query_row.get(4)?, nickname: query_row.get(5)?, age: query_row.get(6)?, gender: query_row.get(7)?, species: query_row.get(8)?, nationality: query_row.get(9)?, status: query_row.get(10)?, category: query_row.get(11)?, title: query_row.get(12)?, image: query_row.get(13)?, role: query_row.get(14)?, biography: query_row.get(15)?, history: query_row.get(16)?, speech_pattern: query_row.get(17)?, catchphrase: query_row.get(18)?, residence: query_row.get(19)?, notes: query_row.get(20)?, color: query_row.get(21)?, last_update: query_row.get(22)?, }) }) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les personnages.".to_string() } else { "Unable to retrieve characters.".to_string() }))? .collect::, _>>() .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les personnages.".to_string() } else { "Unable to retrieve characters.".to_string() }))?; Ok(characters) } pub fn fetch_all_character_attributes_by_book(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult> { let mut statement = conn .prepare("SELECT bca.attr_id, bca.character_id, bca.user_id, bca.attribute_name, bca.attribute_value, bca.last_update FROM book_characters_attributes bca INNER JOIN book_characters bc ON bca.character_id = bc.character_id WHERE bca.user_id=?1 AND bc.book_id=?2") .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les attributs des personnages.".to_string() } else { "Unable to retrieve character attributes.".to_string() }))?; let attributes = statement .query_map(params![user_id, book_id], |query_row| { Ok(BookCharactersAttributesTable { attr_id: query_row.get(0)?, character_id: query_row.get(1)?, user_id: query_row.get(2)?, attribute_name: query_row.get(3)?, attribute_value: query_row.get(4)?, last_update: query_row.get(5)?, }) }) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les attributs des personnages.".to_string() } else { "Unable to retrieve character attributes.".to_string() }))? .collect::, _>>() .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les attributs des personnages.".to_string() } else { "Unable to retrieve character attributes.".to_string() }))?; Ok(attributes) } /// Fetches all synced characters for a user. /// * `conn` - Database connection /// * `user_id` - The unique identifier of the user /// * `lang` - The language for error messages ("fr" or "en") /// Returns an array of synced character results. pub fn fetch_synced_characters(conn: &Connection, user_id: &str, lang: Lang) -> AppResult> { let mut statement = conn .prepare("SELECT character_id, book_id, first_name, last_update FROM book_characters WHERE user_id = ?1") .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les personnages synchronisés.".to_string() } else { "Unable to retrieve synced characters.".to_string() }))?; let synced_characters = statement .query_map(params![user_id], |query_row| { Ok(SyncedCharacterResult { character_id: query_row.get(0)?, book_id: query_row.get(1)?, first_name: query_row.get(2)?, last_update: query_row.get(3)? }) }) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les personnages synchronisés.".to_string() } else { "Unable to retrieve synced characters.".to_string() }))? .collect::, _>>() .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les personnages synchronisés.".to_string() } else { "Unable to retrieve synced characters.".to_string() }))?; Ok(synced_characters) } /// Fetches all synced character attributes for a user. /// * `conn` - Database connection /// * `user_id` - The unique identifier of the user /// * `lang` - The language for error messages ("fr" or "en") /// Returns an array of synced character attribute results. pub fn fetch_synced_character_attributes(conn: &Connection, user_id: &str, lang: Lang) -> AppResult> { let mut statement = conn .prepare("SELECT attr_id, character_id, attribute_name, last_update FROM book_characters_attributes WHERE user_id = ?1") .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les attributs des personnages synchronisés.".to_string() } else { "Unable to retrieve synced character attributes.".to_string() }))?; let synced_attributes = statement .query_map(params![user_id], |query_row| { Ok(SyncedCharacterAttributeResult { attr_id: query_row.get(0)?, character_id: query_row.get(1)?, attribute_name: query_row.get(2)?, last_update: query_row.get(3)? }) }) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les attributs des personnages synchronisés.".to_string() } else { "Unable to retrieve synced character attributes.".to_string() }))? .collect::, _>>() .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les attributs des personnages synchronisés.".to_string() } else { "Unable to retrieve synced character attributes.".to_string() }))?; Ok(synced_attributes) } /// Inserts a synced character into the database. /// * `conn` - Database connection /// * `character_id` - The unique identifier of the character /// * `book_id` - The unique identifier of the book /// * `user_id` - The unique identifier of the user /// * `character_data` - Object containing all character fields /// * `last_update` - The timestamp of the last update /// * `lang` - The language for error messages ("fr" or "en") /// Returns true if the insertion was successful, false otherwise. pub fn insert_sync_character( conn: &Connection, character_id: &str, book_id: &str, user_id: &str, character_data: &SyncCharacterData, last_update: i64, lang: Lang, ) -> AppResult { let insert_result = conn .execute( "INSERT INTO book_characters (character_id, book_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status, category, title, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update) VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15,?16,?17,?18,?19,?20,?21,?22,?23)", params![character_id, book_id, user_id, character_data.first_name, character_data.last_name, character_data.nickname, character_data.age, character_data.gender, character_data.species, character_data.nationality, character_data.status, character_data.category, character_data.title, character_data.image, character_data.role, character_data.biography, character_data.history, character_data.speech_pattern, character_data.catchphrase, character_data.residence, character_data.notes, character_data.color, last_update], ) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'insérer le personnage.".to_string() } else { "Unable to insert character.".to_string() }))?; Ok(insert_result > 0) } /// Inserts a synced character attribute into the database. /// * `conn` - Database connection /// * `attr_id` - The unique identifier of the attribute /// * `character_id` - The unique identifier of the character /// * `user_id` - The unique identifier of the user /// * `attribute_name` - The name of the attribute /// * `attribute_value` - The value of the attribute /// * `last_update` - The timestamp of the last update /// * `lang` - The language for error messages ("fr" or "en") /// Returns true if the insertion was successful, false otherwise. pub fn insert_sync_character_attribute( conn: &Connection, attr_id: &str, character_id: &str, user_id: &str, attribute_name: &str, attribute_value: &str, last_update: i64, lang: Lang, ) -> AppResult { let insert_result = conn .execute( "INSERT INTO book_characters_attributes (attr_id, character_id, user_id, attribute_name, attribute_value, last_update) VALUES (?1,?2,?3,?4,?5,?6)", params![attr_id, character_id, user_id, attribute_name, attribute_value, last_update], ) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'insérer l'attribut du personnage.".to_string() } else { "Unable to insert character attribute.".to_string() }))?; Ok(insert_result > 0) } /// Fetches a complete character by its ID. /// * `conn` - Database connection /// * `id` - The unique identifier of the character /// * `lang` - The language for error messages ("fr" or "en") /// Returns an array of book characters (typically one). pub fn fetch_complete_character_by_id(conn: &Connection, id: &str, lang: Lang) -> AppResult> { let mut statement = conn .prepare("SELECT character_id, book_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status, category, title, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update FROM book_characters WHERE character_id = ?1") .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le personnage complet.".to_string() } else { "Unable to retrieve complete character.".to_string() }))?; let character = statement .query_map(params![id], |query_row| { Ok(BookCharactersTable { character_id: query_row.get(0)?, book_id: query_row.get(1)?, user_id: query_row.get(2)?, first_name: query_row.get(3)?, last_name: query_row.get(4)?, nickname: query_row.get(5)?, age: query_row.get(6)?, gender: query_row.get(7)?, species: query_row.get(8)?, nationality: query_row.get(9)?, status: query_row.get(10)?, category: query_row.get(11)?, title: query_row.get(12)?, image: query_row.get(13)?, role: query_row.get(14)?, biography: query_row.get(15)?, history: query_row.get(16)?, speech_pattern: query_row.get(17)?, catchphrase: query_row.get(18)?, residence: query_row.get(19)?, notes: query_row.get(20)?, color: query_row.get(21)?, last_update: query_row.get(22)?, }) }) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le personnage complet.".to_string() } else { "Unable to retrieve complete character.".to_string() }))? .collect::, _>>() .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le personnage complet.".to_string() } else { "Unable to retrieve complete character.".to_string() }))?; Ok(character) } /// Fetches a complete character attribute by its ID. /// * `conn` - Database connection /// * `id` - The unique identifier of the attribute /// * `lang` - The language for error messages ("fr" or "en") /// Returns an array of character attributes (typically one). pub fn fetch_complete_character_attribute_by_id(conn: &Connection, id: &str, lang: Lang) -> AppResult> { let mut statement = conn .prepare("SELECT attr_id, character_id, user_id, attribute_name, attribute_value, last_update FROM book_characters_attributes WHERE attr_id = ?1") .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer l'attribut de personnage complet.".to_string() } else { "Unable to retrieve complete character attribute.".to_string() }))?; let attribute = statement .query_map(params![id], |query_row| { Ok(BookCharactersAttributesTable { attr_id: query_row.get(0)?, character_id: query_row.get(1)?, user_id: query_row.get(2)?, attribute_name: query_row.get(3)?, attribute_value: query_row.get(4)?, last_update: query_row.get(5)?, }) }) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer l'attribut de personnage complet.".to_string() } else { "Unable to retrieve complete character attribute.".to_string() }))? .collect::, _>>() .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer l'attribut de personnage complet.".to_string() } else { "Unable to retrieve complete character attribute.".to_string() }))?; Ok(attribute) }