From 2a25c064bba47e28dfc491083546251beec6bcad Mon Sep 17 00:00:00 2001 From: D-lyw Date: Tue, 28 May 2024 16:42:30 +0800 Subject: [PATCH] refactor: sqlite db pool connect and error handle --- src-tauri/Cargo.lock | 10 ++-- src-tauri/Cargo.toml | 2 + src-tauri/db.sqlite | Bin 24576 -> 24576 bytes src-tauri/src/commands/todo.rs | 37 ++++++++----- src-tauri/src/error.rs | 36 ++++++++++++ src-tauri/src/main.rs | 16 ++---- src-tauri/src/service/mutation.rs | 26 ++++----- src-tauri/src/service/query.rs | 10 ++-- src-tauri/src/setup.rs | 46 ++++++++++++++++ src-tauri/src/utils.rs | 88 +++++++++++++++--------------- src/components/sidebar.tsx | 2 +- 11 files changed, 180 insertions(+), 93 deletions(-) create mode 100644 src-tauri/src/error.rs create mode 100644 src-tauri/src/setup.rs diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 9c99ec5..8c47f19 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -4787,10 +4787,12 @@ dependencies = [ "sea-orm", "serde", "serde_json", + "sqlx", "tauri", "tauri-build", "tauri-plugin-authenticator", "tauri-plugin-store", + "thiserror", "tokio", "tracing", "tracing-subscriber", @@ -4965,18 +4967,18 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" [[package]] name = "thiserror" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 36aa083..725403f 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -21,11 +21,13 @@ tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", sea-orm = { version = "0.12", features = [ "sqlx-sqlite", "runtime-tokio-rustls", "macros" ] } tokio = { version = "1", features = ["full"] } anyhow = "1.0.83" +thiserror = "1.0.61" dotenv = "0.15.0" chrono = '0.4.38' tauri-plugin-authenticator = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } tracing = "0.1" tracing-subscriber = "0.3.18" +sqlx = "0.7.4" [features] # This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!! diff --git a/src-tauri/db.sqlite b/src-tauri/db.sqlite index 06704f581107a82e2b13f7048f1c9a4b004cdc36..18f09cf9e1d1270874b5ec9434e143357b4f7563 100644 GIT binary patch delta 222 zcmZoTz}Rqrae_2s^+XwG#_Ej;^Zhx@dHIAGB)E@EW)INj;t}S4&3%OXEBDupjUC+e zs^+W=vK+P{sl_DbI9JVfrDaDCt zDT&3%>ddVS%&iQ~^o$M6qLfWp86-Jv6O)pG0ItX+#L&RX#L~*hOwYo?JW9!gl|h`- y)+IGDZL)KW93$uCq?jxqc{WA~NPdXf1|*lqssPC|u@it~Y+Mf`=jK;&VvGQ%S{WGXnJJmDGKh0b twu@0_F4>8+-tW>B{3=e25db3+E$ILN diff --git a/src-tauri/src/commands/todo.rs b/src-tauri/src/commands/todo.rs index 12b5db1..e775929 100644 --- a/src-tauri/src/commands/todo.rs +++ b/src-tauri/src/commands/todo.rs @@ -1,32 +1,39 @@ use entity::todos; -use sea_orm::DbErr; +use sea_orm::DbConn; +use tauri::State; -use crate::service::{Mutation, Query}; +use crate::{ + error::AppError, + service::{Mutation, Query}, +}; #[tauri::command] -pub async fn add_todo_item(title: String) -> Result { - let result = Mutation::create_todo_item(title).await; +pub async fn add_todo_item(db: State<'_, DbConn>, title: String) -> Result { + let result = Mutation::create_todo_item(db.inner(), title).await; Ok(result.is_ok()) } #[tauri::command] -pub async fn query_list_by_page(page: u64) -> Result, String> { - match Query::query_todo_list(page).await { +pub async fn query_list_by_page( + db: State<'_, DbConn>, + page: u64, +) -> Result, String> { + match Query::query_todo_list(db.inner(), page).await { Ok(result) => Ok(result), Err(err) => Err(err.to_string()), } } #[tauri::command] -pub async fn delete_item_by_id(id: i32) -> Result { - let result = Mutation::delete_item_by_id(id).await; - Ok(result.is_ok()) +pub async fn delete_item_by_id(db: State<'_, DbConn>, id: i32) -> Result { + Mutation::delete_item_by_id(&db, id).await } #[tauri::command] -pub async fn switch_item_status(id: i32, is_done: bool) -> Result { - match Mutation::switch_item_status(id, is_done).await { - Ok(result) => Ok(result), - Err(err) => Err(err.to_string()), - } -} \ No newline at end of file +pub async fn switch_item_status( + db: State<'_, DbConn>, + id: i32, + is_done: bool, +) -> Result { + Mutation::switch_item_status(db.inner(), id, is_done).await +} diff --git a/src-tauri/src/error.rs b/src-tauri/src/error.rs new file mode 100644 index 0000000..89aa574 --- /dev/null +++ b/src-tauri/src/error.rs @@ -0,0 +1,36 @@ +use sea_orm::DbErr; +use serde::Serialize; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum AppError { + #[error("The item with a specified id is not found")] + RowNotFound, + #[error("Call command parameter is not valid")] + ParamsError, + #[error(transparent)] + DatabaseError(DbErr), + #[error(transparent)] + Unexpected(anyhow::Error), +} + +impl From for AppError { + fn from(err: anyhow::Error) -> Self { + AppError::Unexpected(err) + } +} + +impl From for AppError { + fn from(err: DbErr) -> Self { + AppError::DatabaseError(err) + } +} + +impl Serialize for AppError { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index e641692..5e12802 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -4,21 +4,23 @@ mod commands; mod service; mod utils; +mod setup; +mod error; pub use commands::todo; use std::{env, sync::OnceLock}; -pub use utils::*; // use anyhow::Result; use dotenv::dotenv; use tauri::{ - CustomMenuItem, Menu, MenuEntry, MenuItem, Submenu, SystemTray, SystemTrayMenu, - SystemTrayMenuItem, + CustomMenuItem, Manager, Menu, MenuEntry, MenuItem, Submenu, SystemTray, SystemTrayMenu, SystemTrayMenuItem }; pub static APP: OnceLock = OnceLock::new(); #[tokio::main] async fn main() { + tauri::async_runtime::set(tokio::runtime::Handle::current()); + dotenv().ok(); let quit = CustomMenuItem::new("quit".to_string(), "Quit"); @@ -49,13 +51,7 @@ async fn main() { todo::delete_item_by_id, todo::switch_item_status ]) - .setup(|app| { - APP.set(app.handle()) - .unwrap_or_else(|_| panic!("Failed to initialize APP")); - - init_db(); - Ok(()) - }) + .setup(setup::setup) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/src-tauri/src/service/mutation.rs b/src-tauri/src/service/mutation.rs index 7caf329..c19c3d3 100644 --- a/src-tauri/src/service/mutation.rs +++ b/src-tauri/src/service/mutation.rs @@ -1,38 +1,36 @@ use chrono::SecondsFormat; use entity::todos; -use sea_orm::{ActiveModelTrait, ActiveValue::NotSet, DbErr, EntityTrait, Set}; +use sea_orm::{ActiveModelTrait, ActiveValue::NotSet, DbConn, EntityTrait, Set}; -use crate::establish_connection; +use crate::error::AppError; pub struct Mutation; impl Mutation { - pub async fn create_todo_item(title: String) -> Result { - let db = establish_connection().await?; - todos::ActiveModel { + pub async fn create_todo_item(db: &DbConn,title: String) -> Result { + let result = todos::ActiveModel { id: NotSet, title: Set(title), datetime: Set(chrono::Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true)), ..Default::default() } - .save(&db) - .await + .save(db) + .await?; + Ok(result) } - pub async fn delete_item_by_id(id: i32) -> Result { - let db = establish_connection().await?; - todos::Entity::delete_by_id(id).exec(&db).await?; + pub async fn delete_item_by_id(db: &DbConn, id: i32) -> Result { + todos::Entity::delete_by_id(id).exec(db).await?; Ok(true) } - pub async fn switch_item_status(id: i32, is_done: bool) -> Result { - let db = establish_connection().await?; - let item = todos::Entity::find_by_id(id).one(&db).await?; + pub async fn switch_item_status(db: &DbConn, id: i32, is_done: bool) -> Result { + let item = todos::Entity::find_by_id(id).one(db).await?; match item { Some(item) => { let mut item: todos::ActiveModel = item.into(); item.done = Set(Some(is_done)); - item.save(&db).await?; + item.save(db).await?; Ok(true) } None => Ok(false), diff --git a/src-tauri/src/service/query.rs b/src-tauri/src/service/query.rs index 1897367..b224d41 100644 --- a/src-tauri/src/service/query.rs +++ b/src-tauri/src/service/query.rs @@ -1,15 +1,13 @@ use entity::todos; -use sea_orm::{entity::*, query::*, DbErr}; - -use crate::establish_connection; +use sea_orm::{entity::*, query::*, DbConn}; +use crate::error::AppError; pub struct Query ; impl Query { - pub async fn query_todo_list(page_num: u64 ) -> Result, DbErr> { - let db = establish_connection().await?; - let todo_pages = todos::Entity::find().order_by_desc(todos::Column::Id).paginate(&db, 100); + pub async fn query_todo_list(db: &DbConn, page_num: u64 ) -> Result, AppError> { + let todo_pages = todos::Entity::find().order_by_desc(todos::Column::Id).paginate(db, 100); let list = todo_pages.fetch_page(page_num - 1).await?; Ok(list) diff --git a/src-tauri/src/setup.rs b/src-tauri/src/setup.rs new file mode 100644 index 0000000..af753cd --- /dev/null +++ b/src-tauri/src/setup.rs @@ -0,0 +1,46 @@ +use std::env; + +use migration::{Migrator, MigratorTrait}; +use sea_orm::{DatabaseConnection, SqlxSqliteConnector}; +use sqlx::SqlitePool; +use tauri::{api::path::app_data_dir, App, Config, Manager}; + +pub fn setup(app: &mut App) -> Result<(), Box> { + let handler = app.handle(); + tokio::spawn(async move { + let db_conn = get_database_pool(&handler.config()).await; + handler.manage(db_conn); + }); + + Ok(()) +} + +pub async fn get_database_pool(config: &Config) -> DatabaseConnection { + let database_url = get_db_path(config); + + let pool = SqlitePool::connect(&database_url) + .await + .expect("Failed to connect database"); + + let db_conn = SqlxSqliteConnector::from_sqlx_sqlite_pool(pool); + + Migrator::up(&db_conn, None) + .await + .expect("Failed to migrate"); + + db_conn +} + +pub fn get_db_path(config: &Config) -> String { + if cfg!(debug_assertions) { + env::var("DATABASE_URL").expect("DATABASE_URL must be set") + } else { + let app_data_dir = app_data_dir(config).expect("DATABASE_URL must be"); + std::fs::create_dir_all(&app_data_dir).unwrap(); + + format!( + "sqlite://{}?mode=rwc", + app_data_dir.join("db.sqlite").to_string_lossy() + ) + } +} diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index b4b4dcb..05f52ac 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -4,49 +4,51 @@ use std::path::Path; use migration::Migrator; use migration::MigratorTrait; -use sea_orm::{Database, DbConn, DbErr}; +use sea_orm::{Database, DatabaseConnection, DbConn, DbErr, SqlxSqliteConnector}; +use sqlx::SqlitePool; +use tauri::App; use crate::APP; -pub async fn establish_connection() -> Result { - let database_url = format!("sqlite://{}?mode=rwc", get_db_path()); - - let db = Database::connect(database_url).await?; - - // TODO: refactor only migrate database once - Migrator::up(&db, None).await?; - - Ok(db) -} - -pub fn init_db() { - let db_path = get_db_path(); - let db_dir = Path::new(&db_path).parent().unwrap(); - if !db_dir.exists() { - create_dir_all(db_dir).unwrap(); - } - if !Path::new(&db_path).exists() { - File::create(&db_path).unwrap(); - } - // let db = Database::connect(database_url).await?; - - // Migrator::up(&db, None).await?; -} - -pub fn get_db_path() -> String { - // development environment path - if cfg!(debug_assertions) { - env::var("DATABASE_URL").expect("DATABASE_URL must be set") - } else { - let data_dir = APP - .get() - .expect("fail") - .path_resolver() - .app_data_dir() - .expect("fail"); - let db_path = data_dir.join("db.sqlite"); - println!("{}", db_path.to_string_lossy()); - - db_path.to_str().unwrap().to_string() - } -} +// pub async fn establish_connection() -> Result { +// let database_url = format!("sqlite://{}?mode=rwc", get_db_path()); + +// let db = Database::connect(database_url).await?; + +// // TODO: refactor only migrate database once +// Migrator::up(&db, None).await?; + +// Ok(db) +// } + +// pub fn init_db() { +// let db_path = get_db_path(); +// let db_dir = Path::new(&db_path).parent().unwrap(); +// if !db_dir.exists() { +// create_dir_all(db_dir).unwrap(); +// } +// if !Path::new(&db_path).exists() { +// File::create(&db_path).unwrap(); +// } +// // let db = Database::connect(database_url).await?; + +// // Migrator::up(&db, None).await?; +// } + +// pub fn get_db_path() -> String { +// // development environment path +// if cfg!(debug_assertions) { +// env::var("DATABASE_URL").expect("DATABASE_URL must be set") +// } else { +// let data_dir = APP +// .get() +// .expect("fail") +// .path_resolver() +// .app_data_dir() +// .expect("fail"); +// let db_path = data_dir.join("db.sqlite"); +// println!("{}", db_path.to_string_lossy()); + +// db_path.to_str().unwrap().to_string() +// } +// } diff --git a/src/components/sidebar.tsx b/src/components/sidebar.tsx index 484915e..feddc1f 100644 --- a/src/components/sidebar.tsx +++ b/src/components/sidebar.tsx @@ -22,7 +22,7 @@ const Sidebar = () => {