use crate::common::errors::DomainError; use crate::common::value_objects::{Checksum, DateTimeStamp, StructuredData, SystemId}; // --- Asset --- #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum AssetType { Image, Video, LivePhoto, } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct SourceReference { pub volume_id: SystemId, pub relative_path: String, pub checksum: Checksum, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct Asset { pub asset_id: SystemId, pub source_reference: SourceReference, pub asset_type: AssetType, pub mime_type: String, pub file_size: u64, pub is_processed: bool, pub owner_user_id: SystemId, pub created_at: DateTimeStamp, } impl Asset { pub fn new( source_reference: SourceReference, asset_type: AssetType, mime_type: impl Into, file_size: u64, owner: SystemId, ) -> Self { Self { asset_id: SystemId::new(), source_reference, asset_type, mime_type: mime_type.into(), file_size, is_processed: false, owner_user_id: owner, created_at: DateTimeStamp::now(), } } pub fn mark_processed(&mut self) { self.is_processed = true; } } // --- AssetFilters --- #[derive(Default)] pub struct AssetFilters { pub asset_type: Option, pub mime_type: Option, pub date_from: Option, pub date_to: Option, pub is_processed: Option, pub tag_name: Option, } // --- AssetMetadata --- #[derive( Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, )] pub enum MetadataSource { ExifExtracted, AiGenerated, UserEdited, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct AssetMetadata { pub asset_id: SystemId, pub metadata_source: MetadataSource, pub data: StructuredData, pub updated_at: DateTimeStamp, } impl AssetMetadata { pub fn new(asset_id: SystemId, source: MetadataSource, data: StructuredData) -> Self { Self { asset_id, metadata_source: source, data, updated_at: DateTimeStamp::now(), } } } // --- AssetStack --- #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum StackType { LivePhoto, FormatPair, BurstSequence, ExposureBracket, ManualGroup, } #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum StackMemberRole { PrimaryDisplay, HighResSource, MotionClip, AlternateFrame, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct AssetStackMember { pub asset_id: SystemId, pub role: StackMemberRole, pub sort_order: u32, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct AssetStack { pub stack_id: SystemId, pub stack_type: StackType, pub primary_asset_id: SystemId, pub owner_user_id: SystemId, pub members: Vec, } impl AssetStack { pub fn new(stack_type: StackType, primary_asset_id: SystemId, owner: SystemId) -> Self { let primary_member = AssetStackMember { asset_id: primary_asset_id, role: StackMemberRole::PrimaryDisplay, sort_order: 0, }; Self { stack_id: SystemId::new(), stack_type, primary_asset_id, owner_user_id: owner, members: vec![primary_member], } } 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(), )); } let next_order = self.members.iter().map(|m| m.sort_order).max().unwrap_or(0) + 1; self.members.push(AssetStackMember { asset_id, role, sort_order: next_order, }); Ok(()) } } // --- DerivativeAsset --- #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum DerivativeProfile { ThumbnailSquare, ThumbnailLarge, WebOptimized, VideoSd, } #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum GenerationStatus { Pending, Ready, Failed, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct DerivativeAsset { pub derivative_id: SystemId, pub parent_asset_id: SystemId, pub profile_type: DerivativeProfile, pub storage_path: String, pub mime_type: String, pub file_size: u64, pub dimensions: (u32, u32), pub generation_status: GenerationStatus, } impl DerivativeAsset { pub fn new_pending( parent: SystemId, profile: DerivativeProfile, path: impl Into, ) -> Self { Self { derivative_id: SystemId::new(), parent_asset_id: parent, profile_type: profile, storage_path: path.into(), mime_type: String::new(), file_size: 0, dimensions: (0, 0), generation_status: GenerationStatus::Pending, } } pub fn mark_ready(&mut self, mime_type: impl Into, size: u64, dims: (u32, u32)) { self.mime_type = mime_type.into(); self.file_size = size; self.dimensions = dims; self.generation_status = GenerationStatus::Ready; } pub fn mark_failed(&mut self) { self.generation_status = GenerationStatus::Failed; } } // --- Duplicate --- #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum DetectionMethod { ExactHash, PerceptualHash, } #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum DuplicateStatus { Unresolved, Resolved, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct DuplicateCandidate { pub asset_id: SystemId, pub similarity_score: f64, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct DuplicateGroup { pub group_id: SystemId, pub detection_method: DetectionMethod, pub status: DuplicateStatus, pub candidates: Vec, } impl DuplicateGroup { pub fn new_exact(asset_a: SystemId, asset_b: SystemId) -> Self { Self { group_id: SystemId::new(), 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, }, ], } } pub fn resolve(&mut self) { self.status = DuplicateStatus::Resolved; } }