Pagination: count_by_owner + count_search on AssetRepository,
timeline/search return real total count (not page len).
Auto-derivatives: worker enqueues GenerateDerivative when
ExtractMetadata job completes, closing the upload→thumbnail gap.
List endpoints: GET /albums, GET /stacks with user scoping.
ListAlbumsHandler, ListStacksHandler, find_by_owner on AssetStackRepository.
Tag filtering: tag_name field on AssetFilters, JOIN asset_tags+tags
in postgres search/count queries.
Bulk operations: POST /assets/bulk-delete, POST /assets/bulk-tag.
Album update: PUT /albums/{id} with UpdateAlbumHandler (title, description).
OpenAPI: utoipa annotations on all 47 endpoints + all request/response
schemas registered. Scalar UI at /scalar covers full API.
125 lines
4.0 KiB
Rust
125 lines
4.0 KiB
Rust
use super::entities::{
|
|
Asset, AssetFilters, AssetMetadata, AssetStack, DerivativeAsset, DerivativeProfile,
|
|
DuplicateGroup, MetadataSource,
|
|
};
|
|
use crate::common::errors::DomainError;
|
|
use crate::common::value_objects::{Checksum, StructuredData, SystemId};
|
|
use async_trait::async_trait;
|
|
use bytes::Bytes;
|
|
|
|
// --- AssetRepository ---
|
|
|
|
#[async_trait]
|
|
pub trait AssetRepository: Send + Sync {
|
|
async fn find_by_id(&self, id: &SystemId) -> Result<Option<Asset>, DomainError>;
|
|
async fn find_by_checksum(&self, checksum: &Checksum) -> Result<Vec<Asset>, DomainError>;
|
|
async fn find_by_owner(
|
|
&self,
|
|
owner_id: &SystemId,
|
|
limit: u32,
|
|
offset: u32,
|
|
) -> Result<Vec<Asset>, DomainError>;
|
|
async fn count_by_owner(&self, owner_id: &SystemId) -> Result<u64, DomainError>;
|
|
async fn search(
|
|
&self,
|
|
owner_id: &SystemId,
|
|
filters: &AssetFilters,
|
|
limit: u32,
|
|
offset: u32,
|
|
) -> Result<Vec<Asset>, DomainError>;
|
|
async fn count_search(
|
|
&self,
|
|
owner_id: &SystemId,
|
|
filters: &AssetFilters,
|
|
) -> Result<u64, DomainError>;
|
|
async fn save(&self, asset: &Asset) -> Result<(), DomainError>;
|
|
async fn delete(&self, id: &SystemId) -> Result<(), DomainError>;
|
|
}
|
|
|
|
// --- AssetMetadataRepository ---
|
|
|
|
#[async_trait]
|
|
pub trait AssetMetadataRepository: Send + Sync {
|
|
async fn find_by_asset(&self, asset_id: &SystemId) -> Result<Vec<AssetMetadata>, DomainError>;
|
|
async fn find_by_assets(
|
|
&self,
|
|
asset_ids: &[SystemId],
|
|
) -> Result<Vec<AssetMetadata>, DomainError>;
|
|
async fn find_by_asset_and_source(
|
|
&self,
|
|
asset_id: &SystemId,
|
|
source: MetadataSource,
|
|
) -> Result<Option<AssetMetadata>, DomainError>;
|
|
async fn save(&self, metadata: &AssetMetadata) -> Result<(), DomainError>;
|
|
async fn delete_by_asset_and_source(
|
|
&self,
|
|
asset_id: &SystemId,
|
|
source: MetadataSource,
|
|
) -> Result<(), DomainError>;
|
|
}
|
|
|
|
// --- AssetStackRepository ---
|
|
|
|
#[async_trait]
|
|
pub trait AssetStackRepository: Send + Sync {
|
|
async fn find_by_id(&self, id: &SystemId) -> Result<Option<AssetStack>, DomainError>;
|
|
async fn find_by_owner(&self, owner_id: &SystemId) -> Result<Vec<AssetStack>, DomainError>;
|
|
async fn find_by_asset(&self, asset_id: &SystemId) -> Result<Vec<AssetStack>, DomainError>;
|
|
async fn save(&self, stack: &AssetStack) -> Result<(), DomainError>;
|
|
async fn delete(&self, id: &SystemId) -> Result<(), DomainError>;
|
|
}
|
|
|
|
// --- DerivativeRepository ---
|
|
|
|
#[async_trait]
|
|
pub trait DerivativeRepository: Send + Sync {
|
|
async fn find_by_asset(&self, asset_id: &SystemId)
|
|
-> Result<Vec<DerivativeAsset>, DomainError>;
|
|
async fn find_by_asset_and_profile(
|
|
&self,
|
|
asset_id: &SystemId,
|
|
profile: DerivativeProfile,
|
|
) -> Result<Option<DerivativeAsset>, DomainError>;
|
|
async fn save(&self, derivative: &DerivativeAsset) -> Result<(), DomainError>;
|
|
async fn delete(&self, id: &SystemId) -> Result<(), DomainError>;
|
|
}
|
|
|
|
// --- DuplicateRepository ---
|
|
|
|
#[async_trait]
|
|
pub trait DuplicateRepository: Send + Sync {
|
|
async fn find_by_id(&self, id: &SystemId) -> Result<Option<DuplicateGroup>, DomainError>;
|
|
async fn find_unresolved(
|
|
&self,
|
|
limit: u32,
|
|
offset: u32,
|
|
) -> Result<Vec<DuplicateGroup>, DomainError>;
|
|
async fn find_by_asset(&self, asset_id: &SystemId) -> Result<Vec<DuplicateGroup>, DomainError>;
|
|
async fn save(&self, group: &DuplicateGroup) -> Result<(), DomainError>;
|
|
}
|
|
|
|
// --- MetadataExtractorPort ---
|
|
|
|
pub trait MetadataExtractorPort: Send + Sync {
|
|
fn extract(&self, bytes: &Bytes) -> Result<StructuredData, DomainError>;
|
|
}
|
|
|
|
// --- ThumbnailGeneratorPort ---
|
|
|
|
pub struct ThumbnailOutput {
|
|
pub bytes: Bytes,
|
|
pub width: u32,
|
|
pub height: u32,
|
|
pub mime_type: String,
|
|
}
|
|
|
|
pub trait ThumbnailGeneratorPort: Send + Sync {
|
|
fn generate(
|
|
&self,
|
|
source: &Bytes,
|
|
width: u32,
|
|
height: u32,
|
|
format: &str,
|
|
) -> Result<ThumbnailOutput, DomainError>;
|
|
}
|