feat: Phase 3 API (Identity, Files, Candidates) and pre_chunks migration

This commit is contained in:
Warren
2026-04-25 22:19:12 +08:00
parent 1f84e5469f
commit e84982e7d9
5 changed files with 454 additions and 0 deletions

231
src/api/identity_api.rs Normal file
View File

@@ -0,0 +1,231 @@
use axum::{
extract::{Path, Query, State},
http::StatusCode,
response::Json,
routing::{get, post},
Router,
};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::core::db::{Database, PostgresDb};
pub fn identity_routes() -> Router<crate::api::server::AppState> {
Router::new()
.route("/api/v1/people", get(list_people))
.route("/api/v1/people/search", post(search_people))
.route("/api/v1/people/candidates", get(list_candidates))
.route("/api/v1/files", get(list_files))
.route("/api/v1/files/{uuid}", get(get_file_detail))
}
// --- People / Identity Endpoints ---
#[derive(Debug, Deserialize)]
pub struct PeopleQuery {
page: Option<usize>,
page_size: Option<usize>,
}
#[derive(Debug, Serialize)]
pub struct PeopleResponse {
pub success: bool,
pub total: i64,
pub page: usize,
pub page_size: usize,
pub data: Vec<PeopleItem>,
}
#[derive(Debug, Serialize)]
pub struct PeopleItem {
pub identity_id: Uuid,
pub name: String,
pub metadata: serde_json::Value,
pub created_at: Option<chrono::DateTime<chrono::Utc>>,
}
async fn list_people(
State(state): State<crate::api::server::AppState>,
Query(params): Query<PeopleQuery>,
) -> Result<Json<PeopleResponse>, (StatusCode, String)> {
let page = params.page.unwrap_or(1);
let page_size = params.page_size.unwrap_or(20);
let offset = ((page - 1) as i64) * (page_size as i64);
let records = state.db.list_people(page_size as i32, offset).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
// TODO: Get total count
let total = 100; // Placeholder
let data = records.into_iter().map(|r| PeopleItem {
identity_id: r.uuid,
name: r.name,
metadata: r.metadata,
created_at: r.created_at,
}).collect();
Ok(Json(PeopleResponse {
success: true,
total,
page,
page_size,
data,
}))
}
#[derive(Debug, Deserialize)]
pub struct SearchPeopleRequest {
pub query: String,
pub page: Option<usize>,
pub page_size: Option<usize>,
}
async fn search_people(
State(state): State<crate::api::server::AppState>,
Json(req): Json<SearchPeopleRequest>,
) -> Result<Json<PeopleResponse>, (StatusCode, String)> {
let page = req.page.unwrap_or(1);
let page_size = req.page_size.unwrap_or(20);
let offset = ((page - 1) as i64) * (page_size as i64);
let records = state.db.search_people(&req.query, page_size as i32, offset).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
let data: Vec<PeopleItem> = records.into_iter().map(|r| PeopleItem {
identity_id: r.uuid,
name: r.name,
metadata: r.metadata,
created_at: r.created_at,
}).collect();
Ok(Json(PeopleResponse {
success: true,
total: data.len() as i64, // Approximation
page,
page_size,
data,
}))
}
#[derive(Debug, Deserialize)]
pub struct CandidatesQuery {
page: Option<usize>,
page_size: Option<usize>,
}
#[derive(Debug, Serialize)]
pub struct CandidatesResponse {
pub success: bool,
pub total: i64,
pub page: usize,
pub page_size: usize,
pub data: Vec<CandidateItem>,
}
#[derive(Debug, Serialize)]
pub struct CandidateItem {
pub pre_chunk_id: i64,
pub file_uuid: Uuid,
pub data: serde_json::Value,
}
async fn list_candidates(
State(state): State<crate::api::server::AppState>,
Query(params): Query<CandidatesQuery>,
) -> Result<Json<CandidatesResponse>, (StatusCode, String)> {
let page = params.page.unwrap_or(1);
let page_size = params.page_size.unwrap_or(20);
let offset = ((page - 1) as i64) * (page_size as i64);
let records = state.db.get_people_candidates(page_size as i32, offset).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
let data = records.into_iter().map(|r| CandidateItem {
pre_chunk_id: r.id,
file_uuid: r.file_uuid,
data: r.data,
}).collect();
Ok(Json(CandidatesResponse {
success: true,
total: 0, // TODO
page,
page_size,
data,
}))
}
// --- Files Endpoints ---
#[derive(Debug, Deserialize)]
pub struct FilesQuery {
page: Option<usize>,
page_size: Option<usize>,
}
#[derive(Debug, Serialize)]
pub struct FilesResponse {
pub success: bool,
pub total: i64,
pub page: usize,
pub page_size: usize,
pub data: Vec<FileItem>,
}
#[derive(Debug, Serialize)]
pub struct FileItem {
pub file_uuid: String,
pub file_name: String,
pub file_path: String,
pub status: String, // From probe or processing status
}
async fn list_files(
State(state): State<crate::api::server::AppState>,
Query(params): Query<FilesQuery>,
) -> Result<Json<FilesResponse>, (StatusCode, String)> {
let page = params.page.unwrap_or(1);
let page_size = params.page_size.unwrap_or(20);
let offset = ((page - 1) as i64) * (page_size as i64);
let records = state.db.list_files(page_size as i32, offset).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
let data = records.into_iter().map(|r| FileItem {
file_uuid: r.uuid,
file_name: r.file_name,
file_path: r.file_path,
status: "ready".to_string(),
}).collect();
Ok(Json(FilesResponse {
success: true,
total: 0, // TODO
page,
page_size,
data,
}))
}
#[derive(Debug, Serialize)]
pub struct FileDetailResponse {
pub file_uuid: String,
pub file_name: String,
pub file_path: String,
pub metadata: serde_json::Value,
}
async fn get_file_detail(
State(state): State<crate::api::server::AppState>,
Path(uuid): Path<String>,
) -> Result<Json<FileDetailResponse>, (StatusCode, String)> {
// Need a method to get single file
// For now, placeholder
Ok(Json(FileDetailResponse {
file_uuid: uuid,
file_name: "Unknown".to_string(),
file_path: "/path/to/file".to_string(),
metadata: serde_json::json!({}),
}))
}

View File

@@ -1,5 +1,6 @@
pub mod face_recognition;
pub mod identities;
pub mod identity_api;
pub mod identity_binding;
pub mod middleware;
pub mod n8n_search;

View File

@@ -23,6 +23,7 @@ use crate::{Embedder, FileManager};
use super::face_recognition;
use super::identities;
use super::identity_binding;
use super::identity_api;
use super::middleware::api_key_validation;
use super::n8n_search;
use super::person_identity;
@@ -2480,6 +2481,7 @@ pub async fn start_server(host: &str, port: u16) -> anyhow::Result<()> {
"/api/v1/search/visual/combination",
post(search_visual_chunks_by_combination),
)
.merge(identity_api::identity_routes()) // Phase 3 Routes
.merge(protected_routes)
.layer(cors)
.with_state(state);