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:
175
src-tauri/src/domains/spell/commands.rs
Normal file
175
src-tauri/src/domains/spell/commands.rs
Normal file
@@ -0,0 +1,175 @@
|
||||
use serde::Deserialize;
|
||||
use tauri::State;
|
||||
|
||||
use crate::db::connection::DbManager;
|
||||
use crate::domains::spell::service;
|
||||
use crate::error::AppError;
|
||||
use crate::shared::session::SessionState;
|
||||
use crate::shared::types::Lang;
|
||||
|
||||
fn get_session(session: &State<SessionState>) -> Result<(String, Lang), AppError> {
|
||||
let session_guard = session.lock().map_err(|e| AppError::Internal(format!("Session lock failed: {}", e)))?;
|
||||
let user_id = session_guard.get_user_id().map_err(|e| AppError::Auth(e))?.to_string();
|
||||
let lang = session_guard.lang;
|
||||
Ok((user_id, lang))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetSpellListData {
|
||||
pub book_id: String,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_spell_list(data: GetSpellListData, db: State<DbManager>, session: State<SessionState>) -> Result<service::SpellListResponse, AppError> {
|
||||
let (user_id, lang) = get_session(&session)?;
|
||||
let db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?;
|
||||
let conn = db_manager.get_connection(&user_id)?;
|
||||
service::get_spell_list(conn, &user_id, &data.book_id, lang)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetSpellTagsData {
|
||||
pub book_id: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_spell_tags(data: GetSpellTagsData, db: State<DbManager>, session: State<SessionState>) -> Result<Vec<service::SpellTagProps>, AppError> {
|
||||
let (user_id, lang) = get_session(&session)?;
|
||||
let db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?;
|
||||
let conn = db_manager.get_connection(&user_id)?;
|
||||
service::get_spell_tags(conn, &user_id, &data.book_id, lang)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetSpellDetailData {
|
||||
pub spell_id: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_spell_detail(data: GetSpellDetailData, db: State<DbManager>, session: State<SessionState>) -> Result<service::SpellProps, AppError> {
|
||||
let (user_id, lang) = get_session(&session)?;
|
||||
let db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?;
|
||||
let conn = db_manager.get_connection(&user_id)?;
|
||||
service::get_spell_detail(conn, &user_id, &data.spell_id, lang)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SpellData {
|
||||
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(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreateSpellData {
|
||||
pub book_id: String,
|
||||
pub spell: SpellData,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn create_spell(data: CreateSpellData, db: State<DbManager>, session: State<SessionState>) -> Result<service::SpellProps, AppError> {
|
||||
let (user_id, lang) = get_session(&session)?;
|
||||
let db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?;
|
||||
let conn = db_manager.get_connection(&user_id)?;
|
||||
service::add_spell(
|
||||
conn, &user_id, &data.book_id, &data.spell.name, &data.spell.description, &data.spell.appearance,
|
||||
data.spell.tags, data.spell.power_level.as_deref(), data.spell.components.as_deref(),
|
||||
data.spell.limitations.as_deref(), data.spell.notes.as_deref(), None, lang, data.spell.series_spell_id.as_deref(),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UpdateSpellData {
|
||||
pub spell_id: String,
|
||||
pub spell: SpellData,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn update_spell(data: UpdateSpellData, db: State<DbManager>, session: State<SessionState>) -> Result<bool, AppError> {
|
||||
let (user_id, lang) = get_session(&session)?;
|
||||
let db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?;
|
||||
let conn = db_manager.get_connection(&user_id)?;
|
||||
service::update_spell(
|
||||
conn, &user_id, &data.spell_id, &data.spell.name, &data.spell.description, &data.spell.appearance,
|
||||
data.spell.tags, data.spell.power_level.as_deref(), data.spell.components.as_deref(),
|
||||
data.spell.limitations.as_deref(), data.spell.notes.as_deref(), lang, data.spell.series_spell_id.as_deref(),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DeleteSpellData {
|
||||
pub spell_id: String,
|
||||
pub book_id: String,
|
||||
pub deleted_at: i64,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn delete_spell(data: DeleteSpellData, db: State<DbManager>, session: State<SessionState>) -> Result<bool, AppError> {
|
||||
let (user_id, lang) = get_session(&session)?;
|
||||
let db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?;
|
||||
let conn = db_manager.get_connection(&user_id)?;
|
||||
service::delete_spell(conn, &user_id, &data.book_id, &data.spell_id, data.deleted_at, lang)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreateSpellTagData {
|
||||
pub book_id: String,
|
||||
pub name: String,
|
||||
pub color: Option<String>,
|
||||
pub id: Option<String>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn create_spell_tag(data: CreateSpellTagData, db: State<DbManager>, session: State<SessionState>) -> Result<service::SpellTagProps, AppError> {
|
||||
let (user_id, lang) = get_session(&session)?;
|
||||
let db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?;
|
||||
let conn = db_manager.get_connection(&user_id)?;
|
||||
service::add_spell_tag(conn, &user_id, &data.book_id, &data.name, data.color.as_deref(), data.id.as_deref(), lang)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UpdateSpellTagData {
|
||||
pub tag_id: String,
|
||||
pub name: String,
|
||||
pub color: Option<String>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn update_spell_tag(data: UpdateSpellTagData, db: State<DbManager>, session: State<SessionState>) -> Result<bool, AppError> {
|
||||
let (user_id, lang) = get_session(&session)?;
|
||||
let db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?;
|
||||
let conn = db_manager.get_connection(&user_id)?;
|
||||
service::update_spell_tag(conn, &user_id, &data.tag_id, &data.name, data.color.as_deref(), lang)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DeleteSpellTagData {
|
||||
pub tag_id: String,
|
||||
pub book_id: String,
|
||||
pub deleted_at: i64,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn delete_spell_tag(data: DeleteSpellTagData, db: State<DbManager>, session: State<SessionState>) -> Result<bool, AppError> {
|
||||
let (user_id, lang) = get_session(&session)?;
|
||||
let db_manager = db.lock().map_err(|e| AppError::Internal(format!("DB lock failed: {}", e)))?;
|
||||
let conn = db_manager.get_connection(&user_id)?;
|
||||
service::delete_spell_tag(conn, &user_id, &data.book_id, &data.tag_id, data.deleted_at, lang)
|
||||
}
|
||||
3
src-tauri/src/domains/spell/mod.rs
Normal file
3
src-tauri/src/domains/spell/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod commands;
|
||||
pub mod repo;
|
||||
pub mod service;
|
||||
362
src-tauri/src/domains/spell/repo.rs
Normal file
362
src-tauri/src/domains/spell/repo.rs
Normal file
@@ -0,0 +1,362 @@
|
||||
use rusqlite::{params, Connection};
|
||||
|
||||
use crate::error::{AppError, AppResult};
|
||||
use crate::shared::types::Lang;
|
||||
|
||||
pub struct SpellResult {
|
||||
pub spell_id: String,
|
||||
pub book_id: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub appearance: Option<String>,
|
||||
pub tags: Option<String>,
|
||||
pub power_level: Option<String>,
|
||||
pub components: Option<String>,
|
||||
pub limitations: Option<String>,
|
||||
pub notes: Option<String>,
|
||||
pub series_spell_id: Option<String>,
|
||||
}
|
||||
|
||||
pub struct BookSpellsTable {
|
||||
pub spell_id: String,
|
||||
pub book_id: String,
|
||||
pub user_id: String,
|
||||
pub name: String,
|
||||
pub name_hash: String,
|
||||
pub description: Option<String>,
|
||||
pub appearance: Option<String>,
|
||||
pub tags: Option<String>,
|
||||
pub power_level: Option<String>,
|
||||
pub components: Option<String>,
|
||||
pub limitations: Option<String>,
|
||||
pub notes: Option<String>,
|
||||
pub last_update: i64,
|
||||
}
|
||||
|
||||
pub struct SyncedSpellResult {
|
||||
pub spell_id: String,
|
||||
pub book_id: String,
|
||||
pub name: String,
|
||||
pub last_update: i64,
|
||||
}
|
||||
|
||||
/// Fetches all spells for a specific book owned by the user.
|
||||
/// * `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
|
||||
/// Returns an array of spell results.
|
||||
pub fn fetch_spells(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult<Vec<SpellResult>> {
|
||||
let mut statement = conn
|
||||
.prepare("SELECT spell_id, book_id, name, description, appearance, tags, power_level, components, limitations, notes, series_spell_id FROM book_spells WHERE user_id=?1 AND book_id=?2")
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sorts.".to_string() } else { "Unable to retrieve spells.".to_string() }))?;
|
||||
|
||||
let spells = statement
|
||||
.query_map(params![user_id, book_id], |query_row| {
|
||||
Ok(SpellResult {
|
||||
spell_id: query_row.get(0)?, book_id: query_row.get(1)?,
|
||||
name: query_row.get(2)?, description: query_row.get(3)?,
|
||||
appearance: query_row.get(4)?, tags: query_row.get(5)?,
|
||||
power_level: query_row.get(6)?, components: query_row.get(7)?,
|
||||
limitations: query_row.get(8)?, notes: query_row.get(9)?,
|
||||
series_spell_id: query_row.get(10)?,
|
||||
})
|
||||
})
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sorts.".to_string() } else { "Unable to retrieve spells.".to_string() }))?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sorts.".to_string() } else { "Unable to retrieve spells.".to_string() }))?;
|
||||
|
||||
Ok(spells)
|
||||
}
|
||||
|
||||
/// Fetches a single spell by its ID.
|
||||
/// * `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
|
||||
/// Returns the spell result or None if not found.
|
||||
pub fn fetch_spell_by_id(conn: &Connection, user_id: &str, spell_id: &str, lang: Lang) -> AppResult<Option<SpellResult>> {
|
||||
let mut statement = conn
|
||||
.prepare("SELECT spell_id, book_id, name, description, appearance, tags, power_level, components, limitations, notes, series_spell_id FROM book_spells WHERE user_id=?1 AND spell_id=?2")
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le sort.".to_string() } else { "Unable to retrieve spell.".to_string() }))?;
|
||||
|
||||
let spells = statement
|
||||
.query_map(params![user_id, spell_id], |query_row| {
|
||||
Ok(SpellResult {
|
||||
spell_id: query_row.get(0)?, book_id: query_row.get(1)?,
|
||||
name: query_row.get(2)?, description: query_row.get(3)?,
|
||||
appearance: query_row.get(4)?, tags: query_row.get(5)?,
|
||||
power_level: query_row.get(6)?, components: query_row.get(7)?,
|
||||
limitations: query_row.get(8)?, notes: query_row.get(9)?,
|
||||
series_spell_id: query_row.get(10)?,
|
||||
})
|
||||
})
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le sort.".to_string() } else { "Unable to retrieve spell.".to_string() }))?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le sort.".to_string() } else { "Unable to retrieve spell.".to_string() }))?;
|
||||
|
||||
Ok(spells.into_iter().next())
|
||||
}
|
||||
|
||||
/// Inserts a new spell.
|
||||
/// * `conn` - Database connection
|
||||
/// * `spell_id` - The unique identifier for the new spell
|
||||
/// * `book_id` - The unique identifier of the book
|
||||
/// * `user_id` - The unique identifier of the user
|
||||
/// * `name` - The encrypted name
|
||||
/// * `name_hash` - The hashed name for duplicate detection
|
||||
/// * `description` - The encrypted description
|
||||
/// * `appearance` - The encrypted appearance
|
||||
/// * `tags` - The encrypted JSON tags array
|
||||
/// * `power_level` - The encrypted power level (nullable)
|
||||
/// * `components` - The encrypted components (nullable)
|
||||
/// * `limitations` - The encrypted limitations (nullable)
|
||||
/// * `notes` - The encrypted notes (nullable)
|
||||
/// * `lang` - The language for error messages
|
||||
/// * `series_spell_id` - The optional series spell identifier
|
||||
/// Returns the spell ID if successful.
|
||||
pub fn insert_spell(
|
||||
conn: &Connection, spell_id: &str, book_id: &str, user_id: &str, name: &str,
|
||||
name_hash: &str, description: Option<&str>, appearance: Option<&str>, tags: &str,
|
||||
power_level: Option<&str>, components: Option<&str>, limitations: Option<&str>,
|
||||
notes: Option<&str>, last_update: i64, lang: Lang, series_spell_id: Option<&str>,
|
||||
) -> AppResult<String> {
|
||||
let insert_result = match series_spell_id {
|
||||
Some(series_id) => conn
|
||||
.execute("INSERT INTO book_spells (spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, series_spell_id, last_update) VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14)", params![spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, series_id, last_update])
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'ajouter le sort.".to_string() } else { "Unable to add spell.".to_string() }))?,
|
||||
None => conn
|
||||
.execute("INSERT INTO book_spells (spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update) VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13)", params![spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update])
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'ajouter le sort.".to_string() } else { "Unable to add spell.".to_string() }))?,
|
||||
};
|
||||
|
||||
if insert_result > 0 {
|
||||
Ok(spell_id.to_string())
|
||||
} else {
|
||||
Err(AppError::Internal(if lang == Lang::Fr { "Une erreur s'est produite lors de l'ajout du sort.".to_string() } else { "Error adding spell.".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 encrypted name
|
||||
/// * `name_hash` - The hashed name
|
||||
/// * `description` - The encrypted description
|
||||
/// * `appearance` - The encrypted appearance
|
||||
/// * `tags` - The encrypted JSON tags array
|
||||
/// * `power_level` - The encrypted power level (nullable)
|
||||
/// * `components` - The encrypted components (nullable)
|
||||
/// * `limitations` - The encrypted limitations (nullable)
|
||||
/// * `notes` - The encrypted notes (nullable)
|
||||
/// * `lang` - The language for error messages
|
||||
/// * `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, name_hash: &str,
|
||||
description: Option<&str>, appearance: Option<&str>, tags: &str, power_level: Option<&str>,
|
||||
components: Option<&str>, limitations: Option<&str>, notes: Option<&str>, last_update: i64, lang: Lang,
|
||||
series_spell_id: Option<&str>,
|
||||
) -> AppResult<bool> {
|
||||
let update_result = match series_spell_id {
|
||||
Some(series_id) => conn
|
||||
.execute("UPDATE book_spells SET name=?1, name_hash=?2, description=?3, appearance=?4, tags=?5, power_level=?6, components=?7, limitations=?8, notes=?9, series_spell_id=?10, last_update=?11 WHERE spell_id=?12 AND user_id=?13", params![name, name_hash, description, appearance, tags, power_level, components, limitations, notes, series_id, last_update, spell_id, user_id])
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour le sort.".to_string() } else { "Unable to update spell.".to_string() }))?,
|
||||
None => conn
|
||||
.execute("UPDATE book_spells SET name=?1, name_hash=?2, description=?3, appearance=?4, tags=?5, power_level=?6, components=?7, limitations=?8, notes=?9, last_update=?10 WHERE spell_id=?11 AND user_id=?12", params![name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update, spell_id, user_id])
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour le sort.".to_string() } else { "Unable to update spell.".to_string() }))?,
|
||||
};
|
||||
|
||||
Ok(update_result > 0)
|
||||
}
|
||||
|
||||
/// Deletes a 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
|
||||
/// Returns true if the deletion was successful.
|
||||
pub fn delete_spell(conn: &Connection, user_id: &str, spell_id: &str, lang: Lang) -> AppResult<bool> {
|
||||
let delete_result = conn
|
||||
.execute("DELETE FROM book_spells WHERE spell_id=?1 AND user_id=?2", params![spell_id, user_id])
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de supprimer le sort.".to_string() } else { "Unable to delete spell.".to_string() }))?;
|
||||
|
||||
Ok(delete_result > 0)
|
||||
}
|
||||
|
||||
/// Updates the tags field of a spell.
|
||||
/// * `conn` - Database connection
|
||||
/// * `user_id` - The unique identifier of the user
|
||||
/// * `spell_id` - The unique identifier of the spell
|
||||
/// * `tags` - The new encrypted JSON tags array
|
||||
/// * `lang` - The language for error messages
|
||||
/// Returns true if the update was successful.
|
||||
pub fn update_spell_tags(conn: &Connection, user_id: &str, spell_id: &str, tags: &str, last_update: i64, lang: Lang) -> AppResult<bool> {
|
||||
let update_result = conn
|
||||
.execute("UPDATE book_spells SET tags=?1, last_update=?2 WHERE spell_id=?3 AND user_id=?4", params![tags, last_update, spell_id, user_id])
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour les tags du sort.".to_string() } else { "Unable to update spell tags.".to_string() }))?;
|
||||
|
||||
Ok(update_result > 0)
|
||||
}
|
||||
|
||||
/// Fetches all spells for a book with full table data for sync.
|
||||
/// * `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
|
||||
/// Returns an array of book spells table records.
|
||||
pub fn fetch_book_spells_table(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult<Vec<BookSpellsTable>> {
|
||||
let mut statement = conn
|
||||
.prepare("SELECT spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update FROM book_spells WHERE user_id=?1 AND book_id=?2")
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sorts.".to_string() } else { "Unable to retrieve spells.".to_string() }))?;
|
||||
|
||||
let spells = statement
|
||||
.query_map(params![user_id, book_id], |query_row| {
|
||||
Ok(BookSpellsTable {
|
||||
spell_id: query_row.get(0)?, book_id: query_row.get(1)?,
|
||||
user_id: query_row.get(2)?, name: query_row.get(3)?,
|
||||
name_hash: query_row.get(4)?, description: query_row.get(5)?,
|
||||
appearance: query_row.get(6)?, tags: query_row.get(7)?,
|
||||
power_level: query_row.get(8)?, components: query_row.get(9)?,
|
||||
limitations: query_row.get(10)?, notes: query_row.get(11)?,
|
||||
last_update: query_row.get(12)?,
|
||||
})
|
||||
})
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sorts.".to_string() } else { "Unable to retrieve spells.".to_string() }))?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sorts.".to_string() } else { "Unable to retrieve spells.".to_string() }))?;
|
||||
|
||||
Ok(spells)
|
||||
}
|
||||
|
||||
/// Fetches a complete spell record by its ID.
|
||||
/// * `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
|
||||
/// Returns the spell table record or None.
|
||||
pub fn fetch_spell_table_by_id(conn: &Connection, user_id: &str, spell_id: &str, lang: Lang) -> AppResult<Option<BookSpellsTable>> {
|
||||
let mut statement = conn
|
||||
.prepare("SELECT spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update FROM book_spells WHERE user_id=?1 AND spell_id=?2")
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le sort.".to_string() } else { "Unable to retrieve spell.".to_string() }))?;
|
||||
|
||||
let spells = statement
|
||||
.query_map(params![user_id, spell_id], |query_row| {
|
||||
Ok(BookSpellsTable {
|
||||
spell_id: query_row.get(0)?, book_id: query_row.get(1)?,
|
||||
user_id: query_row.get(2)?, name: query_row.get(3)?,
|
||||
name_hash: query_row.get(4)?, description: query_row.get(5)?,
|
||||
appearance: query_row.get(6)?, tags: query_row.get(7)?,
|
||||
power_level: query_row.get(8)?, components: query_row.get(9)?,
|
||||
limitations: query_row.get(10)?, notes: query_row.get(11)?,
|
||||
last_update: query_row.get(12)?,
|
||||
})
|
||||
})
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le sort.".to_string() } else { "Unable to retrieve spell.".to_string() }))?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le sort.".to_string() } else { "Unable to retrieve spell.".to_string() }))?;
|
||||
|
||||
Ok(spells.into_iter().next())
|
||||
}
|
||||
|
||||
/// Fetches all synced spells for a user.
|
||||
/// * `conn` - Database connection
|
||||
/// * `user_id` - The unique identifier of the user
|
||||
/// * `lang` - The language for error messages
|
||||
/// Returns an array of synced spell results.
|
||||
pub fn fetch_synced_spells(conn: &Connection, user_id: &str, lang: Lang) -> AppResult<Vec<SyncedSpellResult>> {
|
||||
let mut statement = conn
|
||||
.prepare("SELECT spell_id, book_id, name, last_update FROM book_spells WHERE user_id=?1")
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sorts synchronisés.".to_string() } else { "Unable to retrieve synced spells.".to_string() }))?;
|
||||
|
||||
let spells = statement
|
||||
.query_map(params![user_id], |query_row| {
|
||||
Ok(SyncedSpellResult {
|
||||
spell_id: query_row.get(0)?, book_id: query_row.get(1)?,
|
||||
name: query_row.get(2)?, last_update: query_row.get(3)?,
|
||||
})
|
||||
})
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sorts synchronisés.".to_string() } else { "Unable to retrieve synced spells.".to_string() }))?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sorts synchronisés.".to_string() } else { "Unable to retrieve synced spells.".to_string() }))?;
|
||||
|
||||
Ok(spells)
|
||||
}
|
||||
|
||||
/// Inserts or updates a spell from synchronization data.
|
||||
/// * `conn` - Database connection
|
||||
/// * `spell_id` - The unique identifier for the spell
|
||||
/// * `book_id` - The unique identifier of the book
|
||||
/// * `user_id` - The unique identifier of the user
|
||||
/// * `name` - The encrypted name
|
||||
/// * `name_hash` - The hashed name
|
||||
/// * `description` - The encrypted description
|
||||
/// * `appearance` - The encrypted appearance
|
||||
/// * `tags` - The encrypted JSON tags array
|
||||
/// * `power_level` - The encrypted power level (nullable)
|
||||
/// * `components` - The encrypted components (nullable)
|
||||
/// * `limitations` - The encrypted limitations (nullable)
|
||||
/// * `notes` - The encrypted notes (nullable)
|
||||
/// * `last_update` - The timestamp of the last update
|
||||
/// * `lang` - The language for error messages
|
||||
/// Returns true if the insertion was successful.
|
||||
pub fn insert_sync_spell(
|
||||
conn: &Connection, spell_id: &str, book_id: &str, user_id: &str, name: &str,
|
||||
name_hash: &str, description: Option<&str>, appearance: Option<&str>, tags: Option<&str>,
|
||||
power_level: Option<&str>, components: Option<&str>, limitations: Option<&str>,
|
||||
notes: Option<&str>, last_update: i64, lang: Lang,
|
||||
) -> AppResult<bool> {
|
||||
let insert_result = conn
|
||||
.execute("INSERT OR REPLACE INTO book_spells (spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update) VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13)", params![spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update])
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'insérer le sort.".to_string() } else { "Unable to insert spell.".to_string() }))?;
|
||||
|
||||
Ok(insert_result > 0)
|
||||
}
|
||||
|
||||
/// Checks if a spell exists.
|
||||
/// * `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
|
||||
/// Returns true if the spell exists.
|
||||
pub fn is_spell_exist(conn: &Connection, user_id: &str, spell_id: &str, lang: Lang) -> AppResult<bool> {
|
||||
let mut statement = conn
|
||||
.prepare("SELECT 1 FROM book_spells WHERE spell_id=?1 AND user_id=?2")
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence du sort.".to_string() } else { "Unable to check spell existence.".to_string() }))?;
|
||||
|
||||
let exists = statement
|
||||
.exists(params![spell_id, user_id])
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence du sort.".to_string() } else { "Unable to check spell existence.".to_string() }))?;
|
||||
|
||||
Ok(exists)
|
||||
}
|
||||
|
||||
/// Updates a spell with timestamp for sync.
|
||||
/// * `conn` - Database connection
|
||||
/// * `user_id` - The unique identifier of the user
|
||||
/// * `spell_id` - The unique identifier of the spell
|
||||
/// * `name` - The encrypted name
|
||||
/// * `name_hash` - The hashed name
|
||||
/// * `description` - The encrypted description
|
||||
/// * `appearance` - The encrypted appearance
|
||||
/// * `tags` - The encrypted JSON tags array
|
||||
/// * `power_level` - The encrypted power level (nullable)
|
||||
/// * `components` - The encrypted components (nullable)
|
||||
/// * `limitations` - The encrypted limitations (nullable)
|
||||
/// * `notes` - The encrypted notes (nullable)
|
||||
/// * `last_update` - The timestamp of the last update
|
||||
/// * `lang` - The language for error messages
|
||||
/// Returns true if the update was successful.
|
||||
pub fn update_sync_spell(
|
||||
conn: &Connection, user_id: &str, spell_id: &str, name: &str, name_hash: &str,
|
||||
description: Option<&str>, appearance: Option<&str>, tags: Option<&str>,
|
||||
power_level: Option<&str>, components: Option<&str>, limitations: Option<&str>,
|
||||
notes: Option<&str>, last_update: i64, lang: Lang,
|
||||
) -> AppResult<bool> {
|
||||
let update_result = conn
|
||||
.execute("UPDATE book_spells SET name=?1, name_hash=?2, description=?3, appearance=?4, tags=?5, power_level=?6, components=?7, limitations=?8, notes=?9, last_update=?10 WHERE spell_id=?11 AND user_id=?12", params![name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update, spell_id, user_id])
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour le sort.".to_string() } else { "Unable to update spell.".to_string() }))?;
|
||||
|
||||
Ok(update_result > 0)
|
||||
}
|
||||
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