style: cargo fmt --all

This commit is contained in:
2026-05-31 05:31:42 +02:00
parent 4b31a0f74b
commit c2ebca0da0
138 changed files with 2422 additions and 1164 deletions

View File

@@ -56,7 +56,9 @@ impl Asset {
// --- AssetMetadata ---
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
pub enum MetadataSource {
ExifExtracted,
AiGenerated,
@@ -133,7 +135,11 @@ impl AssetStack {
}
}
pub fn add_member(&mut self, asset_id: SystemId, role: StackMemberRole) -> Result<(), DomainError> {
pub fn add_member(
&mut self,
asset_id: SystemId,
role: StackMemberRole,
) -> Result<(), DomainError> {
if self.members.iter().any(|m| m.asset_id == asset_id) {
return Err(DomainError::Conflict(
"Asset already exists in stack".to_string(),
@@ -179,7 +185,11 @@ pub struct DerivativeAsset {
}
impl DerivativeAsset {
pub fn new_pending(parent: SystemId, profile: DerivativeProfile, path: impl Into<String>) -> Self {
pub fn new_pending(
parent: SystemId,
profile: DerivativeProfile,
path: impl Into<String>,
) -> Self {
Self {
derivative_id: SystemId::new(),
parent_asset_id: parent,
@@ -239,8 +249,14 @@ impl DuplicateGroup {
detection_method: DetectionMethod::ExactHash,
status: DuplicateStatus::Unresolved,
candidates: vec![
DuplicateCandidate { asset_id: asset_a, similarity_score: 1.0 },
DuplicateCandidate { asset_id: asset_b, similarity_score: 1.0 },
DuplicateCandidate {
asset_id: asset_a,
similarity_score: 1.0,
},
DuplicateCandidate {
asset_id: asset_b,
similarity_score: 1.0,
},
],
}
}

View File

@@ -1,10 +1,10 @@
use async_trait::async_trait;
use super::entities::{
Asset, AssetMetadata, AssetStack, DerivativeAsset, DerivativeProfile, DuplicateGroup,
MetadataSource,
};
use crate::common::errors::DomainError;
use crate::common::value_objects::{Checksum, SystemId};
use super::entities::{
Asset, AssetMetadata, AssetStack, DerivativeAsset, DerivativeProfile,
DuplicateGroup, MetadataSource,
};
use async_trait::async_trait;
// --- AssetRepository ---
@@ -12,7 +12,12 @@ use super::entities::{
pub trait AssetRepository: Send + Sync {
async fn find_by_id(&self, id: &SystemId) -> Result<Option<Asset>, DomainError>;
async fn find_by_checksum(&self, checksum: &Checksum) -> Result<Vec<Asset>, DomainError>;
async fn find_by_owner(&self, owner_id: &SystemId, limit: u32, offset: u32) -> Result<Vec<Asset>, DomainError>;
async fn find_by_owner(
&self,
owner_id: &SystemId,
limit: u32,
offset: u32,
) -> Result<Vec<Asset>, DomainError>;
async fn save(&self, asset: &Asset) -> Result<(), DomainError>;
async fn delete(&self, id: &SystemId) -> Result<(), DomainError>;
}
@@ -22,9 +27,17 @@ pub trait AssetRepository: Send + Sync {
#[async_trait]
pub trait AssetMetadataRepository: Send + Sync {
async fn find_by_asset(&self, asset_id: &SystemId) -> Result<Vec<AssetMetadata>, DomainError>;
async fn find_by_asset_and_source(&self, asset_id: &SystemId, source: MetadataSource) -> Result<Option<AssetMetadata>, DomainError>;
async fn find_by_asset_and_source(
&self,
asset_id: &SystemId,
source: MetadataSource,
) -> Result<Option<AssetMetadata>, DomainError>;
async fn save(&self, metadata: &AssetMetadata) -> Result<(), DomainError>;
async fn delete_by_asset_and_source(&self, asset_id: &SystemId, source: MetadataSource) -> Result<(), DomainError>;
async fn delete_by_asset_and_source(
&self,
asset_id: &SystemId,
source: MetadataSource,
) -> Result<(), DomainError>;
}
// --- AssetStackRepository ---
@@ -41,8 +54,13 @@ pub trait AssetStackRepository: Send + Sync {
#[async_trait]
pub trait DerivativeRepository: Send + Sync {
async fn find_by_asset(&self, asset_id: &SystemId) -> Result<Vec<DerivativeAsset>, DomainError>;
async fn find_by_asset_and_profile(&self, asset_id: &SystemId, profile: DerivativeProfile) -> Result<Option<DerivativeAsset>, DomainError>;
async fn find_by_asset(&self, asset_id: &SystemId)
-> Result<Vec<DerivativeAsset>, DomainError>;
async fn find_by_asset_and_profile(
&self,
asset_id: &SystemId,
profile: DerivativeProfile,
) -> Result<Option<DerivativeAsset>, DomainError>;
async fn save(&self, derivative: &DerivativeAsset) -> Result<(), DomainError>;
async fn delete(&self, id: &SystemId) -> Result<(), DomainError>;
}

View File

@@ -1,6 +1,6 @@
use async_trait::async_trait;
use crate::common::errors::DomainError;
use crate::common::events::DomainEvent;
use async_trait::async_trait;
#[async_trait]
pub trait EventPublisher: Send + Sync {

View File

@@ -7,9 +7,10 @@ impl Checksum {
pub fn new(hex: impl Into<String>) -> Result<Self, DomainError> {
let hex = hex.into().to_lowercase();
if hex.len() != 64 {
return Err(DomainError::Validation(
format!("Checksum must be 64 hex characters, got {}", hex.len()),
));
return Err(DomainError::Validation(format!(
"Checksum must be 64 hex characters, got {}",
hex.len()
)));
}
if !hex.chars().all(|c| c.is_ascii_hexdigit()) {
return Err(DomainError::Validation(
@@ -19,7 +20,9 @@ impl Checksum {
Ok(Self(hex))
}
pub fn as_str(&self) -> &str { &self.0 }
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for Checksum {

View File

@@ -1,12 +1,20 @@
use chrono::{DateTime, Utc};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
pub struct DateTimeStamp(DateTime<Utc>);
impl DateTimeStamp {
pub fn now() -> Self { Self(Utc::now()) }
pub fn from_datetime(dt: DateTime<Utc>) -> Self { Self(dt) }
pub fn as_datetime(&self) -> &DateTime<Utc> { &self.0 }
pub fn now() -> Self {
Self(Utc::now())
}
pub fn from_datetime(dt: DateTime<Utc>) -> Self {
Self(dt)
}
pub fn as_datetime(&self) -> &DateTime<Utc> {
&self.0
}
}
impl std::fmt::Display for DateTimeStamp {
@@ -16,5 +24,7 @@ impl std::fmt::Display for DateTimeStamp {
}
impl From<DateTime<Utc>> for DateTimeStamp {
fn from(dt: DateTime<Utc>) -> Self { Self(dt) }
fn from(dt: DateTime<Utc>) -> Self {
Self(dt)
}
}

View File

@@ -12,7 +12,9 @@ impl Email {
Ok(Self(value))
}
pub fn as_str(&self) -> &str { &self.0 }
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for Email {

View File

@@ -33,7 +33,11 @@ impl FilterCriteria {
Self::Or(conditions)
}
pub fn condition(field: impl Into<String>, op: FilterOperator, value: serde_json::Value) -> Self {
pub fn condition(
field: impl Into<String>,
op: FilterOperator,
value: serde_json::Value,
) -> Self {
Self::Condition(FilterCondition {
field: field.into(),
op,

View File

@@ -9,6 +9,10 @@ impl std::fmt::Debug for PasswordHash {
}
impl PasswordHash {
pub fn from_hash(hash: String) -> Self { Self(hash) }
pub fn as_str(&self) -> &str { &self.0 }
pub fn from_hash(hash: String) -> Self {
Self(hash)
}
pub fn as_str(&self) -> &str {
&self.0
}
}

View File

@@ -14,7 +14,9 @@ pub enum MetadataValue {
pub struct StructuredData(HashMap<String, MetadataValue>);
impl StructuredData {
pub fn new() -> Self { Self(HashMap::new()) }
pub fn new() -> Self {
Self(HashMap::new())
}
pub fn insert(&mut self, key: impl Into<String>, value: MetadataValue) {
self.0.insert(key.into(), value);
@@ -35,8 +37,12 @@ impl StructuredData {
self.0.keys()
}
pub fn is_empty(&self) -> bool { self.0.is_empty() }
pub fn len(&self) -> usize { self.0.len() }
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn merge_from(&mut self, other: StructuredData) {
self.0.extend(other.0);
@@ -46,9 +52,13 @@ impl StructuredData {
self.0.remove(key)
}
pub fn inner(&self) -> &HashMap<String, MetadataValue> { &self.0 }
pub fn inner(&self) -> &HashMap<String, MetadataValue> {
&self.0
}
}
impl Default for StructuredData {
fn default() -> Self { Self::new() }
fn default() -> Self {
Self::new()
}
}

View File

@@ -4,13 +4,21 @@ use uuid::Uuid;
pub struct SystemId(Uuid);
impl SystemId {
pub fn new() -> Self { Self(Uuid::new_v4()) }
pub fn from_uuid(id: Uuid) -> Self { Self(id) }
pub fn as_uuid(&self) -> &Uuid { &self.0 }
pub fn new() -> Self {
Self(Uuid::new_v4())
}
pub fn from_uuid(id: Uuid) -> Self {
Self(id)
}
pub fn as_uuid(&self) -> &Uuid {
&self.0
}
}
impl Default for SystemId {
fn default() -> Self { Self::new() }
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for SystemId {
@@ -20,5 +28,7 @@ impl std::fmt::Display for SystemId {
}
impl From<Uuid> for SystemId {
fn from(id: Uuid) -> Self { Self(id) }
fn from(id: Uuid) -> Self {
Self(id)
}
}

View File

@@ -1,7 +1,7 @@
use std::collections::HashSet;
use chrono::{DateTime, Utc};
use crate::common::errors::DomainError;
use crate::common::value_objects::{Email, PasswordHash, SystemId};
use chrono::{DateTime, Utc};
use std::collections::HashSet;
// --- Permission ---
@@ -36,7 +36,10 @@ pub struct Permission {
impl Permission {
pub fn new(action: PermissionAction, resource_type: ResourceType) -> Self {
Self { action, resource_type }
Self {
action,
resource_type,
}
}
}
@@ -49,18 +52,39 @@ pub fn viewer_permissions() -> HashSet<Permission> {
pub fn contributor_permissions() -> HashSet<Permission> {
let mut perms = viewer_permissions();
perms.insert(Permission::new(PermissionAction::WriteMetadata, ResourceType::Global));
perms.insert(Permission::new(
PermissionAction::WriteMetadata,
ResourceType::Global,
));
perms
}
pub fn admin_permissions() -> HashSet<Permission> {
let mut perms = contributor_permissions();
perms.insert(Permission::new(PermissionAction::DeleteAsset, ResourceType::Global));
perms.insert(Permission::new(PermissionAction::ManageAccess, ResourceType::Global));
perms.insert(Permission::new(PermissionAction::ManageUsers, ResourceType::Global));
perms.insert(Permission::new(PermissionAction::ManageSystem, ResourceType::Global));
perms.insert(Permission::new(PermissionAction::ReadLocation, ResourceType::Global));
perms.insert(Permission::new(PermissionAction::ReadPerson, ResourceType::Global));
perms.insert(Permission::new(
PermissionAction::DeleteAsset,
ResourceType::Global,
));
perms.insert(Permission::new(
PermissionAction::ManageAccess,
ResourceType::Global,
));
perms.insert(Permission::new(
PermissionAction::ManageUsers,
ResourceType::Global,
));
perms.insert(Permission::new(
PermissionAction::ManageSystem,
ResourceType::Global,
));
perms.insert(Permission::new(
PermissionAction::ReadLocation,
ResourceType::Global,
));
perms.insert(Permission::new(
PermissionAction::ReadPerson,
ResourceType::Global,
));
perms
}
@@ -75,7 +99,11 @@ pub struct Role {
}
impl Role {
pub fn new(name: impl Into<String>, permissions: HashSet<Permission>, is_system_default: bool) -> Self {
pub fn new(
name: impl Into<String>,
permissions: HashSet<Permission>,
is_system_default: bool,
) -> Self {
Self {
role_id: SystemId::new(),
name: name.into(),
@@ -85,7 +113,8 @@ impl Role {
}
pub fn has_permission(&self, action: PermissionAction, resource_type: ResourceType) -> bool {
self.permissions.contains(&Permission::new(action, resource_type))
self.permissions
.contains(&Permission::new(action, resource_type))
}
}
@@ -136,7 +165,9 @@ impl Group {
pub fn add_member(&mut self, user_id: SystemId) -> Result<(), DomainError> {
if self.members.contains(&user_id) {
return Err(DomainError::Conflict(format!("User {user_id} is already a member")));
return Err(DomainError::Conflict(format!(
"User {user_id} is already a member"
)));
}
self.members.insert(user_id);
Ok(())
@@ -144,10 +175,14 @@ impl Group {
pub fn remove_member(&mut self, user_id: SystemId) -> Result<(), DomainError> {
if user_id == self.owner_user_id {
return Err(DomainError::Validation("Cannot remove the group owner".to_string()));
return Err(DomainError::Validation(
"Cannot remove the group owner".to_string(),
));
}
if !self.members.remove(&user_id) {
return Err(DomainError::NotFound(format!("User {user_id} is not a member")));
return Err(DomainError::NotFound(format!(
"User {user_id} is not a member"
)));
}
Ok(())
}

View File

@@ -1,7 +1,7 @@
use async_trait::async_trait;
use super::entities::{Group, Role, User};
use crate::common::errors::DomainError;
use crate::common::value_objects::{Email, PasswordHash, SystemId};
use super::entities::{Group, Role, User};
use async_trait::async_trait;
// --- UserRepository ---

View File

@@ -1,5 +1,5 @@
use std::collections::HashSet;
use super::entities::{Permission, PermissionAction, ResourceType, Role};
use std::collections::HashSet;
pub struct PermissionChecker;
@@ -16,6 +16,9 @@ impl PermissionChecker {
}
pub fn effective_permissions(roles: &[Role]) -> HashSet<Permission> {
roles.iter().flat_map(|r| r.permissions.iter().copied()).collect()
roles
.iter()
.flat_map(|r| r.permissions.iter().copied())
.collect()
}
}

View File

@@ -1,11 +1,11 @@
pub mod catalog;
pub mod common;
pub mod identity;
pub mod storage;
pub mod catalog;
pub mod organization;
pub mod processing;
pub mod sharing;
pub mod sidecar;
pub mod processing;
pub mod storage;
// Facade — old import paths still work
pub mod errors {
@@ -18,31 +18,31 @@ pub mod value_objects {
pub use crate::common::value_objects::*;
}
pub mod entities {
pub use crate::identity::entities::*;
pub use crate::storage::entities::*;
pub use crate::catalog::entities::*;
pub use crate::identity::entities::*;
pub use crate::organization::entities::*;
pub use crate::processing::entities::*;
pub use crate::sharing::entities::*;
pub use crate::sidecar::entities::*;
pub use crate::processing::entities::*;
pub use crate::storage::entities::*;
// Sub-module alias for `domain::entities::permission::` imports
pub mod permission {
pub use crate::identity::entities::{
Permission, PermissionAction, ResourceType,
viewer_permissions, contributor_permissions, admin_permissions,
Permission, PermissionAction, ResourceType, admin_permissions, contributor_permissions,
viewer_permissions,
};
}
}
pub mod ports {
pub use crate::catalog::ports::*;
pub use crate::common::ports::*;
pub use crate::identity::ports::*;
pub use crate::storage::ports::*;
pub use crate::catalog::ports::*;
pub use crate::organization::ports::*;
pub use crate::processing::ports::*;
pub use crate::sharing::ports::*;
pub use crate::sidecar::ports::*;
pub use crate::processing::ports::*;
pub use crate::storage::ports::*;
}
pub mod services {
pub mod permission_service {

View File

@@ -43,7 +43,8 @@ impl Album {
if self.entries.iter().any(|e| e.asset_id == asset_id) {
return Err(DomainError::Conflict("Asset already in album".to_string()));
}
let next_order = self.entries.iter().map(|e| e.sort_order).max().unwrap_or(0) + if self.entries.is_empty() { 0 } else { 1 };
let next_order = self.entries.iter().map(|e| e.sort_order).max().unwrap_or(0)
+ if self.entries.is_empty() { 0 } else { 1 };
self.entries.push(AlbumEntry {
asset_id,
sort_order: next_order,
@@ -54,7 +55,10 @@ impl Album {
}
pub fn remove_asset(&mut self, asset_id: &SystemId) -> Result<(), DomainError> {
let idx = self.entries.iter().position(|e| &e.asset_id == asset_id)
let idx = self
.entries
.iter()
.position(|e| &e.asset_id == asset_id)
.ok_or_else(|| DomainError::NotFound("Asset not in album".to_string()))?;
self.entries.remove(idx);
Ok(())

View File

@@ -1,7 +1,7 @@
use async_trait::async_trait;
use super::entities::{Album, AssetTag, Collection, Tag};
use crate::common::errors::DomainError;
use crate::common::value_objects::SystemId;
use super::entities::{Album, AssetTag, Collection, Tag};
use async_trait::async_trait;
// --- AlbumRepository ---
@@ -19,10 +19,17 @@ pub trait AlbumRepository: Send + Sync {
pub trait TagRepository: Send + Sync {
async fn find_by_id(&self, id: &SystemId) -> Result<Option<Tag>, DomainError>;
async fn find_by_name(&self, name: &str) -> Result<Option<Tag>, DomainError>;
async fn find_tags_for_asset(&self, asset_id: &SystemId) -> Result<Vec<(Tag, AssetTag)>, DomainError>;
async fn find_tags_for_asset(
&self,
asset_id: &SystemId,
) -> Result<Vec<(Tag, AssetTag)>, DomainError>;
async fn save_tag(&self, tag: &Tag) -> Result<(), DomainError>;
async fn save_asset_tag(&self, asset_tag: &AssetTag) -> Result<(), DomainError>;
async fn remove_asset_tag(&self, asset_id: &SystemId, tag_id: &SystemId) -> Result<(), DomainError>;
async fn remove_asset_tag(
&self,
asset_id: &SystemId,
tag_id: &SystemId,
) -> Result<(), DomainError>;
}
// --- CollectionRepository ---

View File

@@ -88,9 +88,10 @@ impl Job {
pub fn start(&mut self) -> Result<(), DomainError> {
if self.status != JobStatus::Queued {
return Err(DomainError::Conflict(
format!("Job can only start from Queued, currently {:?}", self.status),
));
return Err(DomainError::Conflict(format!(
"Job can only start from Queued, currently {:?}",
self.status
)));
}
self.status = JobStatus::Processing;
self.started_at = Some(DateTimeStamp::now());

View File

@@ -1,7 +1,7 @@
use async_trait::async_trait;
use super::entities::{Job, JobBatch, Plugin, ProcessingPipeline};
use crate::common::errors::DomainError;
use crate::common::value_objects::SystemId;
use super::entities::{Job, JobBatch, Plugin, ProcessingPipeline};
use async_trait::async_trait;
// --- JobRepository ---

View File

@@ -1,5 +1,5 @@
use chrono::Utc;
use crate::common::value_objects::{DateTimeStamp, StructuredData, SystemId};
use chrono::Utc;
// --- ShareScope ---
@@ -74,8 +74,18 @@ pub struct ShareTarget {
}
impl ShareTarget {
pub fn new(scope_id: SystemId, target_type: TargetType, target_id: SystemId, role_id: SystemId) -> Self {
Self { scope_id, target_type, target_id, role_id }
pub fn new(
scope_id: SystemId,
target_type: TargetType,
target_id: SystemId,
role_id: SystemId,
) -> Self {
Self {
scope_id,
target_type,
target_id,
role_id,
}
}
}
@@ -99,7 +109,11 @@ pub struct ShareLink {
}
impl ShareLink {
pub fn new(scope_id: SystemId, token: impl Into<String>, access_level: LinkAccessLevel) -> Self {
pub fn new(
scope_id: SystemId,
token: impl Into<String>,
access_level: LinkAccessLevel,
) -> Self {
Self {
scope_id,
token: token.into(),
@@ -115,7 +129,10 @@ impl ShareLink {
if !self.is_active {
return false;
}
if self.expires_at.is_some_and(|exp| exp.as_datetime() < &Utc::now()) {
if self
.expires_at
.is_some_and(|exp| exp.as_datetime() < &Utc::now())
{
return false;
}
if self.max_uses.is_some_and(|max| self.use_count >= max) {
@@ -160,7 +177,10 @@ impl InviteCode {
}
pub fn is_valid(&self) -> bool {
if self.expires_at.is_some_and(|exp| exp.as_datetime() < &Utc::now()) {
if self
.expires_at
.is_some_and(|exp| exp.as_datetime() < &Utc::now())
{
return false;
}
if self.max_uses.is_some_and(|max| self.use_count >= max) {

View File

@@ -1,7 +1,7 @@
use async_trait::async_trait;
use super::entities::{InviteCode, ShareLink, ShareScope, ShareTarget, VisibilityFilter};
use crate::common::errors::DomainError;
use crate::common::value_objects::SystemId;
use super::entities::{InviteCode, ShareLink, ShareScope, ShareTarget, VisibilityFilter};
use async_trait::async_trait;
// --- ShareRepository ---
@@ -9,12 +9,21 @@ use super::entities::{InviteCode, ShareLink, ShareScope, ShareTarget, Visibility
pub trait ShareRepository: Send + Sync {
async fn save_scope(&self, scope: &ShareScope) -> Result<(), DomainError>;
async fn find_scope_by_id(&self, id: &SystemId) -> Result<Option<ShareScope>, DomainError>;
async fn find_scopes_for_resource(&self, resource_id: &SystemId) -> Result<Vec<ShareScope>, DomainError>;
async fn find_scopes_for_resource(
&self,
resource_id: &SystemId,
) -> Result<Vec<ShareScope>, DomainError>;
async fn delete_scope(&self, id: &SystemId) -> Result<(), DomainError>;
async fn save_target(&self, target: &ShareTarget) -> Result<(), DomainError>;
async fn find_targets_for_scope(&self, scope_id: &SystemId) -> Result<Vec<ShareTarget>, DomainError>;
async fn find_targets_for_user(&self, user_id: &SystemId) -> Result<Vec<ShareTarget>, DomainError>;
async fn find_targets_for_scope(
&self,
scope_id: &SystemId,
) -> Result<Vec<ShareTarget>, DomainError>;
async fn find_targets_for_user(
&self,
user_id: &SystemId,
) -> Result<Vec<ShareTarget>, DomainError>;
async fn save_link(&self, link: &ShareLink) -> Result<(), DomainError>;
async fn find_link_by_token(&self, token: &str) -> Result<Option<ShareLink>, DomainError>;
@@ -27,7 +36,11 @@ pub trait ShareRepository: Send + Sync {
#[async_trait]
pub trait VisibilityFilterRepository: Send + Sync {
async fn find_by_scope_and_role(&self, scope_id: &SystemId, role_id: &SystemId) -> Result<Option<VisibilityFilter>, DomainError>;
async fn find_by_scope_and_role(
&self,
scope_id: &SystemId,
role_id: &SystemId,
) -> Result<Option<VisibilityFilter>, DomainError>;
async fn save(&self, filter: &VisibilityFilter) -> Result<(), DomainError>;
async fn delete(&self, id: &SystemId) -> Result<(), DomainError>;
}

View File

@@ -1,13 +1,16 @@
use async_trait::async_trait;
use super::entities::{SidecarRecord, SyncStatus};
use crate::common::errors::DomainError;
use crate::common::value_objects::{StructuredData, SystemId};
use super::entities::{SidecarRecord, SyncStatus};
use async_trait::async_trait;
// --- SidecarRepository ---
#[async_trait]
pub trait SidecarRepository: Send + Sync {
async fn find_by_asset(&self, asset_id: &SystemId) -> Result<Option<SidecarRecord>, DomainError>;
async fn find_by_asset(
&self,
asset_id: &SystemId,
) -> Result<Option<SidecarRecord>, DomainError>;
async fn find_by_status(&self, status: SyncStatus) -> Result<Vec<SidecarRecord>, DomainError>;
async fn save(&self, record: &SidecarRecord) -> Result<(), DomainError>;
async fn delete(&self, asset_id: &SystemId) -> Result<(), DomainError>;

View File

@@ -1,12 +1,11 @@
use super::entities::{
IngestSession, LibraryPath, QuotaDefinition, StorageVolume, UsageLedgerEntry, UsageType,
};
use crate::common::errors::DomainError;
use crate::common::value_objects::{DateTimeStamp, SystemId};
use async_trait::async_trait;
use bytes::Bytes;
use futures::stream::{self, BoxStream, StreamExt};
use crate::common::errors::DomainError;
use crate::common::value_objects::{DateTimeStamp, SystemId};
use super::entities::{
IngestSession, LibraryPath, QuotaDefinition, StorageVolume,
UsageLedgerEntry, UsageType,
};
// --- StorageVolumeRepository ---
@@ -24,7 +23,10 @@ pub trait StorageVolumeRepository: Send + Sync {
pub trait LibraryPathRepository: Send + Sync {
async fn find_by_id(&self, id: &SystemId) -> Result<Option<LibraryPath>, DomainError>;
async fn find_by_volume(&self, volume_id: &SystemId) -> Result<Vec<LibraryPath>, DomainError>;
async fn find_ingest_destinations(&self, owner_id: &SystemId) -> Result<Vec<LibraryPath>, DomainError>;
async fn find_ingest_destinations(
&self,
owner_id: &SystemId,
) -> Result<Vec<LibraryPath>, DomainError>;
async fn save(&self, path: &LibraryPath) -> Result<(), DomainError>;
async fn delete(&self, id: &SystemId) -> Result<(), DomainError>;
}
@@ -42,7 +44,10 @@ pub trait IngestSessionRepository: Send + Sync {
#[async_trait]
pub trait QuotaRepository: Send + Sync {
async fn find_by_owner(&self, owner_id: &SystemId) -> Result<Option<QuotaDefinition>, DomainError>;
async fn find_by_owner(
&self,
owner_id: &SystemId,
) -> Result<Option<QuotaDefinition>, DomainError>;
async fn save(&self, quota: &QuotaDefinition) -> Result<(), DomainError>;
async fn delete(&self, id: &SystemId) -> Result<(), DomainError>;
}
@@ -114,7 +119,8 @@ pub trait StorageWriter: Send + Sync {
/// Convenience: stores an in-memory buffer at `key`. Wraps `put`.
async fn put_bytes(&self, key: &str, data: Bytes) -> Result<(), DomainError> {
self.put(key, Box::pin(stream::once(async move { Ok(data) }))).await
self.put(key, Box::pin(stream::once(async move { Ok(data) })))
.await
}
}

View File

@@ -1,6 +1,6 @@
use chrono::{Datelike, NaiveDate, TimeZone, Utc};
use super::entities::{QuotaDefinition, TimePeriod, UsageType};
use crate::common::value_objects::DateTimeStamp;
use chrono::{Datelike, NaiveDate, TimeZone, Utc};
pub struct QuotaCheckResult {
pub allowed: bool,

View File

@@ -1,7 +1,7 @@
use domain::entities::{
Asset, AssetMetadata, AssetStack, AssetType, DerivativeAsset, DerivativeProfile,
DetectionMethod, DuplicateGroup, DuplicateStatus, GenerationStatus,
MetadataSource, SourceReference, StackMemberRole, StackType,
DetectionMethod, DuplicateGroup, DuplicateStatus, GenerationStatus, MetadataSource,
SourceReference, StackMemberRole, StackType,
};
use domain::errors::DomainError;
use domain::value_objects::{Checksum, MetadataValue, StructuredData, SystemId};
@@ -63,7 +63,9 @@ fn new_stack_contains_primary() {
fn add_motion_clip() {
let mut stack = AssetStack::new(StackType::LivePhoto, SystemId::new(), SystemId::new());
let clip_id = SystemId::new();
stack.add_member(clip_id.clone(), StackMemberRole::MotionClip).unwrap();
stack
.add_member(clip_id.clone(), StackMemberRole::MotionClip)
.unwrap();
assert_eq!(stack.members.len(), 2);
assert_eq!(stack.members[1].asset_id, clip_id);
assert_eq!(stack.members[1].sort_order, 1);

View File

@@ -20,7 +20,10 @@ fn user_edited_overrides_exif() {
#[test]
fn ai_overrides_exif_but_not_user() {
let exif = layer(MetadataSource::ExifExtracted, &[("desc", "raw"), ("camera", "Nikon")]);
let exif = layer(
MetadataSource::ExifExtracted,
&[("desc", "raw"), ("camera", "Nikon")],
);
let ai = layer(MetadataSource::AiGenerated, &[("desc", "ai-desc")]);
let user = layer(MetadataSource::UserEdited, &[("desc", "user-desc")]);
let result = resolve_metadata(&[exif, ai, user]);

View File

@@ -1,2 +1,2 @@
mod value_objects;
mod events;
mod value_objects;

View File

@@ -13,10 +13,14 @@ fn now_is_recent() {
#[test]
fn ordering() {
let a = DateTimeStamp::from_datetime(
chrono::DateTime::parse_from_rfc3339("2024-01-01T00:00:00Z").unwrap().into(),
chrono::DateTime::parse_from_rfc3339("2024-01-01T00:00:00Z")
.unwrap()
.into(),
);
let b = DateTimeStamp::from_datetime(
chrono::DateTime::parse_from_rfc3339("2025-01-01T00:00:00Z").unwrap().into(),
chrono::DateTime::parse_from_rfc3339("2025-01-01T00:00:00Z")
.unwrap()
.into(),
);
assert!(a < b);
}

View File

@@ -1,8 +1,8 @@
mod catalog;
mod common;
mod identity;
mod storage;
mod catalog;
mod organization;
mod processing;
mod sharing;
mod sidecar;
mod processing;
mod storage;

View File

@@ -1,6 +1,6 @@
use domain::entities::permission::{
admin_permissions, contributor_permissions, viewer_permissions,
Permission, PermissionAction, ResourceType,
Permission, PermissionAction, ResourceType, admin_permissions, contributor_permissions,
viewer_permissions,
};
use domain::entities::{Group, Role, User};
use domain::errors::DomainError;
@@ -36,8 +36,16 @@ fn role_checks_permission() {
#[test]
fn creates_user_with_unique_id() {
let a = User::new("alice", Email::new("a@example.com").unwrap(), PasswordHash::from_hash("h".into()));
let b = User::new("bob", Email::new("b@example.com").unwrap(), PasswordHash::from_hash("h".into()));
let a = User::new(
"alice",
Email::new("a@example.com").unwrap(),
PasswordHash::from_hash("h".into()),
);
let b = User::new(
"bob",
Email::new("b@example.com").unwrap(),
PasswordHash::from_hash("h".into()),
);
assert_ne!(a.id, b.id);
assert_eq!(a.username, "alice");
assert_eq!(b.username, "bob");

View File

@@ -1,5 +1,5 @@
use domain::entities::{Permission, PermissionAction, ResourceType, Role};
use domain::entities::permission::{admin_permissions, viewer_permissions};
use domain::entities::{Permission, PermissionAction, ResourceType, Role};
use domain::services::permission_service::PermissionChecker;
#[test]
@@ -24,8 +24,24 @@ fn viewer_cannot_delete() {
#[test]
fn roles_additive() {
let r1 = Role::new("r1", [Permission::new(PermissionAction::ReadAsset, ResourceType::Global)].into(), false);
let r2 = Role::new("r2", [Permission::new(PermissionAction::WriteMetadata, ResourceType::Global)].into(), false);
let r1 = Role::new(
"r1",
[Permission::new(
PermissionAction::ReadAsset,
ResourceType::Global,
)]
.into(),
false,
);
let r2 = Role::new(
"r2",
[Permission::new(
PermissionAction::WriteMetadata,
ResourceType::Global,
)]
.into(),
false,
);
let eff = PermissionChecker::effective_permissions(&[r1, r2]);
assert_eq!(eff.len(), 2);
}

View File

@@ -6,14 +6,26 @@ use domain::value_objects::{DateTimeStamp, SystemId};
#[test]
fn not_expired_when_no_expiry() {
let scope = ShareScope::new(ScopeType::Link, ShareableType::Album, SystemId::new(), SystemId::new());
let scope = ShareScope::new(
ScopeType::Link,
ShareableType::Album,
SystemId::new(),
SystemId::new(),
);
assert!(!scope.is_expired());
}
#[test]
fn expired_when_past() {
let mut scope = ShareScope::new(ScopeType::Link, ShareableType::Album, SystemId::new(), SystemId::new());
scope.expires_at = Some(DateTimeStamp::from_datetime(Utc::now() - Duration::hours(1)));
let mut scope = ShareScope::new(
ScopeType::Link,
ShareableType::Album,
SystemId::new(),
SystemId::new(),
);
scope.expires_at = Some(DateTimeStamp::from_datetime(
Utc::now() - Duration::hours(1),
));
assert!(scope.is_expired());
}

View File

@@ -1,6 +1,6 @@
use domain::entities::{
IngestSession, IngestStatus, LibraryPath, OwnershipPolicy,
QuotaDefinition, StorageVolume, TimePeriod, UsageType,
IngestSession, IngestStatus, LibraryPath, OwnershipPolicy, QuotaDefinition, StorageVolume,
TimePeriod, UsageType,
};
use domain::errors::DomainError;
use domain::value_objects::{Checksum, SystemId};
@@ -63,7 +63,11 @@ fn invalid_transition_rejected() {
#[test]
fn can_fail_from_any_non_terminal() {
for target in [IngestStatus::Uploading, IngestStatus::AwaitingProcessing, IngestStatus::Processing] {
for target in [
IngestStatus::Uploading,
IngestStatus::AwaitingProcessing,
IngestStatus::Processing,
] {
let mut s = make_session();
if target == IngestStatus::AwaitingProcessing || target == IngestStatus::Processing {
s.advance_to(IngestStatus::AwaitingProcessing).unwrap();