mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 19:04:23 +08:00
修复快速设置学生丢失并新增重置入口
This commit is contained in:
+161
-40
@@ -1,62 +1,183 @@
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
use tauri::State;
|
||||
use tauri::{AppHandle, Emitter, State};
|
||||
|
||||
use crate::db::connection::{create_sqlite_connection, DatabaseType};
|
||||
use crate::db::migration::Migration;
|
||||
use crate::services::settings::{SettingsKey, SettingsValue, SettingsService};
|
||||
use crate::services::{AutoScoreService, ThemeService};
|
||||
use crate::services::permission::PermissionLevel;
|
||||
use crate::state::AppState;
|
||||
|
||||
use super::response::IpcResponse;
|
||||
|
||||
|
||||
fn check_admin_permission(state: &Arc<RwLock<AppState>>) -> Result<(), String> {
|
||||
let state_guard = state.read();
|
||||
let mut permissions = state_guard.permissions.write();
|
||||
let sender_id = 0;
|
||||
if !permissions.require_permission(sender_id, PermissionLevel::Admin) {
|
||||
return Err("Permission denied: Admin required".to_string());
|
||||
}
|
||||
let state_guard = state.read();
|
||||
let mut permissions = state_guard.permissions.write();
|
||||
let sender_id = 0;
|
||||
if !permissions.require_permission(sender_id, PermissionLevel::Admin) {
|
||||
return Err("Permission denied: Admin required".to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn data_export_json(
|
||||
state: State<'_, Arc<RwLock<AppState>>>,
|
||||
) -> Result<IpcResponse<String>, String> {
|
||||
check_admin_permission(&state)?;
|
||||
fn sqlite_db_path(app_handle: &AppHandle) -> Result<String, String> {
|
||||
if cfg!(all(debug_assertions, desktop)) {
|
||||
return Ok("data.sql".to_string());
|
||||
}
|
||||
|
||||
let state_guard = state.read();
|
||||
let settings = state_guard.settings.read();
|
||||
let settings_value = settings.get_all();
|
||||
let settings_json = serde_json::to_value(settings_value)
|
||||
.map_err(|e| format!("Failed to serialize settings: {}", e))?;
|
||||
|
||||
let data_service = state_guard.data.read();
|
||||
let students: Vec<crate::services::data::StudentExport> = Vec::new();
|
||||
let events: Vec<crate::services::data::EventExport> = Vec::new();
|
||||
|
||||
let export_data = data_service.export_json(settings_json, students, events);
|
||||
|
||||
let json_string = serde_json::to_string_pretty(&export_data)
|
||||
.map_err(|e| format!("Failed to serialize export data: {}", e))?;
|
||||
|
||||
Ok(IpcResponse::success(json_string))
|
||||
let app_data_dir = app_handle
|
||||
.path()
|
||||
.app_data_dir()
|
||||
.map_err(|e| format!("Failed to get app data directory: {}", e))?;
|
||||
let data_dir = app_data_dir.join("data");
|
||||
std::fs::create_dir_all(&data_dir)
|
||||
.map_err(|e| format!("Failed to create data directory: {}", e))?;
|
||||
let db_path = data_dir.join("data.sql");
|
||||
db_path
|
||||
.to_str()
|
||||
.map(|s| s.to_string())
|
||||
.ok_or_else(|| "Invalid sqlite database path".to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn data_export_json(
|
||||
state: State<'_, Arc<RwLock<AppState>>>,
|
||||
) -> Result<IpcResponse<String>, String> {
|
||||
check_admin_permission(&state)?;
|
||||
|
||||
let state_guard = state.read();
|
||||
let settings = state_guard.settings.read();
|
||||
let settings_value = settings.get_all();
|
||||
let settings_json = serde_json::to_value(settings_value)
|
||||
.map_err(|e| format!("Failed to serialize settings: {}", e))?;
|
||||
|
||||
let data_service = state_guard.data.read();
|
||||
let students: Vec<crate::services::data::StudentExport> = Vec::new();
|
||||
let events: Vec<crate::services::data::EventExport> = Vec::new();
|
||||
|
||||
let export_data = data_service.export_json(settings_json, students, events);
|
||||
|
||||
let json_string = serde_json::to_string_pretty(&export_data)
|
||||
.map_err(|e| format!("Failed to serialize export data: {}", e))?;
|
||||
|
||||
Ok(IpcResponse::success(json_string))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn data_import_json(
|
||||
json_text: String,
|
||||
state: State<'_, Arc<RwLock<AppState>>>,
|
||||
) -> Result<IpcResponse<()>, String> {
|
||||
check_admin_permission(&state)?;
|
||||
|
||||
let state_guard = state.read();
|
||||
let mut data_service = state_guard.data.write();
|
||||
let result = data_service.import_json(&json_text);
|
||||
|
||||
if result.success {
|
||||
Ok(IpcResponse::success_empty())
|
||||
} else {
|
||||
check_admin_permission(&state)?;
|
||||
|
||||
let state_guard = state.read();
|
||||
let mut data_service = state_guard.data.write();
|
||||
let result = data_service.import_json(&json_text);
|
||||
|
||||
if result.success {
|
||||
Ok(IpcResponse::success_empty())
|
||||
} else {
|
||||
Ok(IpcResponse::error(
|
||||
result.message.as_deref().unwrap_or("Import failed"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn data_reset_all(
|
||||
app_handle: AppHandle,
|
||||
state: State<'_, Arc<RwLock<AppState>>>,
|
||||
) -> Result<IpcResponse<()>, String> {
|
||||
check_admin_permission(&state)?;
|
||||
|
||||
let sqlite_path = sqlite_db_path(&app_handle)?;
|
||||
let sqlite_conn = create_sqlite_connection(&sqlite_path)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to open sqlite database: {}", e))?;
|
||||
|
||||
let (active_conn, active_db_type) = {
|
||||
let state_guard = state.read();
|
||||
let active_conn = state_guard
|
||||
.db
|
||||
.read()
|
||||
.clone()
|
||||
.ok_or_else(|| "Database not connected".to_string())?;
|
||||
let db_type = {
|
||||
let settings = state_guard.settings.read();
|
||||
match settings.get_value(SettingsKey::PgConnectionStatus) {
|
||||
SettingsValue::Json(json) => match json.get("type").and_then(|value| value.as_str()) {
|
||||
Some("postgresql") => DatabaseType::PostgreSQL,
|
||||
_ => DatabaseType::SQLite,
|
||||
},
|
||||
_ => DatabaseType::SQLite,
|
||||
}
|
||||
};
|
||||
(active_conn, db_type)
|
||||
};
|
||||
|
||||
if active_db_type == DatabaseType::PostgreSQL {
|
||||
Migration::reset_database(&active_conn, false)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to reset PostgreSQL database: {}", e))?;
|
||||
}
|
||||
|
||||
Migration::reset_database(&sqlite_conn, true)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to reset SQLite database: {}", e))?;
|
||||
|
||||
{
|
||||
let state_guard = state.read();
|
||||
|
||||
{
|
||||
let mut db = state_guard.db.write();
|
||||
*db = Some(sqlite_conn.clone());
|
||||
}
|
||||
|
||||
{
|
||||
let mut settings = state_guard.settings.write();
|
||||
*settings = SettingsService::new();
|
||||
settings.attach_db(Some(sqlite_conn.clone()));
|
||||
settings
|
||||
.initialize()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to reinitialize settings: {}", e))?;
|
||||
settings
|
||||
.set_value(
|
||||
SettingsKey::PgConnectionStatus,
|
||||
SettingsValue::Json(serde_json::json!({
|
||||
"connected": true,
|
||||
"type": "sqlite"
|
||||
})),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to restore sqlite status: {}", e))?;
|
||||
}
|
||||
|
||||
{
|
||||
let mut permissions = state_guard.permissions.write();
|
||||
permissions.clear_all_permissions();
|
||||
permissions.update_password_status(false, false);
|
||||
}
|
||||
|
||||
{
|
||||
let mut theme = state_guard.theme.write();
|
||||
*theme = ThemeService::new();
|
||||
}
|
||||
|
||||
{
|
||||
let mut auto_score = state_guard.auto_score.write();
|
||||
*auto_score = AutoScoreService::new();
|
||||
}
|
||||
}
|
||||
|
||||
let _ = app_handle.emit(
|
||||
"settings:changed",
|
||||
serde_json::json!({
|
||||
"key": "is_wizard_completed",
|
||||
"value": false
|
||||
}),
|
||||
);
|
||||
|
||||
Ok(IpcResponse::success_empty())
|
||||
}
|
||||
|
||||
+386
-385
@@ -1,388 +1,389 @@
|
||||
pub mod commands;
|
||||
pub mod db;
|
||||
pub mod models;
|
||||
pub mod services;
|
||||
pub mod state;
|
||||
pub mod utils;
|
||||
|
||||
use crate::db::connection::DatabaseType;
|
||||
use crate::db::connection::{create_postgres_connection, create_sqlite_connection};
|
||||
use crate::db::migration::run_migration;
|
||||
use crate::services::settings::{SettingsKey, SettingsValue};
|
||||
use crate::{commands::*, state::AppState};
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
#[cfg(desktop)]
|
||||
use tauri::{
|
||||
image::Image,
|
||||
menu::{Menu, MenuItem},
|
||||
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
|
||||
WindowEvent,
|
||||
};
|
||||
use tauri::{App, Manager};
|
||||
use tokio::time::{timeout, Duration};
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.setup(|app| {
|
||||
let state = AppState::new(app.handle().clone());
|
||||
app.manage(Arc::new(RwLock::new(state)));
|
||||
setup_app(app)?;
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
student_query,
|
||||
student_create,
|
||||
student_update,
|
||||
student_delete,
|
||||
student_import_from_xlsx,
|
||||
tags_get_all,
|
||||
tags_get_by_student,
|
||||
tags_create,
|
||||
tags_delete,
|
||||
tags_update_student_tags,
|
||||
reason_query,
|
||||
reason_create,
|
||||
reason_update,
|
||||
reason_delete,
|
||||
reward_setting_query,
|
||||
reward_setting_create,
|
||||
reward_setting_update,
|
||||
reward_setting_delete,
|
||||
reward_redeem,
|
||||
reward_redemption_query,
|
||||
event_query,
|
||||
event_create,
|
||||
event_delete,
|
||||
event_query_by_student,
|
||||
leaderboard_query,
|
||||
db_settlement_query,
|
||||
db_settlement_create,
|
||||
db_settlement_leaderboard,
|
||||
settings_get_all,
|
||||
settings_get,
|
||||
settings_set,
|
||||
auth_get_status,
|
||||
auth_login,
|
||||
auth_logout,
|
||||
auth_set_passwords,
|
||||
auth_generate_recovery,
|
||||
auth_reset_by_recovery,
|
||||
auth_clear_all,
|
||||
theme_list,
|
||||
theme_current,
|
||||
theme_set,
|
||||
theme_save,
|
||||
theme_delete,
|
||||
auto_score_get_rules,
|
||||
auto_score_add_rule,
|
||||
auto_score_update_rule,
|
||||
auto_score_delete_rule,
|
||||
auto_score_toggle_rule,
|
||||
auto_score_get_status,
|
||||
auto_score_sort_rules,
|
||||
board_query_sql,
|
||||
log_query,
|
||||
log_clear,
|
||||
log_set_level,
|
||||
log_write,
|
||||
pub mod commands;
|
||||
pub mod db;
|
||||
pub mod models;
|
||||
pub mod services;
|
||||
pub mod state;
|
||||
pub mod utils;
|
||||
|
||||
use crate::db::connection::DatabaseType;
|
||||
use crate::db::connection::{create_postgres_connection, create_sqlite_connection};
|
||||
use crate::db::migration::run_migration;
|
||||
use crate::services::settings::{SettingsKey, SettingsValue};
|
||||
use crate::{commands::*, state::AppState};
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
#[cfg(desktop)]
|
||||
use tauri::{
|
||||
image::Image,
|
||||
menu::{Menu, MenuItem},
|
||||
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
|
||||
WindowEvent,
|
||||
};
|
||||
use tauri::{App, Manager};
|
||||
use tokio::time::{timeout, Duration};
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.setup(|app| {
|
||||
let state = AppState::new(app.handle().clone());
|
||||
app.manage(Arc::new(RwLock::new(state)));
|
||||
setup_app(app)?;
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
student_query,
|
||||
student_create,
|
||||
student_update,
|
||||
student_delete,
|
||||
student_import_from_xlsx,
|
||||
tags_get_all,
|
||||
tags_get_by_student,
|
||||
tags_create,
|
||||
tags_delete,
|
||||
tags_update_student_tags,
|
||||
reason_query,
|
||||
reason_create,
|
||||
reason_update,
|
||||
reason_delete,
|
||||
reward_setting_query,
|
||||
reward_setting_create,
|
||||
reward_setting_update,
|
||||
reward_setting_delete,
|
||||
reward_redeem,
|
||||
reward_redemption_query,
|
||||
event_query,
|
||||
event_create,
|
||||
event_delete,
|
||||
event_query_by_student,
|
||||
leaderboard_query,
|
||||
db_settlement_query,
|
||||
db_settlement_create,
|
||||
db_settlement_leaderboard,
|
||||
settings_get_all,
|
||||
settings_get,
|
||||
settings_set,
|
||||
auth_get_status,
|
||||
auth_login,
|
||||
auth_logout,
|
||||
auth_set_passwords,
|
||||
auth_generate_recovery,
|
||||
auth_reset_by_recovery,
|
||||
auth_clear_all,
|
||||
theme_list,
|
||||
theme_current,
|
||||
theme_set,
|
||||
theme_save,
|
||||
theme_delete,
|
||||
auto_score_get_rules,
|
||||
auto_score_add_rule,
|
||||
auto_score_update_rule,
|
||||
auto_score_delete_rule,
|
||||
auto_score_toggle_rule,
|
||||
auto_score_get_status,
|
||||
auto_score_sort_rules,
|
||||
board_query_sql,
|
||||
log_query,
|
||||
log_clear,
|
||||
log_set_level,
|
||||
log_write,
|
||||
data_export_json,
|
||||
data_import_json,
|
||||
data_reset_all,
|
||||
window_minimize,
|
||||
window_maximize,
|
||||
window_close,
|
||||
window_is_maximized,
|
||||
toggle_devtools,
|
||||
window_resize,
|
||||
window_set_resizable,
|
||||
db_test_connection,
|
||||
db_switch_connection,
|
||||
db_get_status,
|
||||
db_sync,
|
||||
db_sync_preview,
|
||||
db_sync_apply,
|
||||
fs_get_config_structure,
|
||||
fs_read_json,
|
||||
fs_write_json,
|
||||
fs_read_text,
|
||||
fs_write_text,
|
||||
fs_delete_file,
|
||||
fs_list_files,
|
||||
fs_file_exists,
|
||||
http_server_start,
|
||||
http_server_stop,
|
||||
http_server_status,
|
||||
mcp_server_start,
|
||||
mcp_server_stop,
|
||||
mcp_server_status,
|
||||
register_url_protocol,
|
||||
app_quit,
|
||||
app_restart,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
pub fn setup_app(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
setup_database(app)?;
|
||||
|
||||
setup_tray(app)?;
|
||||
|
||||
setup_window_events(app)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_database(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
const DB_CONNECT_TIMEOUT_SECS: u64 = 15;
|
||||
const DB_MIGRATION_TIMEOUT_SECS: u64 = 20;
|
||||
|
||||
let handle = app.handle().clone();
|
||||
let db_path = if cfg!(all(debug_assertions, desktop)) {
|
||||
std::path::PathBuf::from("data.sql")
|
||||
} else {
|
||||
let app_data_dir = handle
|
||||
.path()
|
||||
.app_data_dir()
|
||||
.map_err(|e| format!("Failed to get app data directory: {}", e))?;
|
||||
let data_dir = app_data_dir.join("data");
|
||||
std::fs::create_dir_all(&data_dir)
|
||||
.map_err(|e| format!("Failed to create data directory: {}", e))?;
|
||||
data_dir.join("data.sql")
|
||||
};
|
||||
|
||||
let db_path_str = db_path.to_str().ok_or("Invalid database path")?.to_string();
|
||||
|
||||
let db_result = tauri::async_runtime::block_on(async {
|
||||
let sqlite_conn = create_sqlite_connection(&db_path_str).await?;
|
||||
run_migration(&sqlite_conn, DatabaseType::SQLite).await?;
|
||||
|
||||
let state = handle.state::<crate::state::SafeAppState>();
|
||||
let state_guard = state.write();
|
||||
let mut active_conn = sqlite_conn.clone();
|
||||
|
||||
{
|
||||
let mut settings = state_guard.settings.write();
|
||||
settings.attach_db(Some(sqlite_conn.clone()));
|
||||
settings
|
||||
.initialize()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to initialize settings from sqlite: {}", e))?;
|
||||
|
||||
let pg_connection_string = match settings.get_value(SettingsKey::PgConnectionString) {
|
||||
SettingsValue::String(s) => s,
|
||||
_ => String::new(),
|
||||
};
|
||||
|
||||
if !pg_connection_string.trim().is_empty() {
|
||||
match timeout(
|
||||
Duration::from_secs(DB_CONNECT_TIMEOUT_SECS),
|
||||
create_postgres_connection(&pg_connection_string),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(_) => {
|
||||
let timeout_message = "PostgreSQL auto-connect timeout".to_string();
|
||||
eprintln!(
|
||||
"PostgreSQL auto-connect timed out on startup, fallback to sqlite"
|
||||
);
|
||||
settings
|
||||
.set_value(
|
||||
SettingsKey::PgConnectionStatus,
|
||||
SettingsValue::Json(serde_json::json!({
|
||||
"connected": false,
|
||||
"type": "sqlite",
|
||||
"error": timeout_message
|
||||
})),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| format!("Failed to save pg status: {}", err))?;
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
eprintln!("PostgreSQL auto-connect failed, fallback to sqlite: {}", e);
|
||||
settings
|
||||
.set_value(
|
||||
SettingsKey::PgConnectionStatus,
|
||||
SettingsValue::Json(serde_json::json!({
|
||||
"connected": false,
|
||||
"type": "sqlite",
|
||||
"error": e.to_string()
|
||||
})),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| format!("Failed to save pg status: {}", err))?;
|
||||
}
|
||||
Ok(Ok(pg_conn)) => {
|
||||
let migration_result = timeout(
|
||||
Duration::from_secs(DB_MIGRATION_TIMEOUT_SECS),
|
||||
run_migration(&pg_conn, DatabaseType::PostgreSQL),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(_) = migration_result {
|
||||
let timeout_message = "PostgreSQL migration timeout".to_string();
|
||||
eprintln!(
|
||||
"PostgreSQL migration timed out on startup, fallback to sqlite"
|
||||
);
|
||||
settings
|
||||
.set_value(
|
||||
SettingsKey::PgConnectionStatus,
|
||||
SettingsValue::Json(serde_json::json!({
|
||||
"connected": false,
|
||||
"type": "sqlite",
|
||||
"error": timeout_message
|
||||
})),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| format!("Failed to save pg status: {}", err))?;
|
||||
} else if let Ok(Err(e)) = migration_result {
|
||||
eprintln!(
|
||||
"PostgreSQL migration failed on startup, fallback to sqlite: {}",
|
||||
e
|
||||
);
|
||||
settings
|
||||
.set_value(
|
||||
SettingsKey::PgConnectionStatus,
|
||||
SettingsValue::Json(serde_json::json!({
|
||||
"connected": false,
|
||||
"type": "sqlite",
|
||||
"error": e.to_string()
|
||||
})),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| format!("Failed to save pg status: {}", err))?;
|
||||
} else {
|
||||
active_conn = pg_conn;
|
||||
settings
|
||||
.set_value(
|
||||
SettingsKey::PgConnectionStatus,
|
||||
SettingsValue::Json(serde_json::json!({
|
||||
"connected": true,
|
||||
"type": "postgresql"
|
||||
})),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| format!("Failed to save pg status: {}", err))?;
|
||||
eprintln!("Auto connected to PostgreSQL from saved connection string");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
settings
|
||||
.set_value(
|
||||
SettingsKey::PgConnectionStatus,
|
||||
SettingsValue::Json(serde_json::json!({
|
||||
"connected": true,
|
||||
"type": "sqlite"
|
||||
})),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| format!("Failed to save sqlite status: {}", err))?;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut db_guard = state_guard.db.write();
|
||||
*db_guard = Some(active_conn);
|
||||
}
|
||||
|
||||
Ok::<_, Box<dyn std::error::Error>>(())
|
||||
});
|
||||
|
||||
if let Err(e) = db_result {
|
||||
eprintln!("Failed to connect to database: {}", e);
|
||||
} else {
|
||||
eprintln!(
|
||||
"Database bootstrap completed, sqlite settings db: {}",
|
||||
db_path_str
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
fn setup_tray(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let show_item = MenuItem::with_id(app, "show", "显示窗口", true, None::<&str>)?;
|
||||
let hide_item = MenuItem::with_id(app, "hide", "隐藏窗口", true, None::<&str>)?;
|
||||
let quit_item = MenuItem::with_id(app, "quit", "退出", true, None::<&str>)?;
|
||||
|
||||
let menu = Menu::with_items(app, &[&show_item, &hide_item, &quit_item])?;
|
||||
|
||||
let _tray = TrayIconBuilder::new()
|
||||
.icon(Image::from_bytes(include_bytes!("../icons/icon.png"))?)
|
||||
.menu(&menu)
|
||||
.show_menu_on_left_click(false)
|
||||
.on_menu_event(|app, event| match event.id.as_ref() {
|
||||
"show" => {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
"hide" => {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.hide();
|
||||
}
|
||||
}
|
||||
"quit" => {
|
||||
app.exit(0);
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.on_tray_icon_event(|tray, event| {
|
||||
if let TrayIconEvent::Click {
|
||||
button: MouseButton::Left,
|
||||
button_state: MouseButtonState::Up,
|
||||
..
|
||||
} = event
|
||||
{
|
||||
let app = tray.app_handle();
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
})
|
||||
.build(app)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(desktop))]
|
||||
fn setup_tray(_app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
fn setup_window_events(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.set_size(tauri::Size::Logical(tauri::LogicalSize {
|
||||
width: 1180.0,
|
||||
height: 680.0,
|
||||
}));
|
||||
let _ = window.center();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let _ = window.set_shadow(true);
|
||||
}
|
||||
|
||||
let window_clone = window.clone();
|
||||
window.on_window_event(move |event| {
|
||||
if let WindowEvent::CloseRequested { api, .. } = event {
|
||||
api.prevent_close();
|
||||
let _ = window_clone.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(desktop))]
|
||||
fn setup_window_events(_app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
window_maximize,
|
||||
window_close,
|
||||
window_is_maximized,
|
||||
toggle_devtools,
|
||||
window_resize,
|
||||
window_set_resizable,
|
||||
db_test_connection,
|
||||
db_switch_connection,
|
||||
db_get_status,
|
||||
db_sync,
|
||||
db_sync_preview,
|
||||
db_sync_apply,
|
||||
fs_get_config_structure,
|
||||
fs_read_json,
|
||||
fs_write_json,
|
||||
fs_read_text,
|
||||
fs_write_text,
|
||||
fs_delete_file,
|
||||
fs_list_files,
|
||||
fs_file_exists,
|
||||
http_server_start,
|
||||
http_server_stop,
|
||||
http_server_status,
|
||||
mcp_server_start,
|
||||
mcp_server_stop,
|
||||
mcp_server_status,
|
||||
register_url_protocol,
|
||||
app_quit,
|
||||
app_restart,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
pub fn setup_app(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
setup_database(app)?;
|
||||
|
||||
setup_tray(app)?;
|
||||
|
||||
setup_window_events(app)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_database(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
const DB_CONNECT_TIMEOUT_SECS: u64 = 15;
|
||||
const DB_MIGRATION_TIMEOUT_SECS: u64 = 20;
|
||||
|
||||
let handle = app.handle().clone();
|
||||
let db_path = if cfg!(all(debug_assertions, desktop)) {
|
||||
std::path::PathBuf::from("data.sql")
|
||||
} else {
|
||||
let app_data_dir = handle
|
||||
.path()
|
||||
.app_data_dir()
|
||||
.map_err(|e| format!("Failed to get app data directory: {}", e))?;
|
||||
let data_dir = app_data_dir.join("data");
|
||||
std::fs::create_dir_all(&data_dir)
|
||||
.map_err(|e| format!("Failed to create data directory: {}", e))?;
|
||||
data_dir.join("data.sql")
|
||||
};
|
||||
|
||||
let db_path_str = db_path.to_str().ok_or("Invalid database path")?.to_string();
|
||||
|
||||
let db_result = tauri::async_runtime::block_on(async {
|
||||
let sqlite_conn = create_sqlite_connection(&db_path_str).await?;
|
||||
run_migration(&sqlite_conn, DatabaseType::SQLite).await?;
|
||||
|
||||
let state = handle.state::<crate::state::SafeAppState>();
|
||||
let state_guard = state.write();
|
||||
let mut active_conn = sqlite_conn.clone();
|
||||
|
||||
{
|
||||
let mut settings = state_guard.settings.write();
|
||||
settings.attach_db(Some(sqlite_conn.clone()));
|
||||
settings
|
||||
.initialize()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to initialize settings from sqlite: {}", e))?;
|
||||
|
||||
let pg_connection_string = match settings.get_value(SettingsKey::PgConnectionString) {
|
||||
SettingsValue::String(s) => s,
|
||||
_ => String::new(),
|
||||
};
|
||||
|
||||
if !pg_connection_string.trim().is_empty() {
|
||||
match timeout(
|
||||
Duration::from_secs(DB_CONNECT_TIMEOUT_SECS),
|
||||
create_postgres_connection(&pg_connection_string),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(_) => {
|
||||
let timeout_message = "PostgreSQL auto-connect timeout".to_string();
|
||||
eprintln!(
|
||||
"PostgreSQL auto-connect timed out on startup, fallback to sqlite"
|
||||
);
|
||||
settings
|
||||
.set_value(
|
||||
SettingsKey::PgConnectionStatus,
|
||||
SettingsValue::Json(serde_json::json!({
|
||||
"connected": false,
|
||||
"type": "sqlite",
|
||||
"error": timeout_message
|
||||
})),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| format!("Failed to save pg status: {}", err))?;
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
eprintln!("PostgreSQL auto-connect failed, fallback to sqlite: {}", e);
|
||||
settings
|
||||
.set_value(
|
||||
SettingsKey::PgConnectionStatus,
|
||||
SettingsValue::Json(serde_json::json!({
|
||||
"connected": false,
|
||||
"type": "sqlite",
|
||||
"error": e.to_string()
|
||||
})),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| format!("Failed to save pg status: {}", err))?;
|
||||
}
|
||||
Ok(Ok(pg_conn)) => {
|
||||
let migration_result = timeout(
|
||||
Duration::from_secs(DB_MIGRATION_TIMEOUT_SECS),
|
||||
run_migration(&pg_conn, DatabaseType::PostgreSQL),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(_) = migration_result {
|
||||
let timeout_message = "PostgreSQL migration timeout".to_string();
|
||||
eprintln!(
|
||||
"PostgreSQL migration timed out on startup, fallback to sqlite"
|
||||
);
|
||||
settings
|
||||
.set_value(
|
||||
SettingsKey::PgConnectionStatus,
|
||||
SettingsValue::Json(serde_json::json!({
|
||||
"connected": false,
|
||||
"type": "sqlite",
|
||||
"error": timeout_message
|
||||
})),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| format!("Failed to save pg status: {}", err))?;
|
||||
} else if let Ok(Err(e)) = migration_result {
|
||||
eprintln!(
|
||||
"PostgreSQL migration failed on startup, fallback to sqlite: {}",
|
||||
e
|
||||
);
|
||||
settings
|
||||
.set_value(
|
||||
SettingsKey::PgConnectionStatus,
|
||||
SettingsValue::Json(serde_json::json!({
|
||||
"connected": false,
|
||||
"type": "sqlite",
|
||||
"error": e.to_string()
|
||||
})),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| format!("Failed to save pg status: {}", err))?;
|
||||
} else {
|
||||
active_conn = pg_conn;
|
||||
settings
|
||||
.set_value(
|
||||
SettingsKey::PgConnectionStatus,
|
||||
SettingsValue::Json(serde_json::json!({
|
||||
"connected": true,
|
||||
"type": "postgresql"
|
||||
})),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| format!("Failed to save pg status: {}", err))?;
|
||||
eprintln!("Auto connected to PostgreSQL from saved connection string");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
settings
|
||||
.set_value(
|
||||
SettingsKey::PgConnectionStatus,
|
||||
SettingsValue::Json(serde_json::json!({
|
||||
"connected": true,
|
||||
"type": "sqlite"
|
||||
})),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| format!("Failed to save sqlite status: {}", err))?;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut db_guard = state_guard.db.write();
|
||||
*db_guard = Some(active_conn);
|
||||
}
|
||||
|
||||
Ok::<_, Box<dyn std::error::Error>>(())
|
||||
});
|
||||
|
||||
if let Err(e) = db_result {
|
||||
eprintln!("Failed to connect to database: {}", e);
|
||||
} else {
|
||||
eprintln!(
|
||||
"Database bootstrap completed, sqlite settings db: {}",
|
||||
db_path_str
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
fn setup_tray(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let show_item = MenuItem::with_id(app, "show", "显示窗口", true, None::<&str>)?;
|
||||
let hide_item = MenuItem::with_id(app, "hide", "隐藏窗口", true, None::<&str>)?;
|
||||
let quit_item = MenuItem::with_id(app, "quit", "退出", true, None::<&str>)?;
|
||||
|
||||
let menu = Menu::with_items(app, &[&show_item, &hide_item, &quit_item])?;
|
||||
|
||||
let _tray = TrayIconBuilder::new()
|
||||
.icon(Image::from_bytes(include_bytes!("../icons/icon.png"))?)
|
||||
.menu(&menu)
|
||||
.show_menu_on_left_click(false)
|
||||
.on_menu_event(|app, event| match event.id.as_ref() {
|
||||
"show" => {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
"hide" => {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.hide();
|
||||
}
|
||||
}
|
||||
"quit" => {
|
||||
app.exit(0);
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.on_tray_icon_event(|tray, event| {
|
||||
if let TrayIconEvent::Click {
|
||||
button: MouseButton::Left,
|
||||
button_state: MouseButtonState::Up,
|
||||
..
|
||||
} = event
|
||||
{
|
||||
let app = tray.app_handle();
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
})
|
||||
.build(app)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(desktop))]
|
||||
fn setup_tray(_app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
fn setup_window_events(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.set_size(tauri::Size::Logical(tauri::LogicalSize {
|
||||
width: 1180.0,
|
||||
height: 680.0,
|
||||
}));
|
||||
let _ = window.center();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let _ = window.set_shadow(true);
|
||||
}
|
||||
|
||||
let window_clone = window.clone();
|
||||
window.on_window_event(move |event| {
|
||||
if let WindowEvent::CloseRequested { api, .. } = event {
|
||||
api.prevent_close();
|
||||
let _ = window_clone.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(desktop))]
|
||||
fn setup_window_events(_app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+985
-963
File diff suppressed because it is too large
Load Diff
+1383
-1321
File diff suppressed because it is too large
Load Diff
+805
-797
File diff suppressed because it is too large
Load Diff
+795
-787
File diff suppressed because it is too large
Load Diff
+401
-400
@@ -1,404 +1,405 @@
|
||||
import { invoke } from "@tauri-apps/api/core"
|
||||
import { listen, UnlistenFn } from "@tauri-apps/api/event"
|
||||
|
||||
export interface themeConfig {
|
||||
name: string
|
||||
id: string
|
||||
mode: "light" | "dark"
|
||||
config: {
|
||||
tdesign: Record<string, string>
|
||||
custom: Record<string, string>
|
||||
}
|
||||
}
|
||||
|
||||
export interface settingChange {
|
||||
key: string
|
||||
value: any
|
||||
oldValue: any
|
||||
}
|
||||
|
||||
export type settingsKey =
|
||||
| "is_wizard_completed"
|
||||
| "log_level"
|
||||
| "window_zoom"
|
||||
| "search_keyboard_layout"
|
||||
| "disable_search_keyboard"
|
||||
| "themes_custom"
|
||||
| "auto_score_enabled"
|
||||
| "auto_score_rules"
|
||||
| "current_theme_id"
|
||||
| "dashboards_config"
|
||||
| "pg_connection_string"
|
||||
| "pg_connection_status"
|
||||
|
||||
export interface settingsSpec {
|
||||
is_wizard_completed: boolean
|
||||
log_level: string
|
||||
window_zoom: number
|
||||
search_keyboard_layout: "t9" | "qwerty26"
|
||||
disable_search_keyboard: boolean
|
||||
themes_custom: themeConfig[]
|
||||
auto_score_enabled: boolean
|
||||
auto_score_rules: any[]
|
||||
current_theme_id: string
|
||||
dashboards_config: any[]
|
||||
pg_connection_string: string
|
||||
pg_connection_status: {
|
||||
connected: boolean
|
||||
type: "sqlite" | "postgresql"
|
||||
error?: string
|
||||
}
|
||||
}
|
||||
|
||||
const api = {
|
||||
// Theme
|
||||
getThemes: (): Promise<{ success: boolean; data: themeConfig[] }> => invoke("theme_list"),
|
||||
getCurrentTheme: (): Promise<{ success: boolean; data: themeConfig }> => invoke("theme_current"),
|
||||
setTheme: (themeId: string): Promise<{ success: boolean }> => invoke("theme_set", { themeId }),
|
||||
saveTheme: (theme: themeConfig): Promise<{ success: boolean }> => invoke("theme_save", { theme }),
|
||||
deleteTheme: (themeId: string): Promise<{ success: boolean }> =>
|
||||
invoke("theme_delete", { themeId }),
|
||||
onThemeChanged: (callback: (theme: themeConfig) => void): Promise<UnlistenFn> => {
|
||||
return listen<themeConfig | { theme?: themeConfig }>("theme:updated", (event) => {
|
||||
const payload = event.payload as themeConfig | { theme?: themeConfig } | undefined
|
||||
const theme =
|
||||
payload && typeof payload === "object" && "theme" in payload
|
||||
? payload.theme
|
||||
: (payload as themeConfig | undefined)
|
||||
if (theme) callback(theme)
|
||||
})
|
||||
},
|
||||
|
||||
// DB - Student
|
||||
queryStudents: (params?: any): Promise<{ success: boolean; data: any[] }> =>
|
||||
invoke("student_query", { params }),
|
||||
createStudent: (data: {
|
||||
name: string
|
||||
}): Promise<{ success: boolean; data?: number; message?: string }> =>
|
||||
invoke("student_create", { data }),
|
||||
updateStudent: (id: number, data: any): Promise<{ success: boolean }> =>
|
||||
invoke("student_update", { id, data }),
|
||||
deleteStudent: (id: number): Promise<{ success: boolean }> => invoke("student_delete", { id }),
|
||||
importStudentsFromXlsx: (params: {
|
||||
names: string[]
|
||||
}): Promise<{ success: boolean; data: { inserted: number; skipped: number; total: number } }> =>
|
||||
invoke("student_import_from_xlsx", { params }),
|
||||
|
||||
// DB - Tags
|
||||
tagsGetAll: (): Promise<{ success: boolean; data: any[] }> => invoke("tags_get_all"),
|
||||
tagsGetByStudent: (studentId: number): Promise<{ success: boolean; data: any[] }> =>
|
||||
invoke("tags_get_by_student", { studentId }),
|
||||
tagsCreate: (name: string): Promise<{ success: boolean; data: any }> =>
|
||||
invoke("tags_create", { name }),
|
||||
tagsDelete: (id: number): Promise<{ success: boolean }> => invoke("tags_delete", { id }),
|
||||
tagsUpdateStudentTags: (studentId: number, tagIds: number[]): Promise<{ success: boolean }> =>
|
||||
invoke("tags_update_student_tags", { studentId, tagIds }),
|
||||
|
||||
// DB - Reason
|
||||
queryReasons: (): Promise<{ success: boolean; data: any[] }> => invoke("reason_query"),
|
||||
createReason: (data: any): Promise<{ success: boolean; data?: number; message?: string }> =>
|
||||
invoke("reason_create", { data }),
|
||||
updateReason: (id: number, data: any): Promise<{ success: boolean }> =>
|
||||
invoke("reason_update", { id, data }),
|
||||
deleteReason: (id: number): Promise<{ success: boolean }> => invoke("reason_delete", { id }),
|
||||
|
||||
// DB - Reward
|
||||
rewardSettingQuery: (): Promise<{ success: boolean; data: any[] }> => invoke("reward_setting_query"),
|
||||
rewardSettingCreate: (data: {
|
||||
name: string
|
||||
cost_points: number
|
||||
}): Promise<{ success: boolean; data?: number; message?: string }> =>
|
||||
invoke("reward_setting_create", { data }),
|
||||
rewardSettingUpdate: (id: number, data: any): Promise<{ success: boolean; message?: string }> =>
|
||||
invoke("reward_setting_update", { id, data }),
|
||||
rewardSettingDelete: (id: number): Promise<{ success: boolean; message?: string }> =>
|
||||
invoke("reward_setting_delete", { id }),
|
||||
rewardRedeem: (data: {
|
||||
student_name: string
|
||||
reward_id: number
|
||||
}): Promise<{
|
||||
success: boolean
|
||||
data?: { redemption_id: number; remaining_reward_points: number }
|
||||
message?: string
|
||||
}> => invoke("reward_redeem", { data }),
|
||||
rewardRedemptionQuery: (params?: { limit?: number }): Promise<{ success: boolean; data: any[] }> =>
|
||||
invoke("reward_redemption_query", { params }),
|
||||
|
||||
// DB - Event
|
||||
queryEvents: (params?: { limit?: number }): Promise<{ success: boolean; data: any[] }> =>
|
||||
invoke("event_query", { params }),
|
||||
createEvent: (data: {
|
||||
studentName: string
|
||||
reasonContent: string
|
||||
delta: number
|
||||
}): Promise<{ success: boolean; data?: number; message?: string }> =>
|
||||
invoke("event_create", { data }),
|
||||
deleteEvent: (uuid: string): Promise<{ success: boolean }> => invoke("event_delete", { uuid }),
|
||||
queryEventsByStudent: (params: {
|
||||
studentName: string
|
||||
limit?: number
|
||||
startTime?: string
|
||||
}): Promise<{ success: boolean; data: any[] }> => invoke("event_query_by_student", { params }),
|
||||
queryLeaderboard: (params: {
|
||||
range: "today" | "week" | "month"
|
||||
}): Promise<{ success: boolean; data: { startTime: string; rows: any[] } }> =>
|
||||
invoke("leaderboard_query", { params }),
|
||||
boardQuerySql: (params: {
|
||||
sql: string
|
||||
limit?: number
|
||||
}): Promise<{ success: boolean; data: any[]; message?: string }> =>
|
||||
invoke("board_query_sql", { params }),
|
||||
|
||||
// Settlement
|
||||
querySettlements: (): Promise<{ success: boolean; data: any[] }> => invoke("db_settlement_query"),
|
||||
createSettlement: (): Promise<{ success: boolean; data: any }> => invoke("db_settlement_create"),
|
||||
querySettlementLeaderboard: (params: {
|
||||
settlementId: number
|
||||
}): Promise<{ success: boolean; data: any }> => invoke("db_settlement_leaderboard", { params }),
|
||||
|
||||
// Settings & Sync
|
||||
getAllSettings: (): Promise<{ success: boolean; data: settingsSpec }> =>
|
||||
invoke("settings_get_all"),
|
||||
getSetting: <K extends settingsKey>(
|
||||
key: K
|
||||
): Promise<{ success: boolean; data: settingsSpec[K] }> => invoke("settings_get", { key }),
|
||||
setSetting: <K extends settingsKey>(
|
||||
key: K,
|
||||
value: settingsSpec[K]
|
||||
): Promise<{ success: boolean }> => invoke("settings_set", { key, value }),
|
||||
onSettingChanged: (callback: (change: settingChange) => void): Promise<UnlistenFn> => {
|
||||
return listen<settingChange>("settings:changed", (event) => {
|
||||
callback(event.payload)
|
||||
})
|
||||
},
|
||||
|
||||
// Auth & Security
|
||||
authGetStatus: (): Promise<{
|
||||
success: boolean
|
||||
data: {
|
||||
permission: string
|
||||
hasAdminPassword: boolean
|
||||
hasPointsPassword: boolean
|
||||
hasRecoveryString: boolean
|
||||
}
|
||||
}> => invoke("auth_get_status"),
|
||||
authLogin: (
|
||||
password: string
|
||||
): Promise<{ success: boolean; data?: { permission: string }; message?: string }> =>
|
||||
invoke("auth_login", { password }),
|
||||
authLogout: (): Promise<{ success: boolean; data: { permission: string } }> =>
|
||||
invoke("auth_logout"),
|
||||
authSetPasswords: (payload: {
|
||||
adminPassword?: string | null
|
||||
pointsPassword?: string | null
|
||||
}): Promise<{ success: boolean; data?: { recoveryString: string }; message?: string }> =>
|
||||
invoke("auth_set_passwords", payload),
|
||||
authGenerateRecovery: (): Promise<{ success: boolean; data: { recoveryString: string } }> =>
|
||||
invoke("auth_generate_recovery"),
|
||||
authResetByRecovery: (
|
||||
recoveryString: string
|
||||
): Promise<{ success: boolean; data?: { recoveryString: string }; message?: string }> =>
|
||||
invoke("auth_reset_by_recovery", { recoveryString }),
|
||||
authClearAll: (): Promise<{ success: boolean }> => invoke("auth_clear_all"),
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core"
|
||||
import { listen, UnlistenFn } from "@tauri-apps/api/event"
|
||||
|
||||
export interface themeConfig {
|
||||
name: string
|
||||
id: string
|
||||
mode: "light" | "dark"
|
||||
config: {
|
||||
tdesign: Record<string, string>
|
||||
custom: Record<string, string>
|
||||
}
|
||||
}
|
||||
|
||||
export interface settingChange {
|
||||
key: string
|
||||
value: any
|
||||
oldValue: any
|
||||
}
|
||||
|
||||
export type settingsKey =
|
||||
| "is_wizard_completed"
|
||||
| "log_level"
|
||||
| "window_zoom"
|
||||
| "search_keyboard_layout"
|
||||
| "disable_search_keyboard"
|
||||
| "themes_custom"
|
||||
| "auto_score_enabled"
|
||||
| "auto_score_rules"
|
||||
| "current_theme_id"
|
||||
| "dashboards_config"
|
||||
| "pg_connection_string"
|
||||
| "pg_connection_status"
|
||||
|
||||
export interface settingsSpec {
|
||||
is_wizard_completed: boolean
|
||||
log_level: string
|
||||
window_zoom: number
|
||||
search_keyboard_layout: "t9" | "qwerty26"
|
||||
disable_search_keyboard: boolean
|
||||
themes_custom: themeConfig[]
|
||||
auto_score_enabled: boolean
|
||||
auto_score_rules: any[]
|
||||
current_theme_id: string
|
||||
dashboards_config: any[]
|
||||
pg_connection_string: string
|
||||
pg_connection_status: {
|
||||
connected: boolean
|
||||
type: "sqlite" | "postgresql"
|
||||
error?: string
|
||||
}
|
||||
}
|
||||
|
||||
const api = {
|
||||
// Theme
|
||||
getThemes: (): Promise<{ success: boolean; data: themeConfig[] }> => invoke("theme_list"),
|
||||
getCurrentTheme: (): Promise<{ success: boolean; data: themeConfig }> => invoke("theme_current"),
|
||||
setTheme: (themeId: string): Promise<{ success: boolean }> => invoke("theme_set", { themeId }),
|
||||
saveTheme: (theme: themeConfig): Promise<{ success: boolean }> => invoke("theme_save", { theme }),
|
||||
deleteTheme: (themeId: string): Promise<{ success: boolean }> =>
|
||||
invoke("theme_delete", { themeId }),
|
||||
onThemeChanged: (callback: (theme: themeConfig) => void): Promise<UnlistenFn> => {
|
||||
return listen<themeConfig | { theme?: themeConfig }>("theme:updated", (event) => {
|
||||
const payload = event.payload as themeConfig | { theme?: themeConfig } | undefined
|
||||
const theme =
|
||||
payload && typeof payload === "object" && "theme" in payload
|
||||
? payload.theme
|
||||
: (payload as themeConfig | undefined)
|
||||
if (theme) callback(theme)
|
||||
})
|
||||
},
|
||||
|
||||
// DB - Student
|
||||
queryStudents: (params?: any): Promise<{ success: boolean; data: any[] }> =>
|
||||
invoke("student_query", { params }),
|
||||
createStudent: (data: {
|
||||
name: string
|
||||
}): Promise<{ success: boolean; data?: number; message?: string }> =>
|
||||
invoke("student_create", { data }),
|
||||
updateStudent: (id: number, data: any): Promise<{ success: boolean }> =>
|
||||
invoke("student_update", { id, data }),
|
||||
deleteStudent: (id: number): Promise<{ success: boolean }> => invoke("student_delete", { id }),
|
||||
importStudentsFromXlsx: (params: {
|
||||
names: string[]
|
||||
}): Promise<{ success: boolean; data: { inserted: number; skipped: number; total: number } }> =>
|
||||
invoke("student_import_from_xlsx", { params }),
|
||||
|
||||
// DB - Tags
|
||||
tagsGetAll: (): Promise<{ success: boolean; data: any[] }> => invoke("tags_get_all"),
|
||||
tagsGetByStudent: (studentId: number): Promise<{ success: boolean; data: any[] }> =>
|
||||
invoke("tags_get_by_student", { studentId }),
|
||||
tagsCreate: (name: string): Promise<{ success: boolean; data: any }> =>
|
||||
invoke("tags_create", { name }),
|
||||
tagsDelete: (id: number): Promise<{ success: boolean }> => invoke("tags_delete", { id }),
|
||||
tagsUpdateStudentTags: (studentId: number, tagIds: number[]): Promise<{ success: boolean }> =>
|
||||
invoke("tags_update_student_tags", { studentId, tagIds }),
|
||||
|
||||
// DB - Reason
|
||||
queryReasons: (): Promise<{ success: boolean; data: any[] }> => invoke("reason_query"),
|
||||
createReason: (data: any): Promise<{ success: boolean; data?: number; message?: string }> =>
|
||||
invoke("reason_create", { data }),
|
||||
updateReason: (id: number, data: any): Promise<{ success: boolean }> =>
|
||||
invoke("reason_update", { id, data }),
|
||||
deleteReason: (id: number): Promise<{ success: boolean }> => invoke("reason_delete", { id }),
|
||||
|
||||
// DB - Reward
|
||||
rewardSettingQuery: (): Promise<{ success: boolean; data: any[] }> => invoke("reward_setting_query"),
|
||||
rewardSettingCreate: (data: {
|
||||
name: string
|
||||
cost_points: number
|
||||
}): Promise<{ success: boolean; data?: number; message?: string }> =>
|
||||
invoke("reward_setting_create", { data }),
|
||||
rewardSettingUpdate: (id: number, data: any): Promise<{ success: boolean; message?: string }> =>
|
||||
invoke("reward_setting_update", { id, data }),
|
||||
rewardSettingDelete: (id: number): Promise<{ success: boolean; message?: string }> =>
|
||||
invoke("reward_setting_delete", { id }),
|
||||
rewardRedeem: (data: {
|
||||
student_name: string
|
||||
reward_id: number
|
||||
}): Promise<{
|
||||
success: boolean
|
||||
data?: { redemption_id: number; remaining_reward_points: number }
|
||||
message?: string
|
||||
}> => invoke("reward_redeem", { data }),
|
||||
rewardRedemptionQuery: (params?: { limit?: number }): Promise<{ success: boolean; data: any[] }> =>
|
||||
invoke("reward_redemption_query", { params }),
|
||||
|
||||
// DB - Event
|
||||
queryEvents: (params?: { limit?: number }): Promise<{ success: boolean; data: any[] }> =>
|
||||
invoke("event_query", { params }),
|
||||
createEvent: (data: {
|
||||
studentName: string
|
||||
reasonContent: string
|
||||
delta: number
|
||||
}): Promise<{ success: boolean; data?: number; message?: string }> =>
|
||||
invoke("event_create", { data }),
|
||||
deleteEvent: (uuid: string): Promise<{ success: boolean }> => invoke("event_delete", { uuid }),
|
||||
queryEventsByStudent: (params: {
|
||||
studentName: string
|
||||
limit?: number
|
||||
startTime?: string
|
||||
}): Promise<{ success: boolean; data: any[] }> => invoke("event_query_by_student", { params }),
|
||||
queryLeaderboard: (params: {
|
||||
range: "today" | "week" | "month"
|
||||
}): Promise<{ success: boolean; data: { startTime: string; rows: any[] } }> =>
|
||||
invoke("leaderboard_query", { params }),
|
||||
boardQuerySql: (params: {
|
||||
sql: string
|
||||
limit?: number
|
||||
}): Promise<{ success: boolean; data: any[]; message?: string }> =>
|
||||
invoke("board_query_sql", { params }),
|
||||
|
||||
// Settlement
|
||||
querySettlements: (): Promise<{ success: boolean; data: any[] }> => invoke("db_settlement_query"),
|
||||
createSettlement: (): Promise<{ success: boolean; data: any }> => invoke("db_settlement_create"),
|
||||
querySettlementLeaderboard: (params: {
|
||||
settlementId: number
|
||||
}): Promise<{ success: boolean; data: any }> => invoke("db_settlement_leaderboard", { params }),
|
||||
|
||||
// Settings & Sync
|
||||
getAllSettings: (): Promise<{ success: boolean; data: settingsSpec }> =>
|
||||
invoke("settings_get_all"),
|
||||
getSetting: <K extends settingsKey>(
|
||||
key: K
|
||||
): Promise<{ success: boolean; data: settingsSpec[K] }> => invoke("settings_get", { key }),
|
||||
setSetting: <K extends settingsKey>(
|
||||
key: K,
|
||||
value: settingsSpec[K]
|
||||
): Promise<{ success: boolean }> => invoke("settings_set", { key, value }),
|
||||
onSettingChanged: (callback: (change: settingChange) => void): Promise<UnlistenFn> => {
|
||||
return listen<settingChange>("settings:changed", (event) => {
|
||||
callback(event.payload)
|
||||
})
|
||||
},
|
||||
|
||||
// Auth & Security
|
||||
authGetStatus: (): Promise<{
|
||||
success: boolean
|
||||
data: {
|
||||
permission: string
|
||||
hasAdminPassword: boolean
|
||||
hasPointsPassword: boolean
|
||||
hasRecoveryString: boolean
|
||||
}
|
||||
}> => invoke("auth_get_status"),
|
||||
authLogin: (
|
||||
password: string
|
||||
): Promise<{ success: boolean; data?: { permission: string }; message?: string }> =>
|
||||
invoke("auth_login", { password }),
|
||||
authLogout: (): Promise<{ success: boolean; data: { permission: string } }> =>
|
||||
invoke("auth_logout"),
|
||||
authSetPasswords: (payload: {
|
||||
adminPassword?: string | null
|
||||
pointsPassword?: string | null
|
||||
}): Promise<{ success: boolean; data?: { recoveryString: string }; message?: string }> =>
|
||||
invoke("auth_set_passwords", payload),
|
||||
authGenerateRecovery: (): Promise<{ success: boolean; data: { recoveryString: string } }> =>
|
||||
invoke("auth_generate_recovery"),
|
||||
authResetByRecovery: (
|
||||
recoveryString: string
|
||||
): Promise<{ success: boolean; data?: { recoveryString: string }; message?: string }> =>
|
||||
invoke("auth_reset_by_recovery", { recoveryString }),
|
||||
authClearAll: (): Promise<{ success: boolean }> => invoke("auth_clear_all"),
|
||||
|
||||
// Data import/export
|
||||
exportDataJson: (): Promise<{ success: boolean; data: string }> => invoke("data_export_json"),
|
||||
importDataJson: (jsonText: string): Promise<{ success: boolean }> =>
|
||||
invoke("data_import_json", { jsonText }),
|
||||
|
||||
// Window
|
||||
windowMinimize: (): Promise<void> => invoke("window_minimize"),
|
||||
windowMaximize: (): Promise<boolean> => invoke("window_maximize"),
|
||||
windowClose: (): Promise<void> => invoke("window_close"),
|
||||
windowIsMaximized: (): Promise<boolean> => invoke("window_is_maximized"),
|
||||
toggleDevTools: (): Promise<void> => invoke("toggle_devtools"),
|
||||
windowResize: (width: number, height: number): Promise<void> =>
|
||||
invoke("window_resize", { width, height }),
|
||||
windowSetResizable: (resizable: boolean): Promise<void> =>
|
||||
invoke("window_set_resizable", { resizable }),
|
||||
onWindowMaximizedChanged: (callback: (maximized: boolean) => void): Promise<UnlistenFn> => {
|
||||
return listen<boolean>("window:maximized-changed", (event) => {
|
||||
callback(event.payload)
|
||||
})
|
||||
},
|
||||
onNavigate: (callback: (route: string) => void): Promise<UnlistenFn> => {
|
||||
return listen<string>("app:navigate", (event) => {
|
||||
callback(event.payload)
|
||||
})
|
||||
},
|
||||
|
||||
// Logger
|
||||
queryLogs: (
|
||||
input?: number | { lines?: number }
|
||||
): Promise<{ success: boolean; data: string[] }> => {
|
||||
const lines = typeof input === "number" ? input : input?.lines
|
||||
return invoke("log_query", { lines })
|
||||
},
|
||||
clearLogs: (): Promise<{ success: boolean }> => invoke("log_clear"),
|
||||
setLogLevel: (level: string): Promise<{ success: boolean }> => invoke("log_set_level", { level }),
|
||||
writeLog: (payload: {
|
||||
level: string
|
||||
message: string
|
||||
meta?: any
|
||||
}): Promise<{ success: boolean }> => invoke("log_write", { payload }),
|
||||
|
||||
// Database Connection
|
||||
dbTestConnection: (
|
||||
connectionString: string
|
||||
): Promise<{ success: boolean; data: { success: boolean; error?: string } }> =>
|
||||
invoke("db_test_connection", { connectionString }),
|
||||
dbSwitchConnection: (
|
||||
connectionString: string
|
||||
): Promise<{ success: boolean; data: { type: "sqlite" | "postgresql" } }> =>
|
||||
invoke("db_switch_connection", { connectionString }),
|
||||
dbGetStatus: (): Promise<{
|
||||
success: boolean
|
||||
data: { type: string; connected: boolean; error?: string }
|
||||
}> => invoke("db_get_status"),
|
||||
dbSync: (): Promise<{ success: boolean; data: { success: boolean; message?: string } }> =>
|
||||
invoke("db_sync"),
|
||||
dbSyncPreview: (): Promise<{
|
||||
success: boolean
|
||||
data: {
|
||||
can_sync: boolean
|
||||
need_sync: boolean
|
||||
local_only: number
|
||||
remote_only: number
|
||||
conflicts: Array<{
|
||||
table: string
|
||||
key: string
|
||||
local_summary: string
|
||||
remote_summary: string
|
||||
}>
|
||||
message?: string
|
||||
}
|
||||
}> => invoke("db_sync_preview"),
|
||||
dbSyncApply: (strategy: "keep_local" | "keep_remote"): Promise<{
|
||||
success: boolean
|
||||
data: { success: boolean; synced_records: number; resolved_conflicts: number; message?: string }
|
||||
}> => invoke("db_sync_apply", { strategy }),
|
||||
|
||||
// HTTP Server
|
||||
httpServerStart: (config?: {
|
||||
port?: number
|
||||
host?: string
|
||||
corsOrigin?: string
|
||||
}): Promise<{ success: boolean; data: { url: string; config: any } }> =>
|
||||
invoke("http_server_start", { config }),
|
||||
httpServerStop: (): Promise<{ success: boolean }> => invoke("http_server_stop"),
|
||||
httpServerStatus: (): Promise<{
|
||||
success: boolean
|
||||
data: { isRunning: boolean; config?: any; url?: string }
|
||||
}> => invoke("http_server_status"),
|
||||
|
||||
// MCP Server
|
||||
mcpServerStart: (config?: {
|
||||
port?: number
|
||||
host?: string
|
||||
}): Promise<{ success: boolean; data?: { url: string; config: { port: number; host: string } } }> =>
|
||||
invoke("mcp_server_start", { config }),
|
||||
mcpServerStop: (): Promise<{ success: boolean }> => invoke("mcp_server_stop"),
|
||||
mcpServerStatus: (): Promise<{
|
||||
success: boolean
|
||||
data?: {
|
||||
is_running: boolean
|
||||
config: { port: number; host: string }
|
||||
url?: string | null
|
||||
}
|
||||
}> => invoke("mcp_server_status"),
|
||||
|
||||
// File System
|
||||
fsGetConfigStructure: (): Promise<{
|
||||
success: boolean
|
||||
data: { configRoot: string; automatic: string; script: string }
|
||||
}> => invoke("fs_get_config_structure"),
|
||||
fsReadJson: (
|
||||
relativePath: string,
|
||||
folder?: "automatic" | "script"
|
||||
): Promise<{ success: boolean; data: any }> => invoke("fs_read_json", { relativePath, folder }),
|
||||
fsWriteJson: (
|
||||
relativePath: string,
|
||||
data: any,
|
||||
folder?: "automatic" | "script"
|
||||
): Promise<{ success: boolean }> => invoke("fs_write_json", { relativePath, data, folder }),
|
||||
fsReadText: (
|
||||
relativePath: string,
|
||||
folder?: "automatic" | "script"
|
||||
): Promise<{ success: boolean; data: string }> =>
|
||||
invoke("fs_read_text", { relativePath, folder }),
|
||||
fsWriteText: (
|
||||
content: string,
|
||||
relativePath: string,
|
||||
folder?: "automatic" | "script"
|
||||
): Promise<{ success: boolean }> => invoke("fs_write_text", { content, relativePath, folder }),
|
||||
fsDeleteFile: (
|
||||
relativePath: string,
|
||||
folder?: "automatic" | "script"
|
||||
): Promise<{ success: boolean }> => invoke("fs_delete_file", { relativePath, folder }),
|
||||
fsListFiles: (folder?: "automatic" | "script"): Promise<{ success: boolean; data: any[] }> =>
|
||||
invoke("fs_list_files", { folder }),
|
||||
fsFileExists: (
|
||||
relativePath: string,
|
||||
folder?: "automatic" | "script"
|
||||
): Promise<{ success: boolean; data: boolean }> =>
|
||||
invoke("fs_file_exists", { relativePath, folder }),
|
||||
|
||||
// App
|
||||
registerUrlProtocol: (): Promise<{
|
||||
success: boolean
|
||||
data?: { registered: boolean }
|
||||
message?: string
|
||||
}> => invoke("register_url_protocol"),
|
||||
appQuit: (): Promise<void> => invoke("app_quit"),
|
||||
appRestart: (): Promise<void> => invoke("app_restart"),
|
||||
|
||||
// Auto Score
|
||||
autoScoreGetRules: (): Promise<{ success: boolean; data: any[] }> =>
|
||||
invoke("auto_score_get_rules"),
|
||||
autoScoreAddRule: (rule: any): Promise<{ success: boolean; data?: number; message?: string }> =>
|
||||
invoke("auto_score_add_rule", { rule }),
|
||||
autoScoreUpdateRule: (
|
||||
rule: any
|
||||
): Promise<{ success: boolean; data?: boolean; message?: string }> =>
|
||||
invoke("auto_score_update_rule", { rule }),
|
||||
autoScoreDeleteRule: (
|
||||
ruleId: number
|
||||
): Promise<{ success: boolean; data?: boolean; message?: string }> =>
|
||||
invoke("auto_score_delete_rule", { ruleId }),
|
||||
autoScoreToggleRule: (params: {
|
||||
ruleId: number
|
||||
enabled: boolean
|
||||
}): Promise<{ success: boolean; data?: boolean; message?: string }> =>
|
||||
invoke("auto_score_toggle_rule", params),
|
||||
autoScoreGetStatus: (): Promise<{ success: boolean; data: { enabled: boolean } }> =>
|
||||
invoke("auto_score_get_status"),
|
||||
autoScoreSortRules: (
|
||||
ruleIds: number[]
|
||||
): Promise<{ success: boolean; data?: boolean; message?: string }> =>
|
||||
invoke("auto_score_sort_rules", { ruleIds }),
|
||||
|
||||
// Generic invoke wrapper for backward compatibility with callers using `api.invoke`
|
||||
invoke: async (channel: string, ...args: any[]): Promise<any> => {
|
||||
switch (channel) {
|
||||
case "auto-score:getRules":
|
||||
return api.autoScoreGetRules()
|
||||
case "auto-score:addRule":
|
||||
return api.autoScoreAddRule(args[0])
|
||||
case "auto-score:updateRule":
|
||||
return api.autoScoreUpdateRule(args[0])
|
||||
case "auto-score:deleteRule":
|
||||
return api.autoScoreDeleteRule(args[0])
|
||||
case "auto-score:toggleRule":
|
||||
return api.autoScoreToggleRule(args[0])
|
||||
case "auto-score:sortRules":
|
||||
return api.autoScoreSortRules(args[0])
|
||||
case "auto-score:getStatus":
|
||||
return api.autoScoreGetStatus()
|
||||
default:
|
||||
throw new Error(`Unsupported legacy invoke channel: ${channel}`)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default api
|
||||
export { api }
|
||||
dataResetAll: (): Promise<{ success: boolean }> => invoke("data_reset_all"),
|
||||
|
||||
// Window
|
||||
windowMinimize: (): Promise<void> => invoke("window_minimize"),
|
||||
windowMaximize: (): Promise<boolean> => invoke("window_maximize"),
|
||||
windowClose: (): Promise<void> => invoke("window_close"),
|
||||
windowIsMaximized: (): Promise<boolean> => invoke("window_is_maximized"),
|
||||
toggleDevTools: (): Promise<void> => invoke("toggle_devtools"),
|
||||
windowResize: (width: number, height: number): Promise<void> =>
|
||||
invoke("window_resize", { width, height }),
|
||||
windowSetResizable: (resizable: boolean): Promise<void> =>
|
||||
invoke("window_set_resizable", { resizable }),
|
||||
onWindowMaximizedChanged: (callback: (maximized: boolean) => void): Promise<UnlistenFn> => {
|
||||
return listen<boolean>("window:maximized-changed", (event) => {
|
||||
callback(event.payload)
|
||||
})
|
||||
},
|
||||
onNavigate: (callback: (route: string) => void): Promise<UnlistenFn> => {
|
||||
return listen<string>("app:navigate", (event) => {
|
||||
callback(event.payload)
|
||||
})
|
||||
},
|
||||
|
||||
// Logger
|
||||
queryLogs: (
|
||||
input?: number | { lines?: number }
|
||||
): Promise<{ success: boolean; data: string[] }> => {
|
||||
const lines = typeof input === "number" ? input : input?.lines
|
||||
return invoke("log_query", { lines })
|
||||
},
|
||||
clearLogs: (): Promise<{ success: boolean }> => invoke("log_clear"),
|
||||
setLogLevel: (level: string): Promise<{ success: boolean }> => invoke("log_set_level", { level }),
|
||||
writeLog: (payload: {
|
||||
level: string
|
||||
message: string
|
||||
meta?: any
|
||||
}): Promise<{ success: boolean }> => invoke("log_write", { payload }),
|
||||
|
||||
// Database Connection
|
||||
dbTestConnection: (
|
||||
connectionString: string
|
||||
): Promise<{ success: boolean; data: { success: boolean; error?: string } }> =>
|
||||
invoke("db_test_connection", { connectionString }),
|
||||
dbSwitchConnection: (
|
||||
connectionString: string
|
||||
): Promise<{ success: boolean; data: { type: "sqlite" | "postgresql" } }> =>
|
||||
invoke("db_switch_connection", { connectionString }),
|
||||
dbGetStatus: (): Promise<{
|
||||
success: boolean
|
||||
data: { type: string; connected: boolean; error?: string }
|
||||
}> => invoke("db_get_status"),
|
||||
dbSync: (): Promise<{ success: boolean; data: { success: boolean; message?: string } }> =>
|
||||
invoke("db_sync"),
|
||||
dbSyncPreview: (): Promise<{
|
||||
success: boolean
|
||||
data: {
|
||||
can_sync: boolean
|
||||
need_sync: boolean
|
||||
local_only: number
|
||||
remote_only: number
|
||||
conflicts: Array<{
|
||||
table: string
|
||||
key: string
|
||||
local_summary: string
|
||||
remote_summary: string
|
||||
}>
|
||||
message?: string
|
||||
}
|
||||
}> => invoke("db_sync_preview"),
|
||||
dbSyncApply: (strategy: "keep_local" | "keep_remote"): Promise<{
|
||||
success: boolean
|
||||
data: { success: boolean; synced_records: number; resolved_conflicts: number; message?: string }
|
||||
}> => invoke("db_sync_apply", { strategy }),
|
||||
|
||||
// HTTP Server
|
||||
httpServerStart: (config?: {
|
||||
port?: number
|
||||
host?: string
|
||||
corsOrigin?: string
|
||||
}): Promise<{ success: boolean; data: { url: string; config: any } }> =>
|
||||
invoke("http_server_start", { config }),
|
||||
httpServerStop: (): Promise<{ success: boolean }> => invoke("http_server_stop"),
|
||||
httpServerStatus: (): Promise<{
|
||||
success: boolean
|
||||
data: { isRunning: boolean; config?: any; url?: string }
|
||||
}> => invoke("http_server_status"),
|
||||
|
||||
// MCP Server
|
||||
mcpServerStart: (config?: {
|
||||
port?: number
|
||||
host?: string
|
||||
}): Promise<{ success: boolean; data?: { url: string; config: { port: number; host: string } } }> =>
|
||||
invoke("mcp_server_start", { config }),
|
||||
mcpServerStop: (): Promise<{ success: boolean }> => invoke("mcp_server_stop"),
|
||||
mcpServerStatus: (): Promise<{
|
||||
success: boolean
|
||||
data?: {
|
||||
is_running: boolean
|
||||
config: { port: number; host: string }
|
||||
url?: string | null
|
||||
}
|
||||
}> => invoke("mcp_server_status"),
|
||||
|
||||
// File System
|
||||
fsGetConfigStructure: (): Promise<{
|
||||
success: boolean
|
||||
data: { configRoot: string; automatic: string; script: string }
|
||||
}> => invoke("fs_get_config_structure"),
|
||||
fsReadJson: (
|
||||
relativePath: string,
|
||||
folder?: "automatic" | "script"
|
||||
): Promise<{ success: boolean; data: any }> => invoke("fs_read_json", { relativePath, folder }),
|
||||
fsWriteJson: (
|
||||
relativePath: string,
|
||||
data: any,
|
||||
folder?: "automatic" | "script"
|
||||
): Promise<{ success: boolean }> => invoke("fs_write_json", { relativePath, data, folder }),
|
||||
fsReadText: (
|
||||
relativePath: string,
|
||||
folder?: "automatic" | "script"
|
||||
): Promise<{ success: boolean; data: string }> =>
|
||||
invoke("fs_read_text", { relativePath, folder }),
|
||||
fsWriteText: (
|
||||
content: string,
|
||||
relativePath: string,
|
||||
folder?: "automatic" | "script"
|
||||
): Promise<{ success: boolean }> => invoke("fs_write_text", { content, relativePath, folder }),
|
||||
fsDeleteFile: (
|
||||
relativePath: string,
|
||||
folder?: "automatic" | "script"
|
||||
): Promise<{ success: boolean }> => invoke("fs_delete_file", { relativePath, folder }),
|
||||
fsListFiles: (folder?: "automatic" | "script"): Promise<{ success: boolean; data: any[] }> =>
|
||||
invoke("fs_list_files", { folder }),
|
||||
fsFileExists: (
|
||||
relativePath: string,
|
||||
folder?: "automatic" | "script"
|
||||
): Promise<{ success: boolean; data: boolean }> =>
|
||||
invoke("fs_file_exists", { relativePath, folder }),
|
||||
|
||||
// App
|
||||
registerUrlProtocol: (): Promise<{
|
||||
success: boolean
|
||||
data?: { registered: boolean }
|
||||
message?: string
|
||||
}> => invoke("register_url_protocol"),
|
||||
appQuit: (): Promise<void> => invoke("app_quit"),
|
||||
appRestart: (): Promise<void> => invoke("app_restart"),
|
||||
|
||||
// Auto Score
|
||||
autoScoreGetRules: (): Promise<{ success: boolean; data: any[] }> =>
|
||||
invoke("auto_score_get_rules"),
|
||||
autoScoreAddRule: (rule: any): Promise<{ success: boolean; data?: number; message?: string }> =>
|
||||
invoke("auto_score_add_rule", { rule }),
|
||||
autoScoreUpdateRule: (
|
||||
rule: any
|
||||
): Promise<{ success: boolean; data?: boolean; message?: string }> =>
|
||||
invoke("auto_score_update_rule", { rule }),
|
||||
autoScoreDeleteRule: (
|
||||
ruleId: number
|
||||
): Promise<{ success: boolean; data?: boolean; message?: string }> =>
|
||||
invoke("auto_score_delete_rule", { ruleId }),
|
||||
autoScoreToggleRule: (params: {
|
||||
ruleId: number
|
||||
enabled: boolean
|
||||
}): Promise<{ success: boolean; data?: boolean; message?: string }> =>
|
||||
invoke("auto_score_toggle_rule", params),
|
||||
autoScoreGetStatus: (): Promise<{ success: boolean; data: { enabled: boolean } }> =>
|
||||
invoke("auto_score_get_status"),
|
||||
autoScoreSortRules: (
|
||||
ruleIds: number[]
|
||||
): Promise<{ success: boolean; data?: boolean; message?: string }> =>
|
||||
invoke("auto_score_sort_rules", { ruleIds }),
|
||||
|
||||
// Generic invoke wrapper for backward compatibility with callers using `api.invoke`
|
||||
invoke: async (channel: string, ...args: any[]): Promise<any> => {
|
||||
switch (channel) {
|
||||
case "auto-score:getRules":
|
||||
return api.autoScoreGetRules()
|
||||
case "auto-score:addRule":
|
||||
return api.autoScoreAddRule(args[0])
|
||||
case "auto-score:updateRule":
|
||||
return api.autoScoreUpdateRule(args[0])
|
||||
case "auto-score:deleteRule":
|
||||
return api.autoScoreDeleteRule(args[0])
|
||||
case "auto-score:toggleRule":
|
||||
return api.autoScoreToggleRule(args[0])
|
||||
case "auto-score:sortRules":
|
||||
return api.autoScoreSortRules(args[0])
|
||||
case "auto-score:getStatus":
|
||||
return api.autoScoreGetStatus()
|
||||
default:
|
||||
throw new Error(`Unsupported legacy invoke channel: ${channel}`)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default api
|
||||
export { api }
|
||||
|
||||
Reference in New Issue
Block a user