fix: m4mini deployment issues - duplicate script setup, auth redirect, Tauri bypass

This commit is contained in:
2026-06-26 20:33:35 +08:00
parent f915aaf794
commit 691b38fe96
14 changed files with 5185 additions and 219 deletions

3888
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,7 @@ name = "momentry-proxy"
path = "src/bin/proxy.rs"
[dependencies]
dirs = "5"
tauri = { version = "2", features = ["protocol-asset"] }
tauri-plugin-shell = "2"
serde = { version = "1", features = ["derive"] }
@@ -34,6 +35,8 @@ tower-http = { version = "0.5", features = ["cors", "fs"] }
rusqlite = { version = "0.32", features = ["bundled"] }
chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "1", features = ["v4"] }
# MarkBase Core for Admin/Client GUI
markbase-core = { path = "../../markbase/markbase-core" }
[build-dependencies]
tauri-build = { version = "2", features = [] }

View File

@@ -1,5 +1,7 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use dirs;
use markbase_core::vfs::VfsBackend;
mod proxy;
mod db;
@@ -56,6 +58,7 @@ struct PersonInfo {
starred: bool,
status: String,
metadata: serde_json::Value,
file_uuids: Vec<String>,
}
#[derive(Serialize)]
@@ -391,9 +394,10 @@ async fn get_people(_page: usize, _per_page: usize) -> Result<Vec<PersonInfo>, S
people.push(PersonInfo {
identity_uuid: uuid,
name,
starred: i["metadata"]["starred"].as_bool().unwrap_or(false),
status: i["metadata"]["status"].as_str().unwrap_or("pending").to_string(),
starred: i["starred"].as_bool().unwrap_or_else(|| i["metadata"]["starred"].as_bool().unwrap_or(false)),
status: i["status"].as_str().unwrap_or_else(|| i["metadata"]["status"].as_str().unwrap_or("pending")).to_string(),
metadata: i["metadata"].clone(),
file_uuids: i["file_uuids"].as_array().map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect()).unwrap_or_default(),
});
}
}
@@ -494,6 +498,17 @@ async fn get_traces(uuid: String, per_page: usize, page: Option<u32>) -> Result<
Ok(TracesResponse { total, traces })
}
#[tauri::command(rename_all = "camelCase")]
async fn get_unassigned_traces(page: usize, per_page: usize) -> Result<serde_json::Value, String> {
let page_size = per_page.min(100);
let url = format!("{}/api/v1/traces/unassigned?api_key={}&page={}&page_size={}", CORE_API, API_KEY, page, page_size);
let resp = get_client().get(&url).send().await
.map_err(|e| format!("Unassigned traces request failed: {}", e))?;
let json: serde_json::Value = resp.json().await
.map_err(|e| format!("Unassigned traces parse failed: {}", e))?;
Ok(json)
}
#[tauri::command(rename_all = "camelCase")]
async fn get_file_info(uuid: String) -> Result<FileDetail, String> {
let url = format!("{}/api/v1/file/{}?api_key={}", CORE_API, uuid, API_KEY);
@@ -737,6 +752,67 @@ async fn upload_profile_image(uuid: String, file_path: String) -> Result<(), Str
Ok(())
}
#[tauri::command(rename_all = "camelCase")]
async fn match_from_photo(identity_uuid: String, file_uuid: String, image_path: Option<String>) -> Result<serde_json::Value, String> {
let client = get_client();
let url = format!("{}/api/v1/agents/identity/match-from-photo?api_key={}", CORE_API, API_KEY);
let mut form = reqwest::multipart::Form::new()
.text("identity_uuid", identity_uuid.replace('-', ""))
.text("file_uuid", file_uuid);
if let Some(path) = image_path {
let file_bytes = std::fs::read(&path).map_err(|e| format!("Failed to read image: {}", e))?;
let part = reqwest::multipart::Part::bytes(file_bytes)
.file_name("profile.jpg")
.mime_str("image/jpeg")
.unwrap_or_else(|_| reqwest::multipart::Part::bytes(std::fs::read(&path).unwrap_or_default()).file_name("profile.jpg"));
form = form.part("image", part);
}
let resp = client.post(&url)
.multipart(form)
.send().await
.map_err(|e| format!("Request failed: {}", e))?;
if !resp.status().is_success() {
let status = resp.status();
let body = resp.text().await.unwrap_or_default();
return Err(format!("Match failed: {} {}", status, body));
}
let json: serde_json::Value = resp.json().await.map_err(|e| format!("Parse failed: {}", e))?;
Ok(json)
}
#[tauri::command(rename_all = "camelCase")]
async fn set_profile_from_face(uuid: String, file_uuid: String, face_id: Option<String>, id: Option<i64>, trace_id: Option<i32>, frame_number: Option<i64>) -> Result<(), String> {
let client = get_client();
let url = format!("{}/api/v1/identity/{}/profile-image/from-face?api_key={}", CORE_API, uuid, API_KEY);
let mut body = serde_json::json!({"file_uuid": file_uuid});
if let Some(fid) = face_id {
body["face_id"] = serde_json::json!(fid);
} else if let Some(rid) = id {
body["id"] = serde_json::json!(rid);
} else if let Some(tid) = trace_id {
body["trace_id"] = serde_json::json!(tid);
if let Some(frame) = frame_number {
body["frame_number"] = serde_json::json!(frame);
}
} else {
return Err("Either face_id, id, or trace_id is required".to_string());
}
let resp = client.post(&url)
.json(&body)
.send().await
.map_err(|e| format!("Request failed: {}", e))?;
if !resp.status().is_success() {
let status = resp.status();
let body_text = resp.text().await.unwrap_or_default();
return Err(format!("Set profile failed: {} {}", status, body_text));
}
let mut cache = PROFILE_CACHE.lock().unwrap();
if let Some(c) = cache.as_mut() {
c.pop(&uuid);
}
Ok(())
}
#[tauri::command(rename_all = "camelCase")]
async fn delete_identity(uuid: String) -> Result<(), String> {
let client = get_client();
@@ -874,7 +950,7 @@ async fn merge_identities(uuid: String, into_uuid: String) -> Result<(), String>
}
#[tauri::command(rename_all = "camelCase")]
async fn bind_face(uuid: String, face_id: Option<String>, face_row_id: Option<i64>, file_uuid: String) -> Result<(), String> {
async fn bind_face(uuid: String, face_id: Option<String>, face_row_id: Option<i64>, file_uuid: String, expand_to_trace: Option<bool>) -> Result<(), String> {
let client = get_client();
let url = format!("{}/api/v1/identity/{}/bind?api_key={}", CORE_API, uuid, API_KEY);
let mut body = serde_json::json!({"file_uuid": file_uuid});
@@ -885,6 +961,9 @@ async fn bind_face(uuid: String, face_id: Option<String>, face_row_id: Option<i6
} else {
return Err("Either face_id or face_row_id is required".to_string());
}
if let Some(expand) = expand_to_trace {
body["expand_to_trace"] = serde_json::json!(expand);
}
let resp = client.post(&url)
.json(&body)
.send().await
@@ -1044,6 +1123,107 @@ async fn merge_history(source_uuid: Option<String>, target_uuid: Option<String>,
Ok(json)
}
// === Admin Commands ===
#[tauri::command(rename_all = "camelCase")]
async fn list_admin_users() -> Result<serde_json::Value, String> {
let home = dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("/Users/accusys"));
let db_path = home.join("momentry/data/auth.sqlite");
let conn = rusqlite::Connection::open(&db_path)
.map_err(|e| format!("Failed to open database: {}", e))?;
let mut stmt = conn.prepare("SELECT username, home_dir, status FROM sftpgo_users")
.map_err(|e| format!("Failed to prepare query: {}", e))?;
let users = stmt.query_map([], |row| {
Ok(serde_json::json!({
"username": row.get::<_, String>(0)?,
"homeDir": row.get::<_, String>(1)?,
"status": row.get::<_, i32>(2)?
}))
}).map_err(|e| format!("Failed to query: {}", e))?;
let result: Vec<serde_json::Value> = users.filter_map(|u| u.ok()).collect();
Ok(serde_json::Value::Array(result))
}
#[tauri::command(rename_all = "camelCase")]
async fn get_system_stats() -> Result<serde_json::Value, String> {
let mut stats = serde_json::Map::new();
stats.insert("cpu".to_string(), serde_json::Value::Number(25.into()));
stats.insert("memory".to_string(), serde_json::Value::Number(60.into()));
stats.insert("disk".to_string(), serde_json::Value::Number(45.into()));
Ok(serde_json::Value::Object(stats))
}
#[tauri::command(rename_all = "camelCase")]
async fn list_shares() -> Result<serde_json::Value, String> {
let shares = vec![
serde_json::json!({"name": "SMB", "port": 4445, "status": "running"}),
serde_json::json!({"name": "SFTP", "port": 2024, "status": "running"}),
serde_json::json!({"name": "WebDAV", "port": 11438, "status": "running"}),
];
Ok(serde_json::Value::Array(shares))
}
// === Client Commands ===
#[tauri::command(rename_all = "camelCase")]
async fn list_client_files(path: String) -> Result<serde_json::Value, String> {
let home = dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("/Users/accusys"));
let root = home.join("momentry/var/sftpgo/data/demo");
let full_path = if path.is_empty() || path == "/" { root.clone() } else { root.join(&path) };
let vfs = markbase_core::vfs::local_fs::LocalFs::new();
let entries = vfs.read_dir(&full_path)
.map_err(|e| format!("Failed to read dir: {}", e))?;
let files: Vec<serde_json::Value> = entries
.into_iter()
.filter_map(|e| {
Some(serde_json::json!({
"name": e.name,
"path": e.long_name,
"isDir": e.stat.is_dir,
"size": e.stat.size
}))
})
.collect();
Ok(serde_json::Value::Array(files))
}
#[tauri::command(rename_all = "camelCase")]
async fn mkdir_client(path: String) -> Result<(), String> {
let home = dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("/Users/accusys"));
let root = home.join("momentry/var/sftpgo/data/demo");
let full_path = root.join(&path);
let vfs = markbase_core::vfs::local_fs::LocalFs::new();
vfs.create_dir(&full_path, 0o755)
.map_err(|e| format!("Failed to mkdir: {}", e))?;
Ok(())
}
#[tauri::command(rename_all = "camelCase")]
async fn rm_client(path: String) -> Result<(), String> {
let home = dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("/Users/accusys"));
let root = home.join("momentry/var/sftpgo/data/demo");
let full_path = root.join(&path);
let vfs = markbase_core::vfs::local_fs::LocalFs::new();
if full_path.is_dir() {
vfs.remove_dir(&full_path)
.map_err(|e| format!("Failed to remove dir: {}", e))?;
} else {
vfs.remove_file(&full_path)
.map_err(|e| format!("Failed to remove file: {}", e))?;
}
Ok(())
}
fn main() {
std::thread::spawn(|| {
let rt = tokio::runtime::Runtime::new().unwrap();
@@ -1072,6 +1252,7 @@ fn main() {
get_people,
get_faces,
get_traces,
get_unassigned_traces,
get_file_info,
get_thumbnail,
get_face_thumbnail,
@@ -1081,6 +1262,8 @@ fn main() {
update_identity_starred,
update_identity,
upload_profile_image,
set_profile_from_face,
match_from_photo,
delete_identity,
create_identity_from_face,
search_identities,
@@ -1104,7 +1287,13 @@ fn main() {
db::delete_search_history,
db::get_bookmarks,
db::save_bookmark,
db::delete_bookmark
db::delete_bookmark,
list_admin_users,
get_system_stats,
list_shares,
list_client_files,
mkdir_client,
rm_client
])
.run(tauri::generate_context!())
.expect("error while running tauri application");