Migrate from window.electron to tauri IPC functions across components

- Replaced `window.electron.invoke` calls with equivalent `tauri` function calls for all IPC interactions.
- Removed `electron.d.ts` TypeScript definitions as they are no longer needed.
- Updated related logic for offline/online state synchronization.
- Added `types.rs` and `shared/mod.rs` modules to support Tauri IPC integration with Rust enums and shared logic.
- Refactored IPC request queues to use updated handler names for consistency with Tauri.
This commit is contained in:
natreex
2026-03-21 09:34:13 -04:00
parent 1a15692e40
commit ee4438834c
144 changed files with 21258 additions and 876 deletions

View File

@@ -0,0 +1,406 @@
use std::collections::HashMap;
use rusqlite::Connection;
use serde::Serialize;
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::domains::book::repo as book_repo;
use crate::domains::spell::repo;
use crate::domains::spell_tag::repo as spell_tag_repo;
use crate::domains::tombstone::repo as tombstone_repo;
use crate::error::{AppError, AppResult};
use crate::helpers::{create_unique_id, timestamp_in_seconds};
use crate::shared::types::Lang;
#[derive(Serialize)]
pub struct SpellTagProps {
pub id: String,
pub name: String,
pub color: Option<String>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SpellProps {
pub id: String,
pub name: String,
pub description: String,
pub appearance: String,
pub tags: Vec<String>,
pub power_level: Option<String>,
pub components: Option<String>,
pub limitations: Option<String>,
pub notes: Option<String>,
pub series_spell_id: Option<String>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SpellListItem {
pub id: String,
pub name: String,
pub description: String,
pub tags: Vec<SpellTagProps>,
pub series_spell_id: Option<String>,
}
#[derive(Serialize)]
pub struct SpellListResponse {
pub enabled: bool,
pub spells: Vec<SpellListItem>,
pub tags: Vec<SpellTagProps>,
}
pub struct SyncedSpell {
pub id: String,
pub name: String,
pub last_update: i64,
}
pub struct SyncedSpellTag {
pub id: String,
pub name: String,
pub last_update: i64,
}
/// Retrieves all spell tags for a specific book.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `book_id` - The unique identifier of the book
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns an array of spell tag props.
pub fn get_spell_tags(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult<Vec<SpellTagProps>> {
let user_key: String = get_user_encryption_key(user_id)?;
let spell_tags: Vec<spell_tag_repo::SpellTagResult> = spell_tag_repo::fetch_spell_tags(conn, user_id, book_id, lang)?;
let mut result: Vec<SpellTagProps> = Vec::with_capacity(spell_tags.len());
for tag in spell_tags {
result.push(SpellTagProps {
id: tag.tag_id,
name: decrypt_data_with_user_key(&tag.name, &user_key)?,
color: tag.color,
});
}
Ok(result)
}
/// Adds a new spell tag to a book.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `book_id` - The unique identifier of the book
/// * `name` - The name of the tag
/// * `color` - The optional color hex code
/// * `existing_tag_id` - Optional existing tag ID for sync
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns the created spell tag props.
pub fn add_spell_tag(
conn: &Connection, user_id: &str, book_id: &str, name: &str, color: Option<&str>,
existing_tag_id: Option<&str>, lang: Lang,
) -> AppResult<SpellTagProps> {
let user_key: String = get_user_encryption_key(user_id)?;
let tag_id: String = create_unique_id(existing_tag_id);
let encrypted_name: String = encrypt_data_with_user_key(name, &user_key)?;
let name_hash: String = hash_element(name);
let last_update: i64 = timestamp_in_seconds();
spell_tag_repo::insert_spell_tag(conn, &tag_id, book_id, user_id, &encrypted_name, &name_hash, color, last_update, lang)?;
Ok(SpellTagProps {
id: tag_id,
name: name.to_string(),
color: color.map(|c| c.to_string()),
})
}
/// Updates an existing spell tag.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `tag_id` - The unique identifier of the tag
/// * `name` - The new name of the tag
/// * `color` - The new optional color hex code
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the update was successful.
pub fn update_spell_tag(
conn: &Connection, user_id: &str, tag_id: &str, name: &str, color: Option<&str>, lang: Lang,
) -> AppResult<bool> {
let user_key: String = get_user_encryption_key(user_id)?;
let encrypted_name: String = encrypt_data_with_user_key(name, &user_key)?;
let name_hash: String = hash_element(name);
let last_update: i64 = timestamp_in_seconds();
spell_tag_repo::update_spell_tag(conn, user_id, tag_id, &encrypted_name, &name_hash, color, last_update, lang)
}
/// Deletes a spell tag and removes its references from all spells in the book.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `book_id` - The unique identifier of the book
/// * `tag_id` - The unique identifier of the tag to delete
/// * `deleted_at` - The timestamp of deletion
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the deletion was successful.
pub fn delete_spell_tag(
conn: &Connection, user_id: &str, book_id: &str, tag_id: &str, deleted_at: i64, lang: Lang,
) -> AppResult<bool> {
let user_key: String = get_user_encryption_key(user_id)?;
let spells: Vec<repo::SpellResult> = repo::fetch_spells(conn, user_id, book_id, lang)?;
let last_update: i64 = timestamp_in_seconds();
for spell in &spells {
let decrypted_tags: Option<String> = if let Some(ref tags) = spell.tags {
Some(decrypt_data_with_user_key(tags, &user_key)?)
} else {
None
};
let tags_array: Vec<String> = match decrypted_tags {
Some(ref tags_str) => serde_json::from_str(tags_str).unwrap_or_default(),
None => Vec::new(),
};
if tags_array.contains(&tag_id.to_string()) {
let updated_tags: Vec<&String> = tags_array.iter().filter(|t| t.as_str() != tag_id).collect();
let serialized_tags: String = serde_json::to_string(&updated_tags).unwrap_or_else(|_| "[]".to_string());
let encrypted_tags: String = encrypt_data_with_user_key(&serialized_tags, &user_key)?;
repo::update_spell_tags(conn, user_id, &spell.spell_id, &encrypted_tags, last_update, lang)?;
}
}
let deleted: bool = spell_tag_repo::delete_spell_tag(conn, user_id, tag_id, lang)?;
if deleted {
let removal_id: String = create_unique_id(None);
tombstone_repo::insert(conn, &removal_id, "book_spell_tags", tag_id, Some(book_id), user_id, deleted_at, lang)?;
}
Ok(deleted)
}
/// Retrieves the spell list with tags for a specific book.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `book_id` - The unique identifier of the book
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns the spell list response with enabled status, spells, and tags.
pub fn get_spell_list(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult<SpellListResponse> {
let user_key: String = get_user_encryption_key(user_id)?;
let book_tools: Option<book_repo::BookToolsTable> = book_repo::fetch_book_tools(conn, user_id, book_id, lang)?;
let enabled: bool = book_tools.map_or(false, |bt| bt.spells_enabled == 1);
let spell_tags: Vec<spell_tag_repo::SpellTagResult> = spell_tag_repo::fetch_spell_tags(conn, user_id, book_id, lang)?;
let mut tags: Vec<SpellTagProps> = Vec::with_capacity(spell_tags.len());
let mut tag_map: HashMap<String, (String, Option<String>)> = HashMap::new();
for tag in spell_tags {
let decrypted_name: String = decrypt_data_with_user_key(&tag.name, &user_key)?;
tag_map.insert(tag.tag_id.clone(), (decrypted_name.clone(), tag.color.clone()));
tags.push(SpellTagProps {
id: tag.tag_id,
name: decrypted_name,
color: tag.color,
});
}
let spell_results: Vec<repo::SpellResult> = repo::fetch_spells(conn, user_id, book_id, lang)?;
let mut spells: Vec<SpellListItem> = Vec::with_capacity(spell_results.len());
for spell in spell_results {
let decrypted_name: String = decrypt_data_with_user_key(&spell.name, &user_key)?;
let decrypted_description: Option<String> = if let Some(ref description) = spell.description {
Some(decrypt_data_with_user_key(description, &user_key)?)
} else {
None
};
let decrypted_tags: Option<String> = if let Some(ref tags_str) = spell.tags {
Some(decrypt_data_with_user_key(tags_str, &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(),
None => Vec::new(),
};
let resolved_tags: Vec<SpellTagProps> = tag_ids
.iter()
.filter_map(|tag_id| {
tag_map.get(tag_id).map(|(name, color)| SpellTagProps {
id: tag_id.clone(),
name: name.clone(),
color: color.clone(),
})
})
.collect();
let truncated_description: String = match decrypted_description {
Some(ref desc) if desc.len() > 150 => format!("{}...", &desc[..150]),
Some(ref desc) => desc.clone(),
None => String::new(),
};
spells.push(SpellListItem {
id: spell.spell_id,
name: decrypted_name,
description: truncated_description,
tags: resolved_tags,
series_spell_id: spell.series_spell_id,
});
}
Ok(SpellListResponse { enabled, spells, tags })
}
/// Retrieves the full details of a specific spell.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `spell_id` - The unique identifier of the spell
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns the spell props with all details.
pub fn get_spell_detail(conn: &Connection, user_id: &str, spell_id: &str, lang: Lang) -> AppResult<SpellProps> {
let user_key: String = get_user_encryption_key(user_id)?;
let spell: repo::SpellResult = repo::fetch_spell_by_id(conn, user_id, spell_id, 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 tag_ids: Vec<String> = match decrypted_tags {
Some(ref tags_str) => serde_json::from_str(tags_str).unwrap_or_default(),
None => Vec::new(),
};
Ok(SpellProps {
id: spell.spell_id,
name: decrypted_name,
description: decrypted_description,
appearance: decrypted_appearance,
tags: tag_ids,
power_level: if let Some(ref power_level) = spell.power_level { Some(decrypt_data_with_user_key(power_level, &user_key)?) } else { None },
components: if let Some(ref components) = spell.components { Some(decrypt_data_with_user_key(components, &user_key)?) } else { None },
limitations: if let Some(ref limitations) = spell.limitations { Some(decrypt_data_with_user_key(limitations, &user_key)?) } else { None },
notes: if let Some(ref notes) = spell.notes { Some(decrypt_data_with_user_key(notes, &user_key)?) } else { None },
series_spell_id: spell.series_spell_id,
})
}
/// Adds a new spell to a book.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `book_id` - The unique identifier of the book
/// * `name` - The name of the spell
/// * `description` - The description of the spell
/// * `appearance` - The appearance of the spell
/// * `tags` - The tag IDs array
/// * `power_level` - The optional power level
/// * `components` - The optional components
/// * `limitations` - The optional limitations
/// * `notes` - The optional notes
/// * `existing_spell_id` - Optional existing spell ID for sync
/// * `lang` - The language for error messages ("fr" or "en")
/// * `series_spell_id` - The optional series spell identifier
/// Returns the created spell props.
pub fn add_spell(
conn: &Connection, user_id: &str, book_id: &str, name: &str, description: &str, appearance: &str,
tags: Vec<String>, power_level: Option<&str>, components: Option<&str>, limitations: Option<&str>,
notes: Option<&str>, existing_spell_id: Option<&str>, lang: Lang, series_spell_id: Option<&str>,
) -> AppResult<SpellProps> {
let user_key: String = get_user_encryption_key(user_id)?;
let spell_id: String = create_unique_id(existing_spell_id);
let encrypted_name: String = encrypt_data_with_user_key(name, &user_key)?;
let name_hash: String = hash_element(name);
let encrypted_description: String = encrypt_data_with_user_key(description, &user_key)?;
let encrypted_appearance: String = encrypt_data_with_user_key(appearance, &user_key)?;
let serialized_tags: String = serde_json::to_string(&tags).unwrap_or_else(|_| "[]".to_string());
let encrypted_tags: String = encrypt_data_with_user_key(&serialized_tags, &user_key)?;
let encrypted_power_level: Option<String> = if let Some(power_level_val) = power_level { Some(encrypt_data_with_user_key(power_level_val, &user_key)?) } else { None };
let encrypted_components: Option<String> = if let Some(components_val) = components { Some(encrypt_data_with_user_key(components_val, &user_key)?) } else { None };
let encrypted_limitations: Option<String> = if let Some(limitations_val) = limitations { Some(encrypt_data_with_user_key(limitations_val, &user_key)?) } else { None };
let encrypted_notes: Option<String> = if let Some(notes_val) = notes { Some(encrypt_data_with_user_key(notes_val, &user_key)?) } else { None };
let last_update: i64 = timestamp_in_seconds();
repo::insert_spell(
conn, &spell_id, book_id, user_id, &encrypted_name, &name_hash,
Some(&encrypted_description), Some(&encrypted_appearance), &encrypted_tags,
encrypted_power_level.as_deref(), encrypted_components.as_deref(),
encrypted_limitations.as_deref(), encrypted_notes.as_deref(),
last_update, lang, series_spell_id,
)?;
Ok(SpellProps {
id: spell_id,
name: name.to_string(),
description: description.to_string(),
appearance: appearance.to_string(),
tags,
power_level: power_level.map(|s| s.to_string()),
components: components.map(|s| s.to_string()),
limitations: limitations.map(|s| s.to_string()),
notes: notes.map(|s| s.to_string()),
series_spell_id: series_spell_id.map(|s| s.to_string()),
})
}
/// Updates an existing spell.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `spell_id` - The unique identifier of the spell
/// * `name` - The name of the spell
/// * `description` - The description of the spell
/// * `appearance` - The appearance of the spell
/// * `tags` - The tag IDs array
/// * `power_level` - The optional power level
/// * `components` - The optional components
/// * `limitations` - The optional limitations
/// * `notes` - The optional notes
/// * `lang` - The language for error messages ("fr" or "en")
/// * `series_spell_id` - The optional series spell identifier
/// Returns true if the update was successful.
pub fn update_spell(
conn: &Connection, user_id: &str, spell_id: &str, name: &str, description: &str, appearance: &str,
tags: Vec<String>, power_level: Option<&str>, components: Option<&str>, limitations: Option<&str>,
notes: Option<&str>, lang: Lang, series_spell_id: Option<&str>,
) -> AppResult<bool> {
let user_key: String = get_user_encryption_key(user_id)?;
let encrypted_name: String = encrypt_data_with_user_key(name, &user_key)?;
let name_hash: String = hash_element(name);
let encrypted_description: String = encrypt_data_with_user_key(description, &user_key)?;
let encrypted_appearance: String = encrypt_data_with_user_key(appearance, &user_key)?;
let serialized_tags: String = serde_json::to_string(&tags).unwrap_or_else(|_| "[]".to_string());
let encrypted_tags: String = encrypt_data_with_user_key(&serialized_tags, &user_key)?;
let encrypted_power_level: Option<String> = if let Some(power_level_val) = power_level { Some(encrypt_data_with_user_key(power_level_val, &user_key)?) } else { None };
let encrypted_components: Option<String> = if let Some(components_val) = components { Some(encrypt_data_with_user_key(components_val, &user_key)?) } else { None };
let encrypted_limitations: Option<String> = if let Some(limitations_val) = limitations { Some(encrypt_data_with_user_key(limitations_val, &user_key)?) } else { None };
let encrypted_notes: Option<String> = if let Some(notes_val) = notes { Some(encrypt_data_with_user_key(notes_val, &user_key)?) } else { None };
let last_update: i64 = timestamp_in_seconds();
repo::update_spell(
conn, user_id, spell_id, &encrypted_name, &name_hash,
Some(&encrypted_description), Some(&encrypted_appearance), &encrypted_tags,
encrypted_power_level.as_deref(), encrypted_components.as_deref(),
encrypted_limitations.as_deref(), encrypted_notes.as_deref(),
last_update, lang, series_spell_id,
)
}
/// Deletes a spell.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `book_id` - The unique identifier of the book
/// * `spell_id` - The unique identifier of the spell
/// * `deleted_at` - The timestamp of deletion
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the deletion was successful.
pub fn delete_spell(conn: &Connection, user_id: &str, book_id: &str, spell_id: &str, deleted_at: i64, lang: Lang) -> AppResult<bool> {
let deleted: bool = repo::delete_spell(conn, user_id, spell_id, lang)?;
if deleted {
let removal_id: String = create_unique_id(None);
tombstone_repo::insert(conn, &removal_id, "book_spells", spell_id, Some(book_id), user_id, deleted_at, lang)?;
}
Ok(deleted)
}