feat: complete Phase 4 Candidate Workflow (Confirm/Reject API)
This commit is contained in:
@@ -15,10 +15,14 @@ pub fn identity_routes() -> Router<crate::api::server::AppState> {
|
|||||||
.route("/api/v1/people", get(list_people))
|
.route("/api/v1/people", get(list_people))
|
||||||
.route("/api/v1/people/search", post(search_people))
|
.route("/api/v1/people/search", post(search_people))
|
||||||
.route("/api/v1/people/candidates", get(list_candidates))
|
.route("/api/v1/people/candidates", get(list_candidates))
|
||||||
|
.route("/api/v1/people/{identity_id}/confirm-candidate", post(confirm_candidate))
|
||||||
|
.route("/api/v1/people/{identity_id}/reject-candidate", post(reject_candidate))
|
||||||
.route("/api/v1/files", get(list_files))
|
.route("/api/v1/files", get(list_files))
|
||||||
.route("/api/v1/files/{uuid}", get(get_file_detail))
|
.route("/api/v1/files/{uuid}", get(get_file_detail))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ... (Keep existing functions) ...
|
||||||
|
|
||||||
// --- People / Identity Endpoints ---
|
// --- People / Identity Endpoints ---
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@@ -156,6 +160,50 @@ async fn list_candidates(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Candidate Workflow Endpoints ---
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct ConfirmCandidateRequest {
|
||||||
|
pub pre_chunk_id: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct ConfirmCandidateResponse {
|
||||||
|
pub success: bool,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn confirm_candidate(
|
||||||
|
State(state): State<crate::api::server::AppState>,
|
||||||
|
Path(identity_id_str): Path<String>,
|
||||||
|
Json(req): Json<ConfirmCandidateRequest>,
|
||||||
|
) -> Result<Json<ConfirmCandidateResponse>, (StatusCode, String)> {
|
||||||
|
let identity_id = Uuid::parse_str(&identity_id_str)
|
||||||
|
.map_err(|e| (StatusCode::BAD_REQUEST, format!("Invalid UUID: {}", e)))?;
|
||||||
|
|
||||||
|
state.db.confirm_candidate(req.pre_chunk_id, identity_id).await
|
||||||
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(Json(ConfirmCandidateResponse {
|
||||||
|
success: true,
|
||||||
|
message: "Candidate confirmed and linked to identity".to_string(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn reject_candidate(
|
||||||
|
State(state): State<crate::api::server::AppState>,
|
||||||
|
Path(_identity_id_str): Path<String>, // Unused, but consistent with route
|
||||||
|
Json(req): Json<ConfirmCandidateRequest>,
|
||||||
|
) -> Result<Json<ConfirmCandidateResponse>, (StatusCode, String)> {
|
||||||
|
state.db.reject_candidate(req.pre_chunk_id).await
|
||||||
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(Json(ConfirmCandidateResponse {
|
||||||
|
success: true,
|
||||||
|
message: "Candidate rejected".to_string(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
// --- Files Endpoints ---
|
// --- Files Endpoints ---
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
|||||||
@@ -1877,6 +1877,64 @@ impl PostgresDb {
|
|||||||
Ok(rows)
|
Ok(rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn confirm_candidate(
|
||||||
|
&self,
|
||||||
|
pre_chunk_id: i64,
|
||||||
|
identity_id: Uuid,
|
||||||
|
) -> Result<()> {
|
||||||
|
// 1. Update the pre_chunk to link it to the identity
|
||||||
|
sqlx::query(
|
||||||
|
"UPDATE pre_chunks SET identity_id = $1 WHERE id = $2"
|
||||||
|
)
|
||||||
|
.bind(identity_id)
|
||||||
|
.bind(pre_chunk_id)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// 2. Ensure a link exists in file_identities table
|
||||||
|
// We need the file_uuid from the pre_chunk
|
||||||
|
let file_uuid: Option<Uuid> = sqlx::query_scalar(
|
||||||
|
"SELECT file_uuid FROM pre_chunks WHERE id = $1"
|
||||||
|
)
|
||||||
|
.bind(pre_chunk_id)
|
||||||
|
.fetch_optional(&self.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if let Some(f_uuid) = file_uuid {
|
||||||
|
// Check if relationship exists
|
||||||
|
let exists: bool = sqlx::query_scalar(
|
||||||
|
"SELECT EXISTS(SELECT 1 FROM file_identities WHERE file_uuid = $1 AND identity_id = $2)"
|
||||||
|
)
|
||||||
|
.bind(f_uuid)
|
||||||
|
.bind(identity_id)
|
||||||
|
.fetch_one(&self.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
sqlx::query(
|
||||||
|
"INSERT INTO file_identities (file_uuid, identity_id, status) VALUES ($1, $2, 'detected')"
|
||||||
|
)
|
||||||
|
.bind(f_uuid)
|
||||||
|
.bind(identity_id)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn reject_candidate(&self, pre_chunk_id: i64) -> Result<()> {
|
||||||
|
// Just ensure it is NULL (or maybe we mark it as ignored in metadata? For now, just NULL)
|
||||||
|
sqlx::query(
|
||||||
|
"UPDATE pre_chunks SET identity_id = NULL WHERE id = $1"
|
||||||
|
)
|
||||||
|
.bind(pre_chunk_id)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn store_chunk(&self, chunk: &Chunk) -> Result<()> {
|
pub async fn store_chunk(&self, chunk: &Chunk) -> Result<()> {
|
||||||
let table = schema::table_name("chunks");
|
let table = schema::table_name("chunks");
|
||||||
let content_with_rule = serde_json::json!({
|
let content_with_rule = serde_json::json!({
|
||||||
|
|||||||
Reference in New Issue
Block a user