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:
@@ -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",
|
||||
|
||||
@@ -20,7 +20,9 @@ use crate::application::dtos::favorites_dto::{
|
||||
};
|
||||
use crate::application::dtos::file_dto::FileDto;
|
||||
use crate::application::dtos::folder_dto::{
|
||||
CreateFolderDto, FolderDto, FolderResourceItemDto, MoveFolderDto, RenameFolderDto,
|
||||
AccessSourceDriveDto, AccessSourceDto, AccessSourceKind, AccessSourceSubjectDto,
|
||||
AccessSourceSubjectKind, CreateFolderDto, FolderAncestorDto, FolderAncestorsDto, FolderDto,
|
||||
FolderResourceItemDto, MoveFolderDto, RenameFolderDto,
|
||||
};
|
||||
use crate::application::dtos::folder_listing_dto::FolderListingDto;
|
||||
use crate::application::dtos::grant_dto::{
|
||||
@@ -96,6 +98,7 @@ use crate::interfaces::api::handlers::file_handler::MoveFilePayload;
|
||||
// Folder handlers (free functions — see folder_handler.rs for why)
|
||||
handlers::folder_handler::create_folder,
|
||||
handlers::folder_handler::get_folder,
|
||||
handlers::folder_handler::get_folder_ancestors,
|
||||
handlers::folder_handler::list_root_folders,
|
||||
handlers::folder_handler::list_folder_resources,
|
||||
handlers::folder_handler::rename_folder,
|
||||
@@ -264,6 +267,14 @@ use crate::interfaces::api::handlers::file_handler::MoveFilePayload;
|
||||
FolderListingDto,
|
||||
FolderResourceItemDto,
|
||||
ResourceContentDto,
|
||||
// Folder ancestor chain (breadcrumb endpoint)
|
||||
FolderAncestorsDto,
|
||||
FolderAncestorDto,
|
||||
AccessSourceDto,
|
||||
AccessSourceKind,
|
||||
AccessSourceDriveDto,
|
||||
AccessSourceSubjectDto,
|
||||
AccessSourceSubjectKind,
|
||||
// File schemas
|
||||
FileDto,
|
||||
// Delta-upload schemas
|
||||
|
||||
@@ -82,7 +82,7 @@ use crate::interfaces::api::handlers::file_handler::{
|
||||
list_files_query, move_file_simple, rename_file, upload_file_with_thumbnails, upload_thumbnail,
|
||||
};
|
||||
use crate::interfaces::api::handlers::folder_handler::{
|
||||
create_folder, delete_folder_with_trash, download_folder_zip, get_folder,
|
||||
create_folder, delete_folder_with_trash, download_folder_zip, get_folder, get_folder_ancestors,
|
||||
list_folder_resources, list_root_folders, move_folder, rename_folder,
|
||||
};
|
||||
use crate::interfaces::api::handlers::i18n_handler::{
|
||||
@@ -218,6 +218,11 @@ pub fn create_api_routes(app_state: &Arc<AppState>) -> Router<Arc<AppState>> {
|
||||
let folders_crud_router = Router::new()
|
||||
.route("/", post(create_folder))
|
||||
.route("/{id}", get(get_folder))
|
||||
// Ancestor chain for the shared breadcrumb component — one
|
||||
// round-trip vs the pre-2026-07-26 per-segment `getFolder` walk
|
||||
// on the /files client. Returns caller-visible parents (walk
|
||||
// stops at share/drive-membership boundary) + access_source.
|
||||
.route("/{id}/ancestors", get(get_folder_ancestors))
|
||||
.route("/{id}/rename", put(rename_folder))
|
||||
.route("/{id}/move", put(move_folder))
|
||||
.with_state(app_state.clone());
|
||||
|
||||
Reference in New Issue
Block a user