use std::sync::Arc; use domain::{ catalog::entities::{AssetMetadata, MetadataSource}, catalog::services::resolve_metadata, entities::{ConflictPolicy, SidecarRecord, SyncStatus}, errors::DomainError, ports::{AssetMetadataRepository, SidecarRepository, SidecarWriterPort}, value_objects::SystemId, }; use crate::sidecar::hash_helper::hash_structured_data; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct ResolveConflictCommand { pub asset_id: SystemId, pub policy: ConflictPolicy, } pub struct ResolveConflictHandler { sidecar_repo: Arc, writer: Arc, metadata_repo: Arc, } impl ResolveConflictHandler { pub fn new( sidecar_repo: Arc, writer: Arc, metadata_repo: Arc, ) -> Self { Self { sidecar_repo, writer, metadata_repo } } pub async fn execute(&self, cmd: ResolveConflictCommand) -> Result { let mut record = self.sidecar_repo.find_by_asset(&cmd.asset_id).await? .ok_or_else(|| DomainError::NotFound(format!("Sidecar record for {} not found", cmd.asset_id)))?; if record.sync_status != SyncStatus::Conflict { return Err(DomainError::Validation( format!("Sidecar is not in conflict (status: {:?})", record.sync_status), )); } match cmd.policy { ConflictPolicy::DbWins => { let layers = self.metadata_repo.find_by_asset(&cmd.asset_id).await?; let resolved = resolve_metadata(&layers); self.writer.write_sidecar(&resolved, &record.sidecar_storage_path).await?; let hash = hash_structured_data(&resolved); record.mark_synced(hash); } ConflictPolicy::FileWins => { let data = self.writer.read_sidecar(&record.sidecar_storage_path).await?; let metadata = AssetMetadata::new(cmd.asset_id, MetadataSource::ExifExtracted, data.clone()); self.metadata_repo.save(&metadata).await?; let hash = hash_structured_data(&data); record.mark_synced(hash); } ConflictPolicy::RequireUserDecision => { return Err(DomainError::Validation("Manual resolution required".to_string())); } } self.sidecar_repo.save(&record).await?; Ok(record) } }