feat(faces): /api/people endpoints

Phase 2 increment 6 — the People HTTP API (mounted only when
OXICLOUD_ENABLE_FACES is on; every handler is caller-scoped):
- GET    /api/people                 list identity clusters
- GET    /api/people/{id}/photos     a person's photo file ids
- PATCH  /api/people/{id}            name / rename a person
- POST   /api/people/{id}/hide       hide / unhide
- POST   /api/people/merge           merge two clusters
- POST   /api/people/recluster       re-run clustering
- DELETE /api/people/data            erase all face data (opt-out)
- GET    /api/people/faces/{file_id} face boxes for lightbox tagging

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JW6ghFMDtnRYuYNzZhb47M
This commit is contained in:
Claude
2026-06-19 11:51:01 +00:00
parent 1dca9d7d05
commit 5c42b4d2b1
3 changed files with 197 additions and 1 deletions
+19 -1
View File
@@ -6,7 +6,7 @@ use axum::{
extract::{DefaultBodyLimit, State},
http::StatusCode,
response::{IntoResponse, Json as AxumJson, Response},
routing::{any, delete, get, post, put},
routing::{any, delete, get, patch, post, put},
};
use serde_json::json;
use std::sync::Arc;
@@ -440,6 +440,24 @@ pub fn create_api_routes(app_state: &Arc<AppState>) -> Router<Arc<AppState>> {
router = router.nest("/photos", photos_router);
}
// People (faces) routes — mounted only when OXICLOUD_ENABLE_FACES is on.
if app_state.people_service.is_some() {
use crate::interfaces::api::handlers::people_handler;
let people_router = Router::new()
.route("/", get(people_handler::list_people))
.route("/merge", post(people_handler::merge_people))
.route("/recluster", post(people_handler::recluster))
.route("/data", delete(people_handler::delete_all))
.route("/faces/{file_id}", get(people_handler::faces_for_file))
.route("/{id}", patch(people_handler::rename_person))
.route("/{id}/photos", get(people_handler::person_photos))
.route("/{id}/hide", post(people_handler::hide_person))
.with_state(app_state.clone());
router = router.nest("/people", people_router);
}
// Re-enable trash routes to make the trash view work
if let Some(_trash_service_ref) = trash_service.clone() {
tracing::info!("Setting up trash routes for trash view");