feat: worker plugin system — domain ports, pipeline executor, built-in plugins
- PluginExecutor + PluginRegistry ports in domain - ExecutePipelineCommand orchestrates job→pipeline→plugin steps - ProcessNextJobCommand polls + executes next queued job - InMemoryPluginRegistry, NoOp/MetadataExtractor/SidecarSync plugins - Worker main rewritten with poll loop, factories module for DI - Deleted template job/runner/jobs remnants
This commit is contained in:
68
crates/worker/src/plugins/metadata_extractor.rs
Normal file
68
crates/worker/src/plugins/metadata_extractor.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use async_trait::async_trait;
|
||||
use domain::{
|
||||
entities::{AssetMetadata, MetadataSource},
|
||||
errors::DomainError,
|
||||
ports::{AssetMetadataRepository, AssetRepository, FileStoragePort, PluginExecutor},
|
||||
value_objects::{MetadataValue, StructuredData, SystemId},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tracing::info;
|
||||
|
||||
pub struct MetadataExtractorPlugin {
|
||||
asset_repo: Arc<dyn AssetRepository>,
|
||||
file_storage: Arc<dyn FileStoragePort>,
|
||||
metadata_repo: Arc<dyn AssetMetadataRepository>,
|
||||
}
|
||||
|
||||
impl MetadataExtractorPlugin {
|
||||
pub fn new(
|
||||
asset_repo: Arc<dyn AssetRepository>,
|
||||
file_storage: Arc<dyn FileStoragePort>,
|
||||
metadata_repo: Arc<dyn AssetMetadataRepository>,
|
||||
) -> Self {
|
||||
Self {
|
||||
asset_repo,
|
||||
file_storage,
|
||||
metadata_repo,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl PluginExecutor for MetadataExtractorPlugin {
|
||||
fn plugin_name(&self) -> &str {
|
||||
"metadata_extractor"
|
||||
}
|
||||
|
||||
async fn execute(
|
||||
&self,
|
||||
asset_id: Option<SystemId>,
|
||||
_payload: &StructuredData,
|
||||
_config: &StructuredData,
|
||||
) -> Result<StructuredData, DomainError> {
|
||||
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 file_size = data.len() as i64;
|
||||
|
||||
let mut extracted = StructuredData::new();
|
||||
extracted.insert("file_size_bytes", MetadataValue::Integer(file_size));
|
||||
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, file_size, "extracted basic metadata");
|
||||
Ok(extracted)
|
||||
}
|
||||
}
|
||||
7
crates/worker/src/plugins/mod.rs
Normal file
7
crates/worker/src/plugins/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
pub mod metadata_extractor;
|
||||
pub mod no_op;
|
||||
pub mod sidecar_sync;
|
||||
|
||||
pub use metadata_extractor::MetadataExtractorPlugin;
|
||||
pub use no_op::NoOpPlugin;
|
||||
pub use sidecar_sync::SidecarSyncPlugin;
|
||||
26
crates/worker/src/plugins/no_op.rs
Normal file
26
crates/worker/src/plugins/no_op.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use async_trait::async_trait;
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
ports::PluginExecutor,
|
||||
value_objects::{StructuredData, SystemId},
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
pub struct NoOpPlugin;
|
||||
|
||||
#[async_trait]
|
||||
impl PluginExecutor for NoOpPlugin {
|
||||
fn plugin_name(&self) -> &str {
|
||||
"no_op"
|
||||
}
|
||||
|
||||
async fn execute(
|
||||
&self,
|
||||
asset_id: Option<SystemId>,
|
||||
_payload: &StructuredData,
|
||||
_config: &StructuredData,
|
||||
) -> Result<StructuredData, DomainError> {
|
||||
info!(asset_id = ?asset_id, "no_op plugin executed");
|
||||
Ok(StructuredData::new())
|
||||
}
|
||||
}
|
||||
50
crates/worker/src/plugins/sidecar_sync.rs
Normal file
50
crates/worker/src/plugins/sidecar_sync.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use application::sidecar::{ExportSidecarCommand, ExportSidecarHandler};
|
||||
use async_trait::async_trait;
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
ports::PluginExecutor,
|
||||
value_objects::{MetadataValue, StructuredData, SystemId},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tracing::info;
|
||||
|
||||
pub struct SidecarSyncPlugin {
|
||||
export_handler: Arc<ExportSidecarHandler>,
|
||||
}
|
||||
|
||||
impl SidecarSyncPlugin {
|
||||
pub fn new(export_handler: Arc<ExportSidecarHandler>) -> Self {
|
||||
Self { export_handler }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl PluginExecutor for SidecarSyncPlugin {
|
||||
fn plugin_name(&self) -> &str {
|
||||
"sidecar_sync"
|
||||
}
|
||||
|
||||
async fn execute(
|
||||
&self,
|
||||
asset_id: Option<SystemId>,
|
||||
_payload: &StructuredData,
|
||||
_config: &StructuredData,
|
||||
) -> Result<StructuredData, DomainError> {
|
||||
let asset_id = asset_id
|
||||
.ok_or_else(|| DomainError::Validation("sidecar_sync requires asset_id".into()))?;
|
||||
|
||||
let record = self
|
||||
.export_handler
|
||||
.execute(ExportSidecarCommand { asset_id })
|
||||
.await?;
|
||||
|
||||
let mut result = StructuredData::new();
|
||||
result.insert(
|
||||
"sidecar_path",
|
||||
MetadataValue::String(record.sidecar_storage_path),
|
||||
);
|
||||
|
||||
info!(asset_id = %asset_id, "sidecar synced");
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user