use rusqlite::{params, Connection, OptionalExtension}; use crate::error::{AppError, AppResult}; use crate::shared::types::Lang; pub struct LocationQueryResult { pub loc_id: String, pub loc_name: String, pub element_id: Option, pub element_name: Option, pub element_description: Option, pub sub_element_id: Option, pub sub_elem_name: Option, pub sub_elem_description: Option, pub series_location_id: Option, } pub struct LocationElementQueryResult { pub sub_element_id: Option, pub sub_elem_name: Option, pub sub_elem_description: Option, pub element_id: String, pub element_name: String, pub element_description: Option, } pub struct BookLocationTable { pub loc_id: String, pub book_id: String, pub user_id: String, pub loc_name: String, pub loc_original_name: String, pub last_update: i64, } pub struct LocationElementTable { pub element_id: String, pub location: String, pub user_id: String, pub element_name: String, pub original_name: String, pub element_description: Option, pub last_update: i64, } pub struct LocationSubElementTable { pub sub_element_id: String, pub element_id: String, pub user_id: String, pub sub_elem_name: String, pub original_name: String, pub sub_elem_description: Option, pub last_update: i64, } pub struct SyncedLocationResult { pub loc_id: String, pub book_id: String, pub loc_name: String, pub last_update: i64, } pub struct SyncedLocationElementResult { pub element_id: String, pub location: String, pub element_name: String, pub last_update: i64, } pub struct SyncedLocationSubElementResult { pub sub_element_id: String, pub element_id: String, pub sub_elem_name: String, pub last_update: i64, } /// Retrieves all locations with their elements and sub-elements for a specific book. /// * `conn` - Database connection /// * `user_id` - The user's unique identifier /// * `book_id` - The book's unique identifier /// * `lang` - The language for error messages ("fr" or "en") /// Returns an array of location query results with nested elements. pub fn get_location(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult> { let mut statement = conn .prepare("SELECT loc_id, loc_name, element.element_id AS element_id, element.element_name, element.element_description, sub_elem.sub_element_id AS sub_element_id, sub_elem.sub_elem_name, sub_elem.sub_elem_description, location.series_location_id FROM book_location AS location LEFT JOIN location_element AS element ON location.loc_id = element.location LEFT JOIN location_sub_element AS sub_elem ON element.element_id = sub_elem.element_id WHERE location.user_id = ?1 AND location.book_id = ?2") .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les emplacements.".to_string() } else { "Unable to retrieve locations.".to_string() }))?; let locations = statement .query_map(params![user_id, book_id], |query_row| { Ok(LocationQueryResult { loc_id: query_row.get(0)?, loc_name: query_row.get(1)?, element_id: query_row.get(2)?, element_name: query_row.get(3)?, element_description: query_row.get(4)?, sub_element_id: query_row.get(5)?, sub_elem_name: query_row.get(6)?, sub_elem_description: query_row.get(7)?, series_location_id: query_row.get(8)?, }) }) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les emplacements.".to_string() } else { "Unable to retrieve locations.".to_string() }))? .collect::, _>>() .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les emplacements.".to_string() } else { "Unable to retrieve locations.".to_string() }))?; Ok(locations) } /// Inserts a new location section for a book. /// * `conn` - Database connection /// * `user_id` - The user's unique identifier /// * `location_id` - The new location's unique identifier /// * `book_id` - The book's unique identifier /// * `encrypted_name` - The encrypted location name /// * `original_name` - The original (unencrypted) location name /// * `last_update` - The timestamp of the last update /// * `series_location_id` - The series location ID (optional) /// * `lang` - The language for error messages ("fr" or "en") /// Returns the location ID if insertion was successful. pub fn insert_location( conn: &Connection, user_id: &str, location_id: &str, book_id: &str, encrypted_name: &str, original_name: &str, last_update: i64, series_location_id: Option<&str>, lang: Lang, ) -> AppResult { let insert_result = match series_location_id { Some(series_id) => conn.execute( "INSERT INTO book_location (loc_id, book_id, user_id, loc_name, loc_original_name, series_location_id, last_update) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", params![location_id, book_id, user_id, encrypted_name, original_name, series_id, last_update], ), None => conn.execute( "INSERT INTO book_location (loc_id, book_id, user_id, loc_name, loc_original_name, last_update) VALUES (?1, ?2, ?3, ?4, ?5, ?6)", params![location_id, book_id, user_id, encrypted_name, original_name, last_update], ), } .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'ajouter la section d'emplacement.".to_string() } else { "Unable to add location section.".to_string() }))?; if insert_result > 0 { Ok(location_id.to_string()) } else { Err(AppError::Internal(if lang == Lang::Fr { "Une erreur s'est produite lors de l'ajout de la section d'emplacement.".to_string() } else { "Error adding location section.".to_string() })) } } /// Inserts a new location element within a location section. /// * `conn` - Database connection /// * `user_id` - The user's unique identifier /// * `element_id` - The new element's unique identifier /// * `location_id` - The parent location's unique identifier /// * `encrypted_name` - The encrypted element name /// * `original_name` - The original (unencrypted) element name /// * `last_update` - The timestamp of the last update /// * `lang` - The language for error messages ("fr" or "en") /// Returns the element ID if insertion was successful. pub fn insert_location_element( conn: &Connection, user_id: &str, element_id: &str, location_id: &str, encrypted_name: &str, original_name: &str, last_update: i64, lang: Lang, ) -> AppResult { let insert_result = conn .execute( "INSERT INTO location_element (element_id, location, user_id, element_name, original_name, element_description, last_update) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", params![element_id, location_id, user_id, encrypted_name, original_name, "", last_update], ) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'ajouter l'élément d'emplacement.".to_string() } else { "Unable to add location element.".to_string() }))?; if insert_result > 0 { Ok(element_id.to_string()) } else { Err(AppError::Internal(if lang == Lang::Fr { "Une erreur s'est produite lors de l'ajout de l'élément d'emplacement.".to_string() } else { "Error adding location element.".to_string() })) } } /// Inserts a new sub-element within a location element. /// * `conn` - Database connection /// * `user_id` - The user's unique identifier /// * `sub_element_id` - The new sub-element's unique identifier /// * `element_id` - The parent element's unique identifier /// * `encrypted_name` - The encrypted sub-element name /// * `original_name` - The original (unencrypted) sub-element name /// * `last_update` - The timestamp of the last update /// * `lang` - The language for error messages ("fr" or "en") /// Returns the sub-element ID if insertion was successful. pub fn insert_location_sub_element( conn: &Connection, user_id: &str, sub_element_id: &str, element_id: &str, encrypted_name: &str, original_name: &str, last_update: i64, lang: Lang, ) -> AppResult { let insert_result = conn .execute( "INSERT INTO location_sub_element (sub_element_id, element_id, user_id, sub_elem_name, original_name, sub_elem_description, last_update) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", params![sub_element_id, element_id, user_id, encrypted_name, original_name, "", last_update], ) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'ajouter le sous-élément d'emplacement.".to_string() } else { "Unable to add location sub-element.".to_string() }))?; if insert_result > 0 { Ok(sub_element_id.to_string()) } else { Err(AppError::Internal(if lang == Lang::Fr { "Une erreur s'est produite lors de l'ajout du sous-élément d'emplacement.".to_string() } else { "Error adding location sub-element.".to_string() })) } } /// Updates an existing location sub-element's name and description. /// * `conn` - Database connection /// * `user_id` - The user's unique identifier /// * `id` - The sub-element's unique identifier /// * `encrypted_name` - The new encrypted sub-element name /// * `original_name` - The new original (unencrypted) sub-element name /// * `encrypt_description` - The new encrypted description /// * `last_update` - The timestamp of the last update /// * `lang` - The language for error messages ("fr" or "en") /// Returns true if the update affected at least one row. pub fn update_location_sub_element( conn: &Connection, user_id: &str, id: &str, encrypted_name: &str, original_name: &str, encrypt_description: &str, last_update: i64, lang: Lang, ) -> AppResult { let update_result = conn .execute( "UPDATE location_sub_element SET sub_elem_name = ?1, original_name = ?2, sub_elem_description = ?3, last_update = ?4 WHERE sub_element_id = ?5 AND user_id = ?6", params![encrypted_name, original_name, encrypt_description, last_update, id, user_id], ) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour le sous-élément d'emplacement.".to_string() } else { "Unable to update location sub-element.".to_string() }))?; Ok(update_result > 0) } /// Updates an existing location element's name and description. /// * `conn` - Database connection /// * `user_id` - The user's unique identifier /// * `id` - The element's unique identifier /// * `encrypted_name` - The new encrypted element name /// * `original_name` - The new original (unencrypted) element name /// * `encrypted_description` - The new encrypted description /// * `last_update` - The timestamp of the last update /// * `lang` - The language for error messages ("fr" or "en") /// Returns true if the update affected at least one row. pub fn update_location_element( conn: &Connection, user_id: &str, id: &str, encrypted_name: &str, original_name: &str, encrypted_description: &str, last_update: i64, lang: Lang, ) -> AppResult { let update_result = conn .execute( "UPDATE location_element SET element_name = ?1, original_name = ?2, element_description = ?3, last_update = ?4 WHERE element_id = ?5 AND user_id = ?6", params![encrypted_name, original_name, encrypted_description, last_update, id, user_id], ) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour l'élément d'emplacement.".to_string() } else { "Unable to update location element.".to_string() }))?; Ok(update_result > 0) } /// Updates an existing location section's name. /// * `conn` - Database connection /// * `user_id` - The user's unique identifier /// * `id` - The location section's unique identifier /// * `encrypted_name` - The new encrypted location name /// * `original_name` - The new original (unencrypted) location name /// * `last_update` - The timestamp of the last update /// * `lang` - The language for error messages ("fr" or "en") /// Returns true if the update affected at least one row. pub fn update_location_section( conn: &Connection, user_id: &str, id: &str, encrypted_name: &str, original_name: &str, last_update: i64, lang: Lang, ) -> AppResult { let update_result = conn .execute( "UPDATE book_location 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, id, user_id], ) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour la section d'emplacement.".to_string() } else { "Unable to update location section.".to_string() }))?; Ok(update_result > 0) } /// Deletes a location section by its ID. /// * `conn` - Database connection /// * `user_id` - The user's unique identifier /// * `location_id` - The location section's unique identifier /// * `lang` - The language for error messages ("fr" or "en") /// Returns true if the deletion affected at least one row. pub fn delete_location_section(conn: &Connection, user_id: &str, location_id: &str, lang: Lang) -> AppResult { let delete_result = conn .execute("DELETE FROM book_location WHERE loc_id = ?1 AND user_id = ?2", params![location_id, user_id]) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de supprimer la section d'emplacement.".to_string() } else { "Unable to delete location section.".to_string() }))?; Ok(delete_result > 0) } /// Deletes a location element by its ID. /// * `conn` - Database connection /// * `user_id` - The user's unique identifier /// * `element_id` - The element's unique identifier /// * `lang` - The language for error messages ("fr" or "en") /// Returns true if the deletion affected at least one row. pub fn delete_location_element(conn: &Connection, user_id: &str, element_id: &str, lang: Lang) -> AppResult { let delete_result = conn .execute("DELETE FROM location_element WHERE element_id = ?1 AND user_id = ?2", params![element_id, user_id]) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de supprimer l'élément d'emplacement.".to_string() } else { "Unable to delete location element.".to_string() }))?; Ok(delete_result > 0) } /// Deletes a location sub-element by its ID. /// * `conn` - Database connection /// * `user_id` - The user's unique identifier /// * `sub_element_id` - The sub-element's unique identifier /// * `lang` - The language for error messages ("fr" or "en") /// Returns true if the deletion affected at least one row. pub fn delete_location_sub_element(conn: &Connection, user_id: &str, sub_element_id: &str, lang: Lang) -> AppResult { let delete_result = conn .execute("DELETE FROM location_sub_element WHERE sub_element_id = ?1 AND user_id = ?2", params![sub_element_id, user_id]) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de supprimer le sous-élément d'emplacement.".to_string() } else { "Unable to delete location sub-element.".to_string() }))?; Ok(delete_result > 0) } /// Fetches all location elements and sub-elements for tagging purposes. /// * `conn` - Database connection /// * `user_id` - The user's unique identifier /// * `book_id` - The book's unique identifier /// * `lang` - The language for error messages ("fr" or "en") /// Returns an array of location elements with their sub-elements. pub fn fetch_location_tags(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult> { let mut statement = conn .prepare("SELECT se.sub_element_id AS sub_element_id, se.sub_elem_name, se.sub_elem_description, el.element_id AS element_id, el.element_name, el.element_description FROM location_sub_element AS se RIGHT JOIN location_element AS el ON se.element_id = el.element_id LEFT JOIN book_location AS lo ON el.location = lo.loc_id WHERE lo.book_id = ?1 AND lo.user_id = ?2") .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les tags d'emplacement.".to_string() } else { "Unable to retrieve location tags.".to_string() }))?; let location_tags = statement .query_map(params![book_id, user_id], |query_row| { Ok(LocationElementQueryResult { sub_element_id: query_row.get(0)?, sub_elem_name: query_row.get(1)?, sub_elem_description: query_row.get(2)?, element_id: query_row.get(3)?, element_name: query_row.get(4)?, element_description: query_row.get(5)?, }) }) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les tags d'emplacement.".to_string() } else { "Unable to retrieve location tags.".to_string() }))? .collect::, _>>() .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les tags d'emplacement.".to_string() } else { "Unable to retrieve location tags.".to_string() }))?; Ok(location_tags) } /// Fetches locations by their tag IDs (element or sub-element IDs). /// * `conn` - Database connection /// * `user_id` - The user's unique identifier /// * `locations` - An array of location tag IDs to search for /// Checks if a location exists in the database. /// * `conn` - Database connection /// * `user_id` - The user's unique identifier /// * `loc_id` - The location's unique identifier /// * `lang` - The language for error messages ("fr" or "en") /// Returns true if the location exists, false otherwise. pub fn is_location_exist(conn: &Connection, user_id: &str, loc_id: &str, lang: Lang) -> AppResult { let mut statement = conn .prepare("SELECT 1 FROM book_location WHERE loc_id = ?1 AND user_id = ?2") .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence de l'emplacement.".to_string() } else { "Unable to check location existence.".to_string() }))?; let existing_location = statement .query_row(params![loc_id, user_id], |query_row| query_row.get::<_, i32>(0)) .optional() .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence de l'emplacement.".to_string() } else { "Unable to check location existence.".to_string() }))?; Ok(existing_location.is_some()) } /// Checks if a location element exists in the database. /// * `conn` - Database connection /// * `user_id` - The user's unique identifier /// * `element_id` - The element's unique identifier /// * `lang` - The language for error messages ("fr" or "en") /// Returns true if the location element exists, false otherwise. pub fn is_location_element_exist(conn: &Connection, user_id: &str, element_id: &str, lang: Lang) -> AppResult { let mut statement = conn .prepare("SELECT 1 FROM location_element WHERE element_id = ?1 AND user_id = ?2") .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence de l'élément d'emplacement.".to_string() } else { "Unable to check location element existence.".to_string() }))?; let existing_element = statement .query_row(params![element_id, user_id], |query_row| query_row.get::<_, i32>(0)) .optional() .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence de l'élément d'emplacement.".to_string() } else { "Unable to check location element existence.".to_string() }))?; Ok(existing_element.is_some()) } /// Checks if a location sub-element exists in the database. /// * `conn` - Database connection /// * `user_id` - The user's unique identifier /// * `sub_element_id` - The sub-element's unique identifier /// * `lang` - The language for error messages ("fr" or "en") /// Returns true if the location sub-element exists, false otherwise. pub fn is_location_sub_element_exist(conn: &Connection, user_id: &str, sub_element_id: &str, lang: Lang) -> AppResult { let mut statement = conn .prepare("SELECT 1 FROM location_sub_element WHERE sub_element_id = ?1 AND user_id = ?2") .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence du sous-élément d'emplacement.".to_string() } else { "Unable to check location sub-element existence.".to_string() }))?; let existing_sub_element = statement .query_row(params![sub_element_id, user_id], |query_row| query_row.get::<_, i32>(0)) .optional() .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence du sous-élément d'emplacement.".to_string() } else { "Unable to check location sub-element existence.".to_string() }))?; Ok(existing_sub_element.is_some()) } /// Fetches all locations for a specific book. /// * `conn` - Database connection /// * `user_id` - The user's unique identifier /// * `book_id` - The book's unique identifier /// * `lang` - The language for error messages ("fr" or "en") /// Returns an array of book location records. pub fn fetch_book_locations(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult> { let mut statement = conn .prepare("SELECT loc_id, book_id, user_id, loc_name, loc_original_name, last_update FROM book_location WHERE user_id = ?1 AND book_id = ?2") .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les lieux.".to_string() } else { "Unable to retrieve locations.".to_string() }))?; let book_locations = statement .query_map(params![user_id, book_id], |query_row| { Ok(BookLocationTable { loc_id: query_row.get(0)?, book_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 les lieux.".to_string() } else { "Unable to retrieve locations.".to_string() }))? .collect::, _>>() .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les lieux.".to_string() } else { "Unable to retrieve locations.".to_string() }))?; Ok(book_locations) } /// Fetches all elements for a specific location. /// * `conn` - Database connection /// * `user_id` - The user's unique identifier /// * `location_id` - The location's unique identifier /// * `lang` - The language for error messages ("fr" or "en") /// Returns an array of location element records. pub fn fetch_location_elements(conn: &Connection, user_id: &str, location_id: &str, lang: Lang) -> AppResult> { let mut statement = conn .prepare("SELECT element_id, location, user_id, element_name, original_name, element_description, last_update FROM location_element WHERE user_id = ?1 AND location = ?2") .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les éléments de lieu.".to_string() } else { "Unable to retrieve location elements.".to_string() }))?; let location_elements = statement .query_map(params![user_id, location_id], |query_row| { Ok(LocationElementTable { element_id: query_row.get(0)?, location: 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.".to_string() } else { "Unable to retrieve location elements.".to_string() }))? .collect::, _>>() .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les éléments de lieu.".to_string() } else { "Unable to retrieve location elements.".to_string() }))?; Ok(location_elements) } /// Fetches all sub-elements for a specific location element. /// * `conn` - Database connection /// * `user_id` - The user's unique identifier /// * `element_id` - The element's unique identifier /// * `lang` - The language for error messages ("fr" or "en") /// Returns an array of location sub-element records. pub fn fetch_location_sub_elements(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 location_sub_element WHERE user_id = ?1 AND element_id = ?2") .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sous-éléments de lieu.".to_string() } else { "Unable to retrieve location sub-elements.".to_string() }))?; let location_sub_elements = statement .query_map(params![user_id, element_id], |query_row| { Ok(LocationSubElementTable { 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 de lieu.".to_string() } else { "Unable to retrieve location sub-elements.".to_string() }))? .collect::, _>>() .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sous-éléments de lieu.".to_string() } else { "Unable to retrieve location sub-elements.".to_string() }))?; Ok(location_sub_elements) } /// Fetches all synced locations for a user (used for synchronization). /// * `conn` - Database connection /// * `user_id` - The user's unique identifier /// * `lang` - The language for error messages ("fr" or "en") /// Returns an array of synced location records. pub fn fetch_synced_locations(conn: &Connection, user_id: &str, lang: Lang) -> AppResult> { let mut statement = conn .prepare("SELECT loc_id, book_id, loc_name, last_update FROM book_location WHERE user_id = ?1") .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les lieux synchronisés.".to_string() } else { "Unable to retrieve synced locations.".to_string() }))?; let synced_locations = statement .query_map(params![user_id], |query_row| { Ok(SyncedLocationResult { loc_id: query_row.get(0)?, book_id: query_row.get(1)?, loc_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 lieux synchronisés.".to_string() } else { "Unable to retrieve synced locations.".to_string() }))? .collect::, _>>() .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les lieux synchronisés.".to_string() } else { "Unable to retrieve synced locations.".to_string() }))?; Ok(synced_locations) } /// Fetches all synced location elements for a user (used for synchronization). /// * `conn` - Database connection /// * `user_id` - The user's unique identifier /// * `lang` - The language for error messages ("fr" or "en") /// Returns an array of synced location element records. pub fn fetch_synced_location_elements(conn: &Connection, user_id: &str, lang: Lang) -> AppResult> { let mut statement = conn .prepare("SELECT element_id, location, element_name, last_update FROM location_element WHERE user_id = ?1") .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les éléments de lieu synchronisés.".to_string() } else { "Unable to retrieve synced location elements.".to_string() }))?; let synced_location_elements = statement .query_map(params![user_id], |query_row| { Ok(SyncedLocationElementResult { element_id: query_row.get(0)?, location: query_row.get(1)?, element_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 éléments de lieu synchronisés.".to_string() } else { "Unable to retrieve synced location elements.".to_string() }))? .collect::, _>>() .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les éléments de lieu synchronisés.".to_string() } else { "Unable to retrieve synced location elements.".to_string() }))?; Ok(synced_location_elements) } /// Fetches all synced location sub-elements for a user (used for synchronization). /// * `conn` - Database connection /// * `user_id` - The user's unique identifier /// * `lang` - The language for error messages ("fr" or "en") /// Returns an array of synced location sub-element records. pub fn fetch_synced_location_sub_elements(conn: &Connection, user_id: &str, lang: Lang) -> AppResult> { let mut statement = conn .prepare("SELECT sub_element_id, element_id, sub_elem_name, last_update FROM location_sub_element WHERE user_id = ?1") .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sous-éléments de lieu synchronisés.".to_string() } else { "Unable to retrieve synced location sub-elements.".to_string() }))?; let synced_location_sub_elements = statement .query_map(params![user_id], |query_row| { Ok(SyncedLocationSubElementResult { sub_element_id: query_row.get(0)?, element_id: query_row.get(1)?, sub_elem_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 sous-éléments de lieu synchronisés.".to_string() } else { "Unable to retrieve synced location sub-elements.".to_string() }))? .collect::, _>>() .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sous-éléments de lieu synchronisés.".to_string() } else { "Unable to retrieve synced location sub-elements.".to_string() }))?; Ok(synced_location_sub_elements) } /// Inserts a synced location from the remote server. /// * `conn` - Database connection /// * `loc_id` - The location's unique identifier /// * `book_id` - The book's unique identifier /// * `user_id` - The user's unique identifier /// * `loc_name` - The encrypted location name /// * `loc_original_name` - The original (unencrypted) location name /// * `last_update` - The timestamp of the last update /// * `lang` - The language for error messages ("fr" or "en") /// Returns true if the insertion affected at least one row. pub fn insert_sync_location( conn: &Connection, loc_id: &str, book_id: &str, user_id: &str, loc_name: &str, loc_original_name: &str, last_update: i64, lang: Lang, ) -> AppResult { let insert_result = conn .execute( "INSERT INTO book_location (loc_id, book_id, user_id, loc_name, loc_original_name, last_update) VALUES (?1, ?2, ?3, ?4, ?5, ?6)", params![loc_id, book_id, user_id, loc_name, loc_original_name, last_update], ) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'insérer le lieu.".to_string() } else { "Unable to insert location.".to_string() }))?; Ok(insert_result > 0) } /// Inserts a synced location element from the remote server. /// * `conn` - Database connection /// * `element_id` - The element's unique identifier /// * `location` - The parent location's unique identifier /// * `user_id` - The user's unique identifier /// * `element_name` - The encrypted element name /// * `original_name` - The original (unencrypted) element name /// * `element_description` - The encrypted element description (can be null) /// * `last_update` - The timestamp of the last update /// * `lang` - The language for error messages ("fr" or "en") /// Returns true if the insertion affected at least one row. pub fn insert_sync_location_element( conn: &Connection, element_id: &str, location: &str, user_id: &str, element_name: &str, original_name: &str, element_description: Option<&str>, last_update: i64, lang: Lang, ) -> AppResult { let insert_result = conn .execute( "INSERT INTO location_element (element_id, location, user_id, element_name, original_name, element_description, last_update) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", params![element_id, location, user_id, element_name, original_name, element_description, last_update], ) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'insérer l'élément du lieu.".to_string() } else { "Unable to insert location element.".to_string() }))?; Ok(insert_result > 0) } /// Inserts a synced location sub-element from the remote server. /// * `conn` - Database connection /// * `sub_element_id` - The sub-element's unique identifier /// * `element_id` - The parent element's unique identifier /// * `user_id` - The user's unique identifier /// * `sub_elem_name` - The encrypted sub-element name /// * `original_name` - The original (unencrypted) sub-element name /// * `sub_elem_description` - The encrypted sub-element description (can be null) /// * `last_update` - The timestamp of the last update /// * `lang` - The language for error messages ("fr" or "en") /// Returns true if the insertion affected at least one row. pub fn insert_sync_location_sub_element( conn: &Connection, sub_element_id: &str, element_id: &str, user_id: &str, sub_elem_name: &str, original_name: &str, sub_elem_description: Option<&str>, last_update: i64, lang: Lang, ) -> AppResult { let insert_result = conn .execute( "INSERT INTO location_sub_element (sub_element_id, element_id, user_id, sub_elem_name, original_name, sub_elem_description, last_update) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", params![sub_element_id, element_id, user_id, sub_elem_name, original_name, sub_elem_description, last_update], ) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'insérer le sous-élément du lieu.".to_string() } else { "Unable to insert location sub-element.".to_string() }))?; Ok(insert_result > 0) } /// Fetches complete location data by its ID (without user filtering). /// * `conn` - Database connection /// * `id` - The location's unique identifier /// * `lang` - The language for error messages ("fr" or "en") /// Returns an array of book location records. pub fn fetch_complete_location_by_id(conn: &Connection, id: &str, lang: Lang) -> AppResult> { let mut statement = conn .prepare("SELECT loc_id, book_id, user_id, loc_name, loc_original_name, last_update FROM book_location 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 complete_location = statement .query_map(params![id], |query_row| { Ok(BookLocationTable { loc_id: query_row.get(0)?, book_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(complete_location) } /// Fetches complete location element data by its ID (without user filtering). /// * `conn` - Database connection /// * `id` - The element's unique identifier /// * `lang` - The language for error messages ("fr" or "en") /// Returns an array of location element records. pub fn fetch_complete_location_element_by_id(conn: &Connection, id: &str, lang: Lang) -> AppResult> { let mut statement = conn .prepare("SELECT element_id, location, user_id, element_name, original_name, element_description, last_update FROM location_element 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 complete_location_element = statement .query_map(params![id], |query_row| { Ok(LocationElementTable { element_id: query_row.get(0)?, location: 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(complete_location_element) } /// Fetches complete location sub-element data by its ID (without user filtering). /// * `conn` - Database connection /// * `id` - The sub-element's unique identifier /// * `lang` - The language for error messages ("fr" or "en") /// Returns an array of location sub-element records. pub fn fetch_complete_location_sub_element_by_id(conn: &Connection, 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 location_sub_element WHERE sub_element_id = ?1") .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le sous-élément de lieu complet.".to_string() } else { "Unable to retrieve complete location sub-element.".to_string() }))?; let complete_location_sub_element = statement .query_map(params![id], |query_row| { Ok(LocationSubElementTable { 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 de lieu complet.".to_string() } else { "Unable to retrieve complete location sub-element.".to_string() }))? .collect::, _>>() .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le sous-élément de lieu complet.".to_string() } else { "Unable to retrieve complete location sub-element.".to_string() }))?; Ok(complete_location_sub_element) } /// Updates a location section with optional name change and series link. /// * `conn` - Database connection /// * `user_id` - The user's unique identifier /// * `section_id` - The section's unique identifier /// * `encrypted_name` - The new encrypted name (optional) /// * `original_name` - The new original name (optional) /// * `series_location_id` - The series location ID to link (optional, None to unlink) /// * `last_update` - The timestamp of the last update /// * `lang` - The language for error messages ("fr" or "en") /// Returns true if the update was successful. pub fn update_section_with_series_link( conn: &Connection, user_id: &str, section_id: &str, encrypted_name: Option<&str>, original_name: Option<&str>, series_location_id: Option<&str>, last_update: i64, lang: Lang, ) -> AppResult { let mut set_clauses: Vec = vec![format!("last_update={}", last_update)]; let mut param_values: Vec> = Vec::new(); if let (Some(enc_name), Some(orig_name)) = (encrypted_name, original_name) { set_clauses.push("loc_name=?".to_string()); set_clauses.push("loc_original_name=?".to_string()); param_values.push(Box::new(enc_name.to_string())); param_values.push(Box::new(orig_name.to_string())); } if let Some(series_id) = series_location_id { set_clauses.push("series_location_id=?".to_string()); param_values.push(Box::new(series_id.to_string())); } param_values.push(Box::new(section_id.to_string())); param_values.push(Box::new(user_id.to_string())); let query = format!("UPDATE book_location SET {} WHERE loc_id=? AND user_id=?", set_clauses.join(", ")); let param_refs: Vec<&dyn rusqlite::types::ToSql> = param_values.iter().map(|parameter| parameter.as_ref() as &dyn rusqlite::types::ToSql).collect(); let update_result = conn .execute(&query, param_refs.as_slice()) .map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour la section d'emplacement.".to_string() } else { "Unable to update location section.".to_string() }))?; Ok(update_result > 0) }