feat: Implement person and tag management services
- Added `Person` and `Tag` models to the core library. - Created `PersonService` and `TagService` traits with implementations for managing persons and tags. - Introduced repositories for `Person`, `Tag`, `FaceRegion`, and `PersonShare` with PostgreSQL support. - Updated authorization logic to include permissions for accessing and editing persons. - Enhanced the schema to support new models and relationships. - Implemented database migrations for new tables related to persons and tags. - Added request and response structures for API interactions with persons and tags.
This commit is contained in:
@@ -2,3 +2,5 @@ pub mod album_handlers;
|
||||
pub mod auth_handlers;
|
||||
pub mod media_handlers;
|
||||
pub mod user_handlers;
|
||||
pub mod tag_handlers;
|
||||
pub mod person_handlers;
|
||||
152
libertas_api/src/handlers/person_handlers.rs
Normal file
152
libertas_api/src/handlers/person_handlers.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
routing::{get, post, put},
|
||||
Json, Router,
|
||||
};
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
error::ApiError,
|
||||
middleware::auth::UserId,
|
||||
schema::{
|
||||
AssignFaceRequest, CreatePersonRequest, FaceRegionResponse, PersonResponse,
|
||||
SharePersonRequest, UpdatePersonRequest,
|
||||
},
|
||||
state::AppState,
|
||||
};
|
||||
|
||||
pub fn people_routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(list_people).post(create_person))
|
||||
.route(
|
||||
"/{person_id}",
|
||||
get(get_person)
|
||||
.put(update_person)
|
||||
.delete(delete_person),
|
||||
)
|
||||
.route("/{person_id}/share", post(share_person).delete(unshare_person))
|
||||
}
|
||||
|
||||
pub fn face_routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/media/{media_id}/faces", get(list_faces_for_media))
|
||||
.route("/faces/{face_id}/person", put(assign_face_to_person))
|
||||
}
|
||||
|
||||
async fn create_person(
|
||||
State(state): State<AppState>,
|
||||
UserId(user_id): UserId,
|
||||
Json(payload): Json<CreatePersonRequest>,
|
||||
) -> Result<impl IntoResponse, ApiError> {
|
||||
let person = state
|
||||
.person_service
|
||||
.create_person(&payload.name, user_id)
|
||||
.await?;
|
||||
Ok((StatusCode::CREATED, Json(PersonResponse::from(person))))
|
||||
}
|
||||
|
||||
async fn get_person(
|
||||
State(state): State<AppState>,
|
||||
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?;
|
||||
Ok(Json(PersonResponse::from(person)))
|
||||
}
|
||||
|
||||
async fn list_people(
|
||||
State(state): State<AppState>,
|
||||
UserId(user_id): UserId,
|
||||
) -> Result<Json<Vec<PersonResponse>>, ApiError> {
|
||||
let people = state.person_service.list_people(user_id).await?;
|
||||
let response = people.into_iter().map(PersonResponse::from).collect();
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
async fn update_person(
|
||||
State(state): State<AppState>,
|
||||
UserId(user_id): UserId,
|
||||
Path(person_id): Path<Uuid>,
|
||||
Json(payload): Json<UpdatePersonRequest>,
|
||||
) -> Result<Json<PersonResponse>, ApiError> {
|
||||
let person = state
|
||||
.person_service
|
||||
.update_person(person_id, &payload.name, user_id)
|
||||
.await?;
|
||||
Ok(Json(PersonResponse::from(person)))
|
||||
}
|
||||
|
||||
async fn delete_person(
|
||||
State(state): State<AppState>,
|
||||
UserId(user_id): UserId,
|
||||
Path(person_id): Path<Uuid>,
|
||||
) -> Result<StatusCode, ApiError> {
|
||||
state
|
||||
.person_service
|
||||
.delete_person(person_id, user_id)
|
||||
.await?;
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
async fn share_person(
|
||||
State(state): State<AppState>,
|
||||
UserId(user_id): UserId,
|
||||
Path(person_id): Path<Uuid>,
|
||||
Json(payload): Json<SharePersonRequest>,
|
||||
) -> Result<StatusCode, ApiError> {
|
||||
state
|
||||
.person_service
|
||||
.share_person(
|
||||
person_id,
|
||||
payload.target_user_id,
|
||||
payload.permission,
|
||||
user_id,
|
||||
)
|
||||
.await?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
async fn unshare_person(
|
||||
State(state): State<AppState>,
|
||||
UserId(user_id): UserId,
|
||||
Path(person_id): Path<Uuid>,
|
||||
Json(payload): Json<SharePersonRequest>,
|
||||
) -> Result<StatusCode, ApiError> {
|
||||
state
|
||||
.person_service
|
||||
.unshare_person(person_id, payload.target_user_id, user_id)
|
||||
.await?;
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
async fn list_faces_for_media(
|
||||
State(state): State<AppState>,
|
||||
UserId(user_id): UserId,
|
||||
Path(media_id): Path<Uuid>,
|
||||
) -> Result<Json<Vec<FaceRegionResponse>>, ApiError> {
|
||||
let faces = state
|
||||
.person_service
|
||||
.list_faces_for_media(media_id, user_id)
|
||||
.await?;
|
||||
let response = faces.into_iter().map(FaceRegionResponse::from).collect();
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
async fn assign_face_to_person(
|
||||
State(state): State<AppState>,
|
||||
UserId(user_id): UserId,
|
||||
Path(face_id): Path<Uuid>,
|
||||
Json(payload): Json<AssignFaceRequest>,
|
||||
) -> Result<Json<FaceRegionResponse>, ApiError> {
|
||||
let face = state
|
||||
.person_service
|
||||
.assign_face_to_person(face_id, payload.person_id, user_id)
|
||||
.await?;
|
||||
Ok(Json(FaceRegionResponse::from(face)))
|
||||
}
|
||||
53
libertas_api/src/handlers/tag_handlers.rs
Normal file
53
libertas_api/src/handlers/tag_handlers.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use axum::{Json, Router, extract::{Path, State}, http::StatusCode, response::IntoResponse, routing::{delete, post}};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{error::ApiError, middleware::auth::UserId, schema::{MediaTagsRequest, TagResponse}, state::AppState};
|
||||
|
||||
pub fn tag_routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", post(add_tags_to_media).get(list_tags_for_media))
|
||||
.route(
|
||||
"/{tag_name}",
|
||||
delete(remove_tag_from_media),
|
||||
)
|
||||
}
|
||||
|
||||
async fn add_tags_to_media(
|
||||
State(state): State<AppState>,
|
||||
UserId(user_id): UserId,
|
||||
Path(media_id): Path<Uuid>,
|
||||
Json(payload): Json<MediaTagsRequest>,
|
||||
) -> Result<impl IntoResponse, ApiError> {
|
||||
let tags = state
|
||||
.tag_service
|
||||
.add_tags_to_media(media_id, &payload.tags, user_id)
|
||||
.await?;
|
||||
|
||||
let response: Vec<TagResponse> = tags.into_iter().map(TagResponse::from).collect();
|
||||
Ok((StatusCode::CREATED, Json(response)))
|
||||
}
|
||||
|
||||
async fn list_tags_for_media(
|
||||
State(state): State<AppState>,
|
||||
UserId(user_id): UserId,
|
||||
Path(media_id): Path<Uuid>,
|
||||
) -> Result<Json<Vec<TagResponse>>, ApiError> {
|
||||
let tags = state
|
||||
.tag_service
|
||||
.list_tags_for_media(media_id, user_id)
|
||||
.await?;
|
||||
let response = tags.into_iter().map(TagResponse::from).collect();
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
async fn remove_tag_from_media(
|
||||
State(state): State<AppState>,
|
||||
UserId(user_id): UserId,
|
||||
Path((media_id, tag_name)): Path<(Uuid, String)>,
|
||||
) -> Result<StatusCode, ApiError> {
|
||||
state
|
||||
.tag_service
|
||||
.remove_tags_from_media(media_id, &[tag_name], user_id)
|
||||
.await?;
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
Reference in New Issue
Block a user