148 lines
3.9 KiB
Rust
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))
|
|
}
|