Refactor decryption logic to handle empty fields consistently across services

This commit is contained in:
natreex
2026-03-22 15:50:36 -04:00
parent 32d2b0fa5a
commit e8aaef108b
26 changed files with 226 additions and 120 deletions

View File

@@ -165,3 +165,7 @@ pub fn get_last_user_id() -> AppResult<Option<String>> {
let vault = read_vault();
Ok(vault.last_user_id)
}
pub fn clear_vault() -> AppResult<()> {
write_vault(&SecureVault::default())
}

View File

@@ -53,6 +53,14 @@ impl DatabaseManager {
pub fn close(&mut self, user_id: &str) {
self.connections.remove(user_id);
}
pub fn close_all(&mut self) {
self.connections.clear();
}
pub fn base_path(&self) -> &PathBuf {
&self.base_path
}
}
pub type DbManager = Arc<Mutex<DatabaseManager>>;

View File

@@ -38,6 +38,7 @@ pub struct CreateBookData {
pub title: String,
pub sub_title: Option<String>,
pub summary: Option<String>,
#[serde(rename = "type")]
pub book_type: String,
pub serie_id: Option<i64>,
pub desired_release_date: Option<String>,

View File

@@ -722,8 +722,14 @@ pub fn get_books(conn: &Connection, user_id: &str, lang: Lang) -> AppResult<Vec<
let mut book_props_list: Vec<BookProps> = Vec::with_capacity(books.len());
for book in books {
let decrypted_title: String = decrypt_data_with_user_key(&book.title, &user_key)?;
let decrypted_sub_title: String = if let Some(ref sub_title) = book.sub_title { decrypt_data_with_user_key(sub_title, &user_key)? } else { String::new() };
let decrypted_summary: String = if let Some(ref summary) = book.summary { decrypt_data_with_user_key(summary, &user_key)? } else { String::new() };
let decrypted_sub_title: String = match book.sub_title.as_deref() {
Some(sub_title) if !sub_title.is_empty() => decrypt_data_with_user_key(sub_title, &user_key)?,
_ => String::new(),
};
let decrypted_summary: String = match book.summary.as_deref() {
Some(summary) if !summary.is_empty() => decrypt_data_with_user_key(summary, &user_key)?,
_ => String::new(),
};
book_props_list.push(BookProps {
book_id: book.book_id,
@@ -769,6 +775,7 @@ pub fn add_book(
let encrypted_title: String = encrypt_data_with_user_key(title, &user_key)?;
let encrypted_sub_title: String = if sub_title.is_empty() { String::new() } else { encrypt_data_with_user_key(sub_title, &user_key)? };
let encrypted_summary: String = if summary.is_empty() { String::new() } else { encrypt_data_with_user_key(summary, &user_key)? };
let hashed_title: String = hash_element(title);
let hashed_sub_title: String = if sub_title.is_empty() { String::new() } else { hash_element(sub_title) };
@@ -795,8 +802,14 @@ pub fn get_book(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) ->
let series_id: Option<String> = repo::fetch_book_series_id(conn, book_id, lang);
let decrypted_title: String = decrypt_data_with_user_key(&book_data.title, &user_key)?;
let decrypted_sub_title: String = if let Some(ref sub_title) = book_data.sub_title { decrypt_data_with_user_key(sub_title, &user_key)? } else { String::new() };
let decrypted_summary: String = if let Some(ref summary) = book_data.summary { decrypt_data_with_user_key(summary, &user_key)? } else { String::new() };
let decrypted_sub_title: String = match book_data.sub_title.as_deref() {
Some(sub_title) if !sub_title.is_empty() => decrypt_data_with_user_key(sub_title, &user_key)?,
_ => String::new(),
};
let decrypted_summary: String = match book_data.summary.as_deref() {
Some(summary) if !summary.is_empty() => decrypt_data_with_user_key(summary, &user_key)?,
_ => String::new(),
};
Ok(BookProps {
book_id: book_data.book_id,
@@ -892,7 +905,7 @@ pub fn complete_book_data(conn: &Connection, user_id: &str, book_id: &str, lang:
for chapter in chapters {
let decrypted_title: String = if chapter.title.is_empty() { String::new() } else { decrypt_data_with_user_key(&chapter.title, &user_key)? };
let decrypted_content: String = if let Some(ref content) = chapter.content { decrypt_data_with_user_key(content, &user_key)? } else { String::new() };
let decrypted_content: String = if let Some(ref content) = chapter.content { if content.is_empty() { String::new() } else { decrypt_data_with_user_key(content, &user_key)? } } else { String::new() };
decrypted_chapters.push(CompleteChapterContent {
id: String::new(),
title: decrypted_title,
@@ -907,12 +920,12 @@ pub fn complete_book_data(conn: &Connection, user_id: &str, book_id: &str, lang:
Ok(CompleteBookData {
book_id: book_id.to_string(),
title: book_title,
sub_title: if let Some(ref sub_title) = book_data.sub_title { decrypt_data_with_user_key(sub_title, &user_key)? } else { String::new() },
summary: if let Some(ref summary) = book_data.summary { decrypt_data_with_user_key(summary, &user_key)? } else { String::new() },
sub_title: if let Some(ref sub_title) = book_data.sub_title { if sub_title.is_empty() { String::new() } else { decrypt_data_with_user_key(sub_title, &user_key)? } } else { String::new() },
summary: if let Some(ref summary) = book_data.summary { if summary.is_empty() { String::new() } else { decrypt_data_with_user_key(summary, &user_key)? } } else { String::new() },
cover_image,
user_infos: BookUserInfos {
username: decrypt_data_with_user_key(&user_infos.username, &user_key)?,
author_name: if let Some(ref author_name) = user_infos.author_name { decrypt_data_with_user_key(author_name, &user_key)? } else { String::new() },
username: if user_infos.username.is_empty() { String::new() } else { decrypt_data_with_user_key(&user_infos.username, &user_key)? },
author_name: if let Some(ref author_name) = user_infos.author_name { if author_name.is_empty() { String::new() } else { decrypt_data_with_user_key(author_name, &user_key)? } } else { String::new() },
},
chapters: decrypted_chapters,
})

View File

@@ -368,8 +368,8 @@ pub fn get_chapter_story(conn: &Connection, user_id: &str, chapter_id: &str, lan
}
if let Some(ref _incident_id) = story_result.incident_id {
let decrypted_incident_title: String = if let Some(ref incident_title) = story_result.incident_title { decrypt_data_with_user_key(incident_title, &user_encryption_key)? } else { String::new() };
let decrypted_incident_summary: String = if let Some(ref incident_summary) = story_result.incident_summary { decrypt_data_with_user_key(incident_summary, &user_encryption_key)? } else { String::new() };
let decrypted_incident_title: String = if let Some(ref incident_title) = story_result.incident_title { if incident_title.is_empty() { String::new() } else { decrypt_data_with_user_key(incident_title, &user_encryption_key)? } } else { String::new() };
let decrypted_incident_summary: String = if let Some(ref incident_summary) = story_result.incident_summary { if incident_summary.is_empty() { String::new() } else { decrypt_data_with_user_key(incident_summary, &user_encryption_key)? } } else { String::new() };
let act_story = act_stories_map.get(&act_id).unwrap();
let incident_already_exists: bool = act_story.incidents.iter().any(
@@ -390,8 +390,8 @@ pub fn get_chapter_story(conn: &Connection, user_id: &str, chapter_id: &str, lan
}
if let Some(ref _plot_point_id) = story_result.plot_point_id {
let decrypted_plot_title: String = if let Some(ref plot_title) = story_result.plot_title { decrypt_data_with_user_key(plot_title, &user_encryption_key)? } else { String::new() };
let decrypted_plot_summary: String = if let Some(ref plot_summary) = story_result.plot_summary { decrypt_data_with_user_key(plot_summary, &user_encryption_key)? } else { String::new() };
let decrypted_plot_title: String = if let Some(ref plot_title) = story_result.plot_title { if plot_title.is_empty() { String::new() } else { decrypt_data_with_user_key(plot_title, &user_encryption_key)? } } else { String::new() };
let decrypted_plot_summary: String = if let Some(ref plot_summary) = story_result.plot_summary { if plot_summary.is_empty() { String::new() } else { decrypt_data_with_user_key(plot_summary, &user_encryption_key)? } } else { String::new() };
let act_story = act_stories_map.get(&act_id).unwrap();
let plot_point_already_exists: bool = act_story.plot_points.iter().any(

View File

@@ -130,23 +130,23 @@ pub fn get_character_list(conn: &Connection, user_id: &str, book_id: &str, lang:
id: encrypted_character.character_id,
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() },
nickname: if let Some(ref nickname) = encrypted_character.nickname { if nickname.is_empty() { String::new() } else { decrypt_data_with_user_key(nickname, &user_key)? } } else { String::new() },
age: if let Some(ref age_val) = encrypted_character.age { if age_val.is_empty() { None } else { Some(decrypt_data_with_user_key(age_val, &user_key)?.parse::<i64>().unwrap_or(0)) } } else { None },
gender: if let Some(ref gender) = encrypted_character.gender { if gender.is_empty() { String::new() } else { decrypt_data_with_user_key(gender, &user_key)? } } else { String::new() },
species: if let Some(ref species) = encrypted_character.species { if species.is_empty() { String::new() } else { decrypt_data_with_user_key(species, &user_key)? } } else { String::new() },
nationality: if let Some(ref nationality) = encrypted_character.nationality { if nationality.is_empty() { String::new() } else { decrypt_data_with_user_key(nationality, &user_key)? } } else { String::new() },
status: if let Some(ref status) = encrypted_character.status { if status.is_empty() { "alive".to_string() } else { decrypt_data_with_user_key(status, &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: if encrypted_character.image.is_empty() { String::new() } else { decrypt_data_with_user_key(&encrypted_character.image, &user_key)? },
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() },
speech_pattern: if let Some(ref speech_pattern) = encrypted_character.speech_pattern { if speech_pattern.is_empty() { String::new() } else { decrypt_data_with_user_key(speech_pattern, &user_key)? } } else { String::new() },
catchphrase: if let Some(ref catchphrase) = encrypted_character.catchphrase { if catchphrase.is_empty() { String::new() } else { decrypt_data_with_user_key(catchphrase, &user_key)? } } else { String::new() },
residence: if let Some(ref residence) = encrypted_character.residence { if residence.is_empty() { String::new() } else { decrypt_data_with_user_key(residence, &user_key)? } } else { String::new() },
notes: if let Some(ref notes) = encrypted_character.notes { if notes.is_empty() { String::new() } else { decrypt_data_with_user_key(notes, &user_key)? } } else { String::new() },
color: if let Some(ref color) = encrypted_character.color { if color.is_empty() { String::new() } else { decrypt_data_with_user_key(color, &user_key)? } } else { String::new() },
series_character_id: encrypted_character.series_character_id,
});
}

View File

@@ -131,11 +131,11 @@ pub fn get_guide_line_ai(conn: &Connection, user_id: &str, book_id: &str, lang:
Ok(GuideLineAI {
narrative_type: ai_guide_line_data.narrative_type,
dialogue_type: ai_guide_line_data.dialogue_type,
global_resume: Some(if let Some(ref global_resume) = ai_guide_line_data.global_resume { decrypt_data_with_user_key(global_resume, &encryption_key)? } else { String::new() }),
atmosphere: Some(if let Some(ref atmosphere) = ai_guide_line_data.atmosphere { decrypt_data_with_user_key(atmosphere, &encryption_key)? } else { String::new() }),
global_resume: Some(if let Some(ref global_resume) = ai_guide_line_data.global_resume { if global_resume.is_empty() { String::new() } else { decrypt_data_with_user_key(global_resume, &encryption_key)? } } else { String::new() }),
atmosphere: Some(if let Some(ref atmosphere) = ai_guide_line_data.atmosphere { if atmosphere.is_empty() { String::new() } else { decrypt_data_with_user_key(atmosphere, &encryption_key)? } } else { String::new() }),
verbe_tense: ai_guide_line_data.verbe_tense,
themes: Some(if let Some(ref themes) = ai_guide_line_data.themes { decrypt_data_with_user_key(themes, &encryption_key)? } else { String::new() }),
current_resume: Some(if let Some(ref current_resume) = ai_guide_line_data.current_resume { decrypt_data_with_user_key(current_resume, &encryption_key)? } else { String::new() }),
themes: Some(if let Some(ref themes) = ai_guide_line_data.themes { if themes.is_empty() { String::new() } else { decrypt_data_with_user_key(themes, &encryption_key)? } } else { String::new() }),
current_resume: Some(if let Some(ref current_resume) = ai_guide_line_data.current_resume { if current_resume.is_empty() { String::new() } else { decrypt_data_with_user_key(current_resume, &encryption_key)? } } else { String::new() }),
langue: ai_guide_line_data.langue,
})
}

View File

@@ -58,7 +58,7 @@ pub fn get_series_list(conn: &Connection, user_id: &str, lang: Lang) -> AppResul
let mut series_list: Vec<SeriesListItemProps> = Vec::with_capacity(series_results.len());
for series_item in series_results {
let decrypted_name: String = decrypt_data_with_user_key(&series_item.name, &user_key)?;
let decrypted_description: String = if let Some(ref description) = series_item.description { decrypt_data_with_user_key(description, &user_key)? } else { String::new() };
let decrypted_description: String = if let Some(ref description) = series_item.description { if description.is_empty() { String::new() } else { decrypt_data_with_user_key(description, &user_key)? } } else { String::new() };
let book_ids: Vec<String> = if let Some(ref book_ids_str) = series_item.book_ids { book_ids_str.split(',').map(|s| s.to_string()).collect() } else { vec![] };
series_list.push(SeriesListItemProps {
@@ -102,7 +102,7 @@ pub fn get_series_detail(conn: &Connection, user_id: &str, series_id: &str, lang
}
let decrypted_name: String = decrypt_data_with_user_key(&series_result.name, &user_key)?;
let decrypted_description: String = if let Some(ref description) = series_result.description { decrypt_data_with_user_key(description, &user_key)? } else { String::new() };
let decrypted_description: String = if let Some(ref description) = series_result.description { if description.is_empty() { String::new() } else { decrypt_data_with_user_key(description, &user_key)? } } else { String::new() };
Ok(SeriesDetailProps {
id: series_result.series_id,

View File

@@ -112,23 +112,23 @@ pub fn get_character_list(conn: &Connection, user_id: &str, series_id: &str, lan
for character in characters {
let decrypted_name: String = if !character.first_name.is_empty() { decrypt_data_with_user_key(&character.first_name, &user_key)? } else { String::new() };
let decrypted_last_name: String = if !character.last_name.is_empty() { decrypt_data_with_user_key(&character.last_name, &user_key)? } else { String::new() };
let decrypted_nickname: String = if let Some(ref nickname) = character.nickname { decrypt_data_with_user_key(nickname, &user_key)? } else { String::new() };
let decrypted_age: Option<i64> = if let Some(ref age) = character.age { Some(decrypt_data_with_user_key(age, &user_key)?.parse::<i64>().unwrap_or(0)) } else { None };
let decrypted_gender: String = if let Some(ref gender) = character.gender { decrypt_data_with_user_key(gender, &user_key)? } else { String::new() };
let decrypted_species: String = if let Some(ref species) = character.species { decrypt_data_with_user_key(species, &user_key)? } else { String::new() };
let decrypted_nationality: String = if let Some(ref nationality) = character.nationality { decrypt_data_with_user_key(nationality, &user_key)? } else { String::new() };
let decrypted_status: String = if let Some(ref status) = character.status { decrypt_data_with_user_key(status, &user_key)? } else { "alive".to_string() };
let decrypted_nickname: String = if let Some(ref nickname) = character.nickname { if nickname.is_empty() { String::new() } else { decrypt_data_with_user_key(nickname, &user_key)? } } else { String::new() };
let decrypted_age: Option<i64> = if let Some(ref age) = character.age { if age.is_empty() { None } else { Some(decrypt_data_with_user_key(age, &user_key)?.parse::<i64>().unwrap_or(0)) } } else { None };
let decrypted_gender: String = if let Some(ref gender) = character.gender { if gender.is_empty() { String::new() } else { decrypt_data_with_user_key(gender, &user_key)? } } else { String::new() };
let decrypted_species: String = if let Some(ref species) = character.species { if species.is_empty() { String::new() } else { decrypt_data_with_user_key(species, &user_key)? } } else { String::new() };
let decrypted_nationality: String = if let Some(ref nationality) = character.nationality { if nationality.is_empty() { String::new() } else { decrypt_data_with_user_key(nationality, &user_key)? } } else { String::new() };
let decrypted_status: String = if let Some(ref status) = character.status { if status.is_empty() { "alive".to_string() } else { decrypt_data_with_user_key(status, &user_key)? } } else { "alive".to_string() };
let decrypted_title: String = if !character.title.is_empty() { decrypt_data_with_user_key(&character.title, &user_key)? } else { String::new() };
let decrypted_category: String = if !character.category.is_empty() { decrypt_data_with_user_key(&character.category, &user_key)? } else { String::new() };
let decrypted_image: String = if !character.image.is_empty() { decrypt_data_with_user_key(&character.image, &user_key)? } else { String::new() };
let decrypted_role: String = if !character.role.is_empty() { decrypt_data_with_user_key(&character.role, &user_key)? } else { String::new() };
let decrypted_biography: String = if !character.biography.is_empty() { decrypt_data_with_user_key(&character.biography, &user_key)? } else { String::new() };
let decrypted_history: String = if !character.history.is_empty() { decrypt_data_with_user_key(&character.history, &user_key)? } else { String::new() };
let decrypted_speech_pattern: String = if let Some(ref speech_pattern) = character.speech_pattern { decrypt_data_with_user_key(speech_pattern, &user_key)? } else { String::new() };
let decrypted_catchphrase: String = if let Some(ref catchphrase) = character.catchphrase { decrypt_data_with_user_key(catchphrase, &user_key)? } else { String::new() };
let decrypted_residence: String = if let Some(ref residence) = character.residence { decrypt_data_with_user_key(residence, &user_key)? } else { String::new() };
let decrypted_notes: String = if let Some(ref notes) = character.notes { decrypt_data_with_user_key(notes, &user_key)? } else { String::new() };
let decrypted_color: String = if let Some(ref color) = character.color { decrypt_data_with_user_key(color, &user_key)? } else { String::new() };
let decrypted_speech_pattern: String = if let Some(ref speech_pattern) = character.speech_pattern { if speech_pattern.is_empty() { String::new() } else { decrypt_data_with_user_key(speech_pattern, &user_key)? } } else { String::new() };
let decrypted_catchphrase: String = if let Some(ref catchphrase) = character.catchphrase { if catchphrase.is_empty() { String::new() } else { decrypt_data_with_user_key(catchphrase, &user_key)? } } else { String::new() };
let decrypted_residence: String = if let Some(ref residence) = character.residence { if residence.is_empty() { String::new() } else { decrypt_data_with_user_key(residence, &user_key)? } } else { String::new() };
let decrypted_notes: String = if let Some(ref notes) = character.notes { if notes.is_empty() { String::new() } else { decrypt_data_with_user_key(notes, &user_key)? } } else { String::new() };
let decrypted_color: String = if let Some(ref color) = character.color { if color.is_empty() { String::new() } else { decrypt_data_with_user_key(color, &user_key)? } } else { String::new() };
character_list.push(SeriesCharacterListProps {
id: character.character_id,

View File

@@ -318,13 +318,13 @@ pub fn get_complete_series_for_upload(conn: &Connection, user_id: &str, series_i
user_id: s.user_id,
name: decrypt_data_with_user_key(&s.name, &user_key)?,
name_hash: s.name_hash,
description: if let Some(ref val) = s.description { decrypt_data_with_user_key(val, &user_key)? } else { String::new() },
appearance: if let Some(ref val) = s.appearance { decrypt_data_with_user_key(val, &user_key)? } else { String::new() },
tags: if let Some(ref val) = s.tags { decrypt_data_with_user_key(val, &user_key)? } else { String::new() },
power_level: if let Some(ref val) = s.power_level { Some(decrypt_data_with_user_key(val, &user_key)?) } else { None },
components: if let Some(ref val) = s.components { Some(decrypt_data_with_user_key(val, &user_key)?) } else { None },
limitations: if let Some(ref val) = s.limitations { Some(decrypt_data_with_user_key(val, &user_key)?) } else { None },
notes: if let Some(ref val) = s.notes { Some(decrypt_data_with_user_key(val, &user_key)?) } else { None },
description: if let Some(ref description) = s.description { if description.is_empty() { String::new() } else { decrypt_data_with_user_key(description, &user_key)? } } else { String::new() },
appearance: if let Some(ref appearance) = s.appearance { if appearance.is_empty() { String::new() } else { decrypt_data_with_user_key(appearance, &user_key)? } } else { String::new() },
tags: if let Some(ref tags) = s.tags { if tags.is_empty() { String::new() } else { decrypt_data_with_user_key(tags, &user_key)? } } else { String::new() },
power_level: if let Some(ref power_level) = s.power_level { if power_level.is_empty() { None } else { Some(decrypt_data_with_user_key(power_level, &user_key)?) } } else { None },
components: if let Some(ref components) = s.components { if components.is_empty() { None } else { Some(decrypt_data_with_user_key(components, &user_key)?) } } else { None },
limitations: if let Some(ref limitations) = s.limitations { if limitations.is_empty() { None } else { Some(decrypt_data_with_user_key(limitations, &user_key)?) } } else { None },
notes: if let Some(ref notes) = s.notes { if notes.is_empty() { None } else { Some(decrypt_data_with_user_key(notes, &user_key)?) } } else { None },
last_update: s.last_update,
});
}

View File

@@ -71,11 +71,11 @@ pub fn get_world_list(conn: &Connection, user_id: &str, series_id: &str, lang: L
worlds_map.insert(row.world_id.clone(), SeriesWorldListProps {
id: row.world_id.clone(),
name: if row.world_name.is_empty() { String::new() } else { decrypt_data_with_user_key(&row.world_name, &user_key)? },
history: if let Some(ref history) = row.history { decrypt_data_with_user_key(history, &user_key)? } else { String::new() },
politics: if let Some(ref politics) = row.politics { decrypt_data_with_user_key(politics, &user_key)? } else { String::new() },
economy: if let Some(ref economy) = row.economy { decrypt_data_with_user_key(economy, &user_key)? } else { String::new() },
religion: if let Some(ref religion) = row.religion { decrypt_data_with_user_key(religion, &user_key)? } else { String::new() },
languages: if let Some(ref languages) = row.languages { decrypt_data_with_user_key(languages, &user_key)? } else { String::new() },
history: if let Some(ref history) = row.history { if history.is_empty() { String::new() } else { decrypt_data_with_user_key(history, &user_key)? } } else { String::new() },
politics: if let Some(ref politics) = row.politics { if politics.is_empty() { String::new() } else { decrypt_data_with_user_key(politics, &user_key)? } } else { String::new() },
economy: if let Some(ref economy) = row.economy { if economy.is_empty() { String::new() } else { decrypt_data_with_user_key(economy, &user_key)? } } else { String::new() },
religion: if let Some(ref religion) = row.religion { if religion.is_empty() { String::new() } else { decrypt_data_with_user_key(religion, &user_key)? } } else { String::new() },
languages: if let Some(ref languages) = row.languages { if languages.is_empty() { String::new() } else { decrypt_data_with_user_key(languages, &user_key)? } } else { String::new() },
laws: Vec::new(),
biomes: Vec::new(),
issues: Vec::new(),
@@ -95,8 +95,8 @@ pub fn get_world_list(conn: &Connection, user_id: &str, series_id: &str, lang: L
let world = worlds_map.get_mut(&row.world_id).unwrap();
let element = SeriesWorldElementProps {
id: element_id.clone(),
name: if let Some(ref element_name) = row.element_name { decrypt_data_with_user_key(element_name, &user_key)? } else { String::new() },
description: if let Some(ref element_description) = row.element_description { decrypt_data_with_user_key(element_description, &user_key)? } else { String::new() },
name: if let Some(ref element_name) = row.element_name { if element_name.is_empty() { String::new() } else { decrypt_data_with_user_key(element_name, &user_key)? } } else { String::new() },
description: if let Some(ref element_description) = row.element_description { if element_description.is_empty() { String::new() } else { decrypt_data_with_user_key(element_description, &user_key)? } } else { String::new() },
};
if let Some(element_type) = row.element_type {

View File

@@ -252,9 +252,9 @@ pub fn get_spell_detail(conn: &Connection, user_id: &str, spell_id: &str, lang:
.ok_or_else(|| AppError::Internal(if lang == Lang::Fr { "Sort non trouvé.".to_string() } else { "Spell not found.".to_string() }))?;
let decrypted_name: String = decrypt_data_with_user_key(&spell.name, &user_key)?;
let decrypted_description: String = if let Some(ref description) = spell.description { decrypt_data_with_user_key(description, &user_key)? } else { String::new() };
let decrypted_appearance: String = if let Some(ref appearance) = spell.appearance { decrypt_data_with_user_key(appearance, &user_key)? } else { String::new() };
let decrypted_tags: Option<String> = if let Some(ref tags) = spell.tags { Some(decrypt_data_with_user_key(tags, &user_key)?) } else { None };
let decrypted_description: String = if let Some(ref description) = spell.description { if description.is_empty() { String::new() } else { decrypt_data_with_user_key(description, &user_key)? } } else { String::new() };
let decrypted_appearance: String = if let Some(ref appearance) = spell.appearance { if appearance.is_empty() { String::new() } else { decrypt_data_with_user_key(appearance, &user_key)? } } else { String::new() };
let decrypted_tags: Option<String> = if let Some(ref tags) = spell.tags { if tags.is_empty() { None } else { Some(decrypt_data_with_user_key(tags, &user_key)?) } } else { None };
let tag_ids: Vec<String> = match decrypted_tags {
Some(ref tags_str) => serde_json::from_str(tags_str).unwrap_or_default(),

View File

@@ -199,7 +199,7 @@ pub fn get_complete_sync_book(conn: &Connection, user_id: &str, sync_compare_dat
book_id: act_summary_record.book_id.clone(),
user_id: act_summary_record.user_id.clone(),
act_number: act_summary_record.act_index,
summary: if let Some(ref summary) = act_summary_record.summary { decrypt_data_with_user_key(summary, &user_encryption_key)? } else { String::new() },
summary: if let Some(ref summary) = act_summary_record.summary { if summary.is_empty() { String::new() } else { decrypt_data_with_user_key(summary, &user_encryption_key)? } } else { String::new() },
last_update: act_summary_record.last_update,
});
}
@@ -529,12 +529,12 @@ pub fn get_complete_sync_book(conn: &Connection, user_id: &str, sync_compare_dat
user_id: spell_record.user_id,
name: decrypt_data_with_user_key(&spell_record.name, &user_encryption_key)?,
name_hash: spell_record.name_hash,
description: if let Some(ref description) = spell_record.description { decrypt_data_with_user_key(description, &user_encryption_key).ok().unwrap_or_default() } else { String::new() },
appearance: if let Some(ref appearance) = spell_record.appearance { decrypt_data_with_user_key(appearance, &user_encryption_key).ok().unwrap_or_default() } else { String::new() },
tags: if let Some(ref tags) = spell_record.tags { decrypt_data_with_user_key(tags, &user_encryption_key).ok().unwrap_or_default() } else { String::new() },
power_level: if let Some(ref power_level) = spell_record.power_level { Some(decrypt_data_with_user_key(power_level, &user_encryption_key)?) } else { None },
components: if let Some(ref components) = spell_record.components { Some(decrypt_data_with_user_key(components, &user_encryption_key)?) } else { None },
limitations: if let Some(ref limitations) = spell_record.limitations { Some(decrypt_data_with_user_key(limitations, &user_encryption_key)?) } else { None },
description: if let Some(ref description) = spell_record.description { if description.is_empty() { String::new() } else { decrypt_data_with_user_key(description, &user_encryption_key).ok().unwrap_or_default() } } else { String::new() },
appearance: if let Some(ref appearance) = spell_record.appearance { if appearance.is_empty() { String::new() } else { decrypt_data_with_user_key(appearance, &user_encryption_key).ok().unwrap_or_default() } } else { String::new() },
tags: if let Some(ref tags) = spell_record.tags { if tags.is_empty() { String::new() } else { decrypt_data_with_user_key(tags, &user_encryption_key).ok().unwrap_or_default() } } else { String::new() },
power_level: if let Some(ref power_level) = spell_record.power_level { if power_level.is_empty() { None } else { Some(decrypt_data_with_user_key(power_level, &user_encryption_key)?) } } else { None },
components: if let Some(ref components) = spell_record.components { if components.is_empty() { None } else { Some(decrypt_data_with_user_key(components, &user_encryption_key)?) } } else { None },
limitations: if let Some(ref limitations) = spell_record.limitations { if limitations.is_empty() { None } else { Some(decrypt_data_with_user_key(limitations, &user_encryption_key)?) } } else { None },
notes: if let Some(ref notes) = spell_record.notes { Some(decrypt_data_with_user_key(notes, &user_encryption_key)?) } else { None },
last_update: spell_record.last_update,
});
@@ -1125,7 +1125,7 @@ pub fn get_synced_books(conn: &Connection, user_id: &str, lang: Lang) -> AppResu
id: current_book_id.to_string(),
book_type: book_record.book_type.clone(),
title: decrypt_data_with_user_key(&book_record.title, &user_encryption_key).unwrap_or_default(),
sub_title: if let Some(ref sub_title) = book_record.sub_title { decrypt_data_with_user_key(sub_title, &user_encryption_key).ok() } else { None },
sub_title: if let Some(ref sub_title) = book_record.sub_title { if sub_title.is_empty() { None } else { decrypt_data_with_user_key(sub_title, &user_encryption_key).ok() } } else { None },
last_update: book_record.last_update,
chapters: book_chapters,
characters: book_characters,
@@ -1250,7 +1250,7 @@ pub fn get_synced_series(conn: &Connection, user_id: &str, lang: Lang) -> AppRes
synced_series_list.push(SyncedSeries {
id: series_id.to_string(),
name: decrypt_data_with_user_key(&series.name, &user_encryption_key).unwrap_or_default(),
description: if let Some(ref description) = series.description { decrypt_data_with_user_key(description, &user_encryption_key).ok() } else { None },
description: if let Some(ref description) = series.description { if description.is_empty() { None } else { decrypt_data_with_user_key(description, &user_encryption_key).ok() } } else { None },
last_update: series.last_update,
books,
characters,

View File

@@ -100,7 +100,7 @@ pub fn upload_book_for_sync(conn: &Connection, user_id: &str, book_id: &str, lan
let mut act_summaries: Vec<BookActSummariesTable> = Vec::with_capacity(encrypted_act_summaries.len());
for act_summary in encrypted_act_summaries {
let decrypted_summary: String = if let Some(ref summary) = act_summary.summary { decrypt_data_with_user_key(summary, &user_encryption_key)? } else { String::new() };
let decrypted_summary: String = if let Some(ref summary) = act_summary.summary { if summary.is_empty() { String::new() } else { decrypt_data_with_user_key(summary, &user_encryption_key)? } } else { String::new() };
act_summaries.push(BookActSummariesTable {
summary_id: act_summary.act_sum_id, book_id: act_summary.book_id,
user_id: act_summary.user_id, act_number: act_summary.act_index,
@@ -333,13 +333,13 @@ pub fn upload_book_for_sync(conn: &Connection, user_id: &str, book_id: &str, lan
let mut spells: Vec<BookSpellsTable> = Vec::with_capacity(encrypted_spells.len());
for spell in encrypted_spells {
let decrypted_name: String = decrypt_data_with_user_key(&spell.name, &user_encryption_key)?;
let decrypted_description: String = if let Some(ref description) = spell.description { decrypt_data_with_user_key(description, &user_encryption_key)? } else { String::new() };
let decrypted_appearance: String = if let Some(ref appearance) = spell.appearance { decrypt_data_with_user_key(appearance, &user_encryption_key)? } else { String::new() };
let decrypted_tags: String = if let Some(ref tags) = spell.tags { decrypt_data_with_user_key(tags, &user_encryption_key)? } else { String::new() };
let decrypted_power_level: Option<String> = if let Some(ref power_level) = spell.power_level { Some(decrypt_data_with_user_key(power_level, &user_encryption_key)?) } else { None };
let decrypted_components: Option<String> = if let Some(ref components) = spell.components { Some(decrypt_data_with_user_key(components, &user_encryption_key)?) } else { None };
let decrypted_limitations: Option<String> = if let Some(ref limitations) = spell.limitations { Some(decrypt_data_with_user_key(limitations, &user_encryption_key)?) } else { None };
let decrypted_notes: Option<String> = if let Some(ref notes) = spell.notes { Some(decrypt_data_with_user_key(notes, &user_encryption_key)?) } else { None };
let decrypted_description: String = if let Some(ref description) = spell.description { if description.is_empty() { String::new() } else { decrypt_data_with_user_key(description, &user_encryption_key)? } } else { String::new() };
let decrypted_appearance: String = if let Some(ref appearance) = spell.appearance { if appearance.is_empty() { String::new() } else { decrypt_data_with_user_key(appearance, &user_encryption_key)? } } else { String::new() };
let decrypted_tags: String = if let Some(ref tags) = spell.tags { if tags.is_empty() { String::new() } else { decrypt_data_with_user_key(tags, &user_encryption_key)? } } else { String::new() };
let decrypted_power_level: Option<String> = if let Some(ref power_level) = spell.power_level { if power_level.is_empty() { None } else { Some(decrypt_data_with_user_key(power_level, &user_encryption_key)?) } } else { None };
let decrypted_components: Option<String> = if let Some(ref components) = spell.components { if components.is_empty() { None } else { Some(decrypt_data_with_user_key(components, &user_encryption_key)?) } } else { None };
let decrypted_limitations: Option<String> = if let Some(ref limitations) = spell.limitations { if limitations.is_empty() { None } else { Some(decrypt_data_with_user_key(limitations, &user_encryption_key)?) } } else { None };
let decrypted_notes: Option<String> = if let Some(ref notes) = spell.notes { if notes.is_empty() { None } else { Some(decrypt_data_with_user_key(notes, &user_encryption_key)?) } } else { None };
spells.push(BookSpellsTable {
spell_id: spell.spell_id, book_id: spell.book_id,
user_id: spell.user_id, name: decrypted_name,

View File

@@ -5,7 +5,7 @@ use crate::crypto::{encryption, key_manager};
use crate::db::connection::DbManager;
use crate::db::schema;
use crate::domains::user::service;
use crate::error::AppError;
use crate::error::{AppError, AppResult};
use crate::shared::session::SessionState;
#[derive(Deserialize)]
@@ -180,3 +180,37 @@ pub fn update_user_info(data: UpdateUserData, db: State<DbManager>, session: Sta
service::update_user_infos(conn, &user_key, &user_id, &data.username, &data.email, data.author_name.as_deref(), lang)
}
#[tauri::command]
pub fn dev_reset_all(db: State<DbManager>, session: State<SessionState>) -> AppResult<bool> {
if !cfg!(debug_assertions) {
return Err(AppError::Internal("dev_reset_all is only available in dev mode".to_string()));
}
// 1. Close all DB connections
let mut db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?;
let base_path = db_manager.base_path().clone();
db_manager.close_all();
drop(db_manager);
// 2. Delete all eritors-local-*.db files (+ WAL/SHM)
if let Ok(entries) = std::fs::read_dir(&base_path) {
for entry in entries.flatten() {
let path = entry.path();
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
if name.starts_with("eritors-local-") {
let _ = std::fs::remove_file(&path);
}
}
}
}
// 3. Clear vault (tokens, encryption keys, PIN hashes)
key_manager::clear_vault()?;
// 4. Reset session
let mut session_guard = session.lock().map_err(|e| AppError::Internal(format!("Session lock failed: {}", e)))?;
session_guard.user_id = None;
Ok(true)
}

View File

@@ -21,7 +21,7 @@ pub fn return_user_infos(conn: &Connection, user_id: &str, lang: Lang) -> AppRes
let username: String = decrypt_data_with_user_key(&user_infos_data.username, &user_encryption_key)?;
let email: String = decrypt_data_with_user_key(&user_infos_data.email, &user_encryption_key)?;
let author_name: String = if let Some(ref author_name_val) = user_infos_data.author_name { decrypt_data_with_user_key(author_name_val, &user_encryption_key)? } else { String::new() };
let author_name: String = if let Some(ref author_name_val) = user_infos_data.author_name { if author_name_val.is_empty() { String::new() } else { decrypt_data_with_user_key(author_name_val, &user_encryption_key)? } } else { String::new() };
Ok(UserInfoResponse {
id: user_id.to_string(),

View File

@@ -140,8 +140,8 @@ pub fn get_worlds(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -
if let Some(element_type) = query_row.element_type {
let world_element: WorldElement = WorldElement {
id: query_row.element_id.clone().unwrap_or_default(),
name: if let Some(ref element_name) = query_row.element_name { decrypt_data_with_user_key(element_name, &user_encryption_key)? } else { String::new() },
description: if let Some(ref element_description) = query_row.element_description { decrypt_data_with_user_key(element_description, &user_encryption_key)? } else { String::new() },
name: if let Some(ref element_name) = query_row.element_name { if element_name.is_empty() { String::new() } else { decrypt_data_with_user_key(element_name, &user_encryption_key)? } } else { String::new() },
description: if let Some(ref element_description) = query_row.element_description { if element_description.is_empty() { String::new() } else { decrypt_data_with_user_key(element_description, &user_encryption_key)? } } else { String::new() },
element_type: None,
};
push_element_to_world(&mut worlds[index], element_type, world_element);
@@ -149,12 +149,12 @@ pub fn get_worlds(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -
} else {
let mut new_world: WorldProps = WorldProps {
id: query_row.world_id.clone(),
name: decrypt_data_with_user_key(&query_row.world_name, &user_encryption_key)?,
history: if let Some(ref history) = query_row.history { decrypt_data_with_user_key(history, &user_encryption_key)? } else { String::new() },
politics: if let Some(ref politics) = query_row.politics { decrypt_data_with_user_key(politics, &user_encryption_key)? } else { String::new() },
economy: if let Some(ref economy) = query_row.economy { decrypt_data_with_user_key(economy, &user_encryption_key)? } else { String::new() },
religion: if let Some(ref religion) = query_row.religion { decrypt_data_with_user_key(religion, &user_encryption_key)? } else { String::new() },
languages: if let Some(ref languages) = query_row.languages { decrypt_data_with_user_key(languages, &user_encryption_key)? } else { String::new() },
name: if query_row.world_name.is_empty() { String::new() } else { decrypt_data_with_user_key(&query_row.world_name, &user_encryption_key)? },
history: if let Some(ref history) = query_row.history { if history.is_empty() { String::new() } else { decrypt_data_with_user_key(history, &user_encryption_key)? } } else { String::new() },
politics: if let Some(ref politics) = query_row.politics { if politics.is_empty() { String::new() } else { decrypt_data_with_user_key(politics, &user_encryption_key)? } } else { String::new() },
economy: if let Some(ref economy) = query_row.economy { if economy.is_empty() { String::new() } else { decrypt_data_with_user_key(economy, &user_encryption_key)? } } else { String::new() },
religion: if let Some(ref religion) = query_row.religion { if religion.is_empty() { String::new() } else { decrypt_data_with_user_key(religion, &user_encryption_key)? } } else { String::new() },
languages: if let Some(ref languages) = query_row.languages { if languages.is_empty() { String::new() } else { decrypt_data_with_user_key(languages, &user_encryption_key)? } } else { String::new() },
laws: Vec::new(),
biomes: Vec::new(),
issues: Vec::new(),
@@ -173,8 +173,8 @@ pub fn get_worlds(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -
if let Some(element_type) = query_row.element_type {
let world_element: WorldElement = WorldElement {
id: query_row.element_id.clone().unwrap_or_default(),
name: if let Some(ref element_name) = query_row.element_name { decrypt_data_with_user_key(element_name, &user_encryption_key)? } else { String::new() },
description: if let Some(ref element_description) = query_row.element_description { decrypt_data_with_user_key(element_description, &user_encryption_key)? } else { String::new() },
name: if let Some(ref element_name) = query_row.element_name { if element_name.is_empty() { String::new() } else { decrypt_data_with_user_key(element_name, &user_encryption_key)? } } else { String::new() },
description: if let Some(ref element_description) = query_row.element_description { if element_description.is_empty() { String::new() } else { decrypt_data_with_user_key(element_description, &user_encryption_key)? } } else { String::new() },
element_type: None,
};
push_element_to_world(&mut new_world, element_type, world_element);

View File

@@ -35,26 +35,22 @@ impl Serialize for AppError {
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("AppError", 2)?;
state.serialize_field("kind", &self.error_kind())?;
state.serialize_field("message", &self.to_string())?;
state.end()
serializer.serialize_str(&self.user_message())
}
}
impl AppError {
fn error_kind(&self) -> &str {
fn user_message(&self) -> String {
match self {
AppError::Database(_) => "DATABASE",
AppError::Encryption(_) => "ENCRYPTION",
AppError::Keyring(_) => "KEYRING",
AppError::Auth(_) => "AUTH",
AppError::NotFound(_) => "NOT_FOUND",
AppError::Validation(_) => "VALIDATION",
AppError::Io(_) => "IO",
AppError::Json(_) => "JSON",
AppError::Internal(_) => "INTERNAL",
AppError::Database(e) => e.to_string(),
AppError::Encryption(msg)
| AppError::Keyring(msg)
| AppError::Auth(msg)
| AppError::NotFound(msg)
| AppError::Validation(msg)
| AppError::Internal(msg) => msg.clone(),
AppError::Io(e) => e.to_string(),
AppError::Json(e) => e.to_string(),
}
}
}

View File

@@ -33,6 +33,7 @@ pub fn run() {
domains::user::commands::get_user_info,
domains::user::commands::sync_user,
domains::user::commands::update_user_info,
domains::user::commands::dev_reset_all,
// ─── Offline ───────────────────────────────────
domains::offline::commands::offline_pin_set,
domains::offline::commands::offline_pin_verify,

View File

View File

@@ -0,0 +1,24 @@
use std::sync::{Arc, Mutex};
use crate::shared::types::Lang;
pub struct Session {
pub user_id: Option<String>,
pub lang: Lang,
}
impl Session {
pub fn new() -> Self {
Self { user_id: None, lang: Lang::Fr }
}
pub fn get_user_id(&self) -> Result<&str, String> {
self.user_id.as_deref().ok_or_else(|| "No user session. Please log in first.".to_string())
}
}
pub type SessionState = Arc<Mutex<Session>>;
pub fn create_session() -> SessionState {
Arc::new(Mutex::new(Session::new()))
}