domain: add Storage & Sources entities (StorageVolume, LibraryPath, IngestSession, Quota)

This commit is contained in:
2026-05-31 03:23:34 +02:00
parent 04811ff436
commit 3c5c4ed9b1
10 changed files with 335 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
use crate::errors::DomainError;
use crate::value_objects::{Checksum, DateTimeStamp, SystemId};
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum IngestStatus {
Uploading,
AwaitingProcessing,
Processing,
Completed,
Failed,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct IngestSession {
pub session_id: SystemId,
pub uploader_user_id: SystemId,
pub client_device_id: String,
pub original_filename: String,
pub client_checksum: Checksum,
pub target_library_path_id: SystemId,
pub status: IngestStatus,
pub created_at: DateTimeStamp,
pub error_message: Option<String>,
}
impl IngestSession {
pub fn new(
uploader: SystemId,
device_id: impl Into<String>,
filename: impl Into<String>,
checksum: Checksum,
target_path: SystemId,
) -> Self {
Self {
session_id: SystemId::new(),
uploader_user_id: uploader,
client_device_id: device_id.into(),
original_filename: filename.into(),
client_checksum: checksum,
target_library_path_id: target_path,
status: IngestStatus::Uploading,
created_at: DateTimeStamp::now(),
error_message: None,
}
}
pub fn advance_to(&mut self, status: IngestStatus) -> Result<(), DomainError> {
let valid = matches!(
(self.status, status),
(IngestStatus::Uploading, IngestStatus::AwaitingProcessing)
| (IngestStatus::AwaitingProcessing, IngestStatus::Processing)
| (IngestStatus::Processing, IngestStatus::Completed)
) || (status == IngestStatus::Failed && !self.is_terminal());
if !valid {
return Err(DomainError::Validation(format!(
"Invalid transition from {:?} to {:?}",
self.status, status
)));
}
self.status = status;
Ok(())
}
pub fn fail(&mut self, message: impl Into<String>) {
self.status = IngestStatus::Failed;
self.error_message = Some(message.into());
}
fn is_terminal(&self) -> bool {
matches!(self.status, IngestStatus::Completed | IngestStatus::Failed)
}
}

View File

@@ -0,0 +1,47 @@
use crate::value_objects::SystemId;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum OwnershipPolicy {
UserOwned,
GroupOwned,
Unassigned,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LibraryPath {
pub path_id: SystemId,
pub volume_id: SystemId,
pub relative_path: String,
pub is_ingest_destination: bool,
pub ownership_policy: OwnershipPolicy,
pub designated_owner_id: Option<SystemId>,
}
impl LibraryPath {
pub fn new_user_owned(
volume_id: SystemId,
relative_path: impl Into<String>,
owner_id: SystemId,
is_ingest_destination: bool,
) -> Self {
Self {
path_id: SystemId::new(),
volume_id,
relative_path: relative_path.into(),
is_ingest_destination,
ownership_policy: OwnershipPolicy::UserOwned,
designated_owner_id: Some(owner_id),
}
}
pub fn new_unassigned(volume_id: SystemId, relative_path: impl Into<String>) -> Self {
Self {
path_id: SystemId::new(),
volume_id,
relative_path: relative_path.into(),
is_ingest_destination: false,
ownership_policy: OwnershipPolicy::Unassigned,
designated_owner_id: None,
}
}
}

View File

@@ -2,8 +2,16 @@ pub mod permission;
pub mod role;
mod user;
mod group;
mod storage_volume;
mod library_path;
mod ingest_session;
mod quota;
pub use permission::{Permission, PermissionAction, ResourceType};
pub use role::Role;
pub use user::User;
pub use group::Group;
pub use storage_volume::StorageVolume;
pub use library_path::{LibraryPath, OwnershipPolicy};
pub use ingest_session::{IngestSession, IngestStatus};
pub use quota::{QuotaDefinition, QuotaRule, TimePeriod, UsageLedgerEntry, UsageType};

View File

@@ -0,0 +1,92 @@
use crate::value_objects::{DateTimeStamp, SystemId};
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum UsageType {
StorageBytes,
ProcessJobs,
ApiCalls,
IndexingSize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum TimePeriod {
Daily,
Monthly,
Lifetime,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct QuotaRule {
pub rule_id: SystemId,
pub dimension: UsageType,
pub limit_value: u64,
pub time_period: TimePeriod,
pub is_unlimited: bool,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct QuotaDefinition {
pub quota_id: SystemId,
pub owner_scope: SystemId,
pub is_enforced: bool,
pub rules: Vec<QuotaRule>,
}
impl QuotaDefinition {
pub fn new(owner_scope: SystemId) -> Self {
Self {
quota_id: SystemId::new(),
owner_scope,
is_enforced: true,
rules: Vec::new(),
}
}
pub fn add_rule(&mut self, dimension: UsageType, limit_value: u64, time_period: TimePeriod) {
self.rules.push(QuotaRule {
rule_id: SystemId::new(),
dimension,
limit_value,
time_period,
is_unlimited: false,
});
}
pub fn add_unlimited_rule(&mut self, dimension: UsageType) {
self.rules.push(QuotaRule {
rule_id: SystemId::new(),
dimension,
limit_value: 0,
time_period: TimePeriod::Lifetime,
is_unlimited: true,
});
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct UsageLedgerEntry {
pub entry_id: SystemId,
pub user_id: SystemId,
pub usage_type: UsageType,
pub consumed_amount: u64,
pub timestamp: DateTimeStamp,
pub context: String,
}
impl UsageLedgerEntry {
pub fn new(
user_id: SystemId,
usage_type: UsageType,
amount: u64,
context: impl Into<String>,
) -> Self {
Self {
entry_id: SystemId::new(),
user_id,
usage_type,
consumed_amount: amount,
timestamp: DateTimeStamp::now(),
context: context.into(),
}
}
}

View File

@@ -0,0 +1,22 @@
use crate::value_objects::SystemId;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct StorageVolume {
pub volume_id: SystemId,
pub volume_name: String,
pub uri_prefix: String,
pub is_writable: bool,
pub available_bytes: u64,
}
impl StorageVolume {
pub fn new(name: impl Into<String>, uri_prefix: impl Into<String>, is_writable: bool) -> Self {
Self {
volume_id: SystemId::new(),
volume_name: name.into(),
uri_prefix: uri_prefix.into(),
is_writable,
available_bytes: 0,
}
}
}