Skip to content

Commit 1694e11

Browse files
committed
feat: persist connection data
1 parent b197560 commit 1694e11

File tree

12 files changed

+249
-21
lines changed

12 files changed

+249
-21
lines changed

src-tauri/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ edition = "2021"
1313
tauri-build = { version = "1.5", features = [] }
1414

1515
[dependencies]
16-
tauri = { version = "1.5.3", features = ["shell-open"] }
17-
tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
16+
tauri = { version = "1.5.2", features = ["shell-open"] }
1817
serde = { version = "1.0.193", features = ["derive"] }
1918
serde_json = "1.0.108"
2019
tokio = "1.34.0"
2120
tokio-postgres = "0.7.10"
2221
chrono = "0.4.31"
22+
sled = "0.34.7"
2323

2424

2525

src-tauri/project_db/conf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
segment_size: 524288
2+
use_compression: false
3+
version: 0.34
4+
vQ�

src-tauri/project_db/db

512 KB
Binary file not shown.
91 Bytes
Binary file not shown.

src-tauri/src/main.rs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,37 @@
22
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
33

44
mod postgres;
5+
mod project_db;
56
mod utils;
67

78
use postgres::{get_schema_tables, get_sql_result, pg_connector};
9+
use project_db::{get_project_details, get_projects, ProjectDB};
810
use std::sync::Arc;
911
#[cfg(debug_assertions)]
1012
use tauri::Manager;
11-
use tauri_plugin_store::StoreBuilder;
1213
use tokio::sync::Mutex;
1314
use tokio_postgres::Client;
1415

15-
#[derive(Default)]
1616
pub struct AppState {
1717
pub connection_strings: Arc<Mutex<String>>,
1818
pub client: Arc<Mutex<Option<Client>>>,
19+
pub project_db: Arc<Mutex<Option<ProjectDB>>>,
20+
}
21+
22+
impl Default for AppState {
23+
fn default() -> Self {
24+
Self {
25+
connection_strings: Arc::new(Mutex::new(String::new())),
26+
client: Arc::new(Mutex::new(None)),
27+
project_db: Arc::new(Mutex::new(Some(ProjectDB::default()))),
28+
}
29+
}
1930
}
2031

2132
fn main() {
2233
tauri::Builder::default()
23-
.plugin(tauri_plugin_store::Builder::default().build())
34+
.manage(AppState::default())
2435
.setup(|app| {
25-
// create persistent storage
26-
StoreBuilder::new(app.app_handle(), "local_storage.bin".parse().unwrap()).build();
27-
2836
// open devtools if we are in debug mode
2937
#[cfg(debug_assertions)]
3038
{
@@ -35,8 +43,9 @@ fn main() {
3543

3644
Ok(())
3745
})
38-
.manage(AppState::default())
3946
.invoke_handler(tauri::generate_handler![
47+
get_projects,
48+
get_project_details,
4049
get_schema_tables,
4150
pg_connector,
4251
get_sql_result

src-tauri/src/postgres.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,22 @@ use tokio_postgres::{connect, NoTls};
44
use crate::{utils::reflective_get, AppState};
55

66
#[tauri::command]
7-
pub async fn pg_connector(key: &str, app_state: State<'_, AppState>) -> Result<Vec<String>> {
7+
pub async fn pg_connector(
8+
project: &str,
9+
key: &str,
10+
app_state: State<'_, AppState>,
11+
) -> Result<Vec<String>> {
812
if key.is_empty() {
913
return Ok(Vec::new());
1014
}
11-
let (client, connection) = connect(key, NoTls).await.expect("connection error");
1215

16+
// Save connection string to local db
17+
let db_path = app_state.project_db.lock().await;
18+
let db_path = db_path.as_ref().unwrap().db_path.as_str();
19+
let db = sled::open(db_path).unwrap();
20+
db.insert(project, key).unwrap();
21+
22+
let (client, connection) = connect(key, NoTls).await.expect("connection error");
1323
tokio::spawn(async move {
1424
if let Err(e) = connection.await {
1525
eprintln!("connection error: {}", e);

src-tauri/src/project_db.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use serde::{Deserialize, Serialize};
2+
use tauri::{Result, State};
3+
4+
use crate::AppState;
5+
6+
#[tauri::command]
7+
pub async fn get_projects(app_state: State<'_, AppState>) -> Result<Vec<String>> {
8+
let project_db = app_state.project_db.lock().await;
9+
let project_db = project_db.as_ref().unwrap();
10+
11+
let db = sled::open(&project_db.db_path).unwrap();
12+
let projects = db
13+
.iter()
14+
.map(|r| {
15+
let (project, _) = r.unwrap();
16+
String::from_utf8(project.to_vec()).unwrap()
17+
})
18+
.collect();
19+
20+
Ok(projects)
21+
}
22+
23+
#[tauri::command]
24+
pub async fn get_project_details(
25+
project: String,
26+
app_state: State<'_, AppState>,
27+
) -> Result<ProjectDetails> {
28+
let project_db = app_state.project_db.lock().await;
29+
let project_db = project_db.as_ref().unwrap();
30+
31+
let project_details = project_db.get_connection_string(project.as_str())?;
32+
33+
Ok(project_details)
34+
}
35+
36+
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
37+
pub struct ProjectDetails {
38+
pub user: String,
39+
pub password: String,
40+
pub host: String,
41+
pub port: String,
42+
}
43+
44+
#[derive(Serialize, Deserialize, Debug, Clone)]
45+
pub struct ProjectDB {
46+
pub db_path: String,
47+
}
48+
49+
impl Default for ProjectDB {
50+
fn default() -> Self {
51+
Self::new()
52+
}
53+
}
54+
55+
impl ProjectDB {
56+
pub fn new() -> Self {
57+
Self {
58+
db_path: String::from("project_db"),
59+
}
60+
}
61+
62+
pub fn get_connection_string(&self, project: &str) -> Result<ProjectDetails> {
63+
let db = sled::open(&self.db_path).unwrap();
64+
let connection_string = db.get(project).unwrap();
65+
let mut project_details = ProjectDetails::default();
66+
67+
if let Some(connection_string) = connection_string {
68+
let connection_string = connection_string.to_vec();
69+
let connection_string = String::from_utf8(connection_string).unwrap();
70+
let connection_string = connection_string.split(" ").collect::<Vec<&str>>();
71+
72+
for connection_string in connection_string {
73+
let connection_string = connection_string.split("=").collect::<Vec<&str>>();
74+
let key = connection_string[0];
75+
let value = connection_string[1];
76+
77+
match key {
78+
"user" => project_details.user = value.to_string(),
79+
"password" => project_details.password = value.to_string(),
80+
"host" => project_details.host = value.to_string(),
81+
"port" => project_details.port = value.to_string(),
82+
_ => (),
83+
}
84+
}
85+
}
86+
87+
Ok(project_details)
88+
}
89+
}
90+

src/db_connector.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ use crate::store::{db::DBStore, query::QueryState};
44

55
pub fn db_connector() -> impl IntoView {
66
let db = use_context::<DBStore>().unwrap();
7+
let refetch_projects = use_context::<Resource<(), Vec<String>>>().unwrap();
78
let connect = create_action(move |db: &DBStore| {
89
let mut db_clone = *db;
9-
async move { db_clone.connect().await }
10+
async move {
11+
db_clone.connect().await;
12+
refetch_projects.refetch();
13+
}
1014
});
1115
let query_state = use_context::<QueryState>().unwrap();
1216
let run_query = create_action(move |query_state: &QueryState| {
@@ -22,6 +26,16 @@ pub fn db_connector() -> impl IntoView {
2226
.child(
2327
div()
2428
.attr("class", "flex flex-row gap-2")
29+
.child(
30+
input()
31+
.attr("class", "border-1 border-neutral-200 p-1 rounded-md")
32+
.attr("type", "text")
33+
.attr("value", move || db.project.get())
34+
.attr("placeholder", "project")
35+
.on(ev::input, move |e| {
36+
db.project.set(event_target_value(&e));
37+
}),
38+
)
2539
.child(
2640
input()
2741
.attr("class", "border-1 border-neutral-200 p-1 rounded-md")
@@ -79,8 +93,14 @@ pub fn db_connector() -> impl IntoView {
7993
button()
8094
.attr(
8195
"class",
82-
"px-4 py-2 border-1 border-neutral-200 hover:bg-neutral-200 rounded-md",
83-
)
96+
"px-4 py-2 border-1 border-neutral-200 hover:bg-neutral-200 rounded-md disabled:opacity-50",
97+
).attr("disabled", move || {
98+
db.is_connecting.get()
99+
|| db.db_host.get().is_empty()
100+
|| db.db_port.get().is_empty()
101+
|| db.db_user.get().is_empty()
102+
|| db.db_password.get().is_empty()
103+
})
84104
.on(ev::click, move |_| {
85105
connect.dispatch(db);
86106
})

src/invoke.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize};
22

33
#[derive(Serialize, Deserialize)]
44
pub struct InvokePostgresConnectionArgs {
5+
pub project: String,
56
pub key: String,
67
}
78

@@ -15,3 +16,11 @@ pub struct InvokeQueryArgs {
1516
pub sql: String,
1617
}
1718

19+
#[derive(Serialize, Deserialize)]
20+
pub struct InvokeProjectsArgs;
21+
22+
#[derive(Serialize, Deserialize)]
23+
pub struct InvokeProjectDetailsArgs {
24+
pub project: String,
25+
}
26+

src/sidebar.rs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,59 @@
1-
use crate::{store::db::DBStore, tables::tables};
1+
use crate::{
2+
invoke::InvokeProjectsArgs, store::db::DBStore, tables::tables, wasm_functions::invoke,
3+
};
24
use leptos::{html::*, *};
35

46
pub fn sidebar() -> impl IntoView {
57
let db = use_context::<DBStore>().unwrap();
8+
let get_project_details = create_action(move |(db, project): &(DBStore, String)| {
9+
let mut db_clone = *db;
10+
let project = project.clone();
11+
async move { db_clone.get_project_details(project).await }
12+
});
13+
let projects = create_resource(
14+
|| {},
15+
move |_| async move {
16+
let projects = invoke(
17+
"get_projects",
18+
serde_wasm_bindgen::to_value(&InvokeProjectsArgs).unwrap(),
19+
)
20+
.await;
21+
let projects = serde_wasm_bindgen::from_value::<Vec<String>>(projects).unwrap();
22+
projects
23+
},
24+
);
25+
provide_context(projects);
626

727
div()
828
.attr(
929
"class",
1030
"flex border-r-1 border-neutral-200 flex-col gap-2 px-4 pt-4 overflow-auto",
1131
)
32+
.child(Suspense(SuspenseProps {
33+
children: ChildrenFn::to_children(move || {
34+
Fragment::new(vec![div()
35+
.child(p().attr("class", "font-semibold").child("Projects"))
36+
.child(move || {
37+
projects
38+
.get()
39+
.unwrap_or(Vec::new())
40+
.into_iter()
41+
.enumerate()
42+
.map(|(idx, project)| {
43+
button()
44+
.attr("key", idx)
45+
.attr("class", "hover:font-semibold")
46+
.child(&project)
47+
.on(ev::click, move |_| {
48+
get_project_details.dispatch((db.clone(), project.clone()))
49+
})
50+
})
51+
.collect_view()
52+
})
53+
.into_view()])
54+
}),
55+
fallback: ViewFn::from(|| p().child("Loading...")),
56+
}))
1257
.child(p().attr("class", "font-semibold").child("Schemas"))
1358
.child(Show(ShowProps {
1459
when: move || db.is_connecting.get(),

0 commit comments

Comments
 (0)