feat: add Tauri v2 Rust backend with PostgreSQL support

Add Rust backend: AppState with connection pool management,
TuskError handling, ConnectionConfig model, and all commands
for connections, schema browsing, query execution, data CRUD,
and CSV/JSON export via sqlx.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 19:06:27 +03:00
parent 27bdbf0112
commit 9b675babd5
36 changed files with 6918 additions and 0 deletions

View File

@@ -0,0 +1,142 @@
use crate::error::{TuskError, TuskResult};
use crate::models::connection::ConnectionConfig;
use crate::state::AppState;
use sqlx::PgPool;
use sqlx::Row;
use std::fs;
use tauri::{AppHandle, Manager, State};
fn get_connections_path(app: &AppHandle) -> TuskResult<std::path::PathBuf> {
let dir = app
.path()
.app_data_dir()
.map_err(|e| TuskError::Custom(e.to_string()))?;
fs::create_dir_all(&dir)?;
Ok(dir.join("connections.json"))
}
#[tauri::command]
pub async fn get_connections(app: AppHandle) -> TuskResult<Vec<ConnectionConfig>> {
let path = get_connections_path(&app)?;
if !path.exists() {
return Ok(vec![]);
}
let data = fs::read_to_string(&path)?;
let connections: Vec<ConnectionConfig> = serde_json::from_str(&data)?;
Ok(connections)
}
#[tauri::command]
pub async fn save_connection(app: AppHandle, config: ConnectionConfig) -> TuskResult<()> {
let path = get_connections_path(&app)?;
let mut connections = if path.exists() {
let data = fs::read_to_string(&path)?;
serde_json::from_str::<Vec<ConnectionConfig>>(&data)?
} else {
vec![]
};
if let Some(pos) = connections.iter().position(|c| c.id == config.id) {
connections[pos] = config;
} else {
connections.push(config);
}
let data = serde_json::to_string_pretty(&connections)?;
fs::write(&path, data)?;
Ok(())
}
#[tauri::command]
pub async fn delete_connection(
app: AppHandle,
state: State<'_, AppState>,
id: String,
) -> TuskResult<()> {
let path = get_connections_path(&app)?;
if path.exists() {
let data = fs::read_to_string(&path)?;
let mut connections: Vec<ConnectionConfig> = serde_json::from_str(&data)?;
connections.retain(|c| c.id != id);
let data = serde_json::to_string_pretty(&connections)?;
fs::write(&path, data)?;
}
// Close pool if connected
let mut pools = state.pools.write().await;
if let Some(pool) = pools.remove(&id) {
pool.close().await;
}
Ok(())
}
#[tauri::command]
pub async fn test_connection(config: ConnectionConfig) -> TuskResult<String> {
let pool = PgPool::connect(&config.connection_url())
.await
.map_err(TuskError::Database)?;
let row = sqlx::query("SELECT version()")
.fetch_one(&pool)
.await
.map_err(TuskError::Database)?;
let version: String = row.get(0);
pool.close().await;
Ok(version)
}
#[tauri::command]
pub async fn connect(state: State<'_, AppState>, config: ConnectionConfig) -> TuskResult<()> {
let pool = PgPool::connect(&config.connection_url())
.await
.map_err(TuskError::Database)?;
// Verify connection
sqlx::query("SELECT 1")
.execute(&pool)
.await
.map_err(TuskError::Database)?;
let mut pools = state.pools.write().await;
pools.insert(config.id.clone(), pool);
Ok(())
}
#[tauri::command]
pub async fn switch_database(
state: State<'_, AppState>,
config: ConnectionConfig,
database: String,
) -> TuskResult<()> {
let mut switched = config.clone();
switched.database = database;
let pool = PgPool::connect(&switched.connection_url())
.await
.map_err(TuskError::Database)?;
sqlx::query("SELECT 1")
.execute(&pool)
.await
.map_err(TuskError::Database)?;
let mut pools = state.pools.write().await;
if let Some(old_pool) = pools.remove(&config.id) {
old_pool.close().await;
}
pools.insert(config.id.clone(), pool);
Ok(())
}
#[tauri::command]
pub async fn disconnect(state: State<'_, AppState>, id: String) -> TuskResult<()> {
let mut pools = state.pools.write().await;
if let Some(pool) = pools.remove(&id) {
pool.close().await;
}
Ok(())
}