use async_trait::async_trait; use domain::{ entities::{AssetMetadata, MetadataSource}, errors::DomainError, ports::{ AssetMetadataRepository, AssetRepository, FileStoragePort, MetadataExtractorPort, PluginExecutor, }, value_objects::{MetadataValue, StructuredData, SystemId}, }; use std::sync::Arc; use tracing::info; pub struct MetadataExtractorPlugin { asset_repo: Arc, file_storage: Arc, metadata_repo: Arc, extractor: Arc, } impl MetadataExtractorPlugin { pub fn new( asset_repo: Arc, file_storage: Arc, metadata_repo: Arc, extractor: Arc, ) -> Self { Self { asset_repo, file_storage, metadata_repo, extractor, } } } #[async_trait] impl PluginExecutor for MetadataExtractorPlugin { fn plugin_name(&self) -> &str { "metadata_extractor" } async fn execute( &self, asset_id: Option, _payload: &StructuredData, _config: &StructuredData, ) -> Result { let asset_id = asset_id.ok_or_else(|| { DomainError::Validation("metadata_extractor requires asset_id".into()) })?; let asset = self .asset_repo .find_by_id(&asset_id) .await? .ok_or_else(|| DomainError::NotFound(format!("Asset {} not found", asset_id)))?; let path = &asset.source_reference.relative_path; let data = self.file_storage.read_file(path).await?; let mut extracted = self.extractor.extract(&data)?; extracted.insert("file_size_bytes", MetadataValue::Integer(data.len() as i64)); extracted.insert("mime_type", MetadataValue::String(asset.mime_type.clone())); let metadata = AssetMetadata::new(asset_id, MetadataSource::ExifExtracted, extracted.clone()); self.metadata_repo.save(&metadata).await?; info!(asset_id = %asset_id, tags = extracted.len(), "extracted metadata"); Ok(extracted) } }