app: add sidecar sync commands (export, detect, import, resolve, full export/import)

This commit is contained in:
2026-05-31 05:29:03 +02:00
parent d1394ce7bb
commit 4b31a0f74b
43 changed files with 1685 additions and 6 deletions

View File

@@ -4,16 +4,18 @@ use tokio::sync::Mutex;
use domain::{
entities::{
Album, Asset, AssetMetadata, AssetTag, DuplicateGroup, DuplicateStatus,
Group, IngestSession, InviteCode, Job, JobStatus, LibraryPath,
MetadataSource, QuotaDefinition, Role, ShareLink, ShareScope, ShareTarget,
Group, IngestSession, InviteCode, Job, JobBatch, JobStatus, LibraryPath,
MetadataSource, Plugin, ProcessingPipeline, QuotaDefinition, Role,
ShareLink, ShareScope, ShareTarget, SidecarRecord, SyncStatus,
StorageVolume, Tag, UsageLedgerEntry, UsageType, User,
},
errors::DomainError,
ports::{
AlbumRepository, AssetMetadataRepository, AssetRepository,
DuplicateRepository, GroupRepository, IngestSessionRepository,
JobRepository, LibraryPathRepository, QuotaRepository,
RoleRepository, ShareRepository, StorageVolumeRepository,
JobBatchRepository, JobRepository, LibraryPathRepository,
PipelineRepository, PluginRepository, QuotaRepository,
RoleRepository, ShareRepository, SidecarRepository, StorageVolumeRepository,
TagRepository, UsageLedgerRepository, UserRepository,
},
value_objects::{Checksum, DateTimeStamp, Email, SystemId},
@@ -716,3 +718,141 @@ impl DuplicateRepository for InMemoryDuplicateRepository {
Ok(())
}
}
// --- InMemorySidecarRepository ---
pub struct InMemorySidecarRepository {
data: Mutex<HashMap<String, SidecarRecord>>,
}
impl InMemorySidecarRepository {
pub fn new() -> Self {
Self { data: Mutex::new(HashMap::new()) }
}
}
impl Default for InMemorySidecarRepository {
fn default() -> Self { Self::new() }
}
#[async_trait]
impl SidecarRepository for InMemorySidecarRepository {
async fn find_by_asset(&self, asset_id: &SystemId) -> Result<Option<SidecarRecord>, DomainError> {
Ok(self.data.lock().await.get(&asset_id.to_string()).cloned())
}
async fn find_by_status(&self, status: SyncStatus) -> Result<Vec<SidecarRecord>, DomainError> {
Ok(self.data.lock().await.values()
.filter(|r| r.sync_status == status)
.cloned()
.collect())
}
async fn save(&self, record: &SidecarRecord) -> Result<(), DomainError> {
self.data.lock().await.insert(record.asset_id.to_string(), record.clone());
Ok(())
}
async fn delete(&self, asset_id: &SystemId) -> Result<(), DomainError> {
self.data.lock().await.remove(&asset_id.to_string());
Ok(())
}
}
// --- InMemoryJobBatchRepository ---
pub struct InMemoryJobBatchRepository {
data: Mutex<HashMap<String, JobBatch>>,
}
impl InMemoryJobBatchRepository {
pub fn new() -> Self {
Self { data: Mutex::new(HashMap::new()) }
}
}
impl Default for InMemoryJobBatchRepository {
fn default() -> Self { Self::new() }
}
#[async_trait]
impl JobBatchRepository for InMemoryJobBatchRepository {
async fn find_by_id(&self, id: &SystemId) -> Result<Option<JobBatch>, DomainError> {
Ok(self.data.lock().await.get(&id.to_string()).cloned())
}
async fn save(&self, batch: &JobBatch) -> Result<(), DomainError> {
self.data.lock().await.insert(batch.batch_id.to_string(), batch.clone());
Ok(())
}
}
// --- InMemoryPluginRepository ---
pub struct InMemoryPluginRepository {
data: Mutex<HashMap<String, Plugin>>,
}
impl InMemoryPluginRepository {
pub fn new() -> Self {
Self { data: Mutex::new(HashMap::new()) }
}
}
impl Default for InMemoryPluginRepository {
fn default() -> Self { Self::new() }
}
#[async_trait]
impl PluginRepository for InMemoryPluginRepository {
async fn find_by_id(&self, id: &SystemId) -> Result<Option<Plugin>, DomainError> {
Ok(self.data.lock().await.get(&id.to_string()).cloned())
}
async fn find_enabled(&self) -> Result<Vec<Plugin>, DomainError> {
Ok(self.data.lock().await.values()
.filter(|p| p.is_enabled)
.cloned()
.collect())
}
async fn save(&self, plugin: &Plugin) -> Result<(), DomainError> {
self.data.lock().await.insert(plugin.plugin_id.to_string(), plugin.clone());
Ok(())
}
}
// --- InMemoryPipelineRepository ---
pub struct InMemoryPipelineRepository {
data: Mutex<HashMap<String, ProcessingPipeline>>,
}
impl InMemoryPipelineRepository {
pub fn new() -> Self {
Self { data: Mutex::new(HashMap::new()) }
}
}
impl Default for InMemoryPipelineRepository {
fn default() -> Self { Self::new() }
}
#[async_trait]
impl PipelineRepository for InMemoryPipelineRepository {
async fn find_by_id(&self, id: &SystemId) -> Result<Option<ProcessingPipeline>, DomainError> {
Ok(self.data.lock().await.get(&id.to_string()).cloned())
}
async fn find_by_trigger(&self, event: &str) -> Result<Vec<ProcessingPipeline>, DomainError> {
Ok(self.data.lock().await.values()
.filter(|p| p.trigger_event == event)
.cloned()
.collect())
}
async fn save(&self, pipeline: &ProcessingPipeline) -> Result<(), DomainError> {
self.data.lock().await.insert(pipeline.pipeline_id.to_string(), pipeline.clone());
Ok(())
}
}