diff --git a/libertas_api/src/handlers/person_handlers.rs b/libertas_api/src/handlers/person_handlers.rs index 151fde0..28db4b0 100644 --- a/libertas_api/src/handlers/person_handlers.rs +++ b/libertas_api/src/handlers/person_handlers.rs @@ -1,9 +1,9 @@ use axum::{ + Json, Router, extract::{Path, State}, http::StatusCode, response::IntoResponse, routing::{get, post, put}, - Json, Router, }; use uuid::Uuid; @@ -12,8 +12,8 @@ use crate::{ error::ApiError, middleware::auth::UserId, schema::{ - AssignFaceRequest, CreatePersonRequest, FaceRegionResponse, PersonResponse, - SharePersonRequest, UpdatePersonRequest, + AssignFaceRequest, CreatePersonRequest, FaceRegionResponse, MergePersonRequest, + PersonResponse, SharePersonRequest, UpdatePersonRequest, }, state::AppState, }; @@ -23,11 +23,13 @@ pub fn people_routes() -> Router { .route("/", get(list_people).post(create_person)) .route( "/{person_id}", - get(get_person) - .put(update_person) - .delete(delete_person), + get(get_person).put(update_person).delete(delete_person), ) - .route("/{person_id}/share", post(share_person).delete(unshare_person)) + .route( + "/{person_id}/share", + post(share_person).delete(unshare_person), + ) + .route("/{person_id}/merge", post(merge_person)) } pub fn face_routes() -> Router { @@ -53,10 +55,7 @@ async fn get_person( UserId(user_id): UserId, Path(person_id): Path, ) -> Result, ApiError> { - let person = state - .person_service - .get_person(person_id, user_id) - .await?; + let person = state.person_service.get_person(person_id, user_id).await?; Ok(Json(PersonResponse::from(person))) } @@ -149,4 +148,17 @@ async fn assign_face_to_person( .assign_face_to_person(face_id, payload.person_id, user_id) .await?; Ok(Json(FaceRegionResponse::from(face))) -} \ No newline at end of file +} + +async fn merge_person( + State(state): State, + UserId(user_id): UserId, + Path(target_person_id): Path, + Json(payload): Json, +) -> Result { + state + .person_service + .merge_people(target_person_id, payload.source_person_id, user_id) + .await?; + Ok(StatusCode::NO_CONTENT) +} diff --git a/libertas_api/src/schema.rs b/libertas_api/src/schema.rs index f11471d..1312523 100644 --- a/libertas_api/src/schema.rs +++ b/libertas_api/src/schema.rs @@ -239,3 +239,8 @@ pub struct PublicAlbumBundleResponse { pub album: AlbumResponse, pub media: Vec, } + +#[derive(Deserialize)] +pub struct MergePersonRequest { + pub source_person_id: Uuid, +} diff --git a/libertas_api/src/services/person_service.rs b/libertas_api/src/services/person_service.rs index e248d43..173bb4d 100644 --- a/libertas_api/src/services/person_service.rs +++ b/libertas_api/src/services/person_service.rs @@ -183,4 +183,36 @@ impl PersonService for PersonServiceImpl { .remove_share(person_id, target_user_id) .await } + + async fn merge_people( + &self, + target_person_id: Uuid, + source_person_id: Uuid, + user_id: Uuid, + ) -> CoreResult<()> { + if target_person_id == source_person_id { + return Err(CoreError::Validation( + "Cannot merge the same person".to_string(), + )); + } + + self.auth_service + .check_permission( + Some(user_id), + authz::Permission::EditPerson(target_person_id), + ) + .await?; + self.auth_service + .check_permission( + Some(user_id), + authz::Permission::EditPerson(source_person_id), + ) + .await?; + + self.face_repo + .reassign_person(source_person_id, target_person_id) + .await?; + + self.person_repo.delete(source_person_id).await + } } diff --git a/libertas_core/src/repositories.rs b/libertas_core/src/repositories.rs index 36500ff..366f3e1 100644 --- a/libertas_core/src/repositories.rs +++ b/libertas_core/src/repositories.rs @@ -99,6 +99,7 @@ pub trait FaceRegionRepository: Send + Sync { async fn find_by_id(&self, face_region_id: Uuid) -> CoreResult>; async fn update_person_id(&self, face_region_id: Uuid, person_id: Uuid) -> CoreResult<()>; async fn delete(&self, face_region_id: Uuid) -> CoreResult<()>; + async fn reassign_person(&self, old_person_id: Uuid, new_person_id: Uuid) -> CoreResult<()>; } #[async_trait] diff --git a/libertas_core/src/services.rs b/libertas_core/src/services.rs index 0cc5006..ddb6806 100644 --- a/libertas_core/src/services.rs +++ b/libertas_core/src/services.rs @@ -106,6 +106,13 @@ pub trait PersonService: Send + Sync { target_user_id: Uuid, owner_id: Uuid, ) -> CoreResult<()>; + + async fn merge_people( + &self, + target_person_id: Uuid, + source_person_id: Uuid, + user_id: Uuid, + ) -> CoreResult<()>; } #[async_trait] diff --git a/libertas_infra/src/repositories/face_region_repository.rs b/libertas_infra/src/repositories/face_region_repository.rs index 68b404a..7fead94 100644 --- a/libertas_infra/src/repositories/face_region_repository.rs +++ b/libertas_infra/src/repositories/face_region_repository.rs @@ -1,5 +1,9 @@ use async_trait::async_trait; -use libertas_core::{error::{CoreError, CoreResult}, models::FaceRegion, repositories::FaceRegionRepository}; +use libertas_core::{ + error::{CoreError, CoreResult}, + models::FaceRegion, + repositories::FaceRegionRepository, +}; use sqlx::PgPool; use uuid::Uuid; @@ -125,4 +129,20 @@ impl FaceRegionRepository for PostgresFaceRegionRepository { .map_err(|e| CoreError::Database(e.to_string()))?; Ok(()) } -} \ No newline at end of file + + async fn reassign_person(&self, old_person_id: Uuid, new_person_id: Uuid) -> CoreResult<()> { + sqlx::query!( + r#" + UPDATE face_regions + SET person_id = $1 + WHERE person_id = $2 + "#, + new_person_id, + old_person_id + ) + .execute(&self.pool) + .await + .map_err(|e| CoreError::Database(e.to_string()))?; + Ok(()) + } +}