Refactor: Remove unused structs, redundant services, and streamline repository models
This commit is contained in:
@@ -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
|
||||
);
|
||||
|
||||
-- =========================================================================
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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() })),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -89,7 +89,6 @@ pub struct SaveChapterContentData {
|
||||
pub version: i64,
|
||||
pub content: Value,
|
||||
pub total_word_count: i64,
|
||||
pub content_id: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -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() }))?
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() }))?
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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)?,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,499 +1,349 @@
|
||||
use rusqlite::{params, Connection};
|
||||
|
||||
use crate::error::{AppError, AppResult};
|
||||
use crate::shared::types::Lang;
|
||||
|
||||
pub struct SeriesWorldResult {
|
||||
pub world_id: String,
|
||||
pub world_name: String,
|
||||
pub history: Option<String>,
|
||||
pub politics: Option<String>,
|
||||
pub economy: Option<String>,
|
||||
pub religion: Option<String>,
|
||||
pub languages: Option<String>,
|
||||
pub element_id: Option<String>,
|
||||
pub element_name: Option<String>,
|
||||
pub element_description: Option<String>,
|
||||
pub element_type: Option<i64>,
|
||||
}
|
||||
|
||||
pub struct SeriesWorldsTableResult {
|
||||
pub world_id: String,
|
||||
pub series_id: String,
|
||||
pub user_id: String,
|
||||
pub name: String,
|
||||
pub hashed_name: String,
|
||||
pub history: Option<String>,
|
||||
pub politics: Option<String>,
|
||||
pub economy: Option<String>,
|
||||
pub religion: Option<String>,
|
||||
pub languages: Option<String>,
|
||||
pub last_update: i64,
|
||||
}
|
||||
|
||||
pub struct SeriesWorldElementsTableResult {
|
||||
pub element_id: String,
|
||||
pub world_id: String,
|
||||
pub user_id: String,
|
||||
pub element_type: i64,
|
||||
pub name: String,
|
||||
pub original_name: String,
|
||||
pub description: Option<String>,
|
||||
pub last_update: i64,
|
||||
}
|
||||
|
||||
pub struct SyncedSeriesWorldResult {
|
||||
pub world_id: String,
|
||||
pub series_id: String,
|
||||
pub name: String,
|
||||
pub last_update: i64,
|
||||
}
|
||||
|
||||
pub struct SyncedSeriesWorldElementResult {
|
||||
pub element_id: String,
|
||||
pub world_id: String,
|
||||
pub name: String,
|
||||
pub last_update: i64,
|
||||
}
|
||||
|
||||
/// Checks if a world with the given hashed name already exists for a user and series.
|
||||
pub fn check_world_exist(conn: &Connection, user_id: &str, series_id: &str, world_name: &str, lang: Lang) -> AppResult<bool> {
|
||||
let mut statement = conn
|
||||
.prepare("SELECT world_id FROM series_worlds WHERE user_id=?1 AND series_id=?2 AND hashed_name=?3")
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence du monde.".to_string() } else { "Unable to verify world existence.".to_string() }))?;
|
||||
|
||||
let exists = statement
|
||||
.exists(params![user_id, series_id, world_name])
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence du monde.".to_string() } else { "Unable to verify world existence.".to_string() }))?;
|
||||
|
||||
Ok(exists)
|
||||
}
|
||||
|
||||
/// Inserts a new world into the series.
|
||||
pub fn insert_new_world(conn: &Connection, world_id: &str, user_id: &str, series_id: &str, encrypted_name: &str, hashed_name: &str, last_update: i64, lang: Lang) -> AppResult<String> {
|
||||
let insert_result = conn
|
||||
.execute(
|
||||
"INSERT INTO series_worlds (world_id, user_id, series_id, name, hashed_name, last_update) VALUES (?1,?2,?3,?4,?5,?6)",
|
||||
params![world_id, user_id, series_id, encrypted_name, hashed_name, last_update],
|
||||
)
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'ajouter le monde.".to_string() } else { "Unable to add world.".to_string() }))?;
|
||||
|
||||
if insert_result > 0 {
|
||||
Ok(world_id.to_string())
|
||||
} else {
|
||||
Err(AppError::Internal(if lang == Lang::Fr { "Erreur lors de l'ajout du monde.".to_string() } else { "Error adding world.".to_string() }))
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetches all worlds and their elements for a given series.
|
||||
pub fn fetch_worlds(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesWorldResult>> {
|
||||
let mut statement = conn
|
||||
.prepare("SELECT world.world_id AS world_id, world.name AS world_name, world.history, world.politics, world.economy, world.religion, world.languages, element.element_id AS element_id, element.name AS element_name, element.description AS element_description, element.element_type FROM series_worlds AS world LEFT JOIN series_world_elements AS element ON world.world_id = element.world_id WHERE world.user_id = ?1 AND world.series_id = ?2")
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les mondes.".to_string() } else { "Unable to retrieve worlds.".to_string() }))?;
|
||||
|
||||
let worlds = statement
|
||||
.query_map(params![user_id, series_id], |query_row| {
|
||||
Ok(SeriesWorldResult {
|
||||
world_id: query_row.get(0)?, world_name: query_row.get(1)?,
|
||||
history: query_row.get(2)?, politics: query_row.get(3)?,
|
||||
economy: query_row.get(4)?, religion: query_row.get(5)?,
|
||||
languages: query_row.get(6)?, element_id: query_row.get(7)?,
|
||||
element_name: query_row.get(8)?, element_description: query_row.get(9)?,
|
||||
element_type: query_row.get(10)?,
|
||||
})
|
||||
})
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les mondes.".to_string() } else { "Unable to retrieve worlds.".to_string() }))?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les mondes.".to_string() } else { "Unable to retrieve worlds.".to_string() }))?;
|
||||
|
||||
Ok(worlds)
|
||||
}
|
||||
|
||||
/// Updates a world's information.
|
||||
pub fn update_world(
|
||||
conn: &Connection, user_id: &str, world_id: &str, encrypted_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![encrypted_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.".to_string() } else { "Unable to update world.".to_string() }))?;
|
||||
|
||||
Ok(update_result > 0)
|
||||
}
|
||||
|
||||
/// Inserts a new element for a world.
|
||||
pub fn insert_element(
|
||||
conn: &Connection, element_id: &str, world_id: &str, user_id: &str, element_type: i64,
|
||||
encrypted_name: &str, original_name: &str, description: Option<&str>, last_update: i64, lang: Lang,
|
||||
) -> AppResult<String> {
|
||||
let insert_result = conn
|
||||
.execute(
|
||||
"INSERT INTO series_world_elements (element_id, world_id, user_id, element_type, name, original_name, description, last_update) VALUES (?1,?2,?3,?4,?5,?6,?7,?8)",
|
||||
params![element_id, world_id, user_id, element_type, encrypted_name, original_name, description, last_update],
|
||||
)
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'ajouter l'élément.".to_string() } else { "Unable to add element.".to_string() }))?;
|
||||
|
||||
if insert_result > 0 {
|
||||
Ok(element_id.to_string())
|
||||
} else {
|
||||
Err(AppError::Internal(if lang == Lang::Fr { "Erreur lors de l'ajout de l'élément.".to_string() } else { "Error adding element.".to_string() }))
|
||||
}
|
||||
}
|
||||
|
||||
/// Deletes an element from a world.
|
||||
pub fn delete_element(conn: &Connection, user_id: &str, element_id: &str, lang: Lang) -> AppResult<bool> {
|
||||
let delete_result = conn
|
||||
.execute(
|
||||
"DELETE FROM series_world_elements WHERE element_id=?1 AND user_id=?2",
|
||||
params![element_id, user_id],
|
||||
)
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de supprimer l'élément.".to_string() } else { "Unable to delete element.".to_string() }))?;
|
||||
|
||||
Ok(delete_result > 0)
|
||||
}
|
||||
|
||||
/// Fetches all worlds for a series for sync.
|
||||
pub fn fetch_series_worlds_table(conn: &Connection, user_id: &str, 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 AND user_id = ?2")
|
||||
.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, user_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 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
|
||||
.prepare("SELECT world_id, series_id, name, last_update FROM series_worlds WHERE user_id = ?1")
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les mondes de série pour sync.".to_string() } else { "Unable to retrieve series worlds for sync.".to_string() }))?;
|
||||
|
||||
let worlds = statement
|
||||
.query_map(params![user_id], |query_row| {
|
||||
Ok(SyncedSeriesWorldResult { world_id: query_row.get(0)?, series_id: query_row.get(1)?, name: query_row.get(2)?, last_update: query_row.get(3)? })
|
||||
})
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les mondes de série pour sync.".to_string() } else { "Unable to retrieve series worlds for sync.".to_string() }))?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les mondes de série pour sync.".to_string() } else { "Unable to retrieve series worlds for sync.".to_string() }))?;
|
||||
|
||||
Ok(worlds)
|
||||
}
|
||||
|
||||
/// Fetches all series world elements for a user for sync comparison.
|
||||
pub fn fetch_synced_series_world_elements(conn: &Connection, user_id: &str, lang: Lang) -> AppResult<Vec<SyncedSeriesWorldElementResult>> {
|
||||
let mut statement = conn
|
||||
.prepare("SELECT element_id, world_id, name, last_update FROM series_world_elements WHERE user_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![user_id], |query_row| {
|
||||
Ok(SyncedSeriesWorldElementResult { element_id: query_row.get(0)?, world_id: query_row.get(1)?, name: query_row.get(2)?, last_update: query_row.get(3)? })
|
||||
})
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les é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 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
|
||||
.prepare("SELECT 1 FROM series_worlds WHERE world_id=?1 AND user_id=?2")
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence du monde.".to_string() } else { "Unable to check world existence.".to_string() }))?;
|
||||
|
||||
let exists = statement
|
||||
.exists(params![world_id, user_id])
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence du monde.".to_string() } else { "Unable to check world existence.".to_string() }))?;
|
||||
|
||||
Ok(exists)
|
||||
}
|
||||
|
||||
/// Checks if a world element exists.
|
||||
pub fn is_world_element_exist(conn: &Connection, user_id: &str, element_id: &str, lang: Lang) -> AppResult<bool> {
|
||||
let mut statement = conn
|
||||
.prepare("SELECT 1 FROM series_world_elements WHERE element_id=?1 AND user_id=?2")
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence de l'élément.".to_string() } else { "Unable to check element existence.".to_string() }))?;
|
||||
|
||||
let exists = statement
|
||||
.exists(params![element_id, user_id])
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence de l'élément.".to_string() } else { "Unable to check element existence.".to_string() }))?;
|
||||
|
||||
Ok(exists)
|
||||
}
|
||||
|
||||
/// Inserts a series world for sync.
|
||||
pub fn insert_sync_world(
|
||||
conn: &Connection, world_id: &str, series_id: &str, user_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 insert_result = conn
|
||||
.execute(
|
||||
"INSERT INTO series_worlds (world_id, series_id, user_id, name, hashed_name, history, politics, economy, religion, languages, last_update) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11) ON CONFLICT(world_id) DO UPDATE SET name = excluded.name, hashed_name = excluded.hashed_name, history = excluded.history, politics = excluded.politics, economy = excluded.economy, religion = excluded.religion, languages = excluded.languages, last_update = excluded.last_update",
|
||||
params![world_id, series_id, user_id, name, hashed_name, history, politics, economy, religion, languages, last_update],
|
||||
)
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'insérer le monde pour sync.".to_string() } else { "Unable to insert world for sync.".to_string() }))?;
|
||||
|
||||
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,
|
||||
name: &str, original_name: &str, description: Option<&str>, last_update: i64, lang: Lang,
|
||||
) -> AppResult<bool> {
|
||||
let insert_result = conn
|
||||
.execute(
|
||||
"INSERT INTO series_world_elements (element_id, world_id, user_id, element_type, name, original_name, description, last_update) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8) ON CONFLICT(element_id) DO UPDATE SET element_type = excluded.element_type, name = excluded.name, original_name = excluded.original_name, description = excluded.description, last_update = excluded.last_update",
|
||||
params![element_id, world_id, user_id, element_type, name, original_name, description, last_update],
|
||||
)
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'insérer l'élément de monde pour sync.".to_string() } else { "Unable to insert world element for sync.".to_string() }))?;
|
||||
|
||||
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
|
||||
.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 AND sw.user_id = ?2")
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les éléments de monde par série.".to_string() } else { "Unable to retrieve world elements by series.".to_string() }))?;
|
||||
|
||||
let elements = statement
|
||||
.query_map(params![series_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 par série.".to_string() } else { "Unable to retrieve world elements by series.".to_string() }))?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les éléments de monde par série.".to_string() } else { "Unable to retrieve world elements by series.".to_string() }))?;
|
||||
|
||||
Ok(elements)
|
||||
}
|
||||
|
||||
/// Checks if a series world exists (alias for is_world_exist).
|
||||
pub fn series_world_exists(conn: &Connection, user_id: &str, world_id: &str, lang: Lang) -> AppResult<bool> {
|
||||
is_world_exist(conn, user_id, world_id, lang)
|
||||
}
|
||||
|
||||
/// Checks if a series world element exists (alias for is_world_element_exist).
|
||||
pub fn series_world_element_exists(conn: &Connection, user_id: &str, element_id: &str, lang: Lang) -> AppResult<bool> {
|
||||
is_world_element_exist(conn, user_id, element_id, lang)
|
||||
}
|
||||
|
||||
/// Inserts a series world for sync (alias with compatible signature).
|
||||
pub fn insert_sync_series_world(
|
||||
conn: &Connection, world_id: &str, series_id: &str, user_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> {
|
||||
insert_sync_world(conn, world_id, series_id, user_id, name, hashed_name, history, politics, economy, religion, languages, last_update, lang)
|
||||
}
|
||||
|
||||
/// Updates a series world for sync (without hashed_name).
|
||||
pub fn update_sync_series_world(
|
||||
conn: &Connection, world_id: &str, user_id: &str, 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, history = ?2, politics = ?3, economy = ?4, religion = ?5, languages = ?6, last_update = ?7 WHERE world_id = ?8 AND user_id = ?9",
|
||||
params![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 série pour sync.".to_string() } else { "Unable to update series world for sync.".to_string() }))?;
|
||||
|
||||
Ok(update_result > 0)
|
||||
}
|
||||
|
||||
/// Inserts a series world element for sync (alias with compatible signature).
|
||||
pub fn insert_sync_series_world_element(
|
||||
conn: &Connection, element_id: &str, world_id: &str, user_id: &str, element_type: i64,
|
||||
name: &str, original_name: &str, description: Option<&str>, last_update: i64, lang: Lang,
|
||||
) -> AppResult<bool> {
|
||||
insert_sync_world_element(conn, element_id, world_id, user_id, element_type, name, original_name, description, last_update, lang)
|
||||
}
|
||||
|
||||
/// Updates a series world element for sync (without element_type and original_name).
|
||||
pub fn update_sync_series_world_element(conn: &Connection, element_id: &str, user_id: &str, name: &str, description: Option<&str>, last_update: i64, lang: Lang) -> AppResult<bool> {
|
||||
let update_result = conn
|
||||
.execute(
|
||||
"UPDATE series_world_elements SET name = ?1, description = ?2, last_update = ?3 WHERE element_id = ?4 AND user_id = ?5",
|
||||
params![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 série pour sync.".to_string() } else { "Unable to update series world element for sync.".to_string() }))?;
|
||||
|
||||
Ok(update_result > 0)
|
||||
}
|
||||
use rusqlite::{params, Connection};
|
||||
|
||||
use crate::error::{AppError, AppResult};
|
||||
use crate::shared::types::Lang;
|
||||
|
||||
pub struct SeriesWorldResult {
|
||||
pub world_id: String,
|
||||
pub world_name: String,
|
||||
pub history: Option<String>,
|
||||
pub politics: Option<String>,
|
||||
pub economy: Option<String>,
|
||||
pub religion: Option<String>,
|
||||
pub languages: Option<String>,
|
||||
pub element_id: Option<String>,
|
||||
pub element_name: Option<String>,
|
||||
pub element_description: Option<String>,
|
||||
pub element_type: Option<i64>,
|
||||
}
|
||||
|
||||
pub struct SeriesWorldsTableResult {
|
||||
pub world_id: String,
|
||||
pub series_id: String,
|
||||
pub user_id: String,
|
||||
pub name: String,
|
||||
pub hashed_name: String,
|
||||
pub history: Option<String>,
|
||||
pub politics: Option<String>,
|
||||
pub economy: Option<String>,
|
||||
pub religion: Option<String>,
|
||||
pub languages: Option<String>,
|
||||
pub last_update: i64,
|
||||
}
|
||||
|
||||
pub struct SeriesWorldElementsTableResult {
|
||||
pub element_id: String,
|
||||
pub world_id: String,
|
||||
pub user_id: String,
|
||||
pub element_type: i64,
|
||||
pub name: String,
|
||||
pub original_name: String,
|
||||
pub description: Option<String>,
|
||||
pub last_update: i64,
|
||||
}
|
||||
|
||||
pub struct SyncedSeriesWorldResult {
|
||||
pub world_id: String,
|
||||
pub series_id: String,
|
||||
pub name: String,
|
||||
pub last_update: i64,
|
||||
}
|
||||
|
||||
pub struct SyncedSeriesWorldElementResult {
|
||||
pub element_id: String,
|
||||
pub world_id: String,
|
||||
pub name: String,
|
||||
pub last_update: i64,
|
||||
}
|
||||
|
||||
/// Checks if a world with the given hashed name already exists for a user and series.
|
||||
pub fn check_world_exist(conn: &Connection, user_id: &str, series_id: &str, world_name: &str, lang: Lang) -> AppResult<bool> {
|
||||
let mut statement = conn
|
||||
.prepare("SELECT world_id FROM series_worlds WHERE user_id=?1 AND series_id=?2 AND hashed_name=?3")
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence du monde.".to_string() } else { "Unable to verify world existence.".to_string() }))?;
|
||||
|
||||
let exists = statement
|
||||
.exists(params![user_id, series_id, world_name])
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence du monde.".to_string() } else { "Unable to verify world existence.".to_string() }))?;
|
||||
|
||||
Ok(exists)
|
||||
}
|
||||
|
||||
/// Inserts a new world into the series.
|
||||
pub fn insert_new_world(conn: &Connection, world_id: &str, user_id: &str, series_id: &str, encrypted_name: &str, hashed_name: &str, last_update: i64, lang: Lang) -> AppResult<String> {
|
||||
let insert_result = conn
|
||||
.execute(
|
||||
"INSERT INTO series_worlds (world_id, user_id, series_id, name, hashed_name, last_update) VALUES (?1,?2,?3,?4,?5,?6)",
|
||||
params![world_id, user_id, series_id, encrypted_name, hashed_name, last_update],
|
||||
)
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'ajouter le monde.".to_string() } else { "Unable to add world.".to_string() }))?;
|
||||
|
||||
if insert_result > 0 {
|
||||
Ok(world_id.to_string())
|
||||
} else {
|
||||
Err(AppError::Internal(if lang == Lang::Fr { "Erreur lors de l'ajout du monde.".to_string() } else { "Error adding world.".to_string() }))
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetches all worlds and their elements for a given series.
|
||||
pub fn fetch_worlds(conn: &Connection, user_id: &str, series_id: &str, lang: Lang) -> AppResult<Vec<SeriesWorldResult>> {
|
||||
let mut statement = conn
|
||||
.prepare("SELECT world.world_id AS world_id, world.name AS world_name, world.history, world.politics, world.economy, world.religion, world.languages, element.element_id AS element_id, element.name AS element_name, element.description AS element_description, element.element_type FROM series_worlds AS world LEFT JOIN series_world_elements AS element ON world.world_id = element.world_id WHERE world.user_id = ?1 AND world.series_id = ?2")
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les mondes.".to_string() } else { "Unable to retrieve worlds.".to_string() }))?;
|
||||
|
||||
let worlds = statement
|
||||
.query_map(params![user_id, series_id], |query_row| {
|
||||
Ok(SeriesWorldResult {
|
||||
world_id: query_row.get(0)?, world_name: query_row.get(1)?,
|
||||
history: query_row.get(2)?, politics: query_row.get(3)?,
|
||||
economy: query_row.get(4)?, religion: query_row.get(5)?,
|
||||
languages: query_row.get(6)?, element_id: query_row.get(7)?,
|
||||
element_name: query_row.get(8)?, element_description: query_row.get(9)?,
|
||||
element_type: query_row.get(10)?,
|
||||
})
|
||||
})
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les mondes.".to_string() } else { "Unable to retrieve worlds.".to_string() }))?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les mondes.".to_string() } else { "Unable to retrieve worlds.".to_string() }))?;
|
||||
|
||||
Ok(worlds)
|
||||
}
|
||||
|
||||
/// Updates a world's information.
|
||||
pub fn update_world(
|
||||
conn: &Connection, user_id: &str, world_id: &str, encrypted_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![encrypted_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.".to_string() } else { "Unable to update world.".to_string() }))?;
|
||||
|
||||
Ok(update_result > 0)
|
||||
}
|
||||
|
||||
/// Inserts a new element for a world.
|
||||
pub fn insert_element(
|
||||
conn: &Connection, element_id: &str, world_id: &str, user_id: &str, element_type: i64,
|
||||
encrypted_name: &str, original_name: &str, description: Option<&str>, last_update: i64, lang: Lang,
|
||||
) -> AppResult<String> {
|
||||
let insert_result = conn
|
||||
.execute(
|
||||
"INSERT INTO series_world_elements (element_id, world_id, user_id, element_type, name, original_name, description, last_update) VALUES (?1,?2,?3,?4,?5,?6,?7,?8)",
|
||||
params![element_id, world_id, user_id, element_type, encrypted_name, original_name, description, last_update],
|
||||
)
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'ajouter l'élément.".to_string() } else { "Unable to add element.".to_string() }))?;
|
||||
|
||||
if insert_result > 0 {
|
||||
Ok(element_id.to_string())
|
||||
} else {
|
||||
Err(AppError::Internal(if lang == Lang::Fr { "Erreur lors de l'ajout de l'élément.".to_string() } else { "Error adding element.".to_string() }))
|
||||
}
|
||||
}
|
||||
|
||||
/// Deletes an element from a world.
|
||||
pub fn delete_element(conn: &Connection, user_id: &str, element_id: &str, lang: Lang) -> AppResult<bool> {
|
||||
let delete_result = conn
|
||||
.execute(
|
||||
"DELETE FROM series_world_elements WHERE element_id=?1 AND user_id=?2",
|
||||
params![element_id, user_id],
|
||||
)
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de supprimer l'élément.".to_string() } else { "Unable to delete element.".to_string() }))?;
|
||||
|
||||
Ok(delete_result > 0)
|
||||
}
|
||||
|
||||
/// Fetches all worlds for a series for sync.
|
||||
pub fn fetch_series_worlds_table(conn: &Connection, user_id: &str, 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 AND user_id = ?2")
|
||||
.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, user_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 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
|
||||
.prepare("SELECT world_id, series_id, name, last_update FROM series_worlds WHERE user_id = ?1")
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les mondes de série pour sync.".to_string() } else { "Unable to retrieve series worlds for sync.".to_string() }))?;
|
||||
|
||||
let worlds = statement
|
||||
.query_map(params![user_id], |query_row| {
|
||||
Ok(SyncedSeriesWorldResult { world_id: query_row.get(0)?, series_id: query_row.get(1)?, name: query_row.get(2)?, last_update: query_row.get(3)? })
|
||||
})
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les mondes de série pour sync.".to_string() } else { "Unable to retrieve series worlds for sync.".to_string() }))?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les mondes de série pour sync.".to_string() } else { "Unable to retrieve series worlds for sync.".to_string() }))?;
|
||||
|
||||
Ok(worlds)
|
||||
}
|
||||
|
||||
/// Fetches all series world elements for a user for sync comparison.
|
||||
pub fn fetch_synced_series_world_elements(conn: &Connection, user_id: &str, lang: Lang) -> AppResult<Vec<SyncedSeriesWorldElementResult>> {
|
||||
let mut statement = conn
|
||||
.prepare("SELECT element_id, world_id, name, last_update FROM series_world_elements WHERE user_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![user_id], |query_row| {
|
||||
Ok(SyncedSeriesWorldElementResult { element_id: query_row.get(0)?, world_id: query_row.get(1)?, name: query_row.get(2)?, last_update: query_row.get(3)? })
|
||||
})
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les é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)
|
||||
}
|
||||
|
||||
/// 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
|
||||
.prepare("SELECT 1 FROM series_worlds WHERE world_id=?1 AND user_id=?2")
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence du monde.".to_string() } else { "Unable to check world existence.".to_string() }))?;
|
||||
|
||||
let exists = statement
|
||||
.exists(params![world_id, user_id])
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence du monde.".to_string() } else { "Unable to check world existence.".to_string() }))?;
|
||||
|
||||
Ok(exists)
|
||||
}
|
||||
|
||||
/// Checks if a world element exists.
|
||||
pub fn is_world_element_exist(conn: &Connection, user_id: &str, element_id: &str, lang: Lang) -> AppResult<bool> {
|
||||
let mut statement = conn
|
||||
.prepare("SELECT 1 FROM series_world_elements WHERE element_id=?1 AND user_id=?2")
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence de l'élément.".to_string() } else { "Unable to check element existence.".to_string() }))?;
|
||||
|
||||
let exists = statement
|
||||
.exists(params![element_id, user_id])
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de vérifier l'existence de l'élément.".to_string() } else { "Unable to check element existence.".to_string() }))?;
|
||||
|
||||
Ok(exists)
|
||||
}
|
||||
|
||||
/// Inserts a series world for sync.
|
||||
pub fn insert_sync_world(
|
||||
conn: &Connection, world_id: &str, series_id: &str, user_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 insert_result = conn
|
||||
.execute(
|
||||
"INSERT INTO series_worlds (world_id, series_id, user_id, name, hashed_name, history, politics, economy, religion, languages, last_update) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11) ON CONFLICT(world_id) DO UPDATE SET name = excluded.name, hashed_name = excluded.hashed_name, history = excluded.history, politics = excluded.politics, economy = excluded.economy, religion = excluded.religion, languages = excluded.languages, last_update = excluded.last_update",
|
||||
params![world_id, series_id, user_id, name, hashed_name, history, politics, economy, religion, languages, last_update],
|
||||
)
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'insérer le monde pour sync.".to_string() } else { "Unable to insert world for sync.".to_string() }))?;
|
||||
|
||||
Ok(insert_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,
|
||||
name: &str, original_name: &str, description: Option<&str>, last_update: i64, lang: Lang,
|
||||
) -> AppResult<bool> {
|
||||
let insert_result = conn
|
||||
.execute(
|
||||
"INSERT INTO series_world_elements (element_id, world_id, user_id, element_type, name, original_name, description, last_update) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8) ON CONFLICT(element_id) DO UPDATE SET element_type = excluded.element_type, name = excluded.name, original_name = excluded.original_name, description = excluded.description, last_update = excluded.last_update",
|
||||
params![element_id, world_id, user_id, element_type, name, original_name, description, last_update],
|
||||
)
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible d'insérer l'élément de monde pour sync.".to_string() } else { "Unable to insert world element for sync.".to_string() }))?;
|
||||
|
||||
Ok(insert_result > 0)
|
||||
}
|
||||
|
||||
/// 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
|
||||
.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 AND sw.user_id = ?2")
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les éléments de monde par série.".to_string() } else { "Unable to retrieve world elements by series.".to_string() }))?;
|
||||
|
||||
let elements = statement
|
||||
.query_map(params![series_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 par série.".to_string() } else { "Unable to retrieve world elements by series.".to_string() }))?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|_| AppError::Internal(if lang == Lang::Fr { "Impossible de récupérer les éléments de monde par série.".to_string() } else { "Unable to retrieve world elements by series.".to_string() }))?;
|
||||
|
||||
Ok(elements)
|
||||
}
|
||||
|
||||
/// Checks if a series world exists (alias for is_world_exist).
|
||||
pub fn series_world_exists(conn: &Connection, user_id: &str, world_id: &str, lang: Lang) -> AppResult<bool> {
|
||||
is_world_exist(conn, user_id, world_id, lang)
|
||||
}
|
||||
|
||||
/// Checks if a series world element exists (alias for is_world_element_exist).
|
||||
pub fn series_world_element_exists(conn: &Connection, user_id: &str, element_id: &str, lang: Lang) -> AppResult<bool> {
|
||||
is_world_element_exist(conn, user_id, element_id, lang)
|
||||
}
|
||||
|
||||
/// Inserts a series world for sync (alias with compatible signature).
|
||||
pub fn insert_sync_series_world(
|
||||
conn: &Connection, world_id: &str, series_id: &str, user_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> {
|
||||
insert_sync_world(conn, world_id, series_id, user_id, name, hashed_name, history, politics, economy, religion, languages, last_update, lang)
|
||||
}
|
||||
|
||||
/// Updates a series world for sync (without hashed_name).
|
||||
pub fn update_sync_series_world(
|
||||
conn: &Connection, world_id: &str, user_id: &str, 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, history = ?2, politics = ?3, economy = ?4, religion = ?5, languages = ?6, last_update = ?7 WHERE world_id = ?8 AND user_id = ?9",
|
||||
params![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 série pour sync.".to_string() } else { "Unable to update series world for sync.".to_string() }))?;
|
||||
|
||||
Ok(update_result > 0)
|
||||
}
|
||||
|
||||
/// Inserts a series world element for sync (alias with compatible signature).
|
||||
pub fn insert_sync_series_world_element(
|
||||
conn: &Connection, element_id: &str, world_id: &str, user_id: &str, element_type: i64,
|
||||
name: &str, original_name: &str, description: Option<&str>, last_update: i64, lang: Lang,
|
||||
) -> AppResult<bool> {
|
||||
insert_sync_world_element(conn, element_id, world_id, user_id, element_type, name, original_name, description, last_update, lang)
|
||||
}
|
||||
|
||||
/// Updates a series world element for sync (without element_type and original_name).
|
||||
pub fn update_sync_series_world_element(conn: &Connection, element_id: &str, user_id: &str, name: &str, description: Option<&str>, last_update: i64, lang: Lang) -> AppResult<bool> {
|
||||
let update_result = conn
|
||||
.execute(
|
||||
"UPDATE series_world_elements SET name = ?1, description = ?2, last_update = ?3 WHERE element_id = ?4 AND user_id = ?5",
|
||||
params![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 série pour sync.".to_string() } else { "Unable to update series world element for sync.".to_string() }))?;
|
||||
|
||||
Ok(update_result > 0)
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)?,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)?,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
pub mod commands;
|
||||
pub mod repo;
|
||||
pub mod service;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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() }),
|
||||
|
||||
@@ -1,156 +1,61 @@
|
||||
use rusqlite::Connection;
|
||||
|
||||
use crate::crypto::encryption::{encrypt_data_with_user_key, decrypt_data_with_user_key, hash_element};
|
||||
use crate::crypto::key_manager::get_user_encryption_key;
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
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)
|
||||
}
|
||||
|
||||
/// 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,
|
||||
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);
|
||||
let hashed_username: String = hash_element(username);
|
||||
let mut encrypted_author_name: String = String::new();
|
||||
let mut hashed_author_name: String = String::new();
|
||||
if let Some(author_name_val) = author_name {
|
||||
encrypted_author_name = encrypt_data_with_user_key(author_name_val, user_key)?;
|
||||
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,
|
||||
})
|
||||
}
|
||||
use rusqlite::Connection;
|
||||
|
||||
use crate::crypto::encryption::{encrypt_data_with_user_key, decrypt_data_with_user_key, hash_element};
|
||||
use crate::crypto::key_manager::get_user_encryption_key;
|
||||
use crate::domains::user::repo;
|
||||
use crate::error::AppResult;
|
||||
use crate::shared::types::Lang;
|
||||
|
||||
pub struct UserInfoResponse {
|
||||
pub id: String,
|
||||
pub username: String,
|
||||
pub email: String,
|
||||
pub author_name: String,
|
||||
pub group_id: i64,
|
||||
}
|
||||
|
||||
/// Retrieves complete user information including associated books.
|
||||
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 username: String = decrypt_data_with_user_key(&user_infos_data.username, &user_encryption_key)?;
|
||||
let email: String = decrypt_data_with_user_key(&user_infos_data.email, &user_encryption_key)?;
|
||||
let author_name: String = if let Some(ref author_name_val) = user_infos_data.author_name { decrypt_data_with_user_key(author_name_val, &user_encryption_key)? } else { String::new() };
|
||||
|
||||
Ok(UserInfoResponse {
|
||||
id: user_id.to_string(),
|
||||
username, email, author_name,
|
||||
group_id: user_infos_data.user_group,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new user in the database with encrypted personal information.
|
||||
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_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_username, &hashed_username, &encrypted_email, &hashed_email, lang)
|
||||
}
|
||||
|
||||
/// Updates an existing user's profile information in the database.
|
||||
pub fn update_user_infos(
|
||||
conn: &Connection, user_key: &str, user_id: &str,
|
||||
username: &str, email: &str, author_name: Option<&str>, lang: Lang,
|
||||
) -> AppResult<bool> {
|
||||
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);
|
||||
let hashed_username: String = hash_element(username);
|
||||
let mut encrypted_author_name: String = String::new();
|
||||
let mut hashed_author_name: String = String::new();
|
||||
if let Some(author_name_val) = author_name {
|
||||
encrypted_author_name = encrypt_data_with_user_key(author_name_val, user_key)?;
|
||||
hashed_author_name = hash_element(author_name_val);
|
||||
}
|
||||
|
||||
repo::update_user_infos(conn, user_id, &encrypted_username, &hashed_username, &encrypted_email, &hashed_email, &hashed_author_name, &encrypted_author_name, lang)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()`.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
pub mod ai_models;
|
||||
pub mod session;
|
||||
pub mod types;
|
||||
|
||||
Reference in New Issue
Block a user