feat: Implement album and person sharing with user search and a dedicated share dialog.
This commit is contained in:
@@ -83,6 +83,46 @@ async fn list_user_albums(
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
async fn unshare_album(
|
||||
State(state): State<AppState>,
|
||||
UserId(user_id): UserId,
|
||||
Path(album_id): Path<Uuid>,
|
||||
Json(payload): Json<crate::schema::UnshareAlbumRequest>,
|
||||
) -> Result<StatusCode, ApiError> {
|
||||
state
|
||||
.album_service
|
||||
.unshare_album(album_id, payload.target_user_id, user_id)
|
||||
.await?;
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
async fn list_album_shares(
|
||||
State(state): State<AppState>,
|
||||
UserId(user_id): UserId,
|
||||
Path(album_id): Path<Uuid>,
|
||||
) -> Result<Json<Vec<crate::schema::AlbumShareResponse>>, ApiError> {
|
||||
let shares = state
|
||||
.album_service
|
||||
.get_album_shares(album_id, user_id)
|
||||
.await?;
|
||||
|
||||
let response = shares
|
||||
.into_iter()
|
||||
.map(|(user, permission)| crate::schema::AlbumShareResponse {
|
||||
user: crate::schema::UserResponse {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
storage_used: user.storage_used,
|
||||
storage_quota: user.storage_quota,
|
||||
},
|
||||
permission,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
async fn get_album_details(
|
||||
State(state): State<AppState>,
|
||||
UserId(user_id): UserId,
|
||||
@@ -179,5 +219,10 @@ pub fn album_routes() -> Router<AppState> {
|
||||
.get(get_media_for_album)
|
||||
.delete(remove_media_from_album),
|
||||
)
|
||||
.route("/{id}/share", post(share_album))
|
||||
.route(
|
||||
"/{id}/share",
|
||||
post(share_album)
|
||||
.get(list_album_shares)
|
||||
.delete(unshare_album),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,9 @@ pub fn people_routes() -> Router<AppState> {
|
||||
)
|
||||
.route(
|
||||
"/{person_id}/share",
|
||||
post(share_person).delete(unshare_person),
|
||||
post(share_person)
|
||||
.delete(unshare_person)
|
||||
.get(list_person_shares),
|
||||
)
|
||||
.route("/{person_id}/merge", post(merge_person))
|
||||
.route("/{person_id}/thumbnail", put(set_person_thumbnail))
|
||||
@@ -120,7 +122,7 @@ async fn unshare_person(
|
||||
State(state): State<AppState>,
|
||||
UserId(user_id): UserId,
|
||||
Path(person_id): Path<Uuid>,
|
||||
Json(payload): Json<SharePersonRequest>,
|
||||
Json(payload): Json<crate::schema::UnsharePersonRequest>,
|
||||
) -> Result<StatusCode, ApiError> {
|
||||
state
|
||||
.person_service
|
||||
@@ -129,6 +131,33 @@ async fn unshare_person(
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
async fn list_person_shares(
|
||||
State(state): State<AppState>,
|
||||
UserId(user_id): UserId,
|
||||
Path(person_id): Path<Uuid>,
|
||||
) -> Result<Json<Vec<crate::schema::PersonShareResponse>>, ApiError> {
|
||||
let shares = state
|
||||
.person_service
|
||||
.get_person_shares(person_id, user_id)
|
||||
.await?;
|
||||
|
||||
let response = shares
|
||||
.into_iter()
|
||||
.map(|(user, permission)| crate::schema::PersonShareResponse {
|
||||
user: crate::schema::UserResponse {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
storage_used: user.storage_used,
|
||||
storage_quota: user.storage_quota,
|
||||
},
|
||||
permission,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
async fn list_faces_for_media(
|
||||
State(state): State<AppState>,
|
||||
UserId(user_id): UserId,
|
||||
|
||||
@@ -18,6 +18,33 @@ pub async fn get_me(
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
pub fn user_routes() -> Router<AppState> {
|
||||
Router::new().route("/me", axum::routing::get(get_me))
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct SearchUserQuery {
|
||||
query: String,
|
||||
}
|
||||
|
||||
pub async fn search_users(
|
||||
State(state): State<AppState>,
|
||||
axum::extract::Query(params): axum::extract::Query<SearchUserQuery>,
|
||||
) -> Result<Json<Vec<UserResponse>>, ApiError> {
|
||||
let users = state.user_service.search_users(¶ms.query).await?;
|
||||
|
||||
let response = users
|
||||
.into_iter()
|
||||
.map(|user| UserResponse {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
storage_used: user.storage_used,
|
||||
storage_quota: user.storage_quota,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
pub fn user_routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/me", axum::routing::get(get_me))
|
||||
.route("/search", axum::routing::get(search_users))
|
||||
}
|
||||
|
||||
@@ -66,6 +66,17 @@ pub struct ShareAlbumRequest {
|
||||
pub permission: AlbumPermission,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UnshareAlbumRequest {
|
||||
pub target_user_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AlbumShareResponse {
|
||||
pub user: UserResponse,
|
||||
pub permission: AlbumPermission,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AlbumResponse {
|
||||
pub id: Uuid,
|
||||
@@ -246,12 +257,23 @@ pub struct SharePersonRequest {
|
||||
pub permission: PersonPermission,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UnsharePersonRequest {
|
||||
pub target_user_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct PublicAlbumBundleResponse {
|
||||
pub album: AlbumResponse,
|
||||
pub media: Vec<MediaResponse>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct PersonShareResponse {
|
||||
pub user: UserResponse,
|
||||
pub permission: PersonPermission,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct MergePersonRequest {
|
||||
pub source_person_id: Uuid,
|
||||
|
||||
@@ -213,4 +213,36 @@ impl AlbumService for AlbumServiceImpl {
|
||||
.remove_media_from_album(album_id, media_ids)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_album_shares(
|
||||
&self,
|
||||
album_id: Uuid,
|
||||
user_id: Uuid,
|
||||
) -> CoreResult<
|
||||
Vec<(
|
||||
libertas_core::models::User,
|
||||
libertas_core::models::AlbumPermission,
|
||||
)>,
|
||||
> {
|
||||
self.auth_service
|
||||
.check_permission(Some(user_id), Permission::ShareAlbum(album_id))
|
||||
.await?;
|
||||
|
||||
self.album_share_repo.list_shares_for_album(album_id).await
|
||||
}
|
||||
|
||||
async fn unshare_album(
|
||||
&self,
|
||||
album_id: Uuid,
|
||||
target_user_id: Uuid,
|
||||
owner_id: Uuid,
|
||||
) -> CoreResult<()> {
|
||||
self.auth_service
|
||||
.check_permission(Some(owner_id), Permission::ShareAlbum(album_id))
|
||||
.await?;
|
||||
|
||||
self.album_share_repo
|
||||
.remove_share(album_id, target_user_id)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,21 +187,6 @@ impl PersonService for PersonServiceImpl {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn unshare_person(
|
||||
&self,
|
||||
person_id: Uuid,
|
||||
target_user_id: Uuid,
|
||||
owner_id: Uuid,
|
||||
) -> CoreResult<()> {
|
||||
self.auth_service
|
||||
.check_permission(Some(owner_id), authz::Permission::SharePerson(person_id))
|
||||
.await?;
|
||||
|
||||
self.person_share_repo
|
||||
.remove_share(person_id, target_user_id)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn merge_people(
|
||||
&self,
|
||||
target_person_id: Uuid,
|
||||
@@ -342,4 +327,33 @@ impl PersonService for PersonServiceImpl {
|
||||
let response = PaginatedResponse::new(data, pagination.page, pagination.limit, total_items);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
async fn get_person_shares(
|
||||
&self,
|
||||
person_id: Uuid,
|
||||
user_id: Uuid,
|
||||
) -> CoreResult<Vec<(libertas_core::models::User, PersonPermission)>> {
|
||||
self.auth_service
|
||||
.check_permission(Some(user_id), authz::Permission::SharePerson(person_id))
|
||||
.await?;
|
||||
|
||||
self.person_share_repo
|
||||
.list_shares_for_person(person_id)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn unshare_person(
|
||||
&self,
|
||||
person_id: Uuid,
|
||||
target_user_id: Uuid,
|
||||
owner_id: Uuid,
|
||||
) -> CoreResult<()> {
|
||||
self.auth_service
|
||||
.check_permission(Some(owner_id), authz::Permission::SharePerson(person_id))
|
||||
.await?;
|
||||
|
||||
self.person_share_repo
|
||||
.remove_share(person_id, target_user_id)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,4 +96,8 @@ impl UserService for UserServiceImpl {
|
||||
.await?
|
||||
.ok_or(CoreError::NotFound("User".to_string(), user_id))
|
||||
}
|
||||
|
||||
async fn search_users(&self, query: &str) -> CoreResult<Vec<User>> {
|
||||
self.repo.search_users(query).await
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user