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:
406
src-tauri/src/domains/spell/service.rs
Normal file
406
src-tauri/src/domains/spell/service.rs
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user