feat: Implement merge person functionality with associated request and repository methods

This commit is contained in:
2025-11-15 17:46:36 +01:00
parent 8a735c7c26
commit b80c4e0895
6 changed files with 91 additions and 14 deletions

View File

@@ -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<AppState> {
.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<AppState> {
@@ -53,10 +55,7 @@ async fn get_person(
UserId(user_id): UserId,
Path(person_id): Path<Uuid>,
) -> Result<Json<PersonResponse>, 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)))
}
@@ -150,3 +149,16 @@ async fn assign_face_to_person(
.await?;
Ok(Json(FaceRegionResponse::from(face)))
}
async fn merge_person(
State(state): State<AppState>,
UserId(user_id): UserId,
Path(target_person_id): Path<Uuid>,
Json(payload): Json<MergePersonRequest>,
) -> Result<StatusCode, ApiError> {
state
.person_service
.merge_people(target_person_id, payload.source_person_id, user_id)
.await?;
Ok(StatusCode::NO_CONTENT)
}

View File

@@ -239,3 +239,8 @@ pub struct PublicAlbumBundleResponse {
pub album: AlbumResponse,
pub media: Vec<MediaResponse>,
}
#[derive(Deserialize)]
pub struct MergePersonRequest {
pub source_person_id: Uuid,
}

View File

@@ -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
}
}

View File

@@ -99,6 +99,7 @@ pub trait FaceRegionRepository: Send + Sync {
async fn find_by_id(&self, face_region_id: Uuid) -> CoreResult<Option<FaceRegion>>;
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]

View File

@@ -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]

View File

@@ -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(())
}
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(())
}
}