feat(breadcrumb): build breadcrumb in 1 API call

add /api/folders/{id}/ancestors

    this API to iterate parent up to the drive root or the shared folder
    this will help UI to build the breadcrumb in 1 API call
    and to identify the root element (is it a drive users has access to or
    a shared folder ?)

    ui: now only 1 API call is now required to build the breadcrumb
This commit is contained in:
Edouard Vanbelle
2026-07-26 21:31:04 +02:00
parent 0efbf0ff85
commit 3b31b8911b
33 changed files with 1342 additions and 221 deletions
+36 -2
View File
@@ -12,8 +12,8 @@ use crate::application::dtos::display_helpers::{
};
use crate::application::dtos::file_dto::FileDto;
use crate::application::dtos::folder_dto::{
CreateFolderDto, FolderDto, FolderResourceItemDto, FolderResourcesDto, FolderResourcesQuery,
ListResourcesOptions, MoveFolderDto, RenameFolderDto,
CreateFolderDto, FolderAncestorsDto, FolderDto, FolderResourceItemDto, FolderResourcesDto,
FolderResourcesQuery, ListResourcesOptions, MoveFolderDto, RenameFolderDto,
};
use crate::application::dtos::grant_dto::{ResourceContentDto, ResourceTypeDto};
use crate::application::ports::external_mount_ports::MountEntry;
@@ -112,6 +112,21 @@ impl FolderHandler {
}
}
/// `GET /api/folders/{id}/ancestors` — parent-chain + access-source
/// for the shared breadcrumb component. See `FolderAncestorsDto`
/// for the response shape. Anti-enum via `NotFound` on Read denial.
pub(super) async fn get_folder_ancestors_impl(
State(state): State<Arc<GlobalAppState>>,
auth_user: AuthUser,
Path(id): Path<String>,
) -> impl IntoResponse {
let service = &state.applications.folder_service_concrete;
match service.get_ancestors_with_perms(&id, auth_user.id).await {
Ok(dto) => (StatusCode::OK, Json(dto)).into_response(),
Err(err) => AppError::from(err).into_response(),
}
}
/// Lists root folders for the authenticated user.
/// Only returns folders owned by this user — no information disclosure.
pub(super) async fn list_root_folders_impl(
@@ -367,6 +382,25 @@ pub async fn get_folder(
FolderHandler::get_folder_impl(state, auth_user, path).await
}
#[utoipa::path(
get,
path = "/api/folders/{id}/ancestors",
params(("id" = String, Path, description = "Leaf folder ID — the walk starts here and climbs the parent chain up to the drive root or the caller's share/drive-membership boundary.")),
responses(
(status = 200, description = "Ancestor chain + access-source. `ancestors` is root-first, leaf-last (length ≥ 1). See `FolderAncestorsDto`.", body = FolderAncestorsDto),
(status = 404, description = "Folder not found or caller lacks Read (anti-enum)"),
),
security(("bearerAuth" = [])),
tag = "folders"
)]
pub async fn get_folder_ancestors(
state: State<Arc<GlobalAppState>>,
auth_user: AuthUser,
path: Path<String>,
) -> impl IntoResponse {
FolderHandler::get_folder_ancestors_impl(state, auth_user, path).await
}
#[utoipa::path(
get,
path = "/api/folders",