完善web缺失的端点
This commit is contained in:
@@ -1,10 +1,16 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use axum::{extract::Extension, Json};
|
||||
use serde::Deserialize;
|
||||
use tauri::Manager;
|
||||
|
||||
use crate::acp::manager::ConnectionManager;
|
||||
use crate::acp::preflight::PreflightResult;
|
||||
use crate::acp::registry;
|
||||
use crate::acp::types::{AcpAgentInfo, AcpAgentStatus};
|
||||
use crate::acp::types::{
|
||||
AcpAgentInfo, AcpAgentStatus, AgentSkillContent, AgentSkillLayout, AgentSkillScope,
|
||||
AgentSkillsListResult, ConnectionInfo, ForkResultInfo,
|
||||
};
|
||||
use crate::app_error::AppCommandError;
|
||||
use crate::commands::acp as acp_commands;
|
||||
use crate::db::service::agent_setting_service;
|
||||
@@ -139,3 +145,327 @@ pub async fn acp_prompt(
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
// --- Pattern A: Pure function handlers ---
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AcpPreflightParams {
|
||||
pub agent_type: AgentType,
|
||||
pub force_refresh: Option<bool>,
|
||||
}
|
||||
|
||||
pub async fn acp_preflight(
|
||||
Json(params): Json<AcpPreflightParams>,
|
||||
) -> Result<Json<PreflightResult>, AppCommandError> {
|
||||
let result = acp_commands::acp_preflight(params.agent_type, params.force_refresh)
|
||||
.await
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn acp_clear_binary_cache(
|
||||
Json(params): Json<AgentTypeParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
acp_commands::acp_clear_binary_cache(params.agent_type)
|
||||
.await
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AcpListAgentSkillsParams {
|
||||
pub agent_type: AgentType,
|
||||
pub workspace_path: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn acp_list_agent_skills(
|
||||
Json(params): Json<AcpListAgentSkillsParams>,
|
||||
) -> Result<Json<AgentSkillsListResult>, AppCommandError> {
|
||||
let result =
|
||||
acp_commands::acp_list_agent_skills(params.agent_type, params.workspace_path)
|
||||
.await
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AcpReadAgentSkillParams {
|
||||
pub agent_type: AgentType,
|
||||
pub scope: AgentSkillScope,
|
||||
pub skill_id: String,
|
||||
pub workspace_path: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn acp_read_agent_skill(
|
||||
Json(params): Json<AcpReadAgentSkillParams>,
|
||||
) -> Result<Json<AgentSkillContent>, AppCommandError> {
|
||||
let result = acp_commands::acp_read_agent_skill(
|
||||
params.agent_type,
|
||||
params.scope,
|
||||
params.skill_id,
|
||||
params.workspace_path,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AcpSaveAgentSkillParams {
|
||||
pub agent_type: AgentType,
|
||||
pub scope: AgentSkillScope,
|
||||
pub skill_id: String,
|
||||
pub content: String,
|
||||
pub workspace_path: Option<String>,
|
||||
pub layout: Option<AgentSkillLayout>,
|
||||
}
|
||||
|
||||
pub async fn acp_save_agent_skill(
|
||||
Json(params): Json<AcpSaveAgentSkillParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
acp_commands::acp_save_agent_skill(
|
||||
params.agent_type,
|
||||
params.scope,
|
||||
params.skill_id,
|
||||
params.content,
|
||||
params.workspace_path,
|
||||
params.layout,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AcpDeleteAgentSkillParams {
|
||||
pub agent_type: AgentType,
|
||||
pub scope: AgentSkillScope,
|
||||
pub skill_id: String,
|
||||
pub workspace_path: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn acp_delete_agent_skill(
|
||||
Json(params): Json<AcpDeleteAgentSkillParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
acp_commands::acp_delete_agent_skill(
|
||||
params.agent_type,
|
||||
params.scope,
|
||||
params.skill_id,
|
||||
params.workspace_path,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
// --- Pattern C: ConnectionManager handlers ---
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AcpConnectionIdParams {
|
||||
pub connection_id: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AcpSetModeParams {
|
||||
pub connection_id: String,
|
||||
pub mode_id: String,
|
||||
}
|
||||
|
||||
pub async fn acp_set_mode(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<AcpSetModeParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
let manager = app.state::<ConnectionManager>();
|
||||
manager
|
||||
.set_mode(¶ms.connection_id, params.mode_id)
|
||||
.await
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AcpSetConfigOptionParams {
|
||||
pub connection_id: String,
|
||||
pub config_id: String,
|
||||
pub value_id: String,
|
||||
}
|
||||
|
||||
pub async fn acp_set_config_option(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<AcpSetConfigOptionParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
let manager = app.state::<ConnectionManager>();
|
||||
manager
|
||||
.set_config_option(¶ms.connection_id, params.config_id, params.value_id)
|
||||
.await
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn acp_cancel(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<AcpConnectionIdParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
let manager = app.state::<ConnectionManager>();
|
||||
manager
|
||||
.cancel(¶ms.connection_id)
|
||||
.await
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn acp_fork(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<AcpConnectionIdParams>,
|
||||
) -> Result<Json<ForkResultInfo>, AppCommandError> {
|
||||
let manager = app.state::<ConnectionManager>();
|
||||
let result = manager
|
||||
.fork_session(¶ms.connection_id)
|
||||
.await
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AcpRespondPermissionParams {
|
||||
pub connection_id: String,
|
||||
pub request_id: String,
|
||||
pub option_id: String,
|
||||
}
|
||||
|
||||
pub async fn acp_respond_permission(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<AcpRespondPermissionParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
let manager = app.state::<ConnectionManager>();
|
||||
manager
|
||||
.respond_permission(¶ms.connection_id, ¶ms.request_id, ¶ms.option_id)
|
||||
.await
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn acp_list_connections(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
) -> Result<Json<Vec<ConnectionInfo>>, AppCommandError> {
|
||||
let manager = app.state::<ConnectionManager>();
|
||||
let result = manager.list_connections().await;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
// --- Pattern B+: Core function handlers ---
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AcpUpdateAgentPreferencesParams {
|
||||
pub agent_type: AgentType,
|
||||
pub enabled: bool,
|
||||
pub env: BTreeMap<String, String>,
|
||||
pub config_json: Option<String>,
|
||||
pub opencode_auth_json: Option<String>,
|
||||
pub codex_auth_json: Option<String>,
|
||||
pub codex_config_toml: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn acp_update_agent_preferences(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<AcpUpdateAgentPreferencesParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
acp_commands::acp_update_agent_preferences_core(
|
||||
params.agent_type,
|
||||
params.enabled,
|
||||
params.env,
|
||||
params.config_json,
|
||||
params.opencode_auth_json,
|
||||
params.codex_auth_json,
|
||||
params.codex_config_toml,
|
||||
&db,
|
||||
&app,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn acp_download_agent_binary(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<AgentTypeParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
acp_commands::acp_download_agent_binary_core(params.agent_type, &app)
|
||||
.await
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn acp_detect_agent_local_version(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<AgentTypeParams>,
|
||||
) -> Result<Json<Option<String>>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
let result =
|
||||
acp_commands::acp_detect_agent_local_version_core(params.agent_type, &db.conn)
|
||||
.await
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AcpPrepareNpxAgentParams {
|
||||
pub agent_type: AgentType,
|
||||
pub registry_version: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn acp_prepare_npx_agent(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<AcpPrepareNpxAgentParams>,
|
||||
) -> Result<Json<String>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
let result = acp_commands::acp_prepare_npx_agent_core(
|
||||
params.agent_type,
|
||||
params.registry_version,
|
||||
&db,
|
||||
&app,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn acp_uninstall_agent(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<AgentTypeParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
acp_commands::acp_uninstall_agent_core(params.agent_type, &db, &app)
|
||||
.await
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AcpReorderAgentsParams {
|
||||
pub agent_types: Vec<AgentType>,
|
||||
}
|
||||
|
||||
pub async fn acp_reorder_agents(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<AcpReorderAgentsParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
acp_commands::acp_reorder_agents_core(¶ms.agent_types, &db, &app)
|
||||
.await
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
156
src-tauri/src/web/handlers/files.rs
Normal file
156
src-tauri/src/web/handlers/files.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
use axum::Json;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::app_error::AppCommandError;
|
||||
use crate::commands::folders as folder_commands;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Param structs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReadFilePreviewParams {
|
||||
pub root_path: String,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReadFileBase64Params {
|
||||
pub path: String,
|
||||
pub max_bytes: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReadFileForEditParams {
|
||||
pub root_path: String,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SaveFileContentParams {
|
||||
pub root_path: String,
|
||||
pub path: String,
|
||||
pub content: String,
|
||||
pub expected_etag: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SaveFileCopyParams {
|
||||
pub root_path: String,
|
||||
pub path: String,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RenameFileTreeEntryParams {
|
||||
pub root_path: String,
|
||||
pub path: String,
|
||||
pub new_name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DeleteFileTreeEntryParams {
|
||||
pub root_path: String,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreateFileTreeEntryParams {
|
||||
pub root_path: String,
|
||||
pub path: String,
|
||||
pub name: String,
|
||||
pub kind: String,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Handlers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub async fn read_file_preview(
|
||||
Json(params): Json<ReadFilePreviewParams>,
|
||||
) -> Result<Json<folder_commands::FilePreviewContent>, AppCommandError> {
|
||||
let result =
|
||||
folder_commands::read_file_preview(params.root_path, params.path).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn read_file_base64(
|
||||
Json(params): Json<ReadFileBase64Params>,
|
||||
) -> Result<Json<String>, AppCommandError> {
|
||||
let result =
|
||||
folder_commands::read_file_base64(params.path, params.max_bytes).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn read_file_for_edit(
|
||||
Json(params): Json<ReadFileForEditParams>,
|
||||
) -> Result<Json<folder_commands::FileEditContent>, AppCommandError> {
|
||||
let result =
|
||||
folder_commands::read_file_for_edit(params.root_path, params.path).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn save_file_content(
|
||||
Json(params): Json<SaveFileContentParams>,
|
||||
) -> Result<Json<folder_commands::FileSaveResult>, AppCommandError> {
|
||||
let result = folder_commands::save_file_content(
|
||||
params.root_path,
|
||||
params.path,
|
||||
params.content,
|
||||
params.expected_etag,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn save_file_copy(
|
||||
Json(params): Json<SaveFileCopyParams>,
|
||||
) -> Result<Json<folder_commands::FileSaveResult>, AppCommandError> {
|
||||
let result = folder_commands::save_file_copy(
|
||||
params.root_path,
|
||||
params.path,
|
||||
params.content,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn rename_file_tree_entry(
|
||||
Json(params): Json<RenameFileTreeEntryParams>,
|
||||
) -> Result<Json<String>, AppCommandError> {
|
||||
let result = folder_commands::rename_file_tree_entry(
|
||||
params.root_path,
|
||||
params.path,
|
||||
params.new_name,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn delete_file_tree_entry(
|
||||
Json(params): Json<DeleteFileTreeEntryParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
folder_commands::delete_file_tree_entry(params.root_path, params.path).await?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn create_file_tree_entry(
|
||||
Json(params): Json<CreateFileTreeEntryParams>,
|
||||
) -> Result<Json<String>, AppCommandError> {
|
||||
let result = folder_commands::create_file_tree_entry(
|
||||
params.root_path,
|
||||
params.path,
|
||||
params.name,
|
||||
params.kind,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
@@ -7,12 +7,50 @@ use crate::db::service::folder_command_service;
|
||||
use crate::db::AppDatabase;
|
||||
use crate::models::*;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Param structs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FolderIdParams {
|
||||
pub folder_id: i32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreateFolderCommandParams {
|
||||
pub folder_id: i32,
|
||||
pub name: String,
|
||||
pub command: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UpdateFolderCommandParams {
|
||||
pub id: i32,
|
||||
pub name: Option<String>,
|
||||
pub command: Option<String>,
|
||||
pub sort_order: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DeleteFolderCommandParams {
|
||||
pub id: i32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReorderFolderCommandsParams {
|
||||
pub folder_id: i32,
|
||||
pub ids: Vec<i32>,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Handlers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub async fn list_folder_commands(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<FolderIdParams>,
|
||||
@@ -23,3 +61,62 @@ pub async fn list_folder_commands(
|
||||
.map_err(AppCommandError::from)?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn create_folder_command(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<CreateFolderCommandParams>,
|
||||
) -> Result<Json<FolderCommandInfo>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
let result = folder_command_service::create(
|
||||
&db.conn,
|
||||
params.folder_id,
|
||||
¶ms.name,
|
||||
¶ms.command,
|
||||
)
|
||||
.await
|
||||
.map_err(AppCommandError::from)?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn update_folder_command(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<UpdateFolderCommandParams>,
|
||||
) -> Result<Json<FolderCommandInfo>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
let result = folder_command_service::update(
|
||||
&db.conn,
|
||||
params.id,
|
||||
params.name,
|
||||
params.command,
|
||||
params.sort_order,
|
||||
)
|
||||
.await
|
||||
.map_err(AppCommandError::from)?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn delete_folder_command(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<DeleteFolderCommandParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
folder_command_service::delete(&db.conn, params.id)
|
||||
.await
|
||||
.map_err(AppCommandError::from)?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn reorder_folder_commands(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<ReorderFolderCommandsParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
folder_command_service::reorder(&db.conn, params.folder_id, params.ids)
|
||||
.await
|
||||
.map_err(AppCommandError::from)?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
// TODO: bootstrap_folder_commands_from_package_json — requires access to
|
||||
// `load_package_scripts_as_commands` which is private in commands/folder_commands.rs.
|
||||
// Make it pub(crate) first, then add the web handler here.
|
||||
|
||||
@@ -173,95 +173,6 @@ pub async fn open_settings_window(
|
||||
Ok(Json(SettingsNavigationResult { path }))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitStatusParams {
|
||||
pub path: String,
|
||||
pub show_all_untracked: Option<bool>,
|
||||
}
|
||||
|
||||
pub async fn git_status(
|
||||
Json(params): Json<GitStatusParams>,
|
||||
) -> Result<Json<Vec<folder_commands::GitStatusEntry>>, AppCommandError> {
|
||||
let result =
|
||||
folder_commands::git_status(params.path, params.show_all_untracked).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReadFilePreviewParams {
|
||||
pub root_path: String,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
pub async fn read_file_preview(
|
||||
Json(params): Json<ReadFilePreviewParams>,
|
||||
) -> Result<Json<folder_commands::FilePreviewContent>, AppCommandError> {
|
||||
let result =
|
||||
folder_commands::read_file_preview(params.root_path, params.path).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn git_list_all_branches(
|
||||
Json(params): Json<PathParams>,
|
||||
) -> Result<Json<folder_commands::GitBranchList>, AppCommandError> {
|
||||
let result = folder_commands::git_list_all_branches(params.path).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitCommitBranchesParams {
|
||||
pub path: String,
|
||||
pub commit: String,
|
||||
}
|
||||
|
||||
pub async fn git_commit_branches(
|
||||
Json(params): Json<GitCommitBranchesParams>,
|
||||
) -> Result<Json<Vec<String>>, AppCommandError> {
|
||||
let result =
|
||||
folder_commands::git_commit_branches(params.path, params.commit).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitShowFileParams {
|
||||
pub path: String,
|
||||
pub file: String,
|
||||
pub ref_name: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn git_show_file(
|
||||
Json(params): Json<GitShowFileParams>,
|
||||
) -> Result<Json<String>, AppCommandError> {
|
||||
let result =
|
||||
folder_commands::git_show_file(params.path, params.file, params.ref_name).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitDiffParams {
|
||||
pub path: String,
|
||||
pub file: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn git_diff(
|
||||
Json(params): Json<GitDiffParams>,
|
||||
) -> Result<Json<String>, AppCommandError> {
|
||||
let result = folder_commands::git_diff(params.path, params.file).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn git_list_remotes(
|
||||
Json(params): Json<PathParams>,
|
||||
) -> Result<Json<Vec<folder_commands::GitRemote>>, AppCommandError> {
|
||||
let result = folder_commands::git_list_remotes(params.path).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OpenCommitWindowParams {
|
||||
@@ -276,3 +187,86 @@ pub async fn open_commit_window(
|
||||
path: format!("/commit?folderId={}", params.folder_id),
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OpenMergeWindowParams {
|
||||
pub folder_id: i32,
|
||||
pub operation: Option<String>,
|
||||
pub upstream_commit: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn open_merge_window(
|
||||
Json(params): Json<OpenMergeWindowParams>,
|
||||
) -> Result<Json<SettingsNavigationResult>, AppCommandError> {
|
||||
let mut path = format!("/merge?folderId={}", params.folder_id);
|
||||
if let Some(op) = ¶ms.operation {
|
||||
path.push_str(&format!("&operation={op}"));
|
||||
}
|
||||
if let Some(uc) = ¶ms.upstream_commit {
|
||||
path.push_str(&format!("&upstreamCommit={uc}"));
|
||||
}
|
||||
Ok(Json(SettingsNavigationResult { path }))
|
||||
}
|
||||
|
||||
pub async fn open_stash_window(
|
||||
Json(params): Json<OpenCommitWindowParams>,
|
||||
) -> Result<Json<SettingsNavigationResult>, AppCommandError> {
|
||||
Ok(Json(SettingsNavigationResult {
|
||||
path: format!("/stash?folderId={}", params.folder_id),
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn open_push_window(
|
||||
Json(params): Json<OpenCommitWindowParams>,
|
||||
) -> Result<Json<SettingsNavigationResult>, AppCommandError> {
|
||||
Ok(Json(SettingsNavigationResult {
|
||||
path: format!("/push?folderId={}", params.folder_id),
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SetFolderParentBranchParams {
|
||||
pub path: String,
|
||||
pub parent_branch: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn add_folder_to_history(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<AddFolderParams>,
|
||||
) -> Result<Json<FolderHistoryEntry>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
let result = folder_service::add_folder(&db.conn, ¶ms.path)
|
||||
.await
|
||||
.map_err(AppCommandError::from)?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn set_folder_parent_branch(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<SetFolderParentBranchParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
folder_commands::set_folder_parent_branch_core(&db.conn, ¶ms.path, params.parent_branch)
|
||||
.await?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn remove_folder_from_history(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<AddFolderParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
folder_service::remove_folder(&db.conn, ¶ms.path)
|
||||
.await
|
||||
.map_err(AppCommandError::from)?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn create_folder_directory(
|
||||
Json(params): Json<AddFolderParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
folder_commands::create_folder_directory(params.path).await?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
636
src-tauri/src/web/handlers/git.rs
Normal file
636
src-tauri/src/web/handlers/git.rs
Normal file
@@ -0,0 +1,636 @@
|
||||
use axum::{extract::Extension, Json};
|
||||
use serde::Deserialize;
|
||||
use tauri::Manager;
|
||||
|
||||
use crate::app_error::AppCommandError;
|
||||
use crate::commands::folders as folder_commands;
|
||||
use crate::db::AppDatabase;
|
||||
use crate::models::GitCredentials;
|
||||
|
||||
use super::folders::PathParams;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shared param structs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PathFileParams {
|
||||
pub path: String,
|
||||
pub file: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PathBranchParams {
|
||||
pub path: String,
|
||||
pub branch_name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PathStashRefParams {
|
||||
pub path: String,
|
||||
pub stash_ref: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PathNameParams {
|
||||
pub path: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PathNameUrlParams {
|
||||
pub path: String,
|
||||
pub name: String,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PathOperationParams {
|
||||
pub path: String,
|
||||
pub operation: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PathFileContentParams {
|
||||
pub path: String,
|
||||
pub file: String,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Migrated from folders.rs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitStatusParams {
|
||||
pub path: String,
|
||||
pub show_all_untracked: Option<bool>,
|
||||
}
|
||||
|
||||
pub async fn git_status(
|
||||
Json(params): Json<GitStatusParams>,
|
||||
) -> Result<Json<Vec<folder_commands::GitStatusEntry>>, AppCommandError> {
|
||||
let result =
|
||||
folder_commands::git_status(params.path, params.show_all_untracked).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn git_list_all_branches(
|
||||
Json(params): Json<PathParams>,
|
||||
) -> Result<Json<folder_commands::GitBranchList>, AppCommandError> {
|
||||
let result = folder_commands::git_list_all_branches(params.path).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitCommitBranchesParams {
|
||||
pub path: String,
|
||||
pub commit: String,
|
||||
}
|
||||
|
||||
pub async fn git_commit_branches(
|
||||
Json(params): Json<GitCommitBranchesParams>,
|
||||
) -> Result<Json<Vec<String>>, AppCommandError> {
|
||||
let result =
|
||||
folder_commands::git_commit_branches(params.path, params.commit).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitShowFileParams {
|
||||
pub path: String,
|
||||
pub file: String,
|
||||
pub ref_name: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn git_show_file(
|
||||
Json(params): Json<GitShowFileParams>,
|
||||
) -> Result<Json<String>, AppCommandError> {
|
||||
let result =
|
||||
folder_commands::git_show_file(params.path, params.file, params.ref_name)
|
||||
.await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitDiffParams {
|
||||
pub path: String,
|
||||
pub file: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn git_diff(
|
||||
Json(params): Json<GitDiffParams>,
|
||||
) -> Result<Json<String>, AppCommandError> {
|
||||
let result = folder_commands::git_diff(params.path, params.file).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn git_list_remotes(
|
||||
Json(params): Json<PathParams>,
|
||||
) -> Result<Json<Vec<folder_commands::GitRemote>>, AppCommandError> {
|
||||
let result = folder_commands::git_list_remotes(params.path).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Migrated from version_control.rs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitLogParams {
|
||||
pub path: String,
|
||||
pub limit: Option<u32>,
|
||||
pub branch: Option<String>,
|
||||
pub remote: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn git_log(
|
||||
Json(params): Json<GitLogParams>,
|
||||
) -> Result<Json<folder_commands::GitLogResult>, AppCommandError> {
|
||||
let result = folder_commands::git_log(
|
||||
params.path,
|
||||
params.limit,
|
||||
params.branch,
|
||||
params.remote,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// New pure git handlers (Pattern A – direct function calls)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub async fn git_init(
|
||||
Json(params): Json<PathParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
folder_commands::git_init(params.path).await?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitStartPullMergeParams {
|
||||
pub path: String,
|
||||
pub upstream_commit: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn git_start_pull_merge(
|
||||
Json(params): Json<GitStartPullMergeParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
folder_commands::git_start_pull_merge(params.path, params.upstream_commit).await?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn git_has_merge_head(
|
||||
Json(params): Json<PathParams>,
|
||||
) -> Result<Json<bool>, AppCommandError> {
|
||||
let result = folder_commands::git_has_merge_head(params.path).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn git_push_info(
|
||||
Json(params): Json<PathParams>,
|
||||
) -> Result<Json<folder_commands::GitPushInfo>, AppCommandError> {
|
||||
let result = folder_commands::git_push_info(params.path).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitNewBranchParams {
|
||||
pub path: String,
|
||||
pub branch_name: String,
|
||||
pub start_point: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn git_new_branch(
|
||||
Json(params): Json<GitNewBranchParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
folder_commands::git_new_branch(params.path, params.branch_name, params.start_point)
|
||||
.await?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitWorktreeAddParams {
|
||||
pub path: String,
|
||||
pub branch_name: String,
|
||||
pub worktree_path: String,
|
||||
}
|
||||
|
||||
pub async fn git_worktree_add(
|
||||
Json(params): Json<GitWorktreeAddParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
folder_commands::git_worktree_add(
|
||||
params.path,
|
||||
params.branch_name,
|
||||
params.worktree_path,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn git_checkout(
|
||||
Json(params): Json<PathBranchParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
folder_commands::git_checkout(params.path, params.branch_name).await?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn git_list_branches(
|
||||
Json(params): Json<PathParams>,
|
||||
) -> Result<Json<Vec<String>>, AppCommandError> {
|
||||
let result = folder_commands::git_list_branches(params.path).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitStashPushParams {
|
||||
pub path: String,
|
||||
pub message: Option<String>,
|
||||
pub keep_index: bool,
|
||||
}
|
||||
|
||||
pub async fn git_stash_push(
|
||||
Json(params): Json<GitStashPushParams>,
|
||||
) -> Result<Json<String>, AppCommandError> {
|
||||
let result = folder_commands::git_stash_push(
|
||||
params.path,
|
||||
params.message,
|
||||
params.keep_index,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitStashPopParams {
|
||||
pub path: String,
|
||||
pub stash_ref: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn git_stash_pop(
|
||||
Json(params): Json<GitStashPopParams>,
|
||||
) -> Result<Json<String>, AppCommandError> {
|
||||
let result =
|
||||
folder_commands::git_stash_pop(params.path, params.stash_ref).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn git_stash_list(
|
||||
Json(params): Json<PathParams>,
|
||||
) -> Result<Json<Vec<folder_commands::GitStashEntry>>, AppCommandError> {
|
||||
let result = folder_commands::git_stash_list(params.path).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn git_stash_apply(
|
||||
Json(params): Json<PathStashRefParams>,
|
||||
) -> Result<Json<String>, AppCommandError> {
|
||||
let result =
|
||||
folder_commands::git_stash_apply(params.path, params.stash_ref).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn git_stash_drop(
|
||||
Json(params): Json<PathStashRefParams>,
|
||||
) -> Result<Json<String>, AppCommandError> {
|
||||
let result =
|
||||
folder_commands::git_stash_drop(params.path, params.stash_ref).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn git_stash_clear(
|
||||
Json(params): Json<PathParams>,
|
||||
) -> Result<Json<String>, AppCommandError> {
|
||||
let result = folder_commands::git_stash_clear(params.path).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn git_stash_show(
|
||||
Json(params): Json<PathStashRefParams>,
|
||||
) -> Result<Json<Vec<folder_commands::GitStatusEntry>>, AppCommandError> {
|
||||
let result =
|
||||
folder_commands::git_stash_show(params.path, params.stash_ref).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn git_is_tracked(
|
||||
Json(params): Json<PathFileParams>,
|
||||
) -> Result<Json<bool>, AppCommandError> {
|
||||
let result =
|
||||
folder_commands::git_is_tracked(params.path, params.file).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitDiffWithBranchParams {
|
||||
pub path: String,
|
||||
pub branch: String,
|
||||
pub file: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn git_diff_with_branch(
|
||||
Json(params): Json<GitDiffWithBranchParams>,
|
||||
) -> Result<Json<String>, AppCommandError> {
|
||||
let result = folder_commands::git_diff_with_branch(
|
||||
params.path,
|
||||
params.branch,
|
||||
params.file,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitShowDiffParams {
|
||||
pub path: String,
|
||||
pub commit: String,
|
||||
pub file: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn git_show_diff(
|
||||
Json(params): Json<GitShowDiffParams>,
|
||||
) -> Result<Json<String>, AppCommandError> {
|
||||
let result =
|
||||
folder_commands::git_show_diff(params.path, params.commit, params.file)
|
||||
.await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn git_rollback_file(
|
||||
Json(params): Json<PathFileParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
folder_commands::git_rollback_file(params.path, params.file).await?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitAddFilesParams {
|
||||
pub path: String,
|
||||
pub files: Vec<String>,
|
||||
}
|
||||
|
||||
pub async fn git_add_files(
|
||||
Json(params): Json<GitAddFilesParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
folder_commands::git_add_files(params.path, params.files).await?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn git_add_remote(
|
||||
Json(params): Json<PathNameUrlParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
folder_commands::git_add_remote(params.path, params.name, params.url).await?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn git_remove_remote(
|
||||
Json(params): Json<PathNameParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
folder_commands::git_remove_remote(params.path, params.name).await?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn git_set_remote_url(
|
||||
Json(params): Json<PathNameUrlParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
folder_commands::git_set_remote_url(params.path, params.name, params.url).await?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn git_merge(
|
||||
Json(params): Json<PathBranchParams>,
|
||||
) -> Result<Json<folder_commands::GitMergeResult>, AppCommandError> {
|
||||
let result =
|
||||
folder_commands::git_merge(params.path, params.branch_name).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn git_rebase(
|
||||
Json(params): Json<PathBranchParams>,
|
||||
) -> Result<Json<folder_commands::GitRebaseResult>, AppCommandError> {
|
||||
let result =
|
||||
folder_commands::git_rebase(params.path, params.branch_name).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitDeleteBranchParams {
|
||||
pub path: String,
|
||||
pub branch_name: String,
|
||||
pub force: bool,
|
||||
}
|
||||
|
||||
pub async fn git_delete_branch(
|
||||
Json(params): Json<GitDeleteBranchParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
folder_commands::git_delete_branch(params.path, params.branch_name, params.force)
|
||||
.await?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn git_list_conflicts(
|
||||
Json(params): Json<PathParams>,
|
||||
) -> Result<Json<Vec<String>>, AppCommandError> {
|
||||
let result = folder_commands::git_list_conflicts(params.path).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn git_conflict_file_versions(
|
||||
Json(params): Json<PathFileParams>,
|
||||
) -> Result<Json<folder_commands::GitConflictFileVersions>, AppCommandError> {
|
||||
let result =
|
||||
folder_commands::git_conflict_file_versions(params.path, params.file).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn git_resolve_conflict(
|
||||
Json(params): Json<PathFileContentParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
folder_commands::git_resolve_conflict(params.path, params.file, params.content)
|
||||
.await?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn git_abort_operation(
|
||||
Json(params): Json<PathOperationParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
folder_commands::git_abort_operation(params.path, params.operation).await?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn git_continue_operation(
|
||||
Json(params): Json<PathOperationParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
folder_commands::git_continue_operation(params.path, params.operation).await?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Remote git handlers (Pattern B – need AppHandle for DB access)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitPullParams {
|
||||
pub path: String,
|
||||
pub credentials: Option<GitCredentials>,
|
||||
}
|
||||
|
||||
pub async fn git_pull(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<GitPullParams>,
|
||||
) -> Result<Json<folder_commands::GitPullResult>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
let result = folder_commands::git_pull_core(
|
||||
¶ms.path,
|
||||
params.credentials.as_ref(),
|
||||
&db,
|
||||
&app,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitFetchParams {
|
||||
pub path: String,
|
||||
pub credentials: Option<GitCredentials>,
|
||||
}
|
||||
|
||||
pub async fn git_fetch(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<GitFetchParams>,
|
||||
) -> Result<Json<String>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
let result = folder_commands::git_fetch_core(
|
||||
¶ms.path,
|
||||
params.credentials.as_ref(),
|
||||
&db,
|
||||
&app,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitPushParams {
|
||||
pub folder_id: Option<i32>,
|
||||
pub path: String,
|
||||
pub remote: Option<String>,
|
||||
pub credentials: Option<GitCredentials>,
|
||||
}
|
||||
|
||||
pub async fn git_push(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<GitPushParams>,
|
||||
) -> Result<Json<folder_commands::GitPushResult>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
let result = folder_commands::git_push_core(
|
||||
&app,
|
||||
params.folder_id,
|
||||
¶ms.path,
|
||||
params.remote.as_deref(),
|
||||
params.credentials.as_ref(),
|
||||
&db,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitCommitParams {
|
||||
pub folder_id: Option<i32>,
|
||||
pub path: String,
|
||||
pub message: String,
|
||||
pub files: Vec<String>,
|
||||
}
|
||||
|
||||
pub async fn git_commit(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<GitCommitParams>,
|
||||
) -> Result<Json<folder_commands::GitCommitResult>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
let result = folder_commands::git_commit_core(
|
||||
&app,
|
||||
params.folder_id,
|
||||
&db.conn,
|
||||
¶ms.path,
|
||||
¶ms.message,
|
||||
¶ms.files,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitFetchRemoteParams {
|
||||
pub path: String,
|
||||
pub name: String,
|
||||
pub credentials: Option<GitCredentials>,
|
||||
}
|
||||
|
||||
pub async fn git_fetch_remote(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<GitFetchRemoteParams>,
|
||||
) -> Result<Json<String>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
let result = folder_commands::git_fetch_remote_core(
|
||||
¶ms.path,
|
||||
¶ms.name,
|
||||
params.credentials.as_ref(),
|
||||
&db,
|
||||
&app,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CloneRepositoryParams {
|
||||
pub url: String,
|
||||
pub target_dir: String,
|
||||
pub credentials: Option<GitCredentials>,
|
||||
}
|
||||
|
||||
pub async fn clone_repository(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<CloneRepositoryParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
folder_commands::clone_repository_core(
|
||||
¶ms.url,
|
||||
¶ms.target_dir,
|
||||
params.credentials.as_ref(),
|
||||
&db,
|
||||
&app,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
@@ -1,2 +1,145 @@
|
||||
// MCP (Model Context Protocol) web handlers.
|
||||
// TODO: Implement MCP marketplace and server management handlers for web mode.
|
||||
use axum::Json;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::app_error::AppCommandError;
|
||||
use crate::commands::mcp as mcp_commands;
|
||||
use crate::commands::mcp::{
|
||||
LocalMcpServer, McpAppType, McpMarketplaceItem, McpMarketplaceProvider,
|
||||
McpMarketplaceServerDetail,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Param structs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SearchMarketplaceParams {
|
||||
pub provider_id: String,
|
||||
pub query: Option<String>,
|
||||
pub limit: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetMarketplaceServerDetailParams {
|
||||
pub provider_id: String,
|
||||
pub server_id: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InstallFromMarketplaceParams {
|
||||
pub provider_id: String,
|
||||
pub server_id: String,
|
||||
pub apps: Vec<McpAppType>,
|
||||
pub spec_override: Option<Value>,
|
||||
pub option_id: Option<String>,
|
||||
pub protocol: Option<String>,
|
||||
pub parameter_values: Option<Value>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UpsertLocalServerParams {
|
||||
pub server_id: String,
|
||||
pub spec: Value,
|
||||
pub apps: Vec<McpAppType>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SetServerAppsParams {
|
||||
pub server_id: String,
|
||||
pub apps: Vec<McpAppType>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RemoveServerParams {
|
||||
pub server_id: String,
|
||||
pub apps: Option<Vec<McpAppType>>,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Handlers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub async fn mcp_scan_local() -> Result<Json<Vec<LocalMcpServer>>, AppCommandError> {
|
||||
let result = mcp_commands::mcp_scan_local().await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn mcp_list_marketplaces(
|
||||
) -> Result<Json<Vec<McpMarketplaceProvider>>, AppCommandError> {
|
||||
let result = mcp_commands::mcp_list_marketplaces().await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn mcp_search_marketplace(
|
||||
Json(params): Json<SearchMarketplaceParams>,
|
||||
) -> Result<Json<Vec<McpMarketplaceItem>>, AppCommandError> {
|
||||
let result = mcp_commands::mcp_search_marketplace(
|
||||
params.provider_id,
|
||||
params.query,
|
||||
params.limit,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn mcp_get_marketplace_server_detail(
|
||||
Json(params): Json<GetMarketplaceServerDetailParams>,
|
||||
) -> Result<Json<McpMarketplaceServerDetail>, AppCommandError> {
|
||||
let result = mcp_commands::mcp_get_marketplace_server_detail(
|
||||
params.provider_id,
|
||||
params.server_id,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn mcp_install_from_marketplace(
|
||||
Json(params): Json<InstallFromMarketplaceParams>,
|
||||
) -> Result<Json<LocalMcpServer>, AppCommandError> {
|
||||
let result = mcp_commands::mcp_install_from_marketplace(
|
||||
params.provider_id,
|
||||
params.server_id,
|
||||
params.apps,
|
||||
params.spec_override,
|
||||
params.option_id,
|
||||
params.protocol,
|
||||
params.parameter_values,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn mcp_upsert_local_server(
|
||||
Json(params): Json<UpsertLocalServerParams>,
|
||||
) -> Result<Json<LocalMcpServer>, AppCommandError> {
|
||||
let result = mcp_commands::mcp_upsert_local_server(
|
||||
params.server_id,
|
||||
params.spec,
|
||||
params.apps,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn mcp_set_server_apps(
|
||||
Json(params): Json<SetServerAppsParams>,
|
||||
) -> Result<Json<Option<LocalMcpServer>>, AppCommandError> {
|
||||
let result =
|
||||
mcp_commands::mcp_set_server_apps(params.server_id, params.apps).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn mcp_remove_server(
|
||||
Json(params): Json<RemoveServerParams>,
|
||||
) -> Result<Json<bool>, AppCommandError> {
|
||||
let result =
|
||||
mcp_commands::mcp_remove_server(params.server_id, params.apps).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
mod error;
|
||||
pub mod conversations;
|
||||
pub mod files;
|
||||
pub mod folders;
|
||||
pub mod acp;
|
||||
pub mod terminal;
|
||||
@@ -7,3 +8,4 @@ pub mod system_settings;
|
||||
pub mod version_control;
|
||||
pub mod folder_commands;
|
||||
pub mod mcp;
|
||||
pub mod git;
|
||||
|
||||
@@ -2,24 +2,25 @@ use axum::{extract::Extension, Json};
|
||||
use tauri::Manager;
|
||||
|
||||
use crate::app_error::AppCommandError;
|
||||
use crate::commands::system_settings as settings_commands;
|
||||
use crate::db::service::app_metadata_service;
|
||||
use crate::db::AppDatabase;
|
||||
use crate::models::*;
|
||||
use crate::network::proxy;
|
||||
|
||||
const SYSTEM_PROXY_SETTINGS_KEY: &str = "system_proxy_settings";
|
||||
const SYSTEM_LANGUAGE_SETTINGS_KEY: &str = "system_language_settings";
|
||||
const LANGUAGE_SETTINGS_UPDATED_EVENT: &str = "app://language-settings-updated";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Read handlers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub async fn get_system_proxy_settings(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
) -> Result<Json<SystemProxySettings>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
let raw = app_metadata_service::get_value(&db.conn, SYSTEM_PROXY_SETTINGS_KEY)
|
||||
.await
|
||||
.map_err(AppCommandError::from)?;
|
||||
|
||||
let settings = raw
|
||||
.and_then(|v| serde_json::from_str::<SystemProxySettings>(&v).ok())
|
||||
.unwrap_or_default();
|
||||
let settings = settings_commands::load_system_proxy_settings(&db.conn).await?;
|
||||
Ok(Json(settings))
|
||||
}
|
||||
|
||||
@@ -27,12 +28,64 @@ pub async fn get_system_language_settings(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
) -> Result<Json<SystemLanguageSettings>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
let raw = app_metadata_service::get_value(&db.conn, SYSTEM_LANGUAGE_SETTINGS_KEY)
|
||||
.await
|
||||
.map_err(AppCommandError::from)?;
|
||||
|
||||
let settings = raw
|
||||
.and_then(|v| serde_json::from_str::<SystemLanguageSettings>(&v).ok())
|
||||
.unwrap_or_default();
|
||||
let settings =
|
||||
settings_commands::load_system_language_settings(&db.conn).await?;
|
||||
Ok(Json(settings))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Update handlers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub async fn update_system_proxy_settings(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(settings): Json<SystemProxySettings>,
|
||||
) -> Result<Json<SystemProxySettings>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
|
||||
// TODO: call normalize_proxy_settings once it is made pub(crate) in
|
||||
// commands/system_settings.rs. For now the frontend validates the URL.
|
||||
let serialized = serde_json::to_string(&settings).map_err(|e| {
|
||||
AppCommandError::invalid_input("Failed to serialize proxy settings")
|
||||
.with_detail(e.to_string())
|
||||
})?;
|
||||
|
||||
app_metadata_service::upsert_value(
|
||||
&db.conn,
|
||||
SYSTEM_PROXY_SETTINGS_KEY,
|
||||
&serialized,
|
||||
)
|
||||
.await
|
||||
.map_err(AppCommandError::from)?;
|
||||
|
||||
proxy::apply_system_proxy_settings(&settings)?;
|
||||
Ok(Json(settings))
|
||||
}
|
||||
|
||||
pub async fn update_system_language_settings(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(settings): Json<SystemLanguageSettings>,
|
||||
) -> Result<Json<SystemLanguageSettings>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
|
||||
let serialized = serde_json::to_string(&settings).map_err(|e| {
|
||||
AppCommandError::invalid_input("Failed to serialize language settings")
|
||||
.with_detail(e.to_string())
|
||||
})?;
|
||||
|
||||
app_metadata_service::upsert_value(
|
||||
&db.conn,
|
||||
SYSTEM_LANGUAGE_SETTINGS_KEY,
|
||||
&serialized,
|
||||
)
|
||||
.await
|
||||
.map_err(AppCommandError::from)?;
|
||||
|
||||
crate::web::event_bridge::emit_event(
|
||||
&app,
|
||||
LANGUAGE_SETTINGS_UPDATED_EVENT,
|
||||
settings.clone(),
|
||||
);
|
||||
|
||||
Ok(Json(settings))
|
||||
}
|
||||
|
||||
@@ -5,6 +5,11 @@ use tauri::Manager;
|
||||
use crate::app_error::AppCommandError;
|
||||
use crate::commands::terminal::prepare_credential_env;
|
||||
use crate::terminal::manager::{SpawnOptions, TerminalManager};
|
||||
use crate::terminal::types::TerminalInfo;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Param structs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -13,6 +18,31 @@ pub struct TerminalSpawnParams {
|
||||
pub initial_command: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TerminalIdParams {
|
||||
pub terminal_id: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TerminalWriteParams {
|
||||
pub terminal_id: String,
|
||||
pub data: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TerminalResizeParams {
|
||||
pub terminal_id: String,
|
||||
pub cols: u16,
|
||||
pub rows: u16,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Handlers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub async fn terminal_spawn(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<TerminalSpawnParams>,
|
||||
@@ -43,3 +73,44 @@ pub async fn terminal_spawn(
|
||||
|
||||
Ok(Json(id))
|
||||
}
|
||||
|
||||
pub async fn terminal_write(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<TerminalWriteParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
let manager = app.state::<TerminalManager>();
|
||||
manager
|
||||
.write(¶ms.terminal_id, params.data.as_bytes())
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn terminal_resize(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<TerminalResizeParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
let manager = app.state::<TerminalManager>();
|
||||
manager
|
||||
.resize(¶ms.terminal_id, params.cols, params.rows)
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn terminal_kill(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(params): Json<TerminalIdParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
let manager = app.state::<TerminalManager>();
|
||||
manager
|
||||
.kill(¶ms.terminal_id)
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn terminal_list(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
) -> Result<Json<Vec<TerminalInfo>>, AppCommandError> {
|
||||
let manager = app.state::<TerminalManager>();
|
||||
let result = manager.list_with_exit_check(Some(&app));
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
@@ -1,22 +1,178 @@
|
||||
use axum::Json;
|
||||
use axum::{extract::Extension, Json};
|
||||
use serde::Deserialize;
|
||||
use tauri::Manager;
|
||||
|
||||
use crate::app_error::AppCommandError;
|
||||
use crate::commands::folders as folder_commands;
|
||||
use crate::commands::version_control as vc_commands;
|
||||
use crate::db::service::app_metadata_service;
|
||||
use crate::db::AppDatabase;
|
||||
use crate::models::*;
|
||||
|
||||
const GIT_SETTINGS_KEY: &str = "git_settings";
|
||||
const GITHUB_ACCOUNTS_KEY: &str = "github_accounts";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Param structs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GitLogParams {
|
||||
pub struct TestGitPathParams {
|
||||
pub path: String,
|
||||
pub limit: Option<u32>,
|
||||
pub branch: Option<String>,
|
||||
pub remote: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn git_log(
|
||||
Json(params): Json<GitLogParams>,
|
||||
) -> Result<Json<folder_commands::GitLogResult>, AppCommandError> {
|
||||
let result =
|
||||
folder_commands::git_log(params.path, params.limit, params.branch, params.remote).await?;
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ValidateGitHubTokenParams {
|
||||
pub server_url: String,
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AccountIdParams {
|
||||
pub account_id: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SaveAccountTokenParams {
|
||||
pub account_id: String,
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Git detection
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub async fn detect_git(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
) -> Result<Json<GitDetectResult>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
let result = vc_commands::detect_git_core(&db.conn).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn test_git_path(
|
||||
Json(params): Json<TestGitPathParams>,
|
||||
) -> Result<Json<GitDetectResult>, AppCommandError> {
|
||||
let result = vc_commands::test_git_path(params.path).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Git settings
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub async fn get_git_settings(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
) -> Result<Json<GitSettings>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
let raw = app_metadata_service::get_value(&db.conn, GIT_SETTINGS_KEY)
|
||||
.await
|
||||
.map_err(AppCommandError::from)?;
|
||||
|
||||
let settings = match raw {
|
||||
Some(raw) => serde_json::from_str::<GitSettings>(&raw).map_err(|e| {
|
||||
AppCommandError::configuration_invalid("Failed to parse stored git settings")
|
||||
.with_detail(e.to_string())
|
||||
})?,
|
||||
None => GitSettings::default(),
|
||||
};
|
||||
Ok(Json(settings))
|
||||
}
|
||||
|
||||
pub async fn update_git_settings(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(settings): Json<GitSettings>,
|
||||
) -> Result<Json<GitSettings>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
let serialized = serde_json::to_string(&settings).map_err(|e| {
|
||||
AppCommandError::invalid_input("Failed to serialize git settings")
|
||||
.with_detail(e.to_string())
|
||||
})?;
|
||||
|
||||
app_metadata_service::upsert_value(&db.conn, GIT_SETTINGS_KEY, &serialized)
|
||||
.await
|
||||
.map_err(AppCommandError::from)?;
|
||||
|
||||
Ok(Json(settings))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GitHub accounts
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub async fn get_github_accounts(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
) -> Result<Json<GitHubAccountsSettings>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
let raw = app_metadata_service::get_value(&db.conn, GITHUB_ACCOUNTS_KEY)
|
||||
.await
|
||||
.map_err(AppCommandError::from)?;
|
||||
|
||||
let settings = match raw {
|
||||
Some(raw) => serde_json::from_str::<GitHubAccountsSettings>(&raw).map_err(|e| {
|
||||
AppCommandError::configuration_invalid(
|
||||
"Failed to parse stored GitHub accounts",
|
||||
)
|
||||
.with_detail(e.to_string())
|
||||
})?,
|
||||
None => GitHubAccountsSettings::default(),
|
||||
};
|
||||
Ok(Json(settings))
|
||||
}
|
||||
|
||||
pub async fn update_github_accounts(
|
||||
Extension(app): Extension<tauri::AppHandle>,
|
||||
Json(settings): Json<GitHubAccountsSettings>,
|
||||
) -> Result<Json<GitHubAccountsSettings>, AppCommandError> {
|
||||
let db = app.state::<AppDatabase>();
|
||||
let serialized = serde_json::to_string(&settings).map_err(|e| {
|
||||
AppCommandError::invalid_input("Failed to serialize GitHub accounts")
|
||||
.with_detail(e.to_string())
|
||||
})?;
|
||||
|
||||
app_metadata_service::upsert_value(&db.conn, GITHUB_ACCOUNTS_KEY, &serialized)
|
||||
.await
|
||||
.map_err(AppCommandError::from)?;
|
||||
|
||||
Ok(Json(settings))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GitHub token validation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub async fn validate_github_token(
|
||||
Json(params): Json<ValidateGitHubTokenParams>,
|
||||
) -> Result<Json<GitHubTokenValidation>, AppCommandError> {
|
||||
let result =
|
||||
vc_commands::validate_github_token(params.server_url, params.token).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Keyring token management
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub async fn save_account_token(
|
||||
Json(params): Json<SaveAccountTokenParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
vc_commands::save_account_token(params.account_id, params.token).await?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
pub async fn get_account_token(
|
||||
Json(params): Json<AccountIdParams>,
|
||||
) -> Result<Json<Option<String>>, AppCommandError> {
|
||||
let result = vc_commands::get_account_token(params.account_id).await?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn delete_account_token(
|
||||
Json(params): Json<AccountIdParams>,
|
||||
) -> Result<Json<()>, AppCommandError> {
|
||||
vc_commands::delete_account_token(params.account_id).await?;
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
@@ -20,9 +20,8 @@ pub fn build_router(app: tauri::AppHandle, token: String, static_dir: std::path:
|
||||
let token_for_ws = token.clone();
|
||||
|
||||
let api = Router::new()
|
||||
// Health check (lightweight, used for token validation)
|
||||
.route("/health", post(health_check))
|
||||
// Conversations
|
||||
// ─── Conversations ───
|
||||
.route("/list_conversations", post(handlers::conversations::list_conversations))
|
||||
.route("/get_conversation", post(handlers::conversations::get_conversation))
|
||||
.route("/list_folder_conversations", post(handlers::conversations::list_folder_conversations))
|
||||
@@ -36,45 +35,143 @@ pub fn build_router(app: tauri::AppHandle, token: String, static_dir: std::path:
|
||||
.route("/update_conversation_title", post(handlers::conversations::update_conversation_title))
|
||||
.route("/delete_conversation", post(handlers::conversations::delete_conversation))
|
||||
.route("/update_conversation_external_id", post(handlers::conversations::update_conversation_external_id))
|
||||
// Folders
|
||||
// ─── Folders ───
|
||||
.route("/load_folder_history", post(handlers::folders::load_folder_history))
|
||||
.route("/get_folder", post(handlers::folders::get_folder))
|
||||
.route("/open_folder_window", post(handlers::folders::open_folder_window))
|
||||
// System settings
|
||||
.route("/get_system_proxy_settings", post(handlers::system_settings::get_system_proxy_settings))
|
||||
.route("/get_system_language_settings", post(handlers::system_settings::get_system_language_settings))
|
||||
// Folders (extended)
|
||||
.route("/add_folder_to_history", post(handlers::folders::add_folder_to_history))
|
||||
.route("/set_folder_parent_branch", post(handlers::folders::set_folder_parent_branch))
|
||||
.route("/remove_folder_from_history", post(handlers::folders::remove_folder_from_history))
|
||||
.route("/create_folder_directory", post(handlers::folders::create_folder_directory))
|
||||
.route("/save_folder_opened_conversations", post(handlers::folders::save_folder_opened_conversations))
|
||||
.route("/get_git_branch", post(handlers::folders::get_git_branch))
|
||||
.route("/get_file_tree", post(handlers::folders::get_file_tree))
|
||||
.route("/start_file_tree_watch", post(handlers::folders::start_file_tree_watch))
|
||||
.route("/stop_file_tree_watch", post(handlers::folders::stop_file_tree_watch))
|
||||
// ─── Window navigation ───
|
||||
.route("/open_settings_window", post(handlers::folders::open_settings_window))
|
||||
// Version control
|
||||
.route("/git_log", post(handlers::version_control::git_log))
|
||||
// Folder commands
|
||||
.route("/list_folder_commands", post(handlers::folder_commands::list_folder_commands))
|
||||
// Git operations
|
||||
.route("/git_status", post(handlers::folders::git_status))
|
||||
.route("/git_list_all_branches", post(handlers::folders::git_list_all_branches))
|
||||
.route("/git_commit_branches", post(handlers::folders::git_commit_branches))
|
||||
.route("/git_show_file", post(handlers::folders::git_show_file))
|
||||
.route("/git_diff", post(handlers::folders::git_diff))
|
||||
.route("/git_list_remotes", post(handlers::folders::git_list_remotes))
|
||||
.route("/open_commit_window", post(handlers::folders::open_commit_window))
|
||||
// File operations
|
||||
.route("/read_file_preview", post(handlers::folders::read_file_preview))
|
||||
// ACP
|
||||
.route("/open_merge_window", post(handlers::folders::open_merge_window))
|
||||
.route("/open_stash_window", post(handlers::folders::open_stash_window))
|
||||
.route("/open_push_window", post(handlers::folders::open_push_window))
|
||||
// ─── Git (pure) ───
|
||||
.route("/git_status", post(handlers::git::git_status))
|
||||
.route("/git_init", post(handlers::git::git_init))
|
||||
.route("/git_log", post(handlers::git::git_log))
|
||||
.route("/git_list_all_branches", post(handlers::git::git_list_all_branches))
|
||||
.route("/git_list_branches", post(handlers::git::git_list_branches))
|
||||
.route("/git_commit_branches", post(handlers::git::git_commit_branches))
|
||||
.route("/git_show_file", post(handlers::git::git_show_file))
|
||||
.route("/git_diff", post(handlers::git::git_diff))
|
||||
.route("/git_diff_with_branch", post(handlers::git::git_diff_with_branch))
|
||||
.route("/git_show_diff", post(handlers::git::git_show_diff))
|
||||
.route("/git_list_remotes", post(handlers::git::git_list_remotes))
|
||||
.route("/git_add_remote", post(handlers::git::git_add_remote))
|
||||
.route("/git_remove_remote", post(handlers::git::git_remove_remote))
|
||||
.route("/git_set_remote_url", post(handlers::git::git_set_remote_url))
|
||||
.route("/git_new_branch", post(handlers::git::git_new_branch))
|
||||
.route("/git_checkout", post(handlers::git::git_checkout))
|
||||
.route("/git_delete_branch", post(handlers::git::git_delete_branch))
|
||||
.route("/git_merge", post(handlers::git::git_merge))
|
||||
.route("/git_rebase", post(handlers::git::git_rebase))
|
||||
.route("/git_worktree_add", post(handlers::git::git_worktree_add))
|
||||
.route("/git_push_info", post(handlers::git::git_push_info))
|
||||
.route("/git_start_pull_merge", post(handlers::git::git_start_pull_merge))
|
||||
.route("/git_has_merge_head", post(handlers::git::git_has_merge_head))
|
||||
.route("/git_is_tracked", post(handlers::git::git_is_tracked))
|
||||
.route("/git_rollback_file", post(handlers::git::git_rollback_file))
|
||||
.route("/git_add_files", post(handlers::git::git_add_files))
|
||||
.route("/git_list_conflicts", post(handlers::git::git_list_conflicts))
|
||||
.route("/git_conflict_file_versions", post(handlers::git::git_conflict_file_versions))
|
||||
.route("/git_resolve_conflict", post(handlers::git::git_resolve_conflict))
|
||||
.route("/git_abort_operation", post(handlers::git::git_abort_operation))
|
||||
.route("/git_continue_operation", post(handlers::git::git_continue_operation))
|
||||
.route("/git_stash_push", post(handlers::git::git_stash_push))
|
||||
.route("/git_stash_pop", post(handlers::git::git_stash_pop))
|
||||
.route("/git_stash_list", post(handlers::git::git_stash_list))
|
||||
.route("/git_stash_apply", post(handlers::git::git_stash_apply))
|
||||
.route("/git_stash_drop", post(handlers::git::git_stash_drop))
|
||||
.route("/git_stash_clear", post(handlers::git::git_stash_clear))
|
||||
.route("/git_stash_show", post(handlers::git::git_stash_show))
|
||||
// ─── Git (remote) ───
|
||||
.route("/git_pull", post(handlers::git::git_pull))
|
||||
.route("/git_push", post(handlers::git::git_push))
|
||||
.route("/git_fetch", post(handlers::git::git_fetch))
|
||||
.route("/git_commit", post(handlers::git::git_commit))
|
||||
.route("/git_fetch_remote", post(handlers::git::git_fetch_remote))
|
||||
.route("/clone_repository", post(handlers::git::clone_repository))
|
||||
// ─── Files ───
|
||||
.route("/read_file_preview", post(handlers::files::read_file_preview))
|
||||
.route("/read_file_base64", post(handlers::files::read_file_base64))
|
||||
.route("/read_file_for_edit", post(handlers::files::read_file_for_edit))
|
||||
.route("/save_file_content", post(handlers::files::save_file_content))
|
||||
.route("/save_file_copy", post(handlers::files::save_file_copy))
|
||||
.route("/rename_file_tree_entry", post(handlers::files::rename_file_tree_entry))
|
||||
.route("/delete_file_tree_entry", post(handlers::files::delete_file_tree_entry))
|
||||
.route("/create_file_tree_entry", post(handlers::files::create_file_tree_entry))
|
||||
// ─── Folder commands ───
|
||||
.route("/list_folder_commands", post(handlers::folder_commands::list_folder_commands))
|
||||
.route("/create_folder_command", post(handlers::folder_commands::create_folder_command))
|
||||
.route("/update_folder_command", post(handlers::folder_commands::update_folder_command))
|
||||
.route("/delete_folder_command", post(handlers::folder_commands::delete_folder_command))
|
||||
.route("/reorder_folder_commands", post(handlers::folder_commands::reorder_folder_commands))
|
||||
// ─── MCP ───
|
||||
.route("/mcp_scan_local", post(handlers::mcp::mcp_scan_local))
|
||||
.route("/mcp_list_marketplaces", post(handlers::mcp::mcp_list_marketplaces))
|
||||
.route("/mcp_search_marketplace", post(handlers::mcp::mcp_search_marketplace))
|
||||
.route("/mcp_get_marketplace_server_detail", post(handlers::mcp::mcp_get_marketplace_server_detail))
|
||||
.route("/mcp_install_from_marketplace", post(handlers::mcp::mcp_install_from_marketplace))
|
||||
.route("/mcp_upsert_local_server", post(handlers::mcp::mcp_upsert_local_server))
|
||||
.route("/mcp_set_server_apps", post(handlers::mcp::mcp_set_server_apps))
|
||||
.route("/mcp_remove_server", post(handlers::mcp::mcp_remove_server))
|
||||
// ─── Version control settings ───
|
||||
.route("/detect_git", post(handlers::version_control::detect_git))
|
||||
.route("/test_git_path", post(handlers::version_control::test_git_path))
|
||||
.route("/get_git_settings", post(handlers::version_control::get_git_settings))
|
||||
.route("/update_git_settings", post(handlers::version_control::update_git_settings))
|
||||
.route("/get_github_accounts", post(handlers::version_control::get_github_accounts))
|
||||
.route("/update_github_accounts", post(handlers::version_control::update_github_accounts))
|
||||
.route("/validate_github_token", post(handlers::version_control::validate_github_token))
|
||||
.route("/save_account_token", post(handlers::version_control::save_account_token))
|
||||
.route("/get_account_token", post(handlers::version_control::get_account_token))
|
||||
.route("/delete_account_token", post(handlers::version_control::delete_account_token))
|
||||
// ─── System settings ───
|
||||
.route("/get_system_proxy_settings", post(handlers::system_settings::get_system_proxy_settings))
|
||||
.route("/get_system_language_settings", post(handlers::system_settings::get_system_language_settings))
|
||||
.route("/update_system_proxy_settings", post(handlers::system_settings::update_system_proxy_settings))
|
||||
.route("/update_system_language_settings", post(handlers::system_settings::update_system_language_settings))
|
||||
// ─── ACP ───
|
||||
.route("/acp_get_agent_status", post(handlers::acp::acp_get_agent_status))
|
||||
.route("/acp_list_agents", post(handlers::acp::acp_list_agents))
|
||||
.route("/acp_connect", post(handlers::acp::acp_connect))
|
||||
.route("/acp_disconnect", post(handlers::acp::acp_disconnect))
|
||||
.route("/acp_prompt", post(handlers::acp::acp_prompt))
|
||||
// Terminal
|
||||
.route("/acp_preflight", post(handlers::acp::acp_preflight))
|
||||
.route("/acp_set_mode", post(handlers::acp::acp_set_mode))
|
||||
.route("/acp_set_config_option", post(handlers::acp::acp_set_config_option))
|
||||
.route("/acp_cancel", post(handlers::acp::acp_cancel))
|
||||
.route("/acp_fork", post(handlers::acp::acp_fork))
|
||||
.route("/acp_respond_permission", post(handlers::acp::acp_respond_permission))
|
||||
.route("/acp_list_connections", post(handlers::acp::acp_list_connections))
|
||||
.route("/acp_clear_binary_cache", post(handlers::acp::acp_clear_binary_cache))
|
||||
.route("/acp_update_agent_preferences", post(handlers::acp::acp_update_agent_preferences))
|
||||
.route("/acp_download_agent_binary", post(handlers::acp::acp_download_agent_binary))
|
||||
.route("/acp_detect_agent_local_version", post(handlers::acp::acp_detect_agent_local_version))
|
||||
.route("/acp_prepare_npx_agent", post(handlers::acp::acp_prepare_npx_agent))
|
||||
.route("/acp_uninstall_agent", post(handlers::acp::acp_uninstall_agent))
|
||||
.route("/acp_reorder_agents", post(handlers::acp::acp_reorder_agents))
|
||||
.route("/acp_list_agent_skills", post(handlers::acp::acp_list_agent_skills))
|
||||
.route("/acp_read_agent_skill", post(handlers::acp::acp_read_agent_skill))
|
||||
.route("/acp_save_agent_skill", post(handlers::acp::acp_save_agent_skill))
|
||||
.route("/acp_delete_agent_skill", post(handlers::acp::acp_delete_agent_skill))
|
||||
// ─── Terminal ───
|
||||
.route("/terminal_spawn", post(handlers::terminal::terminal_spawn))
|
||||
// Catch-all: return proper JSON 404 for unimplemented API endpoints
|
||||
.route("/terminal_write", post(handlers::terminal::terminal_write))
|
||||
.route("/terminal_resize", post(handlers::terminal::terminal_resize))
|
||||
.route("/terminal_kill", post(handlers::terminal::terminal_kill))
|
||||
.route("/terminal_list", post(handlers::terminal::terminal_list))
|
||||
// Catch-all
|
||||
.fallback(api_not_found)
|
||||
// Auth middleware for API routes
|
||||
.layer(middleware::from_fn(move |req, next| {
|
||||
auth::require_token(req, next, token.clone())
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user