feat: Phase 3 API (Identity, Files, Candidates) and pre_chunks migration
This commit is contained in:
231
src/api/identity_api.rs
Normal file
231
src/api/identity_api.rs
Normal 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!({}),
|
||||
}))
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user