feat(openapi): implement missing routes
Admin:
- /api/admin/drives, /api/admin/drives/{id}, /api/admin/drives/{id}/members, /api/admin/drives/{id}/members/{kind}/{sid}
- /api/admin/jobs/{name}/pause, /api/admin/jobs/{name}/runs/{id}/findings, /api/admin/jobs/runs/purge
- /api/admin/smtp/info, /api/admin/smtp/test
- /api/admin/storage/entries/{name}/rotate
- /api/admin/users/{id}/promote-to-internal
Auth:
- /api/auth/dpop/bind
- /api/auth/magic-link/send
- /api/auth/me/profile
- /api/auth/upgrade-to-internal
Drives / grants / trash / users / dedup:
- /api/drives/{id}, /api/drives/{id}/members (get + delete), /api/drives/{id}/policies, /api/drives/{id}/quota
- /api/grants/{id}/notify
- /api/trash/drive/{drive_id}
- /api/users/{id}
- /api/dedup/check-batch
Faces
- /api/people — cluster list (PersonDto[])
- /api/people/{id}/photos — file ids for one person
- /api/people/{id} — rename (or clear name)
- /api/people/merge — merge two clusters
- /api/people/recluster — re-run clustering
- /api/people/data — nuke all face data
- /api/people/faces/{file_id} — face boxes per photo (FaceBoxDto[])
This commit is contained in:
@@ -1718,7 +1718,9 @@ async fn reextract_image_metadata(
|
||||
security(("bearerAuth" = [])),
|
||||
tag = "admin"
|
||||
)]
|
||||
async fn get_smtp_info(State(state): State<Arc<AppState>>) -> Result<impl IntoResponse, AppError> {
|
||||
pub async fn get_smtp_info(
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
let smtp = &state.core.config.smtp;
|
||||
let info = SmtpInfoDto {
|
||||
enabled: smtp.is_enabled() && state.email_sender.is_some(),
|
||||
@@ -1802,7 +1804,7 @@ struct CapturedEmailQuery {
|
||||
security(("bearerAuth" = [])),
|
||||
tag = "admin"
|
||||
)]
|
||||
async fn send_smtp_test(
|
||||
pub async fn send_smtp_test(
|
||||
State(state): State<Arc<AppState>>,
|
||||
auth_user: AuthUser,
|
||||
Json(dto): Json<SendSmtpTestDto>,
|
||||
|
||||
@@ -15,9 +15,11 @@ use axum::{
|
||||
use serde::Deserialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::application::dtos::people_dto::{FaceBoxDto, PersonDto};
|
||||
use crate::common::di::AppState;
|
||||
use crate::interfaces::errors::AppError;
|
||||
use crate::interfaces::middleware::auth::AuthUser;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
fn disabled() -> Response {
|
||||
(
|
||||
@@ -36,6 +38,15 @@ fn bad_id() -> Response {
|
||||
}
|
||||
|
||||
/// GET /api/people — identity clusters for the caller.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/people",
|
||||
responses(
|
||||
(status = 200, description = "Identity clusters", body = [PersonDto]),
|
||||
(status = 404, description = "People feature disabled"),
|
||||
),
|
||||
tag = "people"
|
||||
)]
|
||||
pub async fn list_people(State(state): State<Arc<AppState>>, auth_user: AuthUser) -> Response {
|
||||
let Some(svc) = state.people_service.as_ref() else {
|
||||
return disabled();
|
||||
@@ -47,6 +58,17 @@ pub async fn list_people(State(state): State<Arc<AppState>>, auth_user: AuthUser
|
||||
}
|
||||
|
||||
/// GET /api/people/{id}/photos — file ids of a person's photos.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/people/{id}/photos",
|
||||
params(("id" = String, Path, description = "Person cluster id")),
|
||||
responses(
|
||||
(status = 200, description = "File ids where this person appears", body = [String]),
|
||||
(status = 400, description = "Malformed person id"),
|
||||
(status = 404, description = "People feature disabled"),
|
||||
),
|
||||
tag = "people"
|
||||
)]
|
||||
pub async fn person_photos(
|
||||
State(state): State<Arc<AppState>>,
|
||||
auth_user: AuthUser,
|
||||
@@ -64,12 +86,26 @@ pub async fn person_photos(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct RenameBody {
|
||||
/// New display name for the cluster. `null` or omitted clears
|
||||
/// the name, reverting the cluster to its "Unnamed" state.
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
/// PATCH /api/people/{id} — name (or clear the name of) a person.
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
path = "/api/people/{id}",
|
||||
params(("id" = String, Path, description = "Person cluster id")),
|
||||
request_body = RenameBody,
|
||||
responses(
|
||||
(status = 204, description = "Renamed"),
|
||||
(status = 400, description = "Malformed person id"),
|
||||
(status = 404, description = "People feature disabled"),
|
||||
),
|
||||
tag = "people"
|
||||
)]
|
||||
pub async fn rename_person(
|
||||
State(state): State<Arc<AppState>>,
|
||||
auth_user: AuthUser,
|
||||
@@ -88,13 +124,28 @@ pub async fn rename_person(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct MergeBody {
|
||||
/// Cluster id that will absorb the other one (`from`'s photos are
|
||||
/// reattributed to `into`; `from` is deleted). Typically the
|
||||
/// larger / named cluster wins.
|
||||
pub into: String,
|
||||
/// Cluster id being merged into `into`. Deleted after the merge.
|
||||
pub from: String,
|
||||
}
|
||||
|
||||
/// POST /api/people/merge — merge `from` into `into`.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/people/merge",
|
||||
request_body = MergeBody,
|
||||
responses(
|
||||
(status = 204, description = "Merged"),
|
||||
(status = 400, description = "Malformed cluster id(s)"),
|
||||
(status = 404, description = "People feature disabled"),
|
||||
),
|
||||
tag = "people"
|
||||
)]
|
||||
pub async fn merge_people(
|
||||
State(state): State<Arc<AppState>>,
|
||||
auth_user: AuthUser,
|
||||
@@ -113,6 +164,15 @@ pub async fn merge_people(
|
||||
}
|
||||
|
||||
/// POST /api/people/recluster — re-run identity clustering for the caller.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/people/recluster",
|
||||
responses(
|
||||
(status = 200, description = "Recluster complete", body = serde_json::Value),
|
||||
(status = 404, description = "People feature disabled"),
|
||||
),
|
||||
tag = "people"
|
||||
)]
|
||||
pub async fn recluster(State(state): State<Arc<AppState>>, auth_user: AuthUser) -> Response {
|
||||
let Some(svc) = state.people_service.as_ref() else {
|
||||
return disabled();
|
||||
@@ -124,6 +184,15 @@ pub async fn recluster(State(state): State<Arc<AppState>>, auth_user: AuthUser)
|
||||
}
|
||||
|
||||
/// DELETE /api/people/data — erase all of the caller's face data.
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/api/people/data",
|
||||
responses(
|
||||
(status = 204, description = "All face data erased"),
|
||||
(status = 404, description = "People feature disabled"),
|
||||
),
|
||||
tag = "people"
|
||||
)]
|
||||
pub async fn delete_all(State(state): State<Arc<AppState>>, auth_user: AuthUser) -> Response {
|
||||
let Some(svc) = state.people_service.as_ref() else {
|
||||
return disabled();
|
||||
@@ -135,6 +204,17 @@ pub async fn delete_all(State(state): State<Arc<AppState>>, auth_user: AuthUser)
|
||||
}
|
||||
|
||||
/// GET /api/people/faces/{file_id} — face boxes within a photo (lightbox tags).
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/people/faces/{file_id}",
|
||||
params(("file_id" = String, Path, description = "Photo file id")),
|
||||
responses(
|
||||
(status = 200, description = "Face bounding boxes in the photo", body = [FaceBoxDto]),
|
||||
(status = 400, description = "Malformed file id"),
|
||||
(status = 404, description = "People feature disabled"),
|
||||
),
|
||||
tag = "people"
|
||||
)]
|
||||
pub async fn faces_for_file(
|
||||
State(state): State<Arc<AppState>>,
|
||||
auth_user: AuthUser,
|
||||
|
||||
@@ -46,7 +46,7 @@ pub fn user_routes() -> Router<Arc<AppState>> {
|
||||
security(("bearerAuth" = [])),
|
||||
tag = "users",
|
||||
)]
|
||||
async fn get_user_profile(
|
||||
pub async fn get_user_profile(
|
||||
State(state): State<Arc<AppState>>,
|
||||
auth_user: AuthUser,
|
||||
Path(target_id): Path<Uuid>,
|
||||
|
||||
Reference in New Issue
Block a user