diff --git a/Cargo.lock b/Cargo.lock index 048418d..6bb3275 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -446,9 +446,6 @@ name = "cjdic2" version = "0.1.0" dependencies = [ "cjdic2-core", - "rusqlite", - "serde", - "serde_json", "tauri", "tauri-build", "tauri-plugin-opener", @@ -461,6 +458,7 @@ dependencies = [ "rusqlite", "serde", "serde_json", + "thiserror 2.0.18", ] [[package]] diff --git a/src-core/Cargo.toml b/src-core/Cargo.toml index 6528480..6a7d8ad 100644 --- a/src-core/Cargo.toml +++ b/src-core/Cargo.toml @@ -10,3 +10,4 @@ crate-type = ["cdylib", "rlib"] serde = { version = "1", features = ["derive"] } serde_json = "1" rusqlite = { version = "0.38.0", features = ["bundled", "unlock_notify"] } +thiserror = "2.0.18" diff --git a/src-core/src/api.rs b/src-core/src/api.rs new file mode 100644 index 0000000..7aeaafb --- /dev/null +++ b/src-core/src/api.rs @@ -0,0 +1,6 @@ +use crate::{error::CJDicError, service::AppService}; + +pub fn list_entries_json(service: &AppService) -> Result { + let entries = service.list_entries()?; + Ok(serde_json::to_string(&entries)?) +} diff --git a/src-core/src/db.rs b/src-core/src/db.rs new file mode 100644 index 0000000..b448a53 --- /dev/null +++ b/src-core/src/db.rs @@ -0,0 +1,63 @@ +use std::{path::Path, sync::Mutex}; + +use rusqlite::{Connection, Result}; + +use crate::models::Entry; + +pub struct Database { + conn: Mutex, +} + +impl Database { + pub fn new>(path: P) -> Result { + let conn = Connection::open(path)?; + Ok(Self { + conn: Mutex::new(conn), + }) + } + + pub fn init(&self) -> Result<()> { + let conn = self.conn.lock().unwrap(); + + conn.execute_batch( + " + CREATE TABLE IF NOT EXISTS entries ( + id INTEGER PRIMARY KEY, + word TEXT NOT NULL, + definition TEXT NOT NULL + )", + )?; + Ok(()) + } + + pub fn insert_entry(&self, word: &str, definition: &str) -> Result<()> { + let conn = self.conn.lock().unwrap(); + + conn.execute( + "INSERT INTO entries (word, definition) VALUES (?, ?)", + [word, definition], + )?; + Ok(()) + } + + pub fn fetch_all_entries(&self) -> Result> { + let conn = self.conn.lock().unwrap(); + + let mut stmt = conn.prepare("SELECT id, word, definition FROM entries")?; + + let rows = stmt.query_map([], |row| { + Ok(Entry { + id: row.get(0)?, + word: row.get(1)?, + definition: row.get(2)?, + }) + })?; + + let mut entries = Vec::new(); + for entry in rows { + entries.push(entry?); + } + + Ok(entries) + } +} diff --git a/src-core/src/error.rs b/src-core/src/error.rs new file mode 100644 index 0000000..c0c99c8 --- /dev/null +++ b/src-core/src/error.rs @@ -0,0 +1,13 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum CJDicError { + #[error("Database error: {0}")] + Database(#[from] rusqlite::Error), + + #[error("Serialization error: {0}")] + Serialization(#[from] serde_json::Error), + + #[error("Not found")] + NotFound, +} diff --git a/src-core/src/lib.rs b/src-core/src/lib.rs index b645025..3969809 100644 --- a/src-core/src/lib.rs +++ b/src-core/src/lib.rs @@ -1,4 +1,12 @@ +pub mod api; +pub mod db; +pub mod error; pub mod models; +pub mod service; + +pub use error::CJDicError; +pub use models::*; +pub use service::AppService; pub fn add(left: u64, right: u64) -> u64 { left + right diff --git a/src-core/src/service.rs b/src-core/src/service.rs new file mode 100644 index 0000000..7cb579a --- /dev/null +++ b/src-core/src/service.rs @@ -0,0 +1,24 @@ +use std::path::Path; + +use crate::{db::Database, error::CJDicError, models::Entry}; + +pub struct AppService { + db: Database, +} + +impl AppService { + pub fn new>(db_path: P) -> Result { + let db = Database::new(db_path)?; + db.init()?; + Ok(Self { db }) + } + + pub fn add_entry(&self, word: &str, definition: &str) -> Result<(), CJDicError> { + self.db.insert_entry(word, definition)?; + Ok(()) + } + + pub fn list_entries(&self) -> Result, CJDicError> { + Ok(self.db.fetch_all_entries()?) + } +} diff --git a/src-react/.vscode/settings.json b/src-react/.vscode/settings.json index 8101f9a..d151937 100644 --- a/src-react/.vscode/settings.json +++ b/src-react/.vscode/settings.json @@ -15,5 +15,8 @@ }, "[typescriptreact]": { "editor.formatOnSave": true + }, + "[javascriptreact]": { + "editor.formatOnSave": true } } diff --git a/src-react/src/App.tsx b/src-react/src/App.tsx index 8286a76..5e484aa 100644 --- a/src-react/src/App.tsx +++ b/src-react/src/App.tsx @@ -1,15 +1,24 @@ -import { useState } from "react"; -import reactLogo from "./assets/react.svg"; -import { invoke } from "@tauri-apps/api/core"; -import "./App.css"; +import './App.css'; + +import { useEffect, useState } from 'react'; + +import { invoke } from '@tauri-apps/api/core'; + +import reactLogo from './assets/react.svg'; function App() { const [greetMsg, setGreetMsg] = useState(""); const [name, setName] = useState(""); + useEffect(() => { + invoke("list_entries").then((r) => { + setGreetMsg(JSON.stringify(r)); + }); + }, [name]); + async function greet() { + invoke("add_entry", { name }); // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ - setGreetMsg(await invoke("greet", { name })); } return ( @@ -39,6 +48,7 @@ function App() { setName(e.currentTarget.value)} + autoComplete="off" placeholder="Enter a name..." /> diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index dd16f14..811765e 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -20,7 +20,4 @@ tauri-build = { version = "2", features = [] } [dependencies] tauri = { version = "2", features = [] } tauri-plugin-opener = "2" -serde = { version = "1", features = ["derive"] } -serde_json = "1" -rusqlite = { version = "0.38.0", features = ["bundled", "unlock_notify"] } cjdic2-core = { path = "../src-core" } diff --git a/src-tauri/src/command.rs b/src-tauri/src/command.rs new file mode 100644 index 0000000..1ab764d --- /dev/null +++ b/src-tauri/src/command.rs @@ -0,0 +1,14 @@ +// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ +use cjdic2_core::{AppService, Entry}; + +#[tauri::command] +pub fn add_entry(name: &str, state: tauri::State) -> Result<(), String> { + state + .add_entry(name, "definition") + .map_err(|e| e.to_string()) +} + +#[tauri::command] +pub fn list_entries(state: tauri::State) -> Result, String> { + state.list_entries().map_err(|e| e.to_string()) +} diff --git a/src-tauri/src/db.rs b/src-tauri/src/db.rs deleted file mode 100644 index 3ea20a5..0000000 --- a/src-tauri/src/db.rs +++ /dev/null @@ -1,29 +0,0 @@ -use serde::Serialize; - -use crate::state; - -#[derive(Serialize)] -pub struct SearchRow { - a: i32, - b: i32, -} - -#[tauri::command] -pub fn get_db(state: tauri::State) -> Result, String> { - let conn = state.conn.lock().map_err(|e| e.to_string())?; - let mut stmt = conn.prepare("SELECT a, b FROM test").map_err(|e| e.to_string())?; - - let rows = stmt.query_map([], |r| { - Ok(SearchRow { - a: r.get(0)?, - b: r.get(1)?, - }) - }).map_err(|e| e.to_string())?; - - let mut out = Vec::new(); - for r in rows { - out.push(r.map_err(|e| e.to_string())?); - } - - Ok(out) -} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 31fd1d2..0f34906 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,15 +1,8 @@ +use crate::command::*; +use cjdic2_core::*; use tauri::Manager; -use rusqlite::Connection; -use std::sync::Mutex; -mod db; -mod state; - -// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ -#[tauri::command] -fn greet(name: &str) -> String { - format!("Hello, {}! You've been greeted from Rust!", name) -} +mod command; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { @@ -17,31 +10,18 @@ pub fn run() { .plugin(tauri_plugin_opener::init()) .setup(|app| { let cfg_dir = app.path().app_config_dir(); - let app_dir = cfg_dir - .unwrap_or_else(|_| std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."))); - + let app_dir = cfg_dir.unwrap_or_else(|_| { + std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from(".")) + }); std::fs::create_dir_all(&app_dir)?; let db_path = app_dir.join("cjdic.db"); - let is_new = !db_path.exists(); + let service = AppService::new(&db_path)?; + app.manage(service); - println!("{}", cjdic2_core::add(1, 2)); - - let conn = Connection::open(&db_path)?; - - if is_new { - conn.execute_batch("CREATE TABLE test (a, b);")?; - let mut stmt = conn.prepare("INSERT INTO test VALUES (?1, ?2)")?; - stmt.insert([1, 2])?; - stmt.insert([3, 4])?; - } - - app.manage(state::AppState{ - conn: Mutex::new(conn), - }); Ok(()) }) - .invoke_handler(tauri::generate_handler![greet, db::get_db]) + .invoke_handler(tauri::generate_handler![add_entry, list_entries]) .build(tauri::generate_context!()) .expect("error while running tauri application") .run(|_app_handle, event| match event { diff --git a/src-tauri/src/state.rs b/src-tauri/src/state.rs deleted file mode 100644 index 540b91d..0000000 --- a/src-tauri/src/state.rs +++ /dev/null @@ -1,6 +0,0 @@ -use rusqlite::Connection; -use std::sync::Mutex; - -pub struct AppState { - pub conn: Mutex -} \ No newline at end of file