feat(drive): start implementation of drive
- add storage.drives
- prepare migration phase
- add created_by and updated_by on storage.folders
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
//! `GET /api/drives` — list every drive the caller can read.
|
||||
//!
|
||||
//! D0 ships the read-only listing; D2 adds shared-drive membership
|
||||
//! mutations (`POST/DELETE/PUT /api/drives/{id}/members`), D3 adds the
|
||||
//! create-shared-drive flow, etc.
|
||||
//!
|
||||
//! The handler resolves the caller's expanded subject set through the
|
||||
//! engine (so group-mediated drive grants surface — the foundation for
|
||||
//! D2/D3) and asks the `DriveRepository` for every drive that set can
|
||||
//! read. Authorization is purely the subject-expansion step: no
|
||||
//! `require(...)` call here, because "your accessible drives" is a
|
||||
//! listing query, not a permission decision on a specific drive.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{Json, extract::State, http::StatusCode, response::IntoResponse};
|
||||
use tracing::error;
|
||||
|
||||
use crate::application::dtos::drive_dto::DriveDto;
|
||||
use crate::common::di::AppState;
|
||||
use crate::domain::repositories::drive_repository::DriveRepository;
|
||||
use crate::domain::services::authorization::Subject;
|
||||
use crate::interfaces::errors::AppError;
|
||||
use crate::interfaces::middleware::auth::AuthUser;
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/drives",
|
||||
responses(
|
||||
(status = 200, description = "Drives the caller can read", body = Vec<DriveDto>),
|
||||
(status = 500, description = "Internal server error"),
|
||||
),
|
||||
security(("bearerAuth" = [])),
|
||||
tag = "drives"
|
||||
)]
|
||||
pub async fn list_drives(
|
||||
State(state): State<Arc<AppState>>,
|
||||
auth_user: AuthUser,
|
||||
) -> impl IntoResponse {
|
||||
let caller_id = auth_user.id;
|
||||
|
||||
// Expand the caller's `Subject::User` into the `(types, ids)` pair
|
||||
// that includes every group the user transitively belongs to. The
|
||||
// engine caches this expansion in its Moka cache; if the caller
|
||||
// just ran a permission check, this is a hit.
|
||||
let (subject_types, subject_ids) = match state
|
||||
.authorization
|
||||
.expand_subject_for_listing(Subject::User(caller_id))
|
||||
.await
|
||||
{
|
||||
Ok(pair) => pair,
|
||||
Err(e) => {
|
||||
error!("list_drives: subject expansion failed: {e}");
|
||||
return AppError::from(e).into_response();
|
||||
}
|
||||
};
|
||||
|
||||
match state
|
||||
.drive_repo
|
||||
.list_for_subjects(&subject_types, &subject_ids)
|
||||
.await
|
||||
{
|
||||
Ok(drives) => {
|
||||
let dtos: Vec<DriveDto> = drives.into_iter().map(DriveDto::from).collect();
|
||||
(StatusCode::OK, Json(dtos)).into_response()
|
||||
}
|
||||
Err(e) => {
|
||||
error!("list_drives: repo lookup failed: {e}");
|
||||
AppError::internal_error(format!("Failed to list drives: {e}")).into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -719,6 +719,13 @@ pub async fn list_shared_with_me(
|
||||
summary.resource_id
|
||||
),
|
||||
},
|
||||
// Drive grants don't appear in the file/folder "Shared with me"
|
||||
// listing — they're surfaced through `GET /api/drives` (D0).
|
||||
// Silently skipping here is the right behaviour: a drive grant
|
||||
// discovered by `list_incoming_resources_paged` is not a stale
|
||||
// grant, just a different resource type with a different
|
||||
// listing surface.
|
||||
ResourceKind::Drive => continue,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -953,6 +960,10 @@ pub async fn list_my_shares(
|
||||
summary.resource_id
|
||||
),
|
||||
},
|
||||
// Drive grants are surfaced via `GET /api/drives` (D0), not
|
||||
// through the My Shares outgoing-resources surface. Silently
|
||||
// skip — symmetric with the `list_shared_with_me` arm above.
|
||||
ResourceKind::Drive => continue,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ pub mod contacts_handler;
|
||||
pub mod dedup_handler;
|
||||
pub mod delta_upload_handler;
|
||||
pub mod device_auth_handler;
|
||||
pub mod drive_handler;
|
||||
pub mod favorites_handler;
|
||||
pub mod file_handler;
|
||||
pub mod folder_handler;
|
||||
|
||||
@@ -13,6 +13,7 @@ use utoipa::{Modify, OpenApi};
|
||||
use crate::application::dtos::contact_dto::{
|
||||
AddressDto, ContactDto, ContactGroupDto, EmailDto, PhoneDto,
|
||||
};
|
||||
use crate::application::dtos::drive_dto::{DriveDto, DriveKindDto};
|
||||
use crate::application::dtos::favorites_dto::{
|
||||
BatchFavoritesResult, BatchFavoritesStats, FavoriteItemDto, FavoritesResourceItemDto,
|
||||
};
|
||||
@@ -165,6 +166,8 @@ use crate::interfaces::api::handlers::file_handler::MoveFilePayload;
|
||||
// Photos handler (free function)
|
||||
handlers::photos_handler::list_photos,
|
||||
handlers::photos_handler::list_photos_geo,
|
||||
// Drive handler (free function)
|
||||
handlers::drive_handler::list_drives,
|
||||
// Batch handlers (free functions)
|
||||
handlers::batch_handler::move_files_batch,
|
||||
handlers::batch_handler::copy_files_batch,
|
||||
@@ -359,6 +362,9 @@ use crate::interfaces::api::handlers::file_handler::MoveFilePayload;
|
||||
SharedWithMeDto,
|
||||
SharedWithMeItemDto,
|
||||
OutgoingResourceItemDto,
|
||||
// Drive schemas
|
||||
DriveDto,
|
||||
DriveKindDto,
|
||||
// Subject-group (ReBAC named groups) schemas
|
||||
handlers::subject_group_handler::CreateGroupRequest,
|
||||
handlers::subject_group_handler::UpdateGroupRequest,
|
||||
|
||||
@@ -440,6 +440,19 @@ pub fn create_api_routes(app_state: &Arc<AppState>) -> Router<Arc<AppState>> {
|
||||
router = router.nest("/photos", photos_router);
|
||||
}
|
||||
|
||||
// Drives — every drive the caller can read. D0 ships the read-only
|
||||
// listing; D2 adds the membership API + shared-drive endpoints under
|
||||
// `/api/drives/{id}/members`.
|
||||
{
|
||||
use crate::interfaces::api::handlers::drive_handler;
|
||||
|
||||
let drives_router = Router::new()
|
||||
.route("/", get(drive_handler::list_drives))
|
||||
.with_state(app_state.clone());
|
||||
|
||||
router = router.nest("/drives", drives_router);
|
||||
}
|
||||
|
||||
// People (faces) routes — mounted only when OXICLOUD_ENABLE_FACES is on.
|
||||
if app_state.people_service.is_some() {
|
||||
use crate::interfaces::api::handlers::people_handler;
|
||||
|
||||
Reference in New Issue
Block a user