Merge pull request #479 from EdouardVanbelle/feat/drive-impl
feat/drive impl
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
//! DTOs for the `/api/drives` endpoint surface.
|
||||
//!
|
||||
//! D0 surfaces only the read-only list. Mutating endpoints
|
||||
//! (`POST /api/drives` for shared-drive creation, `PATCH` for rename /
|
||||
//! policy edits, membership APIs) land in D2/D3.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::domain::entities::drive::DriveKind;
|
||||
use crate::domain::repositories::drive_repository::DriveWithRootName;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, ToSchema, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum DriveKindDto {
|
||||
Personal,
|
||||
Shared,
|
||||
}
|
||||
|
||||
impl From<DriveKind> for DriveKindDto {
|
||||
fn from(k: DriveKind) -> Self {
|
||||
match k {
|
||||
DriveKind::Personal => DriveKindDto::Personal,
|
||||
DriveKind::Shared => DriveKindDto::Shared,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One row in `GET /api/drives` — a drive the caller can read.
|
||||
///
|
||||
/// `default_for_user` is `Some(<caller_id>)` for the caller's default
|
||||
/// Personal drive and `None` otherwise. The picker UI uses this to put
|
||||
/// the default at the top of the list and mark it as "your home".
|
||||
#[derive(Debug, Clone, Serialize, ToSchema)]
|
||||
pub struct DriveDto {
|
||||
pub id: Uuid,
|
||||
/// Display name. Sourced from `storage.folders.name` of the row
|
||||
/// pointed at by `root_folder_id` (drives have no `name` column —
|
||||
/// see docs/plan/drive.md §3). The wire shape is unchanged from
|
||||
/// the client's perspective.
|
||||
pub name: String,
|
||||
pub kind: DriveKindDto,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub default_for_user: Option<Uuid>,
|
||||
/// The drive's mount-point folder. Folder API calls
|
||||
/// (`POST /api/folders { parent_id: <root_folder_id> }`,
|
||||
/// `PATCH /api/folders/<root_folder_id>` to rename) use this id —
|
||||
/// no polymorphic "create at drive root" surface needed.
|
||||
pub root_folder_id: Uuid,
|
||||
/// Storage cap in bytes. `None` means "no quota" (admin override /
|
||||
/// future system drives). Mutation is OxiCloud-admin only — drive
|
||||
/// owners cannot self-grant capacity.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub quota_bytes: Option<i64>,
|
||||
/// Running total of bytes consumed. Maintained incrementally in D4;
|
||||
/// on D0 this reflects the backfilled baseline.
|
||||
pub used_bytes: i64,
|
||||
/// Capability-flag bag — clients render UI affordances based on
|
||||
/// known keys (`forbid_public_links`, `include_in_photo_index`,
|
||||
/// `forbid_music_index`, …). Unknown keys preserved verbatim.
|
||||
pub policies: serde_json::Value,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
impl From<DriveWithRootName> for DriveDto {
|
||||
fn from(d: DriveWithRootName) -> Self {
|
||||
Self {
|
||||
id: d.drive.id,
|
||||
name: d.root_folder_name,
|
||||
kind: d.drive.kind.into(),
|
||||
default_for_user: d.drive.default_for_user,
|
||||
root_folder_id: d.drive.root_folder_id,
|
||||
quota_bytes: d.drive.quota_bytes,
|
||||
used_bytes: d.drive.used_bytes,
|
||||
policies: d.drive.policies,
|
||||
created_at: d.drive.created_at,
|
||||
updated_at: d.drive.updated_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||
use crate::domain::entities::file::File;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::display_helpers::{
|
||||
category_for, format_file_size, icon_class_for, icon_special_class_for,
|
||||
@@ -75,6 +76,19 @@ pub struct FileDto {
|
||||
/// through `If-Match` / `If-None-Match` on download / mutation
|
||||
/// endpoints without a separate HEAD round-trip.
|
||||
pub etag: String,
|
||||
|
||||
/// §14 provenance: user that originally created this file.
|
||||
/// `None` when the referenced user has been deleted (FK is
|
||||
/// `ON DELETE SET NULL`) or for stub/legacy files.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub created_by: Option<Uuid>,
|
||||
|
||||
/// §14 provenance: user that performed the most recent mutation
|
||||
/// that bumped `updated_at`. Authorship signal — distinct from
|
||||
/// `owner_id`. `None` when the referenced user is deleted or for
|
||||
/// stub/legacy files.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub updated_by: Option<Uuid>,
|
||||
}
|
||||
|
||||
impl From<File> for FileDto {
|
||||
@@ -114,6 +128,8 @@ impl From<File> for FileDto {
|
||||
sort_date: None,
|
||||
content_hash,
|
||||
etag,
|
||||
created_by: parts.created_by,
|
||||
updated_by: parts.updated_by,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,6 +187,8 @@ impl FileDto {
|
||||
content_hash: String::new(),
|
||||
etag: String::new(),
|
||||
sort_date: None,
|
||||
created_by: None,
|
||||
updated_by: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,12 @@ pub struct FolderDto {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub owner_id: Option<String>,
|
||||
|
||||
/// Drive that owns this folder. The scope axis for path-based
|
||||
/// lookups across REST / WebDAV / NextCloud / CalDAV / CardDAV.
|
||||
/// Post-D0 `storage.folders.drive_id` is `NOT NULL`; stub /
|
||||
/// DTO-reconstructed folders carry `Uuid::nil()`.
|
||||
pub drive_id: Uuid,
|
||||
|
||||
/// Creation timestamp
|
||||
pub created_at: u64,
|
||||
|
||||
@@ -80,6 +86,19 @@ pub struct FolderDto {
|
||||
/// pass it back through `If-Match` on rename / move endpoints
|
||||
/// without a separate HEAD round-trip.
|
||||
pub etag: String,
|
||||
|
||||
/// §14 provenance: user that originally created this folder.
|
||||
/// `None` when the referenced user has been deleted (FK is
|
||||
/// `ON DELETE SET NULL`) or for stub/legacy folders.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub created_by: Option<Uuid>,
|
||||
|
||||
/// §14 provenance: user that performed the most recent mutation
|
||||
/// that bumped `updated_at`. Authorship signal — distinct from
|
||||
/// `owner_id`. `None` when the referenced user is deleted or for
|
||||
/// stub/legacy folders.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub updated_by: Option<Uuid>,
|
||||
}
|
||||
|
||||
impl From<Folder> for FolderDto {
|
||||
@@ -93,6 +112,7 @@ impl From<Folder> for FolderDto {
|
||||
path: folder.path_string().to_string(),
|
||||
parent_id: folder.parent_id().map(String::from),
|
||||
owner_id: folder.owner_id().map(|u| u.to_string()),
|
||||
drive_id: folder.drive_id(),
|
||||
created_at: folder.created_at(),
|
||||
modified_at: folder.modified_at(),
|
||||
is_root,
|
||||
@@ -100,6 +120,8 @@ impl From<Folder> for FolderDto {
|
||||
icon_special_class: Arc::from("folder-icon"),
|
||||
category: Arc::from("Folder"),
|
||||
etag,
|
||||
created_by: folder.created_by(),
|
||||
updated_by: folder.updated_by(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,6 +166,7 @@ impl FolderDto {
|
||||
path: "/stub/path".to_string(),
|
||||
parent_id: None,
|
||||
owner_id: None,
|
||||
drive_id: Uuid::nil(),
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
is_root: true,
|
||||
@@ -151,6 +174,8 @@ impl FolderDto {
|
||||
icon_special_class: Arc::from("folder-icon"),
|
||||
category: Arc::from("Folder"),
|
||||
etag: String::new(),
|
||||
created_by: None,
|
||||
updated_by: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ impl From<Subject> for SubjectDto {
|
||||
pub enum ResourceTypeDto {
|
||||
Folder,
|
||||
File,
|
||||
Drive,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
@@ -72,6 +73,7 @@ impl From<ResourceDto> for Resource {
|
||||
match dto.kind {
|
||||
ResourceTypeDto::Folder => Resource::Folder(dto.id),
|
||||
ResourceTypeDto::File => Resource::File(dto.id),
|
||||
ResourceTypeDto::Drive => Resource::Drive(dto.id),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,6 +83,7 @@ impl From<Resource> for ResourceDto {
|
||||
let (kind, id) = match r {
|
||||
Resource::Folder(id) => (ResourceTypeDto::Folder, id),
|
||||
Resource::File(id) => (ResourceTypeDto::File, id),
|
||||
Resource::Drive(id) => (ResourceTypeDto::Drive, id),
|
||||
};
|
||||
ResourceDto { kind, id }
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ pub mod calendar_dto;
|
||||
pub mod contact_dto;
|
||||
pub mod device_auth_dto;
|
||||
pub mod display_helpers;
|
||||
pub mod drive_dto;
|
||||
pub mod favorites_dto;
|
||||
pub mod file_dto;
|
||||
pub mod folder_dto;
|
||||
|
||||
Reference in New Issue
Block a user