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,126 @@
|
||||
//! Drive — the top-level container that owns a tree of folders/files.
|
||||
//!
|
||||
//! Drives replaced the per-user `My Folder - <username>` wrapper at D0.
|
||||
//! Every folder and file row carries a `drive_id` (added by D0's
|
||||
//! migration); a drive is the natural unit of quota, sharing, and
|
||||
//! lifecycle. Membership is expressed through `storage.role_grants` rows
|
||||
//! with `resource_type='drive'` — there is no separate `drive_members`
|
||||
//! table.
|
||||
//!
|
||||
//! ## Kinds
|
||||
//!
|
||||
//! Two kinds today; the discriminant is the `kind` column with a CHECK
|
||||
//! constraint.
|
||||
//!
|
||||
//! - **`personal`** — single-user, single-owner. The owner is captured
|
||||
//! by `default_for_user` (for the default Personal drive) or by an
|
||||
//! Owner role_grant on a secondary personal drive. Personal drives
|
||||
//! refuse `add_member`, `remove_member`, and `delete_drive` (when
|
||||
//! it's the user's only or default drive). A user can have multiple
|
||||
//! personal drives — one is marked default (`default_for_user =
|
||||
//! <uid>`), the others are secondaries (`default_for_user = NULL`,
|
||||
//! one Owner row in role_grants pinning them to the same user).
|
||||
//!
|
||||
//! - **`shared`** — multi-member, group-aware, full role roster
|
||||
//! (viewer / commenter / contributor / editor / owner). Members
|
||||
//! come from role_grants; group subjects expand transitively via
|
||||
//! the existing `subject_groups` machinery. Last-owner protection
|
||||
//! applies on member removal and drive deletion. Quota is set by
|
||||
//! the drive owner (or admin); `used_bytes` tracks consumption.
|
||||
//!
|
||||
//! Future kinds (e.g. `system` for built-in scratch space) drop in by
|
||||
//! extending the CHECK + the `DriveKind` enum.
|
||||
//!
|
||||
//! ## Policies
|
||||
//!
|
||||
//! `policies` is a JSONB bag carrying feature flags / capability toggles
|
||||
//! that drive owners can flip without a schema change. Known keys live in
|
||||
//! `docs/plan/drive.md` §8 and §15 (e.g. `forbid_public_links`,
|
||||
//! `include_in_photo_index`, `forbid_music_index`). Unknown keys are
|
||||
//! preserved by the application — the schema is intentionally permissive
|
||||
//! so future capability flags can land without a migration.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Drive kind discriminant. Mirrors the `storage.drives.kind` CHECK
|
||||
/// constraint values.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum DriveKind {
|
||||
/// Single-owner storage compartment. Cannot have members added or
|
||||
/// removed via the membership API; the owner is fixed for the drive's
|
||||
/// lifetime.
|
||||
Personal,
|
||||
/// Multi-member drive supporting the full role roster. Membership is
|
||||
/// open to admin/owner-driven changes through the membership API.
|
||||
Shared,
|
||||
}
|
||||
|
||||
impl DriveKind {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
DriveKind::Personal => "personal",
|
||||
DriveKind::Shared => "shared",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"personal" => Some(DriveKind::Personal),
|
||||
"shared" => Some(DriveKind::Shared),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Domain entity for a row in `storage.drives`.
|
||||
///
|
||||
/// Field-level constraints are enforced at the SQL layer (CHECK on
|
||||
/// `kind`, partial UNIQUE on `default_for_user`). The struct mirrors
|
||||
/// the column set 1:1; behaviour beyond field access lives in
|
||||
/// `DriveRepository` (D0-5) and `DriveService` (post-D0).
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Drive {
|
||||
/// Stable identifier. Generated server-side at creation.
|
||||
pub id: Uuid,
|
||||
/// Display name. Renameable by owners; defaults to "Personal" for
|
||||
/// the user's default personal drive, or the original sibling-root
|
||||
/// folder name for secondaries promoted by the D0 backfill.
|
||||
pub name: String,
|
||||
/// Discriminant — see [`DriveKind`].
|
||||
pub kind: DriveKind,
|
||||
/// Set iff this is the user's default personal drive (UNIQUE in SQL
|
||||
/// via a partial index `WHERE default_for_user IS NOT NULL`). NULL
|
||||
/// on shared drives and on secondary personal drives.
|
||||
pub default_for_user: Option<Uuid>,
|
||||
/// Soft cap on this drive's storage usage, in bytes. `None` means
|
||||
/// "no quota" (rare; reserved for admin overrides). The default
|
||||
/// initial quota for a fresh personal drive is taken from the
|
||||
/// owner's `auth.users.storage_quota_bytes` at creation time (see
|
||||
/// Open Question 2 in `docs/plan/drive.md`).
|
||||
pub quota_bytes: Option<i64>,
|
||||
/// Running total of bytes consumed. Maintained incrementally by
|
||||
/// upload/delete paths in D4; on D0 still reflects the pre-Drive
|
||||
/// per-user counters via the backfill.
|
||||
pub used_bytes: i64,
|
||||
/// Capability flags / feature toggles. Extensible JSONB — see
|
||||
/// `docs/plan/drive.md` §8 and §15 for the known keys.
|
||||
pub policies: serde_json::Value,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
impl Drive {
|
||||
/// `true` for the user's default personal drive (the only drive for
|
||||
/// which `default_for_user` is set to that user's id).
|
||||
pub fn is_default_for(&self, user_id: Uuid) -> bool {
|
||||
self.default_for_user == Some(user_id)
|
||||
}
|
||||
|
||||
/// `true` if this drive is a personal drive of any kind (default or
|
||||
/// secondary). Encapsulates the kind check at the call site.
|
||||
pub fn is_personal(&self) -> bool {
|
||||
matches!(self.kind, DriveKind::Personal)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user