feat: unify TKG face_track external_id to trace_N, add trace restore endpoint
- tkg.rs: use trace_N as primary external_id, delete duplicate face_track_N nodes - trace_agent_api.rs: add POST /trace/:id/restore to undo soft delete - ensure 1:1 mapping: each trace has exactly one TKG node with full profile
This commit is contained in:
@@ -57,6 +57,10 @@ pub fn trace_agent_routes() -> Router<crate::api::types::AppState> {
|
||||
"/api/v1/file/:file_uuid/trace/:trace_id",
|
||||
delete(delete_trace),
|
||||
)
|
||||
.route(
|
||||
"/api/v1/file/:file_uuid/trace/:trace_id/restore",
|
||||
post(restore_trace),
|
||||
)
|
||||
.route(
|
||||
"/api/v1/file/:file_uuid/trace/:trace_id/merge/:target_trace_id",
|
||||
post(merge_trace),
|
||||
@@ -1898,3 +1902,79 @@ async fn merge_trace(
|
||||
"tkg_nodes_deleted": tkg_deleted
|
||||
}))
|
||||
}
|
||||
|
||||
async fn restore_trace(
|
||||
State(state): State<crate::api::types::AppState>,
|
||||
Path((file_uuid, trace_id)): Path<(String, i32)>,
|
||||
) -> Json<serde_json::Value> {
|
||||
let qdrant = crate::core::db::qdrant_db::QdrantDb::new();
|
||||
|
||||
// Restore Qdrant points: clear deleted status
|
||||
let filter = serde_json::json!({
|
||||
"must": [
|
||||
{"key": "file_uuid", "match": {"value": file_uuid}},
|
||||
{"key": "trace_id", "match": {"value": trace_id}},
|
||||
{"key": "status", "match": {"value": "deleted"}}
|
||||
]
|
||||
});
|
||||
|
||||
// Get count of points to restore
|
||||
let points_to_restore = qdrant.scroll_all_points("_faces", filter.clone(), 1).await
|
||||
.map(|pts| pts.len()).unwrap_or(0);
|
||||
|
||||
if points_to_restore == 0 {
|
||||
return Json(serde_json::json!({
|
||||
"success": false,
|
||||
"error": "Trace not found or not deleted"
|
||||
}));
|
||||
}
|
||||
|
||||
// Clear the deleted status
|
||||
let clear_payload = serde_json::json!({
|
||||
"status": serde_json::Value::Null
|
||||
});
|
||||
|
||||
let qdrant_restored = match qdrant.update_payload_by_filter("_faces", filter, clear_payload).await {
|
||||
Ok(_) => {
|
||||
tracing::info!("[restore_trace] Restored {} Qdrant points for trace {} in {}", points_to_restore, trace_id, file_uuid);
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("[restore_trace] Failed to restore Qdrant points: {}", e);
|
||||
return Json(serde_json::json!({
|
||||
"success": false,
|
||||
"error": format!("Failed to restore Qdrant points: {}", e)
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
// Restore TKG node: clear deleted status
|
||||
let nodes_table = crate::core::db::schema::table_name("tkg_nodes");
|
||||
let ext_id_trace = format!("trace_{}", trace_id);
|
||||
|
||||
let tkg_result = sqlx::query(&format!(
|
||||
"UPDATE {} SET properties = properties - 'status' \
|
||||
WHERE file_uuid = $1 AND node_type = 'face_track' AND external_id = $2",
|
||||
nodes_table
|
||||
))
|
||||
.bind(&file_uuid)
|
||||
.bind(&ext_id_trace)
|
||||
.execute(state.db.pool())
|
||||
.await;
|
||||
|
||||
let tkg_restored = match tkg_result {
|
||||
Ok(r) => r.rows_affected(),
|
||||
Err(e) => {
|
||||
tracing::error!("[restore_trace] Failed to restore TKG node: {}", e);
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
Json(serde_json::json!({
|
||||
"success": true,
|
||||
"file_uuid": file_uuid,
|
||||
"trace_id": trace_id,
|
||||
"qdrant_restored": qdrant_restored,
|
||||
"tkg_nodes_restored": tkg_restored
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -1153,7 +1153,8 @@ async fn build_face_track_nodes_from_qdrant(
|
||||
|
||||
let mut count = 0;
|
||||
for (tid, agg) in &traces {
|
||||
let external_id = format!("face_track_{}", tid);
|
||||
// Use trace_N as primary external_id (consistent with cluster_agent)
|
||||
let external_id = format!("trace_{}", tid);
|
||||
let label = format!("Face Trace {}", tid);
|
||||
|
||||
// Compute average pose for this trace
|
||||
@@ -1217,6 +1218,17 @@ async fn build_face_track_nodes_from_qdrant(
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
// Delete old face_track_N format to avoid duplicates
|
||||
let old_external_id = format!("face_track_{}", tid);
|
||||
let _ = sqlx::query(&format!(
|
||||
"DELETE FROM {} WHERE file_uuid = $1 AND node_type = 'face_track' AND external_id = $2",
|
||||
nodes_table
|
||||
))
|
||||
.bind(file_uuid)
|
||||
.bind(&old_external_id)
|
||||
.execute(pool)
|
||||
.await;
|
||||
|
||||
count += 1;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user