diff --git a/src/api/identity_binding.rs b/src/api/identity_binding.rs index 2a3a9cd..a2c0861 100644 --- a/src/api/identity_binding.rs +++ b/src/api/identity_binding.rs @@ -9,7 +9,8 @@ use serde::{Deserialize, Serialize}; use crate::core::db::{Database, PostgresDb}; use crate::core::person_identity::{ - BindIdentityRequest, Identity, MergeIdentitiesRequest, UnbindIdentityRequest, + BindIdentityRequest, BindIdentityTraceRequest, Identity, MergeIdentitiesRequest, + UnbindIdentityRequest, }; #[derive(Debug, Clone, Serialize)] @@ -388,6 +389,80 @@ pub struct TracesQuery { pub page_size: Option, } +pub async fn bind_identity_trace( + Path(identity_uuid): Path, + Json(req): Json, +) -> Result>, (StatusCode, Json)> { + let fd_table = crate::core::db::schema::table_name("face_detections"); + let id_table = crate::core::db::schema::table_name("identities"); + + let db = sqlx::PgPool::connect(&crate::core::config::DATABASE_URL) + .await + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({"error": e.to_string()})), + ) + })?; + + let identity_row: Option<(i64, String)> = sqlx::query_as(&format!( + "SELECT id::bigint, name FROM {} WHERE uuid = $1::uuid", + id_table + )) + .bind(&identity_uuid) + .fetch_optional(&db) + .await + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({"error": format!("DB error: {}", e)})), + ) + })?; + + let (identity_id, name) = identity_row.ok_or_else(|| { + ( + StatusCode::NOT_FOUND, + Json(serde_json::json!({"error": format!("Identity not found: {}", identity_uuid)})), + ) + })?; + + let result = sqlx::query(&format!( + "UPDATE {} SET identity_id = $1 WHERE file_uuid = $2 AND trace_id = $3", + fd_table + )) + .bind(identity_id) + .bind(&req.file_uuid) + .bind(req.trace_id) + .execute(&db) + .await + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({"error": format!("Update failed: {}", e)})), + ) + })?; + + let uuid_clean = identity_uuid.replace('-', ""); + if let Err(e) = + crate::core::identity::storage::save_identity_file_by_pool(&db, &uuid_clean).await + { + tracing::warn!( + "[bind/trace] Failed to sync identity file for {}: {}", + uuid_clean, + e + ); + } + + Ok(Json(ApiResponse { + success: true, + message: format!( + "Bound trace {} of {} to {}", + req.trace_id, req.file_uuid, name + ), + data: Some(serde_json::json!({"rows_affected": result.rows_affected()})), + })) +} + pub async fn get_identity_traces( State(state): State, Path(identity_uuid): Path, @@ -488,6 +563,10 @@ pub async fn get_identity_traces( pub fn identity_binding_routes() -> Router { Router::new() .route("/api/v1/identity/:identity_uuid/bind", post(bind_identity)) + .route( + "/api/v1/identity/:identity_uuid/bind/trace", + post(bind_identity_trace), + ) .route( "/api/v1/identity/:identity_uuid/unbind", post(unbind_identity), diff --git a/src/core/person_identity.rs b/src/core/person_identity.rs index c324087..a802f6d 100644 --- a/src/core/person_identity.rs +++ b/src/core/person_identity.rs @@ -72,6 +72,12 @@ pub struct BindIdentityRequest { pub face_id: String, } +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct BindIdentityTraceRequest { + pub file_uuid: String, + pub trace_id: i32, +} + #[derive(Debug, Clone, Deserialize, Serialize)] pub struct UnbindIdentityRequest { pub file_uuid: String,