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