Refactor: Remove unused structs, redundant services, and streamline repository models

This commit is contained in:
natreex
2026-03-21 23:01:27 -04:00
parent 1478fe10dd
commit 32d2b0fa5a
44 changed files with 2009 additions and 3256 deletions

View File

@@ -561,18 +561,18 @@ function ScribeContent() {
try {
await tauri.syncUser({
userId: user.id,
firstName: user.name,
lastName: user.lastName,
username: user.username,
email: user.email
});
} catch (syncError) {
console.error('[Page] syncUser failed:', syncError);
errorMessage(t("homePage.errors.syncError"));
}
} else {
errorMessage(t("homePage.errors.dbInitError"));
}
} catch (error) {
console.error('[Page] DB init or sync failed:', error);
errorMessage(t("homePage.errors.syncError"));
}
}

View File

@@ -317,25 +317,15 @@ pub fn initialize_schema(conn: &Connection) -> Result<(), rusqlite::Error> {
-- Erit Users
CREATE TABLE IF NOT EXISTS erit_users (
user_id TEXT PRIMARY KEY,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
username TEXT NOT NULL,
email TEXT NOT NULL,
origin_email TEXT NOT NULL,
origin_username TEXT NOT NULL,
author_name TEXT,
origin_author_name TEXT,
plateform TEXT NOT NULL,
social_id TEXT,
user_group INTEGER NOT NULL DEFAULT 4,
password TEXT,
term_accepted INTEGER NOT NULL DEFAULT 0,
verify_code TEXT,
reg_date INTEGER NOT NULL,
account_verified INTEGER NOT NULL DEFAULT 0,
erite_points INTEGER NOT NULL DEFAULT 100,
stripe_customer_id TEXT,
credits_balance REAL DEFAULT 0
password TEXT
);
-- =========================================================================

View File

@@ -24,15 +24,6 @@ pub struct ActProps {
pub chapters: Option<Vec<ActChapter>>,
}
pub struct ActStory {
pub act_id: i64,
pub summary: String,
pub chapter_summary: String,
pub chapter_goal: String,
pub incidents: Vec<IncidentStory>,
pub plot_points: Vec<PlotPointStory>,
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ActChapter {
@@ -47,13 +38,8 @@ pub struct ActChapter {
pub goal: String,
}
pub struct SyncedActSummary {
pub id: String,
pub last_update: i64,
}
pub use incident_service::{IncidentProps, IncidentStory};
pub use plotpoint_service::{PlotPointProps, PlotPointStory};
pub use incident_service::IncidentProps;
pub use plotpoint_service::PlotPointProps;
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]

View File

@@ -444,7 +444,7 @@ pub fn export_book(data: ExportBookData, db: State<DbManager>, session: State<Se
let conn = db_manager.get_connection(&user_id)?;
let book_data = chapter_service::get_complete_book_data_with_selections(conn, &user_id, &data.book_id, data.selections.as_deref(), lang)?;
match data.format.as_str() {
"epub" => { let result = export_service::transform_to_epub(&book_data)?; Ok(result.buffer) },
"epub" => { let result = export_service::transform_to_epub(&book_data, lang)?; Ok(result.buffer) },
"pdf" => { let result = export_service::transform_to_pdf(&book_data)?; Ok(result.buffer) },
"docx" => { let result = export_service::transform_to_docx(&book_data)?; Ok(result.buffer) },
_ => Err(AppError::Validation(if lang == crate::shared::types::Lang::Fr { "Format non supporté.".to_string() } else { "Unsupported format.".to_string() })),

View File

@@ -9,9 +9,7 @@ pub struct BookQuery {
pub book_type: String,
pub author_id: String,
pub title: String,
pub hashed_title: String,
pub sub_title: Option<String>,
pub hashed_sub_title: Option<String>,
pub summary: Option<String>,
pub serie_id: Option<i64>,
pub desired_release_date: Option<String>,
@@ -86,8 +84,7 @@ pub fn fetch_books(conn: &Connection, user_id: &str, lang: Lang) -> AppResult<Ve
sub_title: query_row.get(4)?, summary: query_row.get(5)?,
serie_id: query_row.get(6)?, desired_release_date: query_row.get(7)?,
desired_word_count: query_row.get(8)?, words_count: query_row.get(9)?,
cover_image: query_row.get(10)?, hashed_title: String::new(),
hashed_sub_title: None,
cover_image: query_row.get(10)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer la liste des livres.".to_string() } else { "Unable to retrieve book list.".to_string() }))?
@@ -125,8 +122,7 @@ pub fn fetch_book(conn: &Connection, book_id: &str, user_id: &str, lang: Lang) -
sub_title: query_row.get(4)?, cover_image: query_row.get(5)?,
desired_release_date: query_row.get(6)?, desired_word_count: query_row.get(7)?,
words_count: query_row.get(8)?, serie_id: query_row.get(9)?,
book_type: String::new(), hashed_title: String::new(),
hashed_sub_title: None,
book_type: String::new(),
})
})
.map_err(|error| match error {

View File

@@ -67,34 +67,10 @@ pub struct CompleteBookData {
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BookUserInfos {
pub first_name: String,
pub last_name: String,
pub username: String,
pub author_name: String,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SyncedBook {
pub id: String,
pub book_type: String,
pub title: String,
pub sub_title: Option<String>,
pub last_update: i64,
pub chapters: Vec<SyncedChapter>,
pub characters: Vec<SyncedCharacter>,
pub locations: Vec<SyncedLocation>,
pub worlds: Vec<SyncedWorld>,
pub incidents: Vec<SyncedIncident>,
pub plot_points: Vec<SyncedPlotPoint>,
pub issues: Vec<SyncedIssue>,
pub act_summaries: Vec<SyncedActSummary>,
pub guide_line: Option<SyncedGuideLine>,
pub ai_guide_line: Option<SyncedAIGuideLine>,
pub book_tools: Option<SyncedBookTools>,
pub spells: Vec<SyncedSpell>,
pub spell_tags: Vec<SyncedSpellTag>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BookSyncCompare {
@@ -115,43 +91,12 @@ pub struct BookSyncCompare {
pub act_summaries: Vec<String>,
pub guide_line: bool,
pub ai_guide_line: bool,
pub book_tools: bool,
#[serde(rename = "bookTools")]
pub _book_tools: bool,
pub spells: Vec<String>,
pub spell_tags: Vec<String>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SyncedChapter {
pub id: String,
pub title: String,
pub last_update: i64,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SyncedCharacter {
pub id: String,
pub name: String,
pub last_update: i64,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SyncedLocation {
pub id: String,
pub name: String,
pub last_update: i64,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SyncedWorld {
pub id: String,
pub name: String,
pub last_update: i64,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SyncedIncident {
@@ -966,8 +911,7 @@ pub fn complete_book_data(conn: &Connection, user_id: &str, book_id: &str, lang:
summary: if let Some(ref summary) = book_data.summary { decrypt_data_with_user_key(summary, &user_key)? } else { String::new() },
cover_image,
user_infos: BookUserInfos {
first_name: if let Some(ref first_name) = user_infos.first_name { decrypt_data_with_user_key(first_name, &user_key)? } else { String::new() },
last_name: if let Some(ref last_name) = user_infos.last_name { decrypt_data_with_user_key(last_name, &user_key)? } else { String::new() },
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() },
},
chapters: decrypted_chapters,

View File

@@ -89,7 +89,6 @@ pub struct SaveChapterContentData {
pub version: i64,
pub content: Value,
pub total_word_count: i64,
pub content_id: String,
}
#[tauri::command]

View File

@@ -23,7 +23,7 @@ pub struct ActChapterQuery {
}
pub struct ChapterStoryQueryResult {
pub chapter_info_id: i64,
pub _chapter_info_id: i64,
pub act_id: i64,
pub summary: String,
pub chapter_summary: String,
@@ -47,18 +47,18 @@ pub struct BookChaptersTable {
pub author_id: String,
pub title: String,
pub hashed_title: String,
pub words_count: Option<i64>,
pub _words_count: Option<i64>,
pub chapter_order: i64,
pub last_update: i64,
}
pub struct BookChapterInfosTable {
pub chapter_info_id: String,
pub _chapter_info_id: String,
pub chapter_id: String,
pub act_id: Option<i64>,
pub incident_id: Option<String>,
pub plot_point_id: Option<String>,
pub book_id: String,
pub _act_id: Option<i64>,
pub _incident_id: Option<String>,
pub _plot_point_id: Option<String>,
pub _book_id: String,
pub author_id: String,
pub summary: Option<String>,
pub goal: Option<String>,
@@ -75,7 +75,7 @@ pub struct SyncedChapterResult {
pub struct SyncedChapterInfoResult {
pub chapter_info_id: String,
pub chapter_id: Option<String>,
pub book_id: String,
pub _book_id: String,
pub last_update: i64,
}
@@ -97,7 +97,7 @@ pub struct SelectedChapterContentResult {
pub title: String,
pub chapter_order: i64,
pub content: String,
pub version: i64,
pub _version: i64,
}
#[derive(serde::Deserialize)]
@@ -307,7 +307,7 @@ pub fn fetch_chapter_story(conn: &Connection, user_id: &str, chapter_id: &str, l
let rows = statement
.query_map(params![chapter_id, user_id], |query_row| {
Ok(ChapterStoryQueryResult {
chapter_info_id: query_row.get(0)?, act_id: query_row.get(1)?,
_chapter_info_id: query_row.get(0)?, act_id: query_row.get(1)?,
summary: query_row.get::<_, Option<String>>(2)?.unwrap_or_default(),
chapter_summary: query_row.get::<_, Option<String>>(3)?.unwrap_or_default(),
chapter_goal: query_row.get::<_, Option<String>>(4)?.unwrap_or_default(),
@@ -393,7 +393,7 @@ pub fn fetch_book_chapters(conn: &Connection, user_id: &str, book_id: &str, lang
Ok(BookChaptersTable {
chapter_id: query_row.get(0)?, book_id: query_row.get(1)?,
author_id: query_row.get(2)?, title: query_row.get(3)?,
hashed_title: query_row.get(4)?, words_count: query_row.get(5)?,
hashed_title: query_row.get(4)?, _words_count: query_row.get(5)?,
chapter_order: query_row.get(6)?, last_update: query_row.get(7)?,
})
})
@@ -413,9 +413,9 @@ pub fn fetch_book_chapter_infos(conn: &Connection, user_id: &str, chapter_id: &s
let rows = statement
.query_map(params![user_id, chapter_id], |query_row| {
Ok(BookChapterInfosTable {
chapter_info_id: query_row.get(0)?, chapter_id: query_row.get(1)?,
act_id: query_row.get(2)?, incident_id: query_row.get(3)?,
plot_point_id: query_row.get(4)?, book_id: query_row.get(5)?,
_chapter_info_id: query_row.get(0)?, chapter_id: query_row.get(1)?,
_act_id: query_row.get(2)?, _incident_id: query_row.get(3)?,
_plot_point_id: query_row.get(4)?, _book_id: query_row.get(5)?,
author_id: query_row.get(6)?, summary: query_row.get(7)?,
goal: query_row.get(8)?, last_update: query_row.get(9)?,
})
@@ -457,7 +457,7 @@ pub fn fetch_synced_chapter_infos(conn: &Connection, user_id: &str, lang: Lang)
.query_map(params![user_id], |query_row| {
Ok(SyncedChapterInfoResult {
chapter_info_id: query_row.get(0)?, chapter_id: query_row.get(1)?,
book_id: query_row.get(2)?, last_update: query_row.get(3)?,
_book_id: query_row.get(2)?, last_update: query_row.get(3)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les infos des chapitres synchronisés.".to_string() } else { "Unable to retrieve synced chapter infos.".to_string() }))?
@@ -500,7 +500,7 @@ pub fn fetch_complete_chapter_by_id(conn: &Connection, chapter_id: &str, lang: L
Ok(BookChaptersTable {
chapter_id: query_row.get(0)?, book_id: query_row.get(1)?,
author_id: query_row.get(2)?, title: query_row.get(3)?,
hashed_title: query_row.get(4)?, words_count: query_row.get(5)?,
hashed_title: query_row.get(4)?, _words_count: query_row.get(5)?,
chapter_order: query_row.get(6)?, last_update: query_row.get(7)?,
})
})
@@ -520,9 +520,9 @@ pub fn fetch_complete_chapter_info_by_id(conn: &Connection, chapter_info_id: &st
let rows = statement
.query_map(params![chapter_info_id], |query_row| {
Ok(BookChapterInfosTable {
chapter_info_id: query_row.get(0)?, chapter_id: query_row.get(1)?,
act_id: query_row.get(2)?, incident_id: query_row.get(3)?,
plot_point_id: query_row.get(4)?, book_id: query_row.get(5)?,
_chapter_info_id: query_row.get(0)?, chapter_id: query_row.get(1)?,
_act_id: query_row.get(2)?, _incident_id: query_row.get(3)?,
_plot_point_id: query_row.get(4)?, _book_id: query_row.get(5)?,
author_id: query_row.get(6)?, summary: query_row.get(7)?,
goal: query_row.get(8)?, last_update: query_row.get(9)?,
})
@@ -581,7 +581,7 @@ pub fn fetch_selected_chapters_content(conn: &Connection, book_id: &str, selecti
Ok(SelectedChapterContentResult {
chapter_id: query_row.get(0)?, title: query_row.get(1)?,
chapter_order: query_row.get(2)?, content: query_row.get(3)?,
version: query_row.get(4)?,
_version: query_row.get(4)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le contenu des chapitres sélectionnés.".to_string() } else { "Unable to retrieve selected chapters content.".to_string() }))?

View File

@@ -24,10 +24,7 @@ pub struct ChapterContent {
pub struct ChapterContentData {
pub title: String,
pub chapter_order: i64,
pub content: String,
pub words_count: i64,
pub version: i64,
}
#[derive(Serialize)]
@@ -47,24 +44,6 @@ pub struct CompanionContent {
pub words_count: i64,
}
pub struct SyncedChapter {
pub id: String,
pub name: String,
pub last_update: i64,
pub contents: Vec<SyncedChapterContent>,
pub info: Option<SyncedChapterInfo>,
}
pub struct SyncedChapterContent {
pub id: String,
pub last_update: i64,
}
pub struct SyncedChapterInfo {
pub id: String,
pub last_update: i64,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CompleteChapterContent {
@@ -601,10 +580,7 @@ pub fn get_chapters_or_sheet(book_chapters: &[CompleteChapterContent]) -> Vec<Ch
let parsed_content: Value = serde_json::from_str(&sheet.content).unwrap_or(Value::Null);
processed_chapters.push(ChapterContentData {
title: sheet.title.clone(),
chapter_order: sheet.order,
content: html_to_text(&tip_tap_to_html(&parsed_content)),
words_count: 0,
version: sheet.version.unwrap_or(0),
});
} else if regular_chapter.is_some() {
for chapter_data in book_chapters {
@@ -612,10 +588,7 @@ pub fn get_chapters_or_sheet(book_chapters: &[CompleteChapterContent]) -> Vec<Ch
let parsed_content: Value = serde_json::from_str(&chapter_data.content).unwrap_or(Value::Null);
processed_chapters.push(ChapterContentData {
title: chapter_data.title.clone(),
chapter_order: chapter_data.order,
content: html_to_text(&tip_tap_to_html(&parsed_content)),
words_count: 0,
version: chapter_data.version.unwrap_or(0),
});
}
}

View File

@@ -29,8 +29,8 @@ pub struct BookChapterContentTable {
pub author_id: String,
pub version: i64,
pub content: Option<String>,
pub words_count: i64,
pub time_on_it: i64,
pub _words_count: i64,
pub _time_on_it: i64,
pub last_update: i64,
}
@@ -188,8 +188,8 @@ pub fn fetch_book_chapter_contents(conn: &Connection, user_id: &str, chapter_id:
Ok(BookChapterContentTable {
content_id: query_row.get(0)?, chapter_id: query_row.get(1)?,
author_id: query_row.get(2)?, version: query_row.get(3)?,
content: query_row.get(4)?, words_count: query_row.get(5)?,
time_on_it: query_row.get(6)?, last_update: query_row.get(7)?,
content: query_row.get(4)?, _words_count: query_row.get(5)?,
_time_on_it: query_row.get(6)?, last_update: query_row.get(7)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le contenu des chapitres.".to_string() } else { "Unable to retrieve chapter contents.".to_string() }))?
@@ -261,8 +261,8 @@ pub fn fetch_complete_chapter_content_by_id(conn: &Connection, content_id: &str,
Ok(BookChapterContentTable {
content_id: query_row.get(0)?, chapter_id: query_row.get(1)?,
author_id: query_row.get(2)?, version: query_row.get(3)?,
content: query_row.get(4)?, words_count: query_row.get(5)?,
time_on_it: query_row.get(6)?, last_update: query_row.get(7)?,
content: query_row.get(4)?, _words_count: query_row.get(5)?,
_time_on_it: query_row.get(6)?, last_update: query_row.get(7)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le contenu de chapitre complet.".to_string() } else { "Unable to retrieve complete chapter content.".to_string() }))?

View File

@@ -18,7 +18,6 @@ fn get_session(session: &State<SessionState>) -> Result<(String, Lang), AppError
#[serde(rename_all = "camelCase")]
pub struct GetCharacterListData {
pub book_id: String,
pub enabled: bool,
}
#[tauri::command]

View File

@@ -106,19 +106,6 @@ pub struct CharacterAttribute {
pub values: Vec<Attribute>,
}
pub struct SyncedCharacter {
pub id: String,
pub name: String,
pub last_update: i64,
pub attributes: Vec<SyncedCharacterAttribute>,
}
pub struct SyncedCharacterAttribute {
pub id: String,
pub name: String,
pub last_update: i64,
}
/// Retrieves a list of all characters for a specific book.
/// Decrypts character data using the user's encryption key.
/// * `conn` - Database connection

View File

@@ -10,6 +10,7 @@ use serde_json::Value;
use crate::domains::book::service::{CompleteBookData, CompleteChapterContent};
use crate::domains::chapter::service::{get_chapters_or_sheet, tip_tap_to_html, ChapterContentData};
use crate::error::{AppError, AppResult};
use crate::shared::types::Lang;
pub const MAIN_STYLE: &str = r#"h1 {
font-size: 24px !important;
@@ -25,7 +26,6 @@ p {
pub struct ExportResult {
pub buffer: Vec<u8>,
pub file_name: String,
}
/// Transforms book data into a DOCX document.
@@ -33,7 +33,6 @@ pub struct ExportResult {
/// Returns the DOCX buffer and filename.
pub fn transform_to_docx(book_data: &CompleteBookData) -> AppResult<ExportResult> {
let book_title: &str = &book_data.title;
let filename: String = format!("{}.docx", book_title);
let mut docx: Docx = Docx::new();
@@ -92,7 +91,7 @@ pub fn transform_to_docx(book_data: &CompleteBookData) -> AppResult<ExportResult
.pack(&mut Cursor::new(&mut buffer))
.map_err(|error| AppError::Internal(format!("DOCX generation failed: {}", error)))?;
Ok(ExportResult { buffer, file_name: filename })
Ok(ExportResult { buffer })
}
/// Transforms book data into a PDF document.
@@ -100,7 +99,6 @@ pub fn transform_to_docx(book_data: &CompleteBookData) -> AppResult<ExportResult
/// Returns the PDF buffer and filename.
pub fn transform_to_pdf(book_data: &CompleteBookData) -> AppResult<ExportResult> {
let book_title: &str = &book_data.title;
let filename: String = format!("{}.pdf", book_title);
let (pdf_document, page_index, layer_index) = PdfDocument::new(book_title, Mm(210.0), Mm(297.0), "Title Page");
let font = pdf_document
@@ -150,13 +148,13 @@ pub fn transform_to_pdf(book_data: &CompleteBookData) -> AppResult<ExportResult>
.save_to_bytes()
.map_err(|error| AppError::Internal(format!("PDF generation failed: {}", error)))?;
Ok(ExportResult { buffer, file_name: filename })
Ok(ExportResult { buffer })
}
/// Transforms book data into an EPUB document.
/// * `book_data` - The complete book data to export
/// Returns the EPUB buffer and filename.
pub fn transform_to_epub(book_data: &CompleteBookData) -> AppResult<ExportResult> {
pub fn transform_to_epub(book_data: &CompleteBookData, lang: Lang) -> AppResult<ExportResult> {
let book_title: &str = &book_data.title;
let book_id: &str = &book_data.book_id;
@@ -179,8 +177,11 @@ pub fn transform_to_epub(book_data: &CompleteBookData) -> AppResult<ExportResult
epub_builder
.metadata("identifier", &format!("urn:uuid:{}", book_id))
.map_err(|error| AppError::Internal(format!("EPUB metadata error: {}", error)))?;
if book_data.user_infos.author_name.is_empty() {
return Err(AppError::Validation(if lang == Lang::Fr { "Veuillez définir votre nom d'auteur avant d'exporter.".to_string() } else { "Please set your author name before exporting.".to_string() }));
}
epub_builder
.metadata("author", &format!("{} {}", &book_data.user_infos.first_name, &book_data.user_infos.last_name))
.metadata("author", &book_data.user_infos.author_name)
.map_err(|error| AppError::Internal(format!("EPUB metadata error: {}", error)))?;
epub_builder
.metadata("publisher", "ERitors Scribe")
@@ -247,5 +248,5 @@ pub fn transform_to_epub(book_data: &CompleteBookData) -> AppResult<ExportResult
.generate(&mut epub_buffer)
.map_err(|error| AppError::Internal(format!("EPUB generation failed: {}", error)))?;
Ok(ExportResult { buffer: epub_buffer, file_name: format!("{}.epub", book_title) })
Ok(ExportResult { buffer: epub_buffer })
}

View File

@@ -58,18 +58,18 @@ pub struct GuideLineQuery {
}
pub struct GuideLineAIQuery {
pub user_id: String,
pub book_id: String,
pub _user_id: String,
pub _book_id: String,
pub global_resume: Option<String>,
pub themes: Option<String>,
pub verbe_tense: Option<i64>,
pub narrative_type: Option<i64>,
pub langue: Option<i64>,
pub dialogue_type: Option<i64>,
pub tone: Option<String>,
pub _tone: Option<String>,
pub atmosphere: Option<String>,
pub current_resume: Option<String>,
pub meta: String,
pub _meta: String,
}
/// Fetches the guideline for a specific book.
@@ -211,8 +211,8 @@ pub fn fetch_guide_line_ai(conn: &Connection, user_id: &str, book_id: &str, lang
global_resume: query_row.get(2)?, atmosphere: query_row.get(3)?,
verbe_tense: query_row.get(4)?, langue: query_row.get(5)?,
themes: query_row.get(6)?, current_resume: query_row.get(7)?,
user_id: user_id.to_string(), book_id: book_id.to_string(),
tone: None, meta: String::new(),
_user_id: user_id.to_string(), _book_id: book_id.to_string(),
_tone: None, _meta: String::new(),
})
})
.map_err(|error| match error {

View File

@@ -8,16 +8,6 @@ use crate::error::{AppError, AppResult};
use crate::helpers::timestamp_in_seconds;
use crate::shared::types::Lang;
/// Represents the synced guideline data for a book.
pub struct SyncedGuideLine {
pub last_update: i64,
}
/// Represents the synced AI guideline data for a book.
pub struct SyncedAIGuideLine {
pub last_update: i64,
}
/// Represents the decrypted guideline properties for a book.
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]

View File

@@ -10,21 +10,6 @@ use crate::error::AppResult;
use crate::helpers::{create_unique_id, timestamp_in_seconds};
use crate::shared::types::Lang;
/// Represents the story details of an incident within a chapter.
pub struct IncidentStory {
pub incident_title: String,
pub incident_summary: String,
pub chapter_summary: String,
pub chapter_goal: String,
}
/// Represents a synced incident with minimal information for comparison.
pub struct SyncedIncident {
pub id: String,
pub name: String,
pub last_update: i64,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IncidentProps {

View File

@@ -9,13 +9,6 @@ use crate::error::AppResult;
use crate::helpers::{create_unique_id, timestamp_in_seconds};
use crate::shared::types::Lang;
/// Represents a synced issue with its metadata.
pub struct SyncedIssue {
pub id: String,
pub name: String,
pub last_update: i64,
}
#[derive(Serialize)]
pub struct IssueProps {
pub id: String,

View File

@@ -18,7 +18,6 @@ fn get_session(session: &State<SessionState>) -> Result<(String, Lang), AppError
#[serde(rename_all = "camelCase")]
pub struct GetAllLocationsData {
pub book_id: String,
pub enabled: bool,
}
#[tauri::command]

View File

@@ -24,13 +24,6 @@ pub struct LocationElementQueryResult {
pub element_description: Option<String>,
}
pub struct LocationByTagResult {
pub element_name: String,
pub element_description: Option<String>,
pub sub_elem_name: Option<String>,
pub sub_elem_description: Option<String>,
}
pub struct BookLocationTable {
pub loc_id: String,
pub book_id: String,
@@ -349,51 +342,6 @@ pub fn fetch_location_tags(conn: &Connection, user_id: &str, book_id: &str, lang
/// * `conn` - Database connection
/// * `user_id` - The user's unique identifier
/// * `locations` - An array of location tag IDs to search for
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns an array of locations matching the provided tags.
/// Errors if no tags are provided or no locations are found.
pub fn fetch_locations_by_tags(conn: &Connection, user_id: &str, locations: &[String], lang: Lang) -> AppResult<Vec<LocationByTagResult>> {
if locations.is_empty() {
return Err(AppError::Validation(if lang == Lang::Fr { "Aucun tag fourni.".to_string() } else { "No tags provided.".to_string() }));
}
let location_placeholders: String = locations.iter().map(|_| "?").collect::<Vec<_>>().join(",");
let query = format!("SELECT el.element_name, el.element_description, se.sub_elem_name, se.sub_elem_description FROM location_element AS el LEFT JOIN location_sub_element AS se ON el.element_id = se.element_id WHERE el.user_id = ?1 AND (el.element_id IN ({}) OR se.sub_element_id IN ({}))", location_placeholders, location_placeholders);
let mut statement = conn
.prepare(&query)
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les emplacements par tags.".to_string() } else { "Unable to retrieve locations by tags.".to_string() }))?;
let mut param_values: Vec<Box<dyn rusqlite::types::ToSql>> = Vec::new();
param_values.push(Box::new(user_id.to_string()));
for location in locations {
param_values.push(Box::new(location.clone()));
}
for location in locations {
param_values.push(Box::new(location.clone()));
}
let param_refs: Vec<&dyn rusqlite::types::ToSql> = param_values.iter().map(|parameter| parameter.as_ref() as &dyn rusqlite::types::ToSql).collect();
let locations_by_tags = statement
.query_map(param_refs.as_slice(), |query_row| {
Ok(LocationByTagResult {
element_name: query_row.get(0)?,
element_description: query_row.get(1)?,
sub_elem_name: query_row.get(2)?,
sub_elem_description: query_row.get(3)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les emplacements par tags.".to_string() } else { "Unable to retrieve locations by tags.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les emplacements par tags.".to_string() } else { "Unable to retrieve locations by tags.".to_string() }))?;
if locations_by_tags.is_empty() {
return Err(AppError::NotFound(if lang == Lang::Fr { "Aucun emplacement trouvé avec ces tags.".to_string() } else { "No locations found with these tags.".to_string() }));
}
Ok(locations_by_tags)
}
/// Checks if a location exists in the database.
/// * `conn` - Database connection
/// * `user_id` - The user's unique identifier

View File

@@ -41,29 +41,6 @@ pub struct LocationListResponse {
pub enabled: bool,
}
/// Synced location sub-element (lightweight, for comparison).
pub struct SyncedLocationSubElement {
pub id: String,
pub name: String,
pub last_update: i64,
}
/// Synced location element (lightweight, for comparison).
pub struct SyncedLocationElement {
pub id: String,
pub name: String,
pub last_update: i64,
pub sub_elements: Vec<SyncedLocationSubElement>,
}
/// Synced location (lightweight, for comparison).
pub struct SyncedLocation {
pub id: String,
pub name: String,
pub last_update: i64,
pub elements: Vec<SyncedLocationElement>,
}
/// Retrieves all locations for a given user and book.
/// * `conn` - Database connection
/// * `user_id` - The user's unique identifier
@@ -383,80 +360,3 @@ pub fn get_location_tags(conn: &Connection, user_id: &str, book_id: &str, lang:
Ok(sub_elements)
}
/// Retrieves location elements filtered by specific tag IDs.
/// * `conn` - Database connection
/// * `user_id` - The user's unique identifier
/// * `locations` - Array of location tag IDs to filter by
/// * `lang` - The language for error messages
/// Returns an array of elements with their associated sub-elements.
pub fn get_locations_by_tags(conn: &Connection, user_id: &str, locations: &[String], lang: Lang) -> AppResult<Vec<Element>> {
let location_tag_records: Vec<repo::LocationByTagResult> = repo::fetch_locations_by_tags(conn, user_id, locations, lang)?;
if location_tag_records.is_empty() {
return Ok(vec![]);
}
let user_key: String = get_user_encryption_key(user_id)?;
let mut location_elements: Vec<Element> = Vec::new();
for record in &location_tag_records {
let element_index: Option<usize> = location_elements.iter().position(|elem| elem.name == record.element_name);
let element_idx: usize = match element_index {
Some(idx) => idx,
None => {
let decrypted_name: String = decrypt_data_with_user_key(&record.element_name, &user_key)?;
let decrypted_description: String = if let Some(ref element_description) = record.element_description {
decrypt_data_with_user_key(element_description, &user_key)?
} else {
String::new()
};
location_elements.push(Element {
id: String::new(),
name: decrypted_name,
description: decrypted_description,
sub_elements: vec![],
});
location_elements.len() - 1
}
};
if let Some(ref sub_elem_name) = record.sub_elem_name {
let sub_element_exists: bool = location_elements[element_idx].sub_elements.iter().any(|sub| sub.name == *sub_elem_name);
if !sub_element_exists {
let decrypted_name: String = decrypt_data_with_user_key(sub_elem_name, &user_key)?;
let decrypted_description: String = if let Some(ref sub_elem_description) = record.sub_elem_description {
decrypt_data_with_user_key(sub_elem_description, &user_key)?
} else {
String::new()
};
location_elements[element_idx].sub_elements.push(SubElement {
id: String::new(),
name: decrypted_name,
description: decrypted_description,
});
}
}
}
Ok(location_elements)
}
/// Generates a formatted description string from an array of location elements.
/// * `locations` - Array of location elements to describe
/// Returns a formatted string with location names and descriptions.
pub fn locations_description(locations: &[Element]) -> String {
locations
.iter()
.map(|location| {
let mut description_fields: Vec<String> = Vec::new();
if !location.name.is_empty() {
description_fields.push(format!("Nom : {}", location.name));
}
if !location.description.is_empty() {
description_fields.push(format!("Description : {}", location.description));
}
description_fields.join("\n")
})
.collect::<Vec<String>>()
.join("\n\n")
}

View File

@@ -31,10 +31,7 @@ pub struct OfflineModeStatus {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OfflineModeData {
pub enabled: bool,
pub sync_interval_days: Option<i64>,
}
pub struct OfflineModeData {}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]

View File

@@ -10,7 +10,7 @@ pub struct BookPlotPointsTable {
pub summary: Option<String>,
pub linked_incident_id: Option<String>,
pub author_id: String,
pub book_id: String,
pub _book_id: String,
pub last_update: i64,
}
@@ -152,7 +152,7 @@ pub fn fetch_book_plot_points(conn: &Connection, user_id: &str, book_id: &str, l
summary: query_row.get(3)?,
linked_incident_id: query_row.get(4)?,
author_id: query_row.get(5)?,
book_id: query_row.get(6)?,
_book_id: query_row.get(6)?,
last_update: query_row.get(7)?,
})
})
@@ -235,7 +235,7 @@ pub fn fetch_complete_plot_point_by_id(conn: &Connection, plot_point_id: &str, l
summary: query_row.get(3)?,
linked_incident_id: query_row.get(4)?,
author_id: query_row.get(5)?,
book_id: query_row.get(6)?,
_book_id: query_row.get(6)?,
last_update: query_row.get(7)?,
})
})

View File

@@ -10,14 +10,6 @@ use crate::error::AppResult;
use crate::helpers::{create_unique_id, timestamp_in_seconds};
use crate::shared::types::Lang;
/// Represents the story details associated with a plot point.
pub struct PlotPointStory {
pub plot_title: String,
pub plot_summary: String,
pub chapter_summary: String,
pub chapter_goal: String,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlotPointProps {
@@ -28,13 +20,6 @@ pub struct PlotPointProps {
pub chapters: Option<Vec<ActChapter>>,
}
/// Represents a synced plot point with minimal information.
pub struct SyncedPlotPoint {
pub id: String,
pub name: String,
pub last_update: i64,
}
/// Retrieves all plot points for a specific book with their associated chapters.
/// Decrypts plot point titles and summaries using the user's encryption key.
/// * `conn` - Database connection

View File

@@ -5,16 +5,16 @@ use crate::shared::types::Lang;
pub struct SeriesResult {
pub series_id: String,
pub user_id: String,
pub _user_id: String,
pub name: String,
pub hashed_name: String,
pub _hashed_name: String,
pub description: Option<String>,
pub cover_image: Option<String>,
pub last_update: i64,
pub _last_update: i64,
}
pub struct SeriesBookResult {
pub series_id: String,
pub _series_id: String,
pub book_id: String,
pub book_order: i64,
pub title: String,
@@ -105,10 +105,10 @@ pub fn fetch_series_by_id(conn: &Connection, user_id: &str, series_id: &str, lan
let series = statement
.query_row(params![series_id, user_id], |query_row| {
Ok(SeriesResult {
series_id: query_row.get(0)?, user_id: query_row.get(1)?,
name: query_row.get(2)?, hashed_name: query_row.get(3)?,
series_id: query_row.get(0)?, _user_id: query_row.get(1)?,
name: query_row.get(2)?, _hashed_name: query_row.get(3)?,
description: query_row.get(4)?, cover_image: query_row.get(5)?,
last_update: query_row.get(6)?,
_last_update: query_row.get(6)?,
})
});
@@ -199,7 +199,7 @@ pub fn fetch_series_books(conn: &Connection, user_id: &str, series_id: &str, lan
let books = statement
.query_map(params![series_id, user_id], |query_row| {
Ok(SeriesBookResult {
series_id: query_row.get(0)?, book_id: query_row.get(1)?,
_series_id: query_row.get(0)?, book_id: query_row.get(1)?,
book_order: query_row.get(2)?, title: query_row.get(3)?,
cover_image: query_row.get(4)?,
})
@@ -403,32 +403,6 @@ pub fn fetch_synced_series_books(conn: &Connection, user_id: &str, lang: Lang) -
Ok(books)
}
/// Fetches a complete series by ID for sync.
/// * `conn` - Database connection
/// * `series_id` - The unique identifier of the series
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns an array containing the series.
pub fn fetch_complete_series_by_id(conn: &Connection, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesTableResult>> {
let mut statement = conn
.prepare("SELECT series_id, user_id, name, hashed_name, description, cover_image, last_update FROM book_series WHERE series_id = ?1")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer la série complète.".to_string() } else { "Unable to retrieve complete series.".to_string() }))?;
let series = statement
.query_map(params![series_id], |query_row| {
Ok(SeriesTableResult {
series_id: query_row.get(0)?, user_id: query_row.get(1)?,
name: query_row.get(2)?, hashed_name: query_row.get(3)?,
description: query_row.get(4)?, cover_image: query_row.get(5)?,
last_update: query_row.get(6)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer la série complète.".to_string() } else { "Unable to retrieve complete series.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer la série complète.".to_string() } else { "Unable to retrieve complete series.".to_string() }))?;
Ok(series)
}
/// Inserts a series for sync purposes.
/// * `conn` - Database connection
/// * `series_id` - The unique identifier of the series
@@ -498,27 +472,6 @@ pub fn insert_sync_series_book(conn: &Connection, series_id: &str, book_id: &str
Ok(insert_result > 0)
}
/// Checks if a series-book relationship exists.
/// * `conn` - Database connection
/// * `series_id` - The unique identifier of the series
/// * `book_id` - The unique identifier of the book
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the relationship exists.
pub fn is_series_book_exist(conn: &Connection, series_id: &str, book_id: &str, lang: Lang) -> AppResult<bool> {
let mut statement = conn
.prepare("SELECT 1 FROM series_books WHERE series_id = ?1 AND book_id = ?2")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier la liaison série-livre.".to_string() } else { "Unable to check series-book.".to_string() }))?;
let exists = statement
.query_row(params![series_id, book_id], |_query_row| Ok(true));
match exists {
Ok(_) => Ok(true),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(false),
Err(_) => Err(AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier la liaison série-livre.".to_string() } else { "Unable to check series-book.".to_string() })),
}
}
/// Checks if a series exists for a user (alias for is_series_exist).
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user

View File

@@ -9,15 +9,6 @@ use crate::error::{AppError, AppResult};
use crate::helpers::{create_unique_id, timestamp_in_seconds};
use crate::shared::types::Lang;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SeriesProps {
pub id: String,
pub name: String,
pub description: String,
pub cover_image: Option<String>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SeriesDetailProps {

View File

@@ -28,7 +28,7 @@ pub struct SeriesCharacterResult {
pub struct SeriesCharacterAttributeResult {
pub attr_id: String,
pub attribute_name: String,
pub _attribute_name: String,
pub attribute_value: String,
}
@@ -90,7 +90,7 @@ pub struct SyncedSeriesCharacterAttributeResult {
pub fn fetch_characters(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesCharacterResult>> {
let mut statement = conn
.prepare("SELECT character_id, first_name, last_name, nickname, age, gender, species, nationality, status, title, category, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color FROM series_characters WHERE series_id = ?1 AND user_id = ?2")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les personnages de la s\u{00e9}rie.".to_string() } else { "Unable to retrieve series characters.".to_string() }))?;
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les personnages de la série.".to_string() } else { "Unable to retrieve series characters.".to_string() }))?;
let characters = statement
.query_map(params![series_id, user_id], |query_row| {
@@ -107,9 +107,9 @@ pub fn fetch_characters(conn: &Connection, user_id: &str, series_id: &str, lang:
notes: query_row.get(18)?, color: query_row.get(19)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les personnages de la s\u{00e9}rie.".to_string() } else { "Unable to retrieve series characters.".to_string() }))?
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les personnages de la série.".to_string() } else { "Unable to retrieve series characters.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les personnages de la s\u{00e9}rie.".to_string() } else { "Unable to retrieve series characters.".to_string() }))?;
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les personnages de la série.".to_string() } else { "Unable to retrieve series characters.".to_string() }))?;
Ok(characters)
}
@@ -234,7 +234,7 @@ pub fn update_character(
"UPDATE series_characters SET first_name = ?1, last_name = ?2, nickname = ?3, age = ?4, gender = ?5, species = ?6, nationality = ?7, status = ?8, title = ?9, category = ?10, image = ?11, role = ?12, biography = ?13, history = ?14, speech_pattern = ?15, catchphrase = ?16, residence = ?17, notes = ?18, color = ?19, last_update = ?20 WHERE character_id = ?21 AND user_id = ?22",
params![encrypted_name, encrypted_last_name, encrypted_nickname, encrypted_age, encrypted_gender, encrypted_species, encrypted_nationality, encrypted_status, encrypted_title, encrypted_category, encrypted_image, encrypted_role, encrypted_biography, encrypted_history, encrypted_speech_pattern, encrypted_catchphrase, encrypted_residence, encrypted_notes, encrypted_color, last_update, character_id, user_id],
)
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre \u{00e0} jour le personnage.".to_string() } else { "Unable to update character.".to_string() }))?;
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour le personnage.".to_string() } else { "Unable to update character.".to_string() }))?;
Ok(update_result > 0)
}
@@ -281,15 +281,15 @@ pub fn delete_attribute(conn: &Connection, user_id: &str, attribute_id: &str, la
pub fn fetch_attributes(conn: &Connection, character_id: &str, user_id: &str, lang: Lang) -> AppResult<Vec<SeriesCharacterAttributeResult>> {
let mut statement = conn
.prepare("SELECT attr_id, attribute_name, attribute_value FROM series_characters_attributes WHERE character_id = ?1 AND user_id = ?2")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les attributs.".to_string() } else { "Unable to retrieve attributes.".to_string() }))?;
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les attributs.".to_string() } else { "Unable to retrieve attributes.".to_string() }))?;
let attributes = statement
.query_map(params![character_id, user_id], |query_row| {
Ok(SeriesCharacterAttributeResult { attr_id: query_row.get(0)?, attribute_name: query_row.get(1)?, attribute_value: query_row.get(2)? })
Ok(SeriesCharacterAttributeResult { attr_id: query_row.get(0)?, _attribute_name: query_row.get(1)?, attribute_value: query_row.get(2)? })
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les attributs.".to_string() } else { "Unable to retrieve attributes.".to_string() }))?
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les attributs.".to_string() } else { "Unable to retrieve attributes.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les attributs.".to_string() } else { "Unable to retrieve attributes.".to_string() }))?;
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les attributs.".to_string() } else { "Unable to retrieve attributes.".to_string() }))?;
Ok(attributes)
}
@@ -303,12 +303,12 @@ pub fn fetch_attributes(conn: &Connection, character_id: &str, user_id: &str, la
pub fn is_character_exist(conn: &Connection, user_id: &str, character_id: &str, lang: Lang) -> AppResult<bool> {
let mut statement = conn
.prepare("SELECT 1 FROM series_characters WHERE character_id = ?1 AND user_id = ?2")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de v\u{00e9}rifier l'existence du personnage.".to_string() } else { "Unable to check character existence.".to_string() }))?;
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence du personnage.".to_string() } else { "Unable to check character existence.".to_string() }))?;
let existence_check = statement
.query_row(params![character_id, user_id], |_query_row| Ok(true))
.optional()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de v\u{00e9}rifier l'existence du personnage.".to_string() } else { "Unable to check character existence.".to_string() }))?;
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence du personnage.".to_string() } else { "Unable to check character existence.".to_string() }))?;
Ok(existence_check.is_some())
}
@@ -322,7 +322,7 @@ pub fn is_character_exist(conn: &Connection, user_id: &str, character_id: &str,
pub fn fetch_series_characters_table(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesCharactersTableResult>> {
let mut statement = conn
.prepare("SELECT character_id, series_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status, title, category, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update FROM series_characters WHERE series_id = ?1 AND user_id = ?2")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les personnages pour sync.".to_string() } else { "Unable to retrieve characters for sync.".to_string() }))?;
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les personnages pour sync.".to_string() } else { "Unable to retrieve characters for sync.".to_string() }))?;
let characters = statement
.query_map(params![series_id, user_id], |query_row| {
@@ -341,39 +341,13 @@ pub fn fetch_series_characters_table(conn: &Connection, user_id: &str, series_id
last_update: query_row.get(22)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les personnages pour sync.".to_string() } else { "Unable to retrieve characters for sync.".to_string() }))?
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les personnages pour sync.".to_string() } else { "Unable to retrieve characters for sync.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les personnages pour sync.".to_string() } else { "Unable to retrieve characters for sync.".to_string() }))?;
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les personnages pour sync.".to_string() } else { "Unable to retrieve characters for sync.".to_string() }))?;
Ok(characters)
}
/// Fetches all attributes for a character for sync.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `character_id` - The unique identifier of the character
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns an array of full attribute table results.
pub fn fetch_series_character_attributes_table(conn: &Connection, user_id: &str, character_id: &str, lang: Lang) -> AppResult<Vec<SeriesCharacterAttributesTableResult>> {
let mut statement = conn
.prepare("SELECT attr_id, character_id, user_id, attribute_name, attribute_value, last_update FROM series_characters_attributes WHERE character_id = ?1 AND user_id = ?2")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les attributs pour sync.".to_string() } else { "Unable to retrieve attributes for sync.".to_string() }))?;
let attributes = statement
.query_map(params![character_id, user_id], |query_row| {
Ok(SeriesCharacterAttributesTableResult {
attr_id: query_row.get(0)?, character_id: query_row.get(1)?,
user_id: query_row.get(2)?, attribute_name: query_row.get(3)?,
attribute_value: query_row.get(4)?, last_update: query_row.get(5)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les attributs pour sync.".to_string() } else { "Unable to retrieve attributes for sync.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les attributs pour sync.".to_string() } else { "Unable to retrieve attributes for sync.".to_string() }))?;
Ok(attributes)
}
/// Fetches all series characters for a user for sync comparison.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
@@ -382,15 +356,15 @@ pub fn fetch_series_character_attributes_table(conn: &Connection, user_id: &str,
pub fn fetch_synced_series_characters(conn: &Connection, user_id: &str, lang: Lang) -> AppResult<Vec<SyncedSeriesCharacterResult>> {
let mut statement = conn
.prepare("SELECT character_id, series_id, first_name, last_update FROM series_characters WHERE user_id = ?1")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les personnages de s\u{00e9}rie pour sync.".to_string() } else { "Unable to retrieve series characters for sync.".to_string() }))?;
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les personnages de série pour sync.".to_string() } else { "Unable to retrieve series characters for sync.".to_string() }))?;
let characters = statement
.query_map(params![user_id], |query_row| {
Ok(SyncedSeriesCharacterResult { character_id: query_row.get(0)?, series_id: query_row.get(1)?, first_name: query_row.get(2)?, last_update: query_row.get(3)? })
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les personnages de s\u{00e9}rie pour sync.".to_string() } else { "Unable to retrieve series characters for sync.".to_string() }))?
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les personnages de série pour sync.".to_string() } else { "Unable to retrieve series characters for sync.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les personnages de s\u{00e9}rie pour sync.".to_string() } else { "Unable to retrieve series characters for sync.".to_string() }))?;
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les personnages de série pour sync.".to_string() } else { "Unable to retrieve series characters for sync.".to_string() }))?;
Ok(characters)
}
@@ -403,74 +377,15 @@ pub fn fetch_synced_series_characters(conn: &Connection, user_id: &str, lang: La
pub fn fetch_synced_series_character_attributes(conn: &Connection, user_id: &str, lang: Lang) -> AppResult<Vec<SyncedSeriesCharacterAttributeResult>> {
let mut statement = conn
.prepare("SELECT attr_id, character_id, attribute_name, last_update FROM series_characters_attributes WHERE user_id = ?1")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les attributs de personnage pour sync.".to_string() } else { "Unable to retrieve character attributes for sync.".to_string() }))?;
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les attributs de personnage pour sync.".to_string() } else { "Unable to retrieve character attributes for sync.".to_string() }))?;
let attributes = statement
.query_map(params![user_id], |query_row| {
Ok(SyncedSeriesCharacterAttributeResult { attr_id: query_row.get(0)?, character_id: query_row.get(1)?, attribute_name: query_row.get(2)?, last_update: query_row.get(3)? })
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les attributs de personnage pour sync.".to_string() } else { "Unable to retrieve character attributes for sync.".to_string() }))?
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les attributs de personnage pour sync.".to_string() } else { "Unable to retrieve character attributes for sync.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les attributs de personnage pour sync.".to_string() } else { "Unable to retrieve character attributes for sync.".to_string() }))?;
Ok(attributes)
}
/// Fetches a complete character by ID for sync.
/// * `conn` - Database connection
/// * `character_id` - The unique identifier of the character
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns an array of full character table results.
pub fn fetch_complete_character_by_id(conn: &Connection, character_id: &str, lang: Lang) -> AppResult<Vec<SeriesCharactersTableResult>> {
let mut statement = conn
.prepare("SELECT character_id, series_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status, title, category, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update FROM series_characters WHERE character_id = ?1")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer le personnage complet.".to_string() } else { "Unable to retrieve complete character.".to_string() }))?;
let characters = statement
.query_map(params![character_id], |query_row| {
Ok(SeriesCharactersTableResult {
character_id: query_row.get(0)?, series_id: query_row.get(1)?,
user_id: query_row.get(2)?, first_name: query_row.get(3)?,
last_name: query_row.get(4)?, nickname: query_row.get(5)?,
age: query_row.get(6)?, gender: query_row.get(7)?,
species: query_row.get(8)?, nationality: query_row.get(9)?,
status: query_row.get(10)?, title: query_row.get(11)?,
category: query_row.get(12)?, image: query_row.get(13)?,
role: query_row.get(14)?, biography: query_row.get(15)?,
history: query_row.get(16)?, speech_pattern: query_row.get(17)?,
catchphrase: query_row.get(18)?, residence: query_row.get(19)?,
notes: query_row.get(20)?, color: query_row.get(21)?,
last_update: query_row.get(22)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer le personnage complet.".to_string() } else { "Unable to retrieve complete character.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer le personnage complet.".to_string() } else { "Unable to retrieve complete character.".to_string() }))?;
Ok(characters)
}
/// Fetches a complete character attribute by ID for sync.
/// * `conn` - Database connection
/// * `attr_id` - The unique identifier of the attribute
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns an array of full attribute table results.
pub fn fetch_complete_attribute_by_id(conn: &Connection, attr_id: &str, lang: Lang) -> AppResult<Vec<SeriesCharacterAttributesTableResult>> {
let mut statement = conn
.prepare("SELECT attr_id, character_id, user_id, attribute_name, attribute_value, last_update FROM series_characters_attributes WHERE attr_id = ?1")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer l'attribut complet.".to_string() } else { "Unable to retrieve complete attribute.".to_string() }))?;
let attributes = statement
.query_map(params![attr_id], |query_row| {
Ok(SeriesCharacterAttributesTableResult {
attr_id: query_row.get(0)?, character_id: query_row.get(1)?,
user_id: query_row.get(2)?, attribute_name: query_row.get(3)?,
attribute_value: query_row.get(4)?, last_update: query_row.get(5)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer l'attribut complet.".to_string() } else { "Unable to retrieve complete attribute.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer l'attribut complet.".to_string() } else { "Unable to retrieve complete attribute.".to_string() }))?;
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les attributs de personnage pour sync.".to_string() } else { "Unable to retrieve character attributes for sync.".to_string() }))?;
Ok(attributes)
}
@@ -515,7 +430,7 @@ pub fn insert_sync_series_character(
"INSERT INTO series_characters (character_id, series_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status, category, title, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23) ON CONFLICT(character_id) DO UPDATE SET first_name = excluded.first_name, last_name = excluded.last_name, nickname = excluded.nickname, age = excluded.age, gender = excluded.gender, species = excluded.species, nationality = excluded.nationality, status = excluded.status, category = excluded.category, title = excluded.title, image = excluded.image, role = excluded.role, biography = excluded.biography, history = excluded.history, speech_pattern = excluded.speech_pattern, catchphrase = excluded.catchphrase, residence = excluded.residence, notes = excluded.notes, color = excluded.color, last_update = excluded.last_update",
params![character_id, series_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status, category, title, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update],
)
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'ins\u{00e9}rer le personnage pour sync.".to_string() } else { "Unable to insert character for sync.".to_string() }))?;
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'insérer le personnage pour sync.".to_string() } else { "Unable to insert character for sync.".to_string() }))?;
Ok(insert_result > 0)
}
@@ -559,7 +474,7 @@ pub fn update_sync_series_character(
"UPDATE series_characters SET first_name = ?1, last_name = ?2, nickname = ?3, age = ?4, gender = ?5, species = ?6, nationality = ?7, status = ?8, category = ?9, title = ?10, image = ?11, role = ?12, biography = ?13, history = ?14, speech_pattern = ?15, catchphrase = ?16, residence = ?17, notes = ?18, color = ?19, last_update = ?20 WHERE character_id = ?21 AND user_id = ?22",
params![first_name, last_name, nickname, age, gender, species, nationality, status, category, title, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update, character_id, user_id],
)
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre \u{00e0} jour le personnage pour sync.".to_string() } else { "Unable to update character for sync.".to_string() }))?;
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour le personnage pour sync.".to_string() } else { "Unable to update character for sync.".to_string() }))?;
Ok(update_result > 0)
}
@@ -583,7 +498,7 @@ pub fn insert_sync_series_character_attribute(
"INSERT INTO series_characters_attributes (attr_id, character_id, user_id, attribute_name, attribute_value, last_update) VALUES (?1, ?2, ?3, ?4, ?5, ?6) ON CONFLICT(attr_id) DO UPDATE SET attribute_name = excluded.attribute_name, attribute_value = excluded.attribute_value, last_update = excluded.last_update",
params![attr_id, character_id, user_id, attribute_name, attribute_value, last_update],
)
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'ins\u{00e9}rer l'attribut pour sync.".to_string() } else { "Unable to insert attribute for sync.".to_string() }))?;
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'insérer l'attribut pour sync.".to_string() } else { "Unable to insert attribute for sync.".to_string() }))?;
Ok(insert_result > 0)
}
@@ -597,12 +512,12 @@ pub fn insert_sync_series_character_attribute(
pub fn is_attribute_exist(conn: &Connection, user_id: &str, attr_id: &str, lang: Lang) -> AppResult<bool> {
let mut statement = conn
.prepare("SELECT 1 FROM series_characters_attributes WHERE attr_id = ?1 AND user_id = ?2")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de v\u{00e9}rifier l'existence de l'attribut.".to_string() } else { "Unable to check attribute existence.".to_string() }))?;
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence de l'attribut.".to_string() } else { "Unable to check attribute existence.".to_string() }))?;
let existence_check = statement
.query_row(params![attr_id, user_id], |_query_row| Ok(true))
.optional()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de v\u{00e9}rifier l'existence de l'attribut.".to_string() } else { "Unable to check attribute existence.".to_string() }))?;
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence de l'attribut.".to_string() } else { "Unable to check attribute existence.".to_string() }))?;
Ok(existence_check.is_some())
}
@@ -625,80 +540,11 @@ pub fn update_sync_series_character_attribute(
"UPDATE series_characters_attributes SET attribute_name = ?1, attribute_value = ?2, last_update = ?3 WHERE attr_id = ?4 AND user_id = ?5",
params![attribute_name, attribute_value, last_update, attr_id, user_id],
)
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre \u{00e0} jour l'attribut pour sync.".to_string() } else { "Unable to update attribute for sync.".to_string() }))?;
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour l'attribut pour sync.".to_string() } else { "Unable to update attribute for sync.".to_string() }))?;
Ok(update_result > 0)
}
/// Fetches all characters for a series for sync (without user filter).
/// * `conn` - Database connection
/// * `series_id` - The unique identifier of the series
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns an array of full character table results.
pub fn fetch_characters_table_for_sync(conn: &Connection, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesCharactersTableResult>> {
let mut statement = conn
.prepare("SELECT character_id, series_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status, title, category, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update FROM series_characters WHERE series_id = ?1")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les personnages pour sync.".to_string() } else { "Unable to retrieve characters for sync.".to_string() }))?;
let characters = statement
.query_map(params![series_id], |query_row| {
Ok(SeriesCharactersTableResult {
character_id: query_row.get(0)?, series_id: query_row.get(1)?,
user_id: query_row.get(2)?, first_name: query_row.get(3)?,
last_name: query_row.get(4)?, nickname: query_row.get(5)?,
age: query_row.get(6)?, gender: query_row.get(7)?,
species: query_row.get(8)?, nationality: query_row.get(9)?,
status: query_row.get(10)?, title: query_row.get(11)?,
category: query_row.get(12)?, image: query_row.get(13)?,
role: query_row.get(14)?, biography: query_row.get(15)?,
history: query_row.get(16)?, speech_pattern: query_row.get(17)?,
catchphrase: query_row.get(18)?, residence: query_row.get(19)?,
notes: query_row.get(20)?, color: query_row.get(21)?,
last_update: query_row.get(22)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les personnages pour sync.".to_string() } else { "Unable to retrieve characters for sync.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les personnages pour sync.".to_string() } else { "Unable to retrieve characters for sync.".to_string() }))?;
Ok(characters)
}
/// Fetches all character attributes for a series for sync (without user filter).
/// * `conn` - Database connection
/// * `series_id` - The unique identifier of the series
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns an array of full attribute table results.
pub fn fetch_character_attributes_table_for_sync(conn: &Connection, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesCharacterAttributesTableResult>> {
let mut statement = conn
.prepare("SELECT sca.attr_id, sca.character_id, sca.user_id, sca.attribute_name, sca.attribute_value, sca.last_update FROM series_characters_attributes sca INNER JOIN series_characters sc ON sca.character_id = sc.character_id WHERE sc.series_id = ?1")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les attributs pour sync.".to_string() } else { "Unable to retrieve attributes for sync.".to_string() }))?;
let attributes = statement
.query_map(params![series_id], |query_row| {
Ok(SeriesCharacterAttributesTableResult {
attr_id: query_row.get(0)?, character_id: query_row.get(1)?,
user_id: query_row.get(2)?, attribute_name: query_row.get(3)?,
attribute_value: query_row.get(4)?, last_update: query_row.get(5)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les attributs pour sync.".to_string() } else { "Unable to retrieve attributes for sync.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les attributs pour sync.".to_string() } else { "Unable to retrieve attributes for sync.".to_string() }))?;
Ok(attributes)
}
/// Fetches all characters for a series (alias for fetch_series_characters_table).
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `series_id` - The unique identifier of the series
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns an array of full character table results.
pub fn fetch_series_characters(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesCharactersTableResult>> {
fetch_series_characters_table(conn, user_id, series_id, lang)
}
/// Fetches all character attributes for a series by series ID.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
@@ -708,7 +554,7 @@ pub fn fetch_series_characters(conn: &Connection, user_id: &str, series_id: &str
pub fn fetch_series_character_attributes_by_series_id(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesCharacterAttributesTableResult>> {
let mut statement = conn
.prepare("SELECT sca.attr_id, sca.character_id, sca.user_id, sca.attribute_name, sca.attribute_value, sca.last_update FROM series_characters_attributes sca INNER JOIN series_characters sc ON sca.character_id = sc.character_id WHERE sc.series_id = ?1 AND sc.user_id = ?2")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les attributs par s\u{00e9}rie.".to_string() } else { "Unable to retrieve attributes by series.".to_string() }))?;
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les attributs par série.".to_string() } else { "Unable to retrieve attributes by series.".to_string() }))?;
let attributes = statement
.query_map(params![series_id, user_id], |query_row| {
@@ -718,9 +564,9 @@ pub fn fetch_series_character_attributes_by_series_id(conn: &Connection, user_id
attribute_value: query_row.get(4)?, last_update: query_row.get(5)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les attributs par s\u{00e9}rie.".to_string() } else { "Unable to retrieve attributes by series.".to_string() }))?
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les attributs par série.".to_string() } else { "Unable to retrieve attributes by series.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de r\u{00e9}cup\u{00e9}rer les attributs par s\u{00e9}rie.".to_string() } else { "Unable to retrieve attributes by series.".to_string() }))?;
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les attributs par série.".to_string() } else { "Unable to retrieve attributes by series.".to_string() }))?;
Ok(attributes)
}

View File

@@ -10,14 +10,14 @@ pub struct SeriesLocationResult {
pub struct SeriesLocationElementResult {
pub element_id: String,
pub location_id: String,
pub _location_id: String,
pub element_name: String,
pub element_description: String,
}
pub struct SeriesLocationSubElementResult {
pub sub_element_id: String,
pub element_id: String,
pub _element_id: String,
pub sub_elem_name: String,
pub sub_elem_description: String,
}
@@ -97,7 +97,7 @@ pub fn fetch_elements(conn: &Connection, user_id: &str, location_id: &str, lang:
let elements = statement
.query_map(params![user_id, location_id], |query_row| {
Ok(SeriesLocationElementResult { element_id: query_row.get(0)?, location_id: query_row.get(1)?, element_name: query_row.get(2)?, element_description: query_row.get(3)? })
Ok(SeriesLocationElementResult { element_id: query_row.get(0)?, _location_id: query_row.get(1)?, element_name: query_row.get(2)?, element_description: query_row.get(3)? })
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les éléments.".to_string() } else { "Unable to retrieve elements.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
@@ -114,7 +114,7 @@ pub fn fetch_sub_elements(conn: &Connection, user_id: &str, element_id: &str, la
let sub_elements = statement
.query_map(params![user_id, element_id], |query_row| {
Ok(SeriesLocationSubElementResult { sub_element_id: query_row.get(0)?, element_id: query_row.get(1)?, sub_elem_name: query_row.get(2)?, sub_elem_description: query_row.get(3)? })
Ok(SeriesLocationSubElementResult { sub_element_id: query_row.get(0)?, _element_id: query_row.get(1)?, sub_elem_name: query_row.get(2)?, sub_elem_description: query_row.get(3)? })
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sous-éléments.".to_string() } else { "Unable to retrieve sub-elements.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
@@ -326,18 +326,6 @@ pub fn insert_sync_location(
Ok(insert_result > 0)
}
/// Updates a series location for sync.
pub fn update_sync_location(conn: &Connection, user_id: &str, location_id: &str, loc_name: &str, loc_original_name: &str, last_update: i64, lang: Lang) -> AppResult<bool> {
let update_result = conn
.execute(
"UPDATE series_locations SET loc_name = ?1, loc_original_name = ?2, last_update = ?3 WHERE loc_id = ?4 AND user_id = ?5",
params![loc_name, loc_original_name, last_update, location_id, user_id],
)
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour le lieu pour sync.".to_string() } else { "Unable to update location for sync.".to_string() }))?;
Ok(update_result > 0)
}
/// Inserts a series location element for sync.
pub fn insert_sync_location_element(
conn: &Connection, element_id: &str, location_id: &str, user_id: &str,
@@ -353,21 +341,6 @@ pub fn insert_sync_location_element(
Ok(insert_result > 0)
}
/// Updates a series location element for sync.
pub fn update_sync_location_element(
conn: &Connection, user_id: &str, element_id: &str, element_name: &str,
original_name: &str, element_description: Option<&str>, last_update: i64, lang: Lang,
) -> AppResult<bool> {
let update_result = conn
.execute(
"UPDATE series_location_elements SET element_name = ?1, original_name = ?2, element_description = ?3, last_update = ?4 WHERE element_id = ?5 AND user_id = ?6",
params![element_name, original_name, element_description, last_update, element_id, user_id],
)
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour l'élément de lieu pour sync.".to_string() } else { "Unable to update location element for sync.".to_string() }))?;
Ok(update_result > 0)
}
/// Inserts a series location sub-element for sync.
pub fn insert_sync_location_sub_element(
conn: &Connection, sub_element_id: &str, element_id: &str, user_id: &str,
@@ -383,88 +356,6 @@ pub fn insert_sync_location_sub_element(
Ok(insert_result > 0)
}
/// Updates a series location sub-element for sync.
pub fn update_sync_location_sub_element(
conn: &Connection, user_id: &str, sub_element_id: &str, sub_elem_name: &str,
original_name: &str, sub_elem_description: Option<&str>, last_update: i64, lang: Lang,
) -> AppResult<bool> {
let update_result = conn
.execute(
"UPDATE series_location_sub_elements SET sub_elem_name = ?1, original_name = ?2, sub_elem_description = ?3, last_update = ?4 WHERE sub_element_id = ?5 AND user_id = ?6",
params![sub_elem_name, original_name, sub_elem_description, last_update, sub_element_id, user_id],
)
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour le sous-élément pour sync.".to_string() } else { "Unable to update sub-element for sync.".to_string() }))?;
Ok(update_result > 0)
}
/// Fetches all locations for a series for sync (without user filter).
pub fn fetch_locations_table_for_sync(conn: &Connection, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesLocationsTableResult>> {
let mut statement = conn
.prepare("SELECT loc_id, series_id, user_id, loc_name, loc_original_name, last_update FROM series_locations WHERE series_id = ?1")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les lieux pour sync.".to_string() } else { "Unable to retrieve locations for sync.".to_string() }))?;
let locations = statement
.query_map(params![series_id], |query_row| {
Ok(SeriesLocationsTableResult {
loc_id: query_row.get(0)?, series_id: query_row.get(1)?, user_id: query_row.get(2)?,
loc_name: query_row.get(3)?, loc_original_name: query_row.get(4)?, last_update: query_row.get(5)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les lieux pour sync.".to_string() } else { "Unable to retrieve locations for sync.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les lieux pour sync.".to_string() } else { "Unable to retrieve locations for sync.".to_string() }))?;
Ok(locations)
}
/// Fetches all location elements for a series for sync (without user filter).
pub fn fetch_location_elements_table_for_sync(conn: &Connection, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesLocationElementsTableResult>> {
let mut statement = conn
.prepare("SELECT sle.element_id, sle.location_id, sle.user_id, sle.element_name, sle.original_name, sle.element_description, sle.last_update FROM series_location_elements sle INNER JOIN series_locations sl ON sle.location_id = sl.loc_id WHERE sl.series_id = ?1")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les éléments de lieu pour sync.".to_string() } else { "Unable to retrieve location elements for sync.".to_string() }))?;
let elements = statement
.query_map(params![series_id], |query_row| {
Ok(SeriesLocationElementsTableResult {
element_id: query_row.get(0)?, location_id: query_row.get(1)?, user_id: query_row.get(2)?,
element_name: query_row.get(3)?, original_name: query_row.get(4)?,
element_description: query_row.get(5)?, last_update: query_row.get(6)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les éléments de lieu pour sync.".to_string() } else { "Unable to retrieve location elements for sync.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les éléments de lieu pour sync.".to_string() } else { "Unable to retrieve location elements for sync.".to_string() }))?;
Ok(elements)
}
/// Fetches all location sub-elements for a series for sync (without user filter).
pub fn fetch_location_sub_elements_table_for_sync(conn: &Connection, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesLocationSubElementsTableResult>> {
let mut statement = conn
.prepare("SELECT slse.sub_element_id, slse.element_id, slse.user_id, slse.sub_elem_name, slse.original_name, slse.sub_elem_description, slse.last_update FROM series_location_sub_elements slse INNER JOIN series_location_elements sle ON slse.element_id = sle.element_id INNER JOIN series_locations sl ON sle.location_id = sl.loc_id WHERE sl.series_id = ?1")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sous-éléments de lieu pour sync.".to_string() } else { "Unable to retrieve location sub-elements for sync.".to_string() }))?;
let sub_elements = statement
.query_map(params![series_id], |query_row| {
Ok(SeriesLocationSubElementsTableResult {
sub_element_id: query_row.get(0)?, element_id: query_row.get(1)?, user_id: query_row.get(2)?,
sub_elem_name: query_row.get(3)?, original_name: query_row.get(4)?,
sub_elem_description: query_row.get(5)?, last_update: query_row.get(6)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sous-éléments de lieu pour sync.".to_string() } else { "Unable to retrieve location sub-elements for sync.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sous-éléments de lieu pour sync.".to_string() } else { "Unable to retrieve location sub-elements for sync.".to_string() }))?;
Ok(sub_elements)
}
/// Fetches all locations for a series (alias for fetch_series_locations_table).
pub fn fetch_series_locations(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesLocationsTableResult>> {
fetch_series_locations_table(conn, user_id, series_id, lang)
}
/// Fetches all location elements for a series by series ID.
pub fn fetch_series_location_elements_by_series_id(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesLocationElementsTableResult>> {
let mut statement = conn

View File

@@ -5,7 +5,7 @@ use crate::shared::types::Lang;
pub struct SeriesSpellResult {
pub spell_id: String,
pub series_id: String,
pub _series_id: String,
pub name: String,
pub description: String,
pub appearance: String,
@@ -71,7 +71,7 @@ pub fn fetch_spells(conn: &Connection, user_id: &str, series_id: &str, lang: Lan
let spells = statement
.query_map(params![user_id, series_id], |query_row| {
Ok(SeriesSpellResult {
spell_id: query_row.get(0)?, series_id: query_row.get(1)?,
spell_id: query_row.get(0)?, _series_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)?,
@@ -94,7 +94,7 @@ pub fn fetch_spell_by_id(conn: &Connection, user_id: &str, spell_id: &str, lang:
let spell = statement
.query_row(params![user_id, spell_id], |query_row| {
Ok(SeriesSpellResult {
spell_id: query_row.get(0)?, series_id: query_row.get(1)?,
spell_id: query_row.get(0)?, _series_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)?,
@@ -310,55 +310,6 @@ pub fn fetch_synced_series_spell_tags(conn: &Connection, user_id: &str, lang: La
Ok(tags)
}
/// Fetches a complete spell by ID for sync.
pub fn fetch_spell_table_by_id(conn: &Connection, user_id: &str, spell_id: &str, lang: Lang) -> AppResult<Option<SeriesSpellsTableResult>> {
let mut statement = conn
.prepare("SELECT spell_id, series_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update FROM series_spells WHERE spell_id = ?1 AND user_id = ?2")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le sort complet.".to_string() } else { "Unable to retrieve complete spell.".to_string() }))?;
let spell = statement
.query_row(params![spell_id, user_id], |query_row| {
Ok(SeriesSpellsTableResult {
spell_id: query_row.get(0)?, series_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)?,
})
});
match spell {
Ok(spell) => Ok(Some(spell)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(_) => Err(AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le sort complet.".to_string() } else { "Unable to retrieve complete spell.".to_string() })),
}
}
/// Fetches a complete spell tag by ID for sync.
pub fn fetch_spell_tag_table_by_id(conn: &Connection, user_id: &str, tag_id: &str, lang: Lang) -> AppResult<Option<SeriesSpellTagsTableResult>> {
let mut statement = conn
.prepare("SELECT tag_id, series_id, user_id, name, hashed_name, color, last_update FROM series_spell_tags WHERE tag_id = ?1 AND user_id = ?2")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le tag complet.".to_string() } else { "Unable to retrieve complete tag.".to_string() }))?;
let tag = statement
.query_row(params![tag_id, user_id], |query_row| {
Ok(SeriesSpellTagsTableResult {
tag_id: query_row.get(0)?, series_id: query_row.get(1)?,
user_id: query_row.get(2)?, name: query_row.get(3)?,
hashed_name: query_row.get(4)?, color: query_row.get(5)?,
last_update: query_row.get(6)?,
})
});
match tag {
Ok(tag) => Ok(Some(tag)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(_) => Err(AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le tag complet.".to_string() } else { "Unable to retrieve complete tag.".to_string() })),
}
}
/// Checks if a spell tag exists.
pub fn is_spell_tag_exist(conn: &Connection, user_id: &str, tag_id: &str, lang: Lang) -> AppResult<bool> {
let mut statement = conn
@@ -390,22 +341,6 @@ pub fn insert_sync_spell(
Ok(insert_result > 0)
}
/// Updates a series spell for sync.
pub fn update_sync_spell(
conn: &Connection, user_id: &str, spell_id: &str, name: &str, name_hash: &str, description: &str,
appearance: &str, tags: &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 series_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 pour sync.".to_string() } else { "Unable to update spell for sync.".to_string() }))?;
Ok(update_result > 0)
}
/// Inserts a series spell tag for sync.
pub fn insert_sync_spell_tag(
conn: &Connection, tag_id: &str, series_id: &str, user_id: &str, name: &str,
@@ -421,78 +356,6 @@ pub fn insert_sync_spell_tag(
Ok(insert_result > 0)
}
/// Updates a series spell tag for sync.
pub fn update_sync_spell_tag(
conn: &Connection, user_id: &str, tag_id: &str, name: &str,
hashed_name: &str, color: Option<&str>, last_update: i64, lang: Lang,
) -> AppResult<bool> {
let update_result = conn
.execute(
"UPDATE series_spell_tags SET name = ?1, hashed_name = ?2, color = ?3, last_update = ?4 WHERE tag_id = ?5 AND user_id = ?6",
params![name, hashed_name, color, last_update, tag_id, user_id],
)
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour le tag pour sync.".to_string() } else { "Unable to update tag for sync.".to_string() }))?;
Ok(update_result > 0)
}
/// Fetches all spells for a series for sync (without user filter).
pub fn fetch_spells_table_for_sync(conn: &Connection, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesSpellsTableResult>> {
let mut statement = conn
.prepare("SELECT spell_id, series_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update FROM series_spells WHERE series_id = ?1")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sorts pour sync.".to_string() } else { "Unable to retrieve spells for sync.".to_string() }))?;
let spells = statement
.query_map(params![series_id], |query_row| {
Ok(SeriesSpellsTableResult {
spell_id: query_row.get(0)?, series_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 pour sync.".to_string() } else { "Unable to retrieve spells for sync.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les sorts pour sync.".to_string() } else { "Unable to retrieve spells for sync.".to_string() }))?;
Ok(spells)
}
/// Fetches all spell tags for a series for sync (without user filter).
pub fn fetch_spell_tags_table_for_sync(conn: &Connection, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesSpellTagsTableResult>> {
let mut statement = conn
.prepare("SELECT tag_id, series_id, user_id, name, hashed_name, color, last_update FROM series_spell_tags WHERE series_id = ?1")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les tags de sort pour sync.".to_string() } else { "Unable to retrieve spell tags for sync.".to_string() }))?;
let tags = statement
.query_map(params![series_id], |query_row| {
Ok(SeriesSpellTagsTableResult {
tag_id: query_row.get(0)?, series_id: query_row.get(1)?,
user_id: query_row.get(2)?, name: query_row.get(3)?,
hashed_name: query_row.get(4)?, color: query_row.get(5)?,
last_update: query_row.get(6)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les tags de sort pour sync.".to_string() } else { "Unable to retrieve spell tags for sync.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les tags de sort pour sync.".to_string() } else { "Unable to retrieve spell tags for sync.".to_string() }))?;
Ok(tags)
}
/// Fetches all spells for a series (alias for fetch_series_spells_table).
pub fn fetch_series_spells(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesSpellsTableResult>> {
fetch_series_spells_table(conn, user_id, series_id, lang)
}
/// Fetches all spell tags for a series (alias for fetch_series_spell_tags_table).
pub fn fetch_series_spell_tags(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesSpellTagsTableResult>> {
fetch_series_spell_tags_table(conn, user_id, series_id, lang)
}
/// Checks if a series spell exists (alias for is_spell_exist).
pub fn series_spell_exists(conn: &Connection, user_id: &str, spell_id: &str, lang: Lang) -> AppResult<bool> {
is_spell_exist(conn, user_id, spell_id, lang)
@@ -503,53 +366,6 @@ pub fn series_spell_tag_exists(conn: &Connection, user_id: &str, tag_id: &str, l
is_spell_tag_exist(conn, user_id, tag_id, lang)
}
/// Fetches a complete spell by ID for sync (array format).
pub fn fetch_complete_spell_by_id(conn: &Connection, spell_id: &str, lang: Lang) -> AppResult<Vec<SeriesSpellsTableResult>> {
let mut statement = conn
.prepare("SELECT spell_id, series_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update FROM series_spells WHERE spell_id = ?1")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le sort complet.".to_string() } else { "Unable to retrieve complete spell.".to_string() }))?;
let spells = statement
.query_map(params![spell_id], |query_row| {
Ok(SeriesSpellsTableResult {
spell_id: query_row.get(0)?, series_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 complet.".to_string() } else { "Unable to retrieve complete spell.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le sort complet.".to_string() } else { "Unable to retrieve complete spell.".to_string() }))?;
Ok(spells)
}
/// Fetches a complete spell tag by ID for sync (array format).
pub fn fetch_complete_spell_tag_by_id(conn: &Connection, tag_id: &str, lang: Lang) -> AppResult<Vec<SeriesSpellTagsTableResult>> {
let mut statement = conn
.prepare("SELECT tag_id, series_id, user_id, name, hashed_name, color, last_update FROM series_spell_tags WHERE tag_id = ?1")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le tag complet.".to_string() } else { "Unable to retrieve complete tag.".to_string() }))?;
let tags = statement
.query_map(params![tag_id], |query_row| {
Ok(SeriesSpellTagsTableResult {
tag_id: query_row.get(0)?, series_id: query_row.get(1)?,
user_id: query_row.get(2)?, name: query_row.get(3)?,
hashed_name: query_row.get(4)?, color: query_row.get(5)?,
last_update: query_row.get(6)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le tag complet.".to_string() } else { "Unable to retrieve complete tag.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le tag complet.".to_string() } else { "Unable to retrieve complete tag.".to_string() }))?;
Ok(tags)
}
/// Inserts a series spell for sync (alias with compatible signature).
pub fn insert_sync_series_spell(
conn: &Connection, spell_id: &str, series_id: &str, user_id: &str, name: &str, name_hash: &str,

View File

@@ -180,28 +180,6 @@ pub fn fetch_series_worlds_table(conn: &Connection, user_id: &str, series_id: &s
Ok(worlds)
}
/// Fetches all elements for a world for sync.
pub fn fetch_series_world_elements_table(conn: &Connection, user_id: &str, world_id: &str, lang: Lang) -> AppResult<Vec<SeriesWorldElementsTableResult>> {
let mut statement = conn
.prepare("SELECT element_id, world_id, user_id, element_type, name, original_name, description, last_update FROM series_world_elements WHERE world_id = ?1 AND user_id = ?2")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les éléments de monde pour sync.".to_string() } else { "Unable to retrieve world elements for sync.".to_string() }))?;
let elements = statement
.query_map(params![world_id, user_id], |query_row| {
Ok(SeriesWorldElementsTableResult {
element_id: query_row.get(0)?, world_id: query_row.get(1)?,
user_id: query_row.get(2)?, element_type: query_row.get(3)?,
name: query_row.get(4)?, original_name: query_row.get(5)?,
description: query_row.get(6)?, last_update: query_row.get(7)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les éléments de monde pour sync.".to_string() } else { "Unable to retrieve world elements for sync.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les éléments de monde pour sync.".to_string() } else { "Unable to retrieve world elements for sync.".to_string() }))?;
Ok(elements)
}
/// Fetches all series worlds for a user for sync comparison.
pub fn fetch_synced_series_worlds(conn: &Connection, user_id: &str, lang: Lang) -> AppResult<Vec<SyncedSeriesWorldResult>> {
let mut statement = conn
@@ -236,52 +214,6 @@ pub fn fetch_synced_series_world_elements(conn: &Connection, user_id: &str, lang
Ok(elements)
}
/// Fetches a complete world by ID for sync.
pub fn fetch_complete_world_by_id(conn: &Connection, world_id: &str, lang: Lang) -> AppResult<Vec<SeriesWorldsTableResult>> {
let mut statement = conn
.prepare("SELECT world_id, series_id, user_id, name, hashed_name, history, politics, economy, religion, languages, last_update FROM series_worlds WHERE world_id = ?1")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le monde complet.".to_string() } else { "Unable to retrieve complete world.".to_string() }))?;
let worlds = statement
.query_map(params![world_id], |query_row| {
Ok(SeriesWorldsTableResult {
world_id: query_row.get(0)?, series_id: query_row.get(1)?,
user_id: query_row.get(2)?, name: query_row.get(3)?,
hashed_name: query_row.get(4)?, history: query_row.get(5)?,
politics: query_row.get(6)?, economy: query_row.get(7)?,
religion: query_row.get(8)?, languages: query_row.get(9)?,
last_update: query_row.get(10)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le monde complet.".to_string() } else { "Unable to retrieve complete world.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer le monde complet.".to_string() } else { "Unable to retrieve complete world.".to_string() }))?;
Ok(worlds)
}
/// Fetches a complete world element by ID for sync.
pub fn fetch_complete_world_element_by_id(conn: &Connection, element_id: &str, lang: Lang) -> AppResult<Vec<SeriesWorldElementsTableResult>> {
let mut statement = conn
.prepare("SELECT element_id, world_id, user_id, element_type, name, original_name, description, last_update FROM series_world_elements WHERE element_id = ?1")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer l'élément de monde complet.".to_string() } else { "Unable to retrieve complete world element.".to_string() }))?;
let elements = statement
.query_map(params![element_id], |query_row| {
Ok(SeriesWorldElementsTableResult {
element_id: query_row.get(0)?, world_id: query_row.get(1)?,
user_id: query_row.get(2)?, element_type: query_row.get(3)?,
name: query_row.get(4)?, original_name: query_row.get(5)?,
description: query_row.get(6)?, last_update: query_row.get(7)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer l'élément de monde complet.".to_string() } else { "Unable to retrieve complete world element.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer l'élément de monde complet.".to_string() } else { "Unable to retrieve complete world element.".to_string() }))?;
Ok(elements)
}
/// Checks if a world exists.
pub fn is_world_exist(conn: &Connection, user_id: &str, world_id: &str, lang: Lang) -> AppResult<bool> {
let mut statement = conn
@@ -324,22 +256,6 @@ pub fn insert_sync_world(
Ok(insert_result > 0)
}
/// Updates a series world for sync.
pub fn update_sync_world(
conn: &Connection, user_id: &str, world_id: &str, name: &str, hashed_name: &str,
history: Option<&str>, politics: Option<&str>, economy: Option<&str>, religion: Option<&str>,
languages: Option<&str>, last_update: i64, lang: Lang,
) -> AppResult<bool> {
let update_result = conn
.execute(
"UPDATE series_worlds SET name = ?1, hashed_name = ?2, history = ?3, politics = ?4, economy = ?5, religion = ?6, languages = ?7, last_update = ?8 WHERE world_id = ?9 AND user_id = ?10",
params![name, hashed_name, history, politics, economy, religion, languages, last_update, world_id, user_id],
)
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour le monde pour sync.".to_string() } else { "Unable to update world for sync.".to_string() }))?;
Ok(update_result > 0)
}
/// Inserts a series world element for sync.
pub fn insert_sync_world_element(
conn: &Connection, element_id: &str, world_id: &str, user_id: &str, element_type: i64,
@@ -355,72 +271,6 @@ pub fn insert_sync_world_element(
Ok(insert_result > 0)
}
/// Updates a series world element for sync.
pub fn update_sync_world_element(
conn: &Connection, user_id: &str, element_id: &str, element_type: i64, name: &str,
original_name: &str, description: Option<&str>, last_update: i64, lang: Lang,
) -> AppResult<bool> {
let update_result = conn
.execute(
"UPDATE series_world_elements SET element_type = ?1, name = ?2, original_name = ?3, description = ?4, last_update = ?5 WHERE element_id = ?6 AND user_id = ?7",
params![element_type, name, original_name, description, last_update, element_id, user_id],
)
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour l'élément de monde pour sync.".to_string() } else { "Unable to update world element for sync.".to_string() }))?;
Ok(update_result > 0)
}
/// Fetches all worlds for a series for sync (without user filter).
pub fn fetch_worlds_table_for_sync(conn: &Connection, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesWorldsTableResult>> {
let mut statement = conn
.prepare("SELECT world_id, series_id, user_id, name, hashed_name, history, politics, economy, religion, languages, last_update FROM series_worlds WHERE series_id = ?1")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les mondes pour sync.".to_string() } else { "Unable to retrieve worlds for sync.".to_string() }))?;
let worlds = statement
.query_map(params![series_id], |query_row| {
Ok(SeriesWorldsTableResult {
world_id: query_row.get(0)?, series_id: query_row.get(1)?,
user_id: query_row.get(2)?, name: query_row.get(3)?,
hashed_name: query_row.get(4)?, history: query_row.get(5)?,
politics: query_row.get(6)?, economy: query_row.get(7)?,
religion: query_row.get(8)?, languages: query_row.get(9)?,
last_update: query_row.get(10)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les mondes pour sync.".to_string() } else { "Unable to retrieve worlds for sync.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les mondes pour sync.".to_string() } else { "Unable to retrieve worlds for sync.".to_string() }))?;
Ok(worlds)
}
/// Fetches all world elements for a series for sync (via series_worlds join).
pub fn fetch_world_elements_table_for_sync(conn: &Connection, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesWorldElementsTableResult>> {
let mut statement = conn
.prepare("SELECT swe.element_id, swe.world_id, swe.user_id, swe.element_type, swe.name, swe.original_name, swe.description, swe.last_update FROM series_world_elements swe INNER JOIN series_worlds sw ON swe.world_id = sw.world_id WHERE sw.series_id = ?1")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les éléments de monde pour sync.".to_string() } else { "Unable to retrieve world elements for sync.".to_string() }))?;
let elements = statement
.query_map(params![series_id], |query_row| {
Ok(SeriesWorldElementsTableResult {
element_id: query_row.get(0)?, world_id: query_row.get(1)?,
user_id: query_row.get(2)?, element_type: query_row.get(3)?,
name: query_row.get(4)?, original_name: query_row.get(5)?,
description: query_row.get(6)?, last_update: query_row.get(7)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les éléments de monde pour sync.".to_string() } else { "Unable to retrieve world elements for sync.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les éléments de monde pour sync.".to_string() } else { "Unable to retrieve world elements for sync.".to_string() }))?;
Ok(elements)
}
/// Fetches all worlds for a series (alias for fetch_series_worlds_table).
pub fn fetch_series_worlds(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesWorldsTableResult>> {
fetch_series_worlds_table(conn, user_id, series_id, lang)
}
/// Fetches all world elements for a series by series ID.
pub fn fetch_series_world_elements_by_series_id(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesWorldElementsTableResult>> {
let mut statement = conn

View File

@@ -18,7 +18,6 @@ fn get_session(session: &State<SessionState>) -> Result<(String, Lang), AppError
#[serde(rename_all = "camelCase")]
pub struct GetSpellListData {
pub book_id: String,
pub enabled: bool,
}
#[tauri::command]

View File

@@ -5,7 +5,7 @@ use crate::shared::types::Lang;
pub struct SpellResult {
pub spell_id: String,
pub book_id: String,
pub _book_id: String,
pub name: String,
pub description: Option<String>,
pub appearance: Option<String>,
@@ -54,7 +54,7 @@ pub fn fetch_spells(conn: &Connection, user_id: &str, book_id: &str, lang: Lang)
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)?,
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)?,
@@ -83,7 +83,7 @@ pub fn fetch_spell_by_id(conn: &Connection, user_id: &str, spell_id: &str, lang:
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)?,
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)?,

View File

@@ -52,18 +52,6 @@ pub struct SpellListResponse {
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

View File

@@ -5,7 +5,7 @@ use crate::shared::types::Lang;
pub struct SpellTagResult {
pub tag_id: String,
pub book_id: String,
pub _book_id: String,
pub name: String,
pub color: Option<String>,
}
@@ -36,7 +36,7 @@ pub fn fetch_spell_tags(conn: &Connection, user_id: &str, book_id: &str, lang: L
let rows = statement
.query_map(params![user_id, book_id], |query_row| {
Ok(SpellTagResult {
tag_id: query_row.get(0)?, book_id: query_row.get(1)?,
tag_id: query_row.get(0)?, _book_id: query_row.get(1)?,
name: query_row.get(2)?, color: query_row.get(3)?,
})
})

View File

@@ -143,30 +143,6 @@ pub struct SyncedBookFull {
pub spell_tags: Vec<SyncedSpellTag>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SyncCharacterData {
pub first_name: String,
pub last_name: Option<String>,
pub nickname: Option<String>,
pub age: Option<String>,
pub gender: Option<String>,
pub species: Option<String>,
pub nationality: Option<String>,
pub status: Option<String>,
pub category: String,
pub title: Option<String>,
pub image: Option<String>,
pub role: Option<String>,
pub biography: Option<String>,
pub history: Option<String>,
pub speech_pattern: Option<String>,
pub catchphrase: Option<String>,
pub residence: Option<String>,
pub notes: Option<String>,
pub color: Option<String>,
}
/// Retrieves a complete book with all its associated entities for synchronization.
/// Decrypts all encrypted fields using the user's encryption key.
/// * `conn` - Database connection

View File

@@ -1,3 +1,2 @@
pub mod commands;
pub mod repo;
pub mod service;

View File

@@ -4,11 +4,11 @@ use crate::error::{AppError, AppResult};
use crate::shared::types::Lang;
pub struct RemovedItemRecord {
pub removal_id: String,
pub _removal_id: String,
pub table_name: String,
pub entity_id: String,
pub book_id: Option<String>,
pub user_id: String,
pub _user_id: String,
pub deleted_at: i64,
}
@@ -51,9 +51,9 @@ pub fn get_deletions_since(conn: &Connection, user_id: &str, since: i64, lang: L
let records = statement
.query_map(params![user_id, since], |query_row| {
Ok(RemovedItemRecord {
removal_id: query_row.get(0)?, table_name: query_row.get(1)?,
_removal_id: query_row.get(0)?, table_name: query_row.get(1)?,
entity_id: query_row.get(2)?, book_id: query_row.get(3)?,
user_id: query_row.get(4)?, deleted_at: query_row.get(5)?,
_user_id: query_row.get(4)?, deleted_at: query_row.get(5)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les suppressions.".to_string() } else { "Unable to retrieve deletions.".to_string() }))?
@@ -63,63 +63,3 @@ pub fn get_deletions_since(conn: &Connection, user_id: &str, since: i64, lang: L
Ok(records)
}
/// Checks if an entity was previously deleted.
/// * `conn` - Database connection
/// * `table_name` - The table name
/// * `entity_id` - The entity ID
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the entity was deleted locally.
pub fn was_deleted(conn: &Connection, table_name: &str, entity_id: &str, lang: Lang) -> AppResult<bool> {
let mut statement = conn
.prepare("SELECT 1 FROM removed_items WHERE table_name = ?1 AND entity_id = ?2 LIMIT 1")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier si l'élément a été supprimé.".to_string() } else { "Unable to check if item was deleted.".to_string() }))?;
let exists = statement
.query_row(params![table_name, entity_id], |_query_row| Ok(true))
.unwrap_or(false);
Ok(exists)
}
/// Retrieves all tracked deletions for a specific book.
/// * `conn` - Database connection
/// * `user_id` - The user ID
/// * `book_id` - The book ID
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns array of removed item records for that book.
pub fn get_deletions_for_book(conn: &Connection, user_id: &str, book_id: &str, lang: Lang) -> AppResult<Vec<RemovedItemRecord>> {
let mut statement = conn
.prepare("SELECT removal_id, table_name, entity_id, book_id, user_id, deleted_at FROM removed_items WHERE user_id = ?1 AND book_id = ?2")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les suppressions pour ce livre.".to_string() } else { "Unable to retrieve deletions for this book.".to_string() }))?;
let records = statement
.query_map(params![user_id, book_id], |query_row| {
Ok(RemovedItemRecord {
removal_id: query_row.get(0)?, table_name: query_row.get(1)?,
entity_id: query_row.get(2)?, book_id: query_row.get(3)?,
user_id: query_row.get(4)?, deleted_at: query_row.get(5)?,
})
})
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les suppressions pour ce livre.".to_string() } else { "Unable to retrieve deletions for this book.".to_string() }))?
.collect::<Result<Vec<_>, _>>()
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les suppressions pour ce livre.".to_string() } else { "Unable to retrieve deletions for this book.".to_string() }))?;
Ok(records)
}
/// Clears all deletion records for a user.
/// WARNING: Only use this when wiping user data completely.
/// * `conn` - Database connection
/// * `user_id` - The user ID
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if cleared successfully.
pub fn clear_all_for_user(conn: &Connection, user_id: &str, lang: Lang) -> AppResult<bool> {
let delete_result = conn
.execute(
"DELETE FROM removed_items WHERE user_id = ?1",
params![user_id],
)
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de supprimer les enregistrements de suppression.".to_string() } else { "Unable to clear deletion records.".to_string() }))?;
Ok(delete_result > 0)
}

View File

@@ -1,25 +0,0 @@
use rusqlite::Connection;
use crate::domains::tombstone::repo;
use crate::error::AppResult;
use crate::helpers::create_unique_id;
use crate::shared::types::Lang;
/// Records a deleted item for sync tracking.
/// Must be called BEFORE the actual deletion from the source table.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `book_id` - The book ID (None for series items)
/// * `table_name` - The name of the table from which the item is deleted
/// * `entity_id` - The UUID of the deleted entity
/// * `deleted_at` - The timestamp of deletion (from UI via timestamp_in_seconds())
/// * `lang` - The language for error messages ("fr" or "en")
/// Returns true if the record was inserted successfully.
pub fn delete_tracker(
conn: &Connection, user_id: &str, book_id: Option<&str>, table_name: &str,
entity_id: &str, deleted_at: i64, lang: Lang,
) -> AppResult<bool> {
let removal_id: String = create_unique_id(None);
repo::insert(conn, &removal_id, table_name, entity_id, book_id, user_id, deleted_at, lang)
}

View File

@@ -8,6 +8,14 @@ use crate::domains::user::service;
use crate::error::AppError;
use crate::shared::session::SessionState;
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateUserData {
pub username: String,
pub email: String,
pub author_name: Option<String>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InitUserData {
@@ -25,8 +33,6 @@ pub struct InitUserResult {
#[serde(rename_all = "camelCase")]
pub struct SyncUserData {
pub user_id: String,
pub first_name: String,
pub last_name: String,
pub username: String,
pub email: String,
}
@@ -35,14 +41,10 @@ pub struct SyncUserData {
#[serde(rename_all = "camelCase")]
pub struct UserInfoResponse {
pub id: String,
pub name: String,
pub last_name: String,
pub username: String,
pub email: String,
pub account_verified: bool,
pub author_name: String,
pub group_id: i64,
pub terms_accepted: bool,
}
#[tauri::command]
@@ -62,6 +64,11 @@ pub fn init_user(data: InitUserData, db: State<DbManager>, session: State<Sessio
key_manager::set_last_user_id(&data.user_id)?;
if key_manager::get_user_encryption_key(&data.user_id).is_err() {
let encryption_key = encryption::generate_user_encryption_key(&data.user_id)?;
key_manager::set_user_encryption_key(&data.user_id, &encryption_key)?;
}
Ok(InitUserResult { success: true, error: None })
}
@@ -139,14 +146,10 @@ pub fn get_user_info(db: State<DbManager>, session: State<SessionState>) -> Resu
Ok(UserInfoResponse {
id: user_info.id,
name: user_info.name,
last_name: user_info.last_name,
username: user_info.username,
email: user_info.email,
account_verified: user_info.account_verified,
author_name: user_info.author_name,
group_id: user_info.group_id,
terms_accepted: user_info.terms_accepted,
})
}
@@ -160,6 +163,20 @@ pub fn sync_user(data: SyncUserData, db: State<DbManager>, session: State<Sessio
db_manager.initialize(&data.user_id)?;
let conn = db_manager.get_connection(&data.user_id)?;
service::add_user(conn, &data.user_id, &data.first_name, &data.last_name, &data.username, &data.email, "", lang)?;
service::add_user(conn, &data.user_id, &data.username, &data.email, lang)?;
Ok(true)
}
#[tauri::command]
pub fn update_user_info(data: UpdateUserData, db: State<DbManager>, session: State<SessionState>) -> Result<bool, 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;
drop(session_guard);
let user_key = key_manager::get_user_encryption_key(&user_id)?;
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_user_infos(conn, &user_key, &user_id, &data.username, &data.email, data.author_name.as_deref(), lang)
}

View File

@@ -4,45 +4,18 @@ use crate::error::{AppError, AppResult};
use crate::shared::types::Lang;
pub struct UserInfosQueryResponse {
pub first_name: String,
pub last_name: String,
pub username: String,
pub email: String,
pub plateform: String,
pub term_accepted: i64,
pub account_verified: i64,
pub author_name: Option<String>,
pub rite_points: i64,
pub user_group: i64,
}
pub struct UserAccountQuery {
pub first_name: Option<String>,
pub last_name: Option<String>,
pub username: String,
pub author_name: Option<String>,
pub email: String,
}
pub struct CredentialResponse {
pub valid: bool,
pub message: Option<String>,
pub user: Option<UserResponse>,
}
pub struct UserResponse {
pub id: String,
pub name: String,
pub last_name: String,
pub username: String,
pub email: String,
pub account_verified: bool,
}
pub struct GuideTourResult {
pub step_tour: String,
}
/// Inserts a new user into the database.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier for the user
@@ -56,21 +29,18 @@ pub struct GuideTourResult {
/// Returns the user's UUID if insertion was successful.
/// Errors if the user cannot be registered.
pub fn insert_user(
conn: &Connection, user_id: &str, first_name: &str, last_name: &str, username: &str,
origin_username: &str, email: &str, origin_email: &str, lang: Lang,
conn: &Connection, user_id: &str, username: &str, origin_username: &str,
email: &str, origin_email: &str, lang: Lang,
) -> AppResult<String> {
let reg_date = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|duration| duration.as_millis() as i64).unwrap_or(0);
// Try INSERT first; if user already exists, UPDATE their info instead
let insert_result = conn.execute(
"INSERT OR IGNORE INTO erit_users (user_id, first_name, last_name, username, email, origin_email, origin_username, plateform, term_accepted, account_verified, reg_date) VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11)",
params![user_id, first_name, last_name, username, email, origin_email, origin_username, "desktop", 0, 1, reg_date],
"INSERT OR IGNORE INTO erit_users (user_id, username, email, origin_email, origin_username) VALUES (?1,?2,?3,?4,?5)",
params![user_id, username, email, origin_email, origin_username],
).map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'enregistrer l'utilisateur.".to_string() } else { "Unable to register user.".to_string() }))?;
if insert_result == 0 {
conn.execute(
"UPDATE erit_users SET first_name = ?1, last_name = ?2, username = ?3, email = ?4, origin_email = ?5, origin_username = ?6 WHERE user_id = ?7",
params![first_name, last_name, username, email, origin_email, origin_username, user_id],
"UPDATE erit_users SET username = ?1, email = ?2, origin_email = ?3, origin_username = ?4 WHERE user_id = ?5",
params![username, email, origin_email, origin_username, user_id],
).map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour l'utilisateur.".to_string() } else { "Unable to update user.".to_string() }))?;
}
@@ -85,17 +55,14 @@ pub fn insert_user(
/// Errors if the user is not found or cannot be retrieved.
pub fn fetch_user_infos(conn: &Connection, user_id: &str, lang: Lang) -> AppResult<UserInfosQueryResponse> {
let mut statement = conn
.prepare("SELECT first_name, last_name, username, email, plateform, term_accepted, account_verified, author_name, erite_points AS rite_points, user_group FROM erit_users WHERE user_id = ?1")
.prepare("SELECT username, email, author_name, user_group FROM erit_users WHERE user_id = ?1")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les informations utilisateur.".to_string() } else { "Unable to retrieve user information.".to_string() }))?;
let user_info = statement
.query_row(params![user_id], |query_row| {
Ok(UserInfosQueryResponse {
first_name: query_row.get(0)?, last_name: query_row.get(1)?,
username: query_row.get(2)?, email: query_row.get(3)?,
plateform: query_row.get(4)?, term_accepted: query_row.get(5)?,
account_verified: query_row.get(6)?, author_name: query_row.get(7)?,
rite_points: query_row.get(8)?, user_group: query_row.get(9)?,
username: query_row.get(0)?, email: query_row.get(1)?,
author_name: query_row.get(2)?, user_group: query_row.get(3)?,
})
})
.map_err(|error| match error {
@@ -121,13 +88,13 @@ pub fn fetch_user_infos(conn: &Connection, user_id: &str, lang: Lang) -> AppResu
/// Returns true if the update was successful, false otherwise.
/// Errors if the update fails.
pub fn update_user_infos(
conn: &Connection, user_id: &str, first_name: &str, last_name: &str, username: &str,
origin_username: &str, email: &str, origin_email: &str, original_author_name: &str, author_name: &str, lang: Lang,
conn: &Connection, user_id: &str, username: &str, origin_username: &str,
email: &str, origin_email: &str, original_author_name: &str, author_name: &str, lang: Lang,
) -> AppResult<bool> {
let update_result = conn
.execute(
"UPDATE erit_users SET first_name = ?1, last_name = ?2, username = ?3, email = ?4, origin_username = ?5, origin_author_name = ?6, author_name = ?7 WHERE user_id = ?8 AND origin_email = ?9",
params![first_name, last_name, username, email, origin_username, original_author_name, author_name, user_id, origin_email],
"UPDATE erit_users SET username = ?1, email = ?2, origin_username = ?3, origin_author_name = ?4, author_name = ?5 WHERE user_id = ?6 AND origin_email = ?7",
params![username, email, origin_username, original_author_name, author_name, user_id, origin_email],
)
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de mettre à jour les informations utilisateur.".to_string() } else { "Unable to update user information.".to_string() }))?;
@@ -142,12 +109,12 @@ pub fn update_user_infos(
/// Errors if the account is not found or cannot be retrieved.
pub fn fetch_account_information(conn: &Connection, user_id: &str, lang: Lang) -> AppResult<UserAccountQuery> {
let mut statement = conn
.prepare("SELECT first_name, last_name, username, author_name, email FROM erit_users WHERE user_id = ?1")
.prepare("SELECT username, author_name, email FROM erit_users WHERE user_id = ?1")
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les informations du compte.".to_string() } else { "Unable to retrieve account information.".to_string() }))?;
let account_info = statement
.query_row(params![user_id], |query_row| {
Ok(UserAccountQuery { first_name: query_row.get(0)?, last_name: query_row.get(1)?, username: query_row.get(2)?, author_name: query_row.get(3)?, email: query_row.get(4)? })
Ok(UserAccountQuery { username: query_row.get(0)?, author_name: query_row.get(1)?, email: query_row.get(2)? })
})
.map_err(|error| match error {
rusqlite::Error::QueryReturnedNoRows => AppError::NotFound(if lang == Lang::Fr { "Compte non trouvé.".to_string() } else { "Account not found.".to_string() }),

View File

@@ -6,116 +6,46 @@ use crate::domains::user::repo;
use crate::error::AppResult;
use crate::shared::types::Lang;
pub struct UserAccount {
pub first_name: String,
pub last_name: String,
pub username: String,
pub author_name: String,
pub email: String,
}
pub struct GuideTour {
pub key: String,
pub value: bool,
}
pub struct BookSummary {
pub book_id: String,
pub title: String,
pub sub_title: Option<String>,
}
pub struct UserInfoResponse {
pub id: String,
pub name: String,
pub last_name: String,
pub username: String,
pub email: String,
pub account_verified: bool,
pub author_name: String,
pub group_id: i64,
pub terms_accepted: bool,
pub guide_tour: Vec<GuideTour>,
}
/// Retrieves complete user information including associated books.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user to fetch
/// * `lang` - The language for error messages
/// Returns the complete user information response.
/// Errors if the user is not found or decryption fails.
pub fn return_user_infos(conn: &Connection, user_id: &str, lang: Lang) -> AppResult<UserInfoResponse> {
let user_infos_data: repo::UserInfosQueryResponse = repo::fetch_user_infos(conn, user_id, lang)?;
let user_encryption_key: String = get_user_encryption_key(user_id)?;
let first_name: String = decrypt_data_with_user_key(&user_infos_data.first_name, &user_encryption_key)?;
let last_name: String = decrypt_data_with_user_key(&user_infos_data.last_name, &user_encryption_key)?;
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 account_verified: bool = user_infos_data.account_verified == 1;
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 group_id: i64 = user_infos_data.user_group;
let terms_accepted: bool = user_infos_data.term_accepted == 1;
let guide_tour_status: Vec<GuideTour> = vec![];
Ok(UserInfoResponse {
id: user_id.to_string(),
name: first_name,
last_name,
username,
email,
account_verified,
author_name,
group_id,
terms_accepted,
guide_tour: guide_tour_status,
username, email, author_name,
group_id: user_infos_data.user_group,
})
}
/// Creates a new user in the database with encrypted personal information.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier for the new user
/// * `first_name` - The user's first name (will be encrypted)
/// * `last_name` - The user's last name (will be encrypted)
/// * `username` - The user's username (will be encrypted and hashed)
/// * `email` - The user's email address (will be encrypted and hashed)
/// * `not_encrypt_password` - The user's password in plain text (unused in current implementation)
/// * `lang` - The preferred language for the user
/// Returns the created user's identifier.
/// Errors if encryption or insertion fails.
pub fn add_user(
conn: &Connection, user_id: &str, first_name: &str, last_name: &str, username: &str,
email: &str, _not_encrypt_password: &str, lang: Lang,
) -> AppResult<String> {
pub fn add_user(conn: &Connection, user_id: &str, username: &str, email: &str, lang: Lang) -> AppResult<String> {
let user_encryption_key: String = get_user_encryption_key(user_id)?;
let encrypted_first_name: String = encrypt_data_with_user_key(first_name, &user_encryption_key)?;
let encrypted_last_name: String = encrypt_data_with_user_key(last_name, &user_encryption_key)?;
let encrypted_username: String = encrypt_data_with_user_key(username, &user_encryption_key)?;
let encrypted_email: String = encrypt_data_with_user_key(email, &user_encryption_key)?;
let hashed_email: String = hash_element(email);
let hashed_username: String = hash_element(username);
repo::insert_user(conn, user_id, &encrypted_first_name, &encrypted_last_name, &encrypted_username, &hashed_username, &encrypted_email, &hashed_email, lang)
repo::insert_user(conn, user_id, &encrypted_username, &hashed_username, &encrypted_email, &hashed_email, lang)
}
/// Updates an existing user's profile information in the database.
/// * `conn` - Database connection
/// * `user_key` - The encryption key for the user's data
/// * `user_id` - The unique identifier of the user to update
/// * `first_name` - The updated first name (will be encrypted)
/// * `last_name` - The updated last name (will be encrypted)
/// * `username` - The updated username (will be encrypted and hashed)
/// * `email` - The updated email address (will be encrypted and hashed)
/// * `author_name` - The optional author/pen name (will be encrypted and hashed if provided)
/// * `lang` - The preferred language for the user
/// Returns true if the update was successful.
/// Errors if encryption or update fails.
pub fn update_user_infos(
conn: &Connection, user_key: &str, user_id: &str, first_name: &str, last_name: &str,
conn: &Connection, user_key: &str, user_id: &str,
username: &str, email: &str, author_name: Option<&str>, lang: Lang,
) -> AppResult<bool> {
let encrypted_first_name: String = encrypt_data_with_user_key(first_name, user_key)?;
let encrypted_last_name: String = encrypt_data_with_user_key(last_name, user_key)?;
let encrypted_username: String = encrypt_data_with_user_key(username, user_key)?;
let encrypted_email: String = encrypt_data_with_user_key(email, user_key)?;
let hashed_email: String = hash_element(email);
@@ -127,30 +57,5 @@ pub fn update_user_infos(
hashed_author_name = hash_element(author_name_val);
}
repo::update_user_infos(conn, user_id, &encrypted_first_name, &encrypted_last_name, &encrypted_username, &hashed_username, &encrypted_email, &hashed_email, &hashed_author_name, &encrypted_author_name, lang)
}
/// Retrieves and decrypts the user's account information from the database.
/// * `conn` - Database connection
/// * `user_id` - The unique identifier of the user
/// * `lang` - The language for error messages
/// Returns the decrypted user account information.
/// Errors if the user is not found or decryption fails.
pub fn get_user_account_information(conn: &Connection, user_id: &str, lang: Lang) -> AppResult<UserAccount> {
let account_data: repo::UserAccountQuery = repo::fetch_account_information(conn, user_id, lang)?;
let user_encryption_key: String = get_user_encryption_key(user_id)?;
let decrypted_first_name: String = if let Some(ref first_name) = account_data.first_name { decrypt_data_with_user_key(first_name, &user_encryption_key)? } else { String::new() };
let decrypted_last_name: String = if let Some(ref last_name) = account_data.last_name { decrypt_data_with_user_key(last_name, &user_encryption_key)? } else { String::new() };
let decrypted_username: String = decrypt_data_with_user_key(&account_data.username, &user_encryption_key)?;
let decrypted_author_name: String = if let Some(ref author_name) = account_data.author_name { decrypt_data_with_user_key(author_name, &user_encryption_key)? } else { String::new() };
let decrypted_email: String = decrypt_data_with_user_key(&account_data.email, &user_encryption_key)?;
Ok(UserAccount {
first_name: decrypted_first_name,
last_name: decrypted_last_name,
username: decrypted_username,
author_name: decrypted_author_name,
email: decrypted_email,
})
repo::update_user_infos(conn, user_id, &encrypted_username, &hashed_username, &encrypted_email, &hashed_email, &hashed_author_name, &encrypted_author_name, lang)
}

View File

@@ -10,23 +10,6 @@ use crate::error::{AppError, AppResult};
use crate::helpers::{create_unique_id, timestamp_in_seconds};
use crate::shared::types::Lang;
/// Represents a synced world with its elements for synchronization.
#[derive(Debug, Serialize, Deserialize)]
pub struct SyncedWorld {
pub id: String,
pub name: String,
pub last_update: i64,
pub elements: Vec<SyncedWorldElement>,
}
/// Represents a synced world element for synchronization.
#[derive(Debug, Serialize, Deserialize)]
pub struct SyncedWorldElement {
pub id: String,
pub name: String,
pub last_update: i64,
}
/// Represents a single world element with its properties.
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct WorldElement {

View File

@@ -1,4 +1,3 @@
use chrono::Utc;
use regex::Regex;
@@ -20,20 +19,6 @@ pub fn create_unique_id(existing_id: Option<&str>) -> String {
}
}
/// Returns the current date as an ISO 8601 string.
/// Equivalent to TS `System.getCurrentDate()`.
pub fn get_current_date() -> String {
Utc::now().to_rfc3339()
}
/// Converts an ISO date string to a MySQL-compatible date (YYYY-MM-DD).
/// Equivalent to TS `System.dateToMySqlDate()`.
pub fn date_to_mysql_date(iso_date_string: &str) -> String {
match chrono::DateTime::parse_from_rfc3339(iso_date_string) {
Ok(date_object) => date_object.format("%Y-%m-%d").to_string(),
Err(_) => iso_date_string.to_string(),
}
}
/// Converts HTML content to plain text by stripping tags and decoding entities.
/// Equivalent to TS `System.htmlToText()`.

View File

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

View File

@@ -1,3 +1,2 @@
pub mod ai_models;
pub mod session;
pub mod types;