Files
momentry_core/src/api/who.rs

148 lines
3.9 KiB
Rust

//! Who API - 身份識別與 ID 映射接口 (Video-Scoped)
use axum::{
extract::State,
http::StatusCode,
response::Json,
routing::{get, post},
Router,
};
use serde::{Deserialize, Serialize};
use crate::core::db::Database;
// --- Request / Response Structures ---
#[derive(Debug, Deserialize)]
pub struct WhoQuery {
pub face_id: Option<String>,
pub speaker_id: Option<String>,
pub uuid: Option<String>,
pub chunk_id: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct WhoCandidatesRequest {
pub query: String,
pub video_uuid: Option<String>,
pub limit: Option<i32>,
}
#[derive(Debug, Deserialize)]
pub struct DefinePersonRequest {
pub uuid: String,
pub identity_id: Option<i32>,
pub name: String,
pub face_ids: Option<Vec<String>>,
pub speaker_ids: Option<Vec<String>>,
}
#[derive(Debug, Serialize)]
pub struct WhoIdentity {
pub identity_id: i32,
pub uuid: String,
pub name: String,
pub tags: Option<Vec<String>>,
pub face_ids: Vec<String>,
pub speaker_ids: Vec<String>,
}
// --- API Handlers ---
/// GET /api/v1/who
pub async fn get_who_identity(
State(state): State<crate::api::server::AppState>,
axum::extract::Query(query): axum::extract::Query<WhoQuery>,
) -> Result<Json<serde_json::Value>, (StatusCode, Json<serde_json::Value>)> {
let db = &state.db;
// Priority 1: Query by Chunk (UUID + Chunk ID)
if let (Some(uuid), Some(chunk_id)) = (&query.uuid, &query.chunk_id) {
let info = db
.get_who_info_by_chunk(uuid, chunk_id)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({ "error": e.to_string() })),
)
})?;
return Ok(Json(info));
}
// Priority 2: List all for a specific UUID
if let Some(uuid) = &query.uuid {
// TODO: Implement list_all_persons(uuid)
return Ok(Json(serde_json::json!({ "message": "List all pending" })));
}
Err((
StatusCode::BAD_REQUEST,
Json(serde_json::json!({ "error": "Missing uuid" })),
))
}
/// POST /api/v1/who/candidates
/// Search person_identities table for n8n workflow
pub async fn get_who_candidates(
State(state): State<crate::api::server::AppState>,
Json(req): Json<WhoCandidatesRequest>,
) -> Result<Json<serde_json::Value>, (StatusCode, Json<serde_json::Value>)> {
let db = &state.db;
let limit = req.limit.unwrap_or(20);
let query_str = format!("%{}%", req.query);
let results = db
.search_person_candidates(&query_str, &req.video_uuid, limit)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({ "error": e.to_string() })),
)
})?;
// Format for n8n
let response = serde_json::json!({
"query": req.query,
"items": results,
"total": results.len()
});
Ok(Json(response))
}
/// POST /api/v1/who
pub async fn define_person(
State(state): State<crate::api::server::AppState>,
Json(req): Json<DefinePersonRequest>,
) -> Result<Json<WhoIdentity>, (StatusCode, Json<serde_json::Value>)> {
let db = &state.db;
let identity = db
.create_or_update_person(
&req.uuid,
req.identity_id,
req.name.clone(),
req.face_ids.unwrap_or_default(),
req.speaker_ids.unwrap_or_default(),
)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({ "error": e.to_string() })),
)
})?;
Ok(Json(identity))
}
// --- Router Setup ---
pub fn who_routes() -> Router<crate::api::server::AppState> {
Router::new()
.route("/api/v1/who", get(get_who_identity).post(define_person))
.route("/api/v1/who/candidates", post(get_who_candidates))
}