feat: Identity JSON sync mechanism

- storage.rs: add local_profile field, check disk for profile.jpg in save_identity_file_by_pool
- 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: replace DB fallback with Lazy Sync (generates JSON from DB if missing)
- Fixes missing/obsolete JSON files for all identity mutations
This commit is contained in:
Accusys
2026-05-19 22:20:19 +08:00
parent 7680c202ef
commit 0eb08acaae
4 changed files with 129 additions and 54 deletions

View File

@@ -115,10 +115,9 @@ pub async fn bind_identity(
})?;
let uuid_clean = identity_uuid.replace('-', "");
if let Ok(ref db) = PostgresDb::init().await {
if let Err(e) = crate::core::identity::storage::save_identity_file(db, &uuid_clean).await {
tracing::warn!("[bind] Failed to save identity file for {}: {}", uuid_clean, e);
}
// Sync identity JSON file
if let Err(e) = crate::core::identity::storage::save_identity_file_by_pool(&db, &uuid_clean).await {
tracing::warn!("[bind] Failed to sync identity file for {}: {}", uuid_clean, e);
}
Ok(Json(ApiResponse {
@@ -136,6 +135,7 @@ pub async fn unbind_identity(
Json(req): Json<UnbindIdentityRequest>,
) -> Result<Json<ApiResponse<serde_json::Value>>, (StatusCode, Json<serde_json::Value>)> {
let 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
@@ -146,6 +146,22 @@ pub async fn unbind_identity(
)
})?;
// Find the identity_id before unbinding to sync it later
let identity_id: Option<i64> = sqlx::query_scalar(&format!(
"SELECT identity_id FROM {} WHERE file_uuid = $1 AND face_id = $2 AND identity_id IS NOT NULL",
table
))
.bind(&req.file_uuid)
.bind(&req.face_id)
.fetch_optional(&db)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({"error": e.to_string()})),
)
})?;
let result = sqlx::query(&format!(
"UPDATE {} SET identity_id = NULL WHERE file_uuid = $1 AND face_id = $2",
table
@@ -161,6 +177,24 @@ pub async fn unbind_identity(
)
})?;
// Sync the identity JSON if we found an identity
if let Some(id) = identity_id {
let uuid: Option<String> = sqlx::query_scalar(&format!(
"SELECT uuid::text FROM {} WHERE id = $1",
id_table
))
.bind(id)
.fetch_optional(&db)
.await
.ok()
.flatten();
if let Some(identity_uuid) = uuid {
if let Err(e) = crate::core::identity::storage::save_identity_file_by_pool(&db, &identity_uuid).await {
tracing::warn!("[unbind] Failed to sync identity file for {}: {}", identity_uuid, e);
}
}
}
Ok(Json(ApiResponse {
success: true,
message: format!("Unbound face {} from {}", req.face_id, req.file_uuid),
@@ -263,6 +297,18 @@ pub async fn merge_identities(
})?;
}
// Sync target identity JSON
let into_uuid_clean = req.into_uuid.replace('-', "");
if let Err(e) = crate::core::identity::storage::save_identity_file_by_pool(&db, &into_uuid_clean).await {
tracing::warn!("[merge] Failed to sync target identity file for {}: {}", into_uuid_clean, e);
}
// Delete source identity JSON if not keeping history
if !keep {
let from_uuid_clean = identity_uuid.replace('-', "");
let _ = crate::core::identity::storage::delete_identity_file(&from_uuid_clean);
}
Ok(Json(ApiResponse {
success: true,
message: format!(