Files
ERitors-Scribe-Desktop/src-tauri/src/domains/location/repo.rs

737 lines
42 KiB
Rust

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<String>,
pub element_name: Option<String>,
pub element_description: Option<String>,
pub sub_element_id: Option<String>,
pub sub_elem_name: Option<String>,
pub sub_elem_description: Option<String>,
pub series_location_id: Option<String>,
}
pub struct LocationElementQueryResult {
pub sub_element_id: Option<String>,
pub sub_elem_name: Option<String>,
pub sub_elem_description: Option<String>,
pub element_id: String,
pub element_name: String,
pub element_description: Option<String>,
}
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<String>,
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<String>,
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<Vec<LocationQueryResult>> {
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::<Result<Vec<_>, _>>()
.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<String> {
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<String> {
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<String> {
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<bool> {
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<bool> {
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<bool> {
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<bool> {
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<bool> {
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<bool> {
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<Vec<LocationElementQueryResult>> {
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::<Result<Vec<_>, _>>()
.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<bool> {
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<bool> {
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<bool> {
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<Vec<BookLocationTable>> {
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::<Result<Vec<_>, _>>()
.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<Vec<LocationElementTable>> {
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::<Result<Vec<_>, _>>()
.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<Vec<LocationSubElementTable>> {
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::<Result<Vec<_>, _>>()
.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<Vec<SyncedLocationResult>> {
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::<Result<Vec<_>, _>>()
.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<Vec<SyncedLocationElementResult>> {
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::<Result<Vec<_>, _>>()
.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<Vec<SyncedLocationSubElementResult>> {
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::<Result<Vec<_>, _>>()
.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<bool> {
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<bool> {
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<bool> {
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<Vec<BookLocationTable>> {
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::<Result<Vec<_>, _>>()
.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<Vec<LocationElementTable>> {
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::<Result<Vec<_>, _>>()
.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<Vec<LocationSubElementTable>> {
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::<Result<Vec<_>, _>>()
.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<bool> {
let mut set_clauses: Vec<String> = vec![format!("last_update={}", last_update)];
let mut param_values: Vec<Box<dyn rusqlite::types::ToSql>> = 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)
}