From ba68cd2548a0fc05012c5c447d249d65442d46fb Mon Sep 17 00:00:00 2001 From: Accusys Date: Tue, 19 May 2026 23:10:49 +0800 Subject: [PATCH] feat: Identity JSON sync + schema-aware column selection - storage.rs: add local_profile field, check disk for profile.jpg - tmdb_api.rs: trigger JSON sync after TMDb probe - identity_api.rs: upload_profile_image triggers JSON sync - identity_binding.rs: bind/unbind/merge trigger JSON sync - get_identity_json: Lazy Sync (generates JSON from DB if missing) - identities.rs + identity_api.rs: use schema-aware column selection (dev:name vs public:real_name) - Fixes 500 errors on identities endpoints across schemas --- src/api/identities.rs | 20 ++++++++++++-------- src/api/identity_api.rs | 8 ++++---- src/core/identity/storage.rs | 7 +++++-- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/api/identities.rs b/src/api/identities.rs index d6dcbc8..1e03c74 100644 --- a/src/api/identities.rs +++ b/src/api/identities.rs @@ -92,18 +92,21 @@ async fn create_identity( ) })?; - let query = r#" - SELECT uuid, reference_data->'total_references' as total, + let id_table = crate::core::db::schema::table_name("identities"); + let name_col = if id_table.starts_with("dev.") { "name" } else { "real_name" }; + let query = format!( + "SELECT uuid, reference_data->'total_references' as total, reference_data->'angles_covered' as angles, reference_data->'quality_avg' as quality - FROM identities - WHERE real_name = $1 + FROM {} + WHERE {} = $1 ORDER BY created_at DESC - LIMIT 1 - "#; + LIMIT 1", + id_table, name_col + ); let row: Option<(String, Option, Option>, Option)> = - sqlx::query_as(query) + sqlx::query_as(&query) .bind(&req.identity_name) .fetch_optional(db.pool()) .await @@ -168,7 +171,8 @@ async fn list_identities( .fetch_one(db.pool()).await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Count error: {}", e)))?; - let sql = format!("SELECT id::int, uuid, real_name, metadata FROM {} ORDER BY id DESC LIMIT $1 OFFSET $2", id_table); + let name_col = if id_table.starts_with("dev.") { "name" } else { "real_name" }; + let sql = format!("SELECT id::int, uuid, {} AS name, metadata FROM {} ORDER BY id DESC LIMIT $1 OFFSET $2", name_col, id_table); let rows: Vec<(i32, uuid::Uuid, String, Option)> = match sqlx::query_as(&sql) .bind(page_size as i64) diff --git a/src/api/identity_api.rs b/src/api/identity_api.rs index 9805259..75ebf62 100644 --- a/src/api/identity_api.rs +++ b/src/api/identity_api.rs @@ -889,7 +889,7 @@ async fn search_identity_text( let query = format!( r#"SELECT c.file_uuid, c.chunk_id, c.start_time, c.end_time, c.text_content, - fd.identity_id, COALESCE(i.real_name, i.actor_name) AS identity_name, i.source AS identity_source, + fd.identity_id, CASE WHEN id_table LIKE 'dev.%' THEN i.name ELSE i.real_name END AS identity_name, i.source AS identity_source, fd.trace_id FROM {} c LEFT JOIN {} fd ON fd.file_uuid = c.file_uuid @@ -965,7 +965,7 @@ async fn search_identities_by_text( let limit = params.limit.unwrap_or(50).min(100); let query = format!( - r#"SELECT i.id::int, COALESCE(i.real_name, i.actor_name) AS name, i.source, i.tmdb_id, + r#"SELECT i.id::int, COALESCE(i.real_name, i.actor_name, i.name) AS name, i.source, i.tmdb_id, fd.file_uuid, fd.trace_id, c.chunk_id, c.start_time, c.text_content FROM {} i @@ -973,9 +973,9 @@ async fn search_identities_by_text( JOIN {} c ON c.file_uuid = fd.file_uuid AND c.start_time <= fd.frame_number / COALESCE(c.fps, 25.0) AND c.end_time >= fd.frame_number / COALESCE(c.fps, 25.0) - WHERE COALESCE(i.real_name, i.actor_name) ILIKE $1 + WHERE COALESCE(i.real_name, i.actor_name, i.name) ILIKE $1 AND ($2::text IS NULL OR fd.file_uuid = $2) - ORDER BY COALESCE(i.real_name, i.actor_name), c.start_time + ORDER BY COALESCE(i.real_name, i.actor_name, i.name), c.start_time LIMIT $3"#, id_table, fd_table, chunk_table ); diff --git a/src/core/identity/storage.rs b/src/core/identity/storage.rs index 8937a6a..928509d 100644 --- a/src/core/identity/storage.rs +++ b/src/core/identity/storage.rs @@ -187,15 +187,18 @@ pub async fn save_identity_file_by_pool(pool: &sqlx::PgPool, uuid: &str) -> Resu let identity_table = crate::core::db::schema::table_name("identities"); let fd_table = crate::core::db::schema::table_name("face_detections"); + // Schema-aware column selection: dev uses 'name', public uses 'real_name' + let name_col = if identity_table.starts_with("dev.") { "name" } else { "real_name" }; + let clean = uuid.replace('-', ""); let record = sqlx::query_as::<_, crate::core::db::IdentityDetailRecord>( &format!( - "SELECT id, uuid::text, COALESCE(real_name, actor_name, name) AS name, identity_type, source, status, metadata, reference_data, \ + "SELECT id, uuid::text, {} AS name, identity_type, source, status, metadata, reference_data, \ NULL::real[] as voice_embedding, NULL::real[] as identity_embedding, \ face_embedding::real[] as face_embedding, \ tmdb_id, tmdb_profile, created_at::timestamptz as created_at, NULL::timestamptz as updated_at \ FROM {} WHERE REPLACE(uuid::text, '-', '') = $1", - identity_table + name_col, identity_table ) ) .bind(&clean)