diff --git a/crates/adapters/auth/src/jwt.rs b/crates/adapters/auth/src/jwt.rs index 87d047c..3f7b210 100644 --- a/crates/adapters/auth/src/jwt.rs +++ b/crates/adapters/auth/src/jwt.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; use chrono::Utc; use domain::{errors::DomainError, ports::TokenIssuer, value_objects::SystemId}; -use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation}; +use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation, decode, encode}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] diff --git a/crates/adapters/postgres/src/lib.rs b/crates/adapters/postgres/src/lib.rs index 6616b69..46f66b2 100644 --- a/crates/adapters/postgres/src/lib.rs +++ b/crates/adapters/postgres/src/lib.rs @@ -1,5 +1,5 @@ pub mod db; pub mod user_repository; -pub use db::{connect, run_migrations, PgPool}; +pub use db::{PgPool, connect, run_migrations}; pub use user_repository::PostgresUserRepository; diff --git a/crates/adapters/postgres/src/user_repository.rs b/crates/adapters/postgres/src/user_repository.rs index bd8db4f..2cb2c76 100644 --- a/crates/adapters/postgres/src/user_repository.rs +++ b/crates/adapters/postgres/src/user_repository.rs @@ -1,3 +1,4 @@ +use crate::db::PgPool; use async_trait::async_trait; use domain::{ entities::User, @@ -5,14 +6,15 @@ use domain::{ ports::UserRepository, value_objects::{Email, PasswordHash, SystemId}, }; -use crate::db::PgPool; pub struct PostgresUserRepository { pool: PgPool, } impl PostgresUserRepository { - pub fn new(pool: PgPool) -> Self { Self { pool } } + pub fn new(pool: PgPool) -> Self { + Self { pool } + } } #[async_trait] @@ -26,12 +28,14 @@ impl UserRepository for PostgresUserRepository { .await .map_err(|e| DomainError::Internal(e.to_string()))?; - row.map(|r| Ok(User { - id: SystemId::from_uuid(r.id), - email: Email::new(r.email)?, - password_hash: PasswordHash::from_hash(r.password_hash), - created_at: r.created_at, - })) + row.map(|r| { + Ok(User { + id: SystemId::from_uuid(r.id), + email: Email::new(r.email)?, + password_hash: PasswordHash::from_hash(r.password_hash), + created_at: r.created_at, + }) + }) .transpose() } @@ -44,12 +48,14 @@ impl UserRepository for PostgresUserRepository { .await .map_err(|e| DomainError::Internal(e.to_string()))?; - row.map(|r| Ok(User { - id: SystemId::from_uuid(r.id), - email: Email::new(r.email)?, - password_hash: PasswordHash::from_hash(r.password_hash), - created_at: r.created_at, - })) + row.map(|r| { + Ok(User { + id: SystemId::from_uuid(r.id), + email: Email::new(r.email)?, + password_hash: PasswordHash::from_hash(r.password_hash), + created_at: r.created_at, + }) + }) .transpose() } diff --git a/crates/adapters/storage/src/adapter.rs b/crates/adapters/storage/src/adapter.rs index 8facae3..0b41773 100644 --- a/crates/adapters/storage/src/adapter.rs +++ b/crates/adapters/storage/src/adapter.rs @@ -1,10 +1,10 @@ -use std::sync::Arc; use async_trait::async_trait; use bytes::Bytes; -use futures::stream::StreamExt; -use object_store::{ObjectStore, path::Path, Error as OsError}; use domain::errors::DomainError; use domain::ports::{DataStream, StorageReader, StorageWriter}; +use futures::stream::StreamExt; +use object_store::{Error as OsError, ObjectStore, path::Path}; +use std::sync::Arc; pub struct ObjectStorageAdapter { store: Arc, @@ -12,7 +12,10 @@ pub struct ObjectStorageAdapter { } impl ObjectStorageAdapter { - pub fn new(store: Arc, prefix: impl Into) -> Result { + pub fn new( + store: Arc, + prefix: impl Into, + ) -> Result { let prefix = prefix.into(); if !prefix.is_empty() { validate_key(&prefix)?; @@ -38,17 +41,19 @@ fn map_err(e: OsError, key: &str) -> DomainError { fn validate_key(key: &str) -> Result<(), DomainError> { if key.is_empty() { - return Err(DomainError::Validation("storage key must not be empty".into())); + return Err(DomainError::Validation( + "storage key must not be empty".into(), + )); } if key.starts_with('/') { - return Err(DomainError::Validation( - format!("storage key must not start with '/': {key}"), - )); + return Err(DomainError::Validation(format!( + "storage key must not start with '/': {key}" + ))); } if key.split('/').any(|seg| seg == ".." || seg == ".") { - return Err(DomainError::Validation( - format!("storage key contains invalid path segment: {key}"), - )); + return Err(DomainError::Validation(format!( + "storage key contains invalid path segment: {key}" + ))); } Ok(()) } @@ -79,7 +84,10 @@ impl StorageWriter for ObjectStorageAdapter { } } } - upload.complete().await.map_err(|e| DomainError::Internal(e.to_string()))?; + upload + .complete() + .await + .map_err(|e| DomainError::Internal(e.to_string()))?; Ok(()) } @@ -99,11 +107,7 @@ impl StorageReader for ObjectStorageAdapter { async fn get(&self, key: &str) -> Result { validate_key(key)?; let path = self.path(key); - let result = self - .store - .get(&path) - .await - .map_err(|e| map_err(e, key))?; + let result = self.store.get(&path).await.map_err(|e| map_err(e, key))?; let s = result .into_stream() .map(|r| r.map_err(|e| DomainError::Internal(e.to_string()))); @@ -128,10 +132,12 @@ impl StorageReader for ObjectStorageAdapter { let key = meta.location.to_string(); let stripped = if !self.prefix.is_empty() { key.strip_prefix(&format!("{}/", self.prefix)) - .ok_or_else(|| DomainError::Internal(format!( - "listed key '{key}' does not start with expected prefix '{}'", - self.prefix - )))? + .ok_or_else(|| { + DomainError::Internal(format!( + "listed key '{key}' does not start with expected prefix '{}'", + self.prefix + )) + })? .to_string() } else { key @@ -172,7 +178,10 @@ mod tests { #[tokio::test] async fn get_missing_is_not_found() { let a = make_adapter(); - assert!(matches!(a.get("nope.txt").await, Err(DomainError::NotFound(_)))); + assert!(matches!( + a.get("nope.txt").await, + Err(DomainError::NotFound(_)) + )); } #[tokio::test] @@ -186,7 +195,10 @@ mod tests { let a = make_adapter(); a.put("file.txt", one_shot(b"data")).await.unwrap(); a.delete("file.txt").await.unwrap(); - assert!(matches!(a.get("file.txt").await, Err(DomainError::NotFound(_)))); + assert!(matches!( + a.get("file.txt").await, + Err(DomainError::NotFound(_)) + )); } #[tokio::test] @@ -213,9 +225,15 @@ mod tests { #[tokio::test] async fn rejects_empty_key() { let a = make_adapter(); - assert!(matches!(a.put("", one_shot(b"x")).await, Err(DomainError::Validation(_)))); + assert!(matches!( + a.put("", one_shot(b"x")).await, + Err(DomainError::Validation(_)) + )); assert!(matches!(a.get("").await, Err(DomainError::Validation(_)))); - assert!(matches!(a.delete("").await, Err(DomainError::Validation(_)))); + assert!(matches!( + a.delete("").await, + Err(DomainError::Validation(_)) + )); } #[tokio::test] @@ -230,8 +248,14 @@ mod tests { #[tokio::test] async fn rejects_path_traversal() { let a = make_adapter(); - assert!(matches!(a.get("../escape").await, Err(DomainError::Validation(_)))); - assert!(matches!(a.get("a/../../../etc").await, Err(DomainError::Validation(_)))); + assert!(matches!( + a.get("../escape").await, + Err(DomainError::Validation(_)) + )); + assert!(matches!( + a.get("a/../../../etc").await, + Err(DomainError::Validation(_)) + )); } #[tokio::test] @@ -246,8 +270,14 @@ mod tests { #[tokio::test] async fn rejects_invalid_list_prefix() { let a = make_adapter(); - assert!(matches!(a.list(Some("")).await, Err(DomainError::Validation(_)))); - assert!(matches!(a.list(Some("../escape")).await, Err(DomainError::Validation(_)))); + assert!(matches!( + a.list(Some("")).await, + Err(DomainError::Validation(_)) + )); + assert!(matches!( + a.list(Some("../escape")).await, + Err(DomainError::Validation(_)) + )); } #[tokio::test] @@ -275,7 +305,9 @@ mod tests { #[tokio::test] async fn put_bytes_get_bytes_roundtrip() { let a = make_adapter(); - a.put_bytes("data.bin", Bytes::from("hello bytes")).await.unwrap(); + a.put_bytes("data.bin", Bytes::from("hello bytes")) + .await + .unwrap(); let got = a.get_bytes("data.bin").await.unwrap(); assert_eq!(got.as_ref(), b"hello bytes"); } @@ -283,7 +315,10 @@ mod tests { #[tokio::test] async fn get_bytes_missing_is_not_found() { let a = make_adapter(); - assert!(matches!(a.get_bytes("nope.bin").await, Err(DomainError::NotFound(_)))); + assert!(matches!( + a.get_bytes("nope.bin").await, + Err(DomainError::NotFound(_)) + )); } #[test] diff --git a/crates/adapters/storage/src/config.rs b/crates/adapters/storage/src/config.rs index 8263134..e352bdb 100644 --- a/crates/adapters/storage/src/config.rs +++ b/crates/adapters/storage/src/config.rs @@ -1,7 +1,7 @@ -use std::sync::Arc; use anyhow::{Context, Result}; use object_store::ObjectStore; use object_store::local::LocalFileSystem; +use std::sync::Arc; /// All storage configuration. Populate once via `from_env()` and pass /// explicitly to `build_store` and `ObjectStorageAdapter::new`. @@ -41,7 +41,9 @@ impl StorageConfig { pub fn build_store(config: &StorageConfig) -> Result> { match config.backend.as_str() { "local" => { - let path = config.local_path.as_deref() + let path = config + .local_path + .as_deref() .context("STORAGE_PATH must be set when STORAGE_BACKEND=local")?; std::fs::create_dir_all(path) .with_context(|| format!("failed to create storage dir: {path}"))?; @@ -53,18 +55,28 @@ pub fn build_store(config: &StorageConfig) -> Result> { use object_store::aws::AmazonS3Builder; let store = AmazonS3Builder::new() .with_endpoint( - config.s3_endpoint.as_deref().context("S3_ENDPOINT must be set")?, + config + .s3_endpoint + .as_deref() + .context("S3_ENDPOINT must be set")?, ) .with_access_key_id( - config.s3_access_key_id.as_deref() + config + .s3_access_key_id + .as_deref() .context("S3_ACCESS_KEY_ID must be set")?, ) .with_secret_access_key( - config.s3_secret_access_key.as_deref() + config + .s3_secret_access_key + .as_deref() .context("S3_SECRET_ACCESS_KEY must be set")?, ) .with_bucket_name( - config.s3_bucket.as_deref().context("S3_BUCKET must be set")?, + config + .s3_bucket + .as_deref() + .context("S3_BUCKET must be set")?, ) .with_region(config.s3_region.as_deref().unwrap_or("us-east-1")) .with_allow_http(true) @@ -76,7 +88,10 @@ pub fn build_store(config: &StorageConfig) -> Result> { use object_store::gcp::GoogleCloudStorageBuilder; let store = GoogleCloudStorageBuilder::new() .with_bucket_name( - config.gcs_bucket.as_deref().context("GCS_BUCKET must be set")?, + config + .gcs_bucket + .as_deref() + .context("GCS_BUCKET must be set")?, ) .build()?; Ok(Arc::new(store)) diff --git a/crates/adapters/storage/src/lib.rs b/crates/adapters/storage/src/lib.rs index f32d0d1..4cb3160 100644 --- a/crates/adapters/storage/src/lib.rs +++ b/crates/adapters/storage/src/lib.rs @@ -2,4 +2,4 @@ pub mod adapter; pub mod config; pub use adapter::ObjectStorageAdapter; -pub use config::{build_store, StorageConfig}; +pub use config::{StorageConfig, build_store}; diff --git a/crates/application/src/catalog/commands/register_asset.rs b/crates/application/src/catalog/commands/register_asset.rs index c74e587..3a2b89e 100644 --- a/crates/application/src/catalog/commands/register_asset.rs +++ b/crates/application/src/catalog/commands/register_asset.rs @@ -1,4 +1,3 @@ -use std::sync::Arc; use domain::{ catalog::entities::{Asset, AssetType, DuplicateGroup, SourceReference}, errors::DomainError, @@ -6,6 +5,7 @@ use domain::{ ports::{AssetRepository, DuplicateRepository, EventPublisher}, value_objects::{Checksum, DateTimeStamp, SystemId}, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct RegisterAssetCommand { @@ -30,10 +30,17 @@ impl RegisterAssetHandler { duplicate_repo: Arc, event_pub: Arc, ) -> Self { - Self { asset_repo, duplicate_repo, event_pub } + Self { + asset_repo, + duplicate_repo, + event_pub, + } } - pub async fn execute(&self, cmd: RegisterAssetCommand) -> Result<(Asset, Option), DomainError> { + pub async fn execute( + &self, + cmd: RegisterAssetCommand, + ) -> Result<(Asset, Option), DomainError> { let checksum = Checksum::new(&cmd.checksum)?; let existing = self.asset_repo.find_by_checksum(&checksum).await?; @@ -44,7 +51,13 @@ impl RegisterAssetHandler { checksum, }; - let asset = Asset::new(source_ref, cmd.asset_type, cmd.mime_type, cmd.file_size, cmd.owner_id); + let asset = Asset::new( + source_ref, + cmd.asset_type, + cmd.mime_type, + cmd.file_size, + cmd.owner_id, + ); self.asset_repo.save(&asset).await?; let dup_group = if let Some(first) = existing.first() { @@ -55,11 +68,13 @@ impl RegisterAssetHandler { None }; - self.event_pub.publish(DomainEvent::AssetIngested { - asset_id: asset.asset_id, - owner_user_id: asset.owner_user_id, - timestamp: DateTimeStamp::now(), - }).await?; + self.event_pub + .publish(DomainEvent::AssetIngested { + asset_id: asset.asset_id, + owner_user_id: asset.owner_user_id, + timestamp: DateTimeStamp::now(), + }) + .await?; Ok((asset, dup_group)) } diff --git a/crates/application/src/catalog/commands/update_metadata.rs b/crates/application/src/catalog/commands/update_metadata.rs index 584e435..41323c0 100644 --- a/crates/application/src/catalog/commands/update_metadata.rs +++ b/crates/application/src/catalog/commands/update_metadata.rs @@ -1,11 +1,11 @@ -use std::sync::Arc; use domain::{ catalog::entities::{AssetMetadata, MetadataSource}, errors::DomainError, events::DomainEvent, - ports::{AssetRepository, AssetMetadataRepository, EventPublisher}, + ports::{AssetMetadataRepository, AssetRepository, EventPublisher}, value_objects::{DateTimeStamp, StructuredData, SystemId}, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct UpdateMetadataCommand { @@ -26,21 +26,29 @@ impl UpdateMetadataHandler { metadata_repo: Arc, event_pub: Arc, ) -> Self { - Self { asset_repo, metadata_repo, event_pub } + Self { + asset_repo, + metadata_repo, + event_pub, + } } pub async fn execute(&self, cmd: UpdateMetadataCommand) -> Result { - self.asset_repo.find_by_id(&cmd.asset_id).await? + self.asset_repo + .find_by_id(&cmd.asset_id) + .await? .ok_or_else(|| DomainError::NotFound(format!("Asset {} not found", cmd.asset_id)))?; let metadata = AssetMetadata::new(cmd.asset_id, MetadataSource::UserEdited, cmd.data); self.metadata_repo.save(&metadata).await?; - self.event_pub.publish(DomainEvent::MetadataUpdated { - asset_id: cmd.asset_id, - updated_by: cmd.user_id, - timestamp: DateTimeStamp::now(), - }).await?; + self.event_pub + .publish(DomainEvent::MetadataUpdated { + asset_id: cmd.asset_id, + updated_by: cmd.user_id, + timestamp: DateTimeStamp::now(), + }) + .await?; Ok(metadata) } diff --git a/crates/application/src/catalog/mod.rs b/crates/application/src/catalog/mod.rs index 3c2dbf2..ce7ab77 100644 --- a/crates/application/src/catalog/mod.rs +++ b/crates/application/src/catalog/mod.rs @@ -3,5 +3,5 @@ pub mod queries; pub use commands::register_asset::{RegisterAssetCommand, RegisterAssetHandler}; pub use commands::update_metadata::{UpdateMetadataCommand, UpdateMetadataHandler}; -pub use queries::get_timeline::{GetTimelineQuery, GetTimelineHandler}; -pub use queries::get_asset::{GetAssetQuery, GetAssetHandler}; +pub use queries::get_asset::{GetAssetHandler, GetAssetQuery}; +pub use queries::get_timeline::{GetTimelineHandler, GetTimelineQuery}; diff --git a/crates/application/src/catalog/queries/get_asset.rs b/crates/application/src/catalog/queries/get_asset.rs index 8dfb29d..09fbdd2 100644 --- a/crates/application/src/catalog/queries/get_asset.rs +++ b/crates/application/src/catalog/queries/get_asset.rs @@ -1,11 +1,11 @@ -use std::sync::Arc; use domain::{ catalog::entities::Asset, catalog::services::resolve_metadata, errors::DomainError, - ports::{AssetRepository, AssetMetadataRepository}, + ports::{AssetMetadataRepository, AssetRepository}, value_objects::{StructuredData, SystemId}, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct GetAssetQuery { @@ -22,11 +22,20 @@ impl GetAssetHandler { asset_repo: Arc, metadata_repo: Arc, ) -> Self { - Self { asset_repo, metadata_repo } + Self { + asset_repo, + metadata_repo, + } } - pub async fn execute(&self, query: GetAssetQuery) -> Result<(Asset, StructuredData), DomainError> { - let asset = self.asset_repo.find_by_id(&query.asset_id).await? + pub async fn execute( + &self, + query: GetAssetQuery, + ) -> Result<(Asset, StructuredData), DomainError> { + let asset = self + .asset_repo + .find_by_id(&query.asset_id) + .await? .ok_or_else(|| DomainError::NotFound(format!("Asset {} not found", query.asset_id)))?; let layers = self.metadata_repo.find_by_asset(&asset.asset_id).await?; diff --git a/crates/application/src/catalog/queries/get_timeline.rs b/crates/application/src/catalog/queries/get_timeline.rs index 35872b8..dea02f1 100644 --- a/crates/application/src/catalog/queries/get_timeline.rs +++ b/crates/application/src/catalog/queries/get_timeline.rs @@ -1,11 +1,11 @@ -use std::sync::Arc; use domain::{ catalog::entities::Asset, catalog::services::resolve_metadata, errors::DomainError, - ports::{AssetRepository, AssetMetadataRepository}, + ports::{AssetMetadataRepository, AssetRepository}, value_objects::{StructuredData, SystemId}, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct GetTimelineQuery { @@ -24,11 +24,20 @@ impl GetTimelineHandler { asset_repo: Arc, metadata_repo: Arc, ) -> Self { - Self { asset_repo, metadata_repo } + Self { + asset_repo, + metadata_repo, + } } - pub async fn execute(&self, query: GetTimelineQuery) -> Result, DomainError> { - let assets = self.asset_repo.find_by_owner(&query.owner_id, query.limit, query.offset).await?; + pub async fn execute( + &self, + query: GetTimelineQuery, + ) -> Result, DomainError> { + let assets = self + .asset_repo + .find_by_owner(&query.owner_id, query.limit, query.offset) + .await?; let mut results = Vec::with_capacity(assets.len()); for asset in assets { diff --git a/crates/application/src/catalog/queries/mod.rs b/crates/application/src/catalog/queries/mod.rs index ebcc54e..917e934 100644 --- a/crates/application/src/catalog/queries/mod.rs +++ b/crates/application/src/catalog/queries/mod.rs @@ -1,2 +1,2 @@ -pub mod get_timeline; pub mod get_asset; +pub mod get_timeline; diff --git a/crates/application/src/identity/commands/login_user.rs b/crates/application/src/identity/commands/login_user.rs index 69676e5..34b7b85 100644 --- a/crates/application/src/identity/commands/login_user.rs +++ b/crates/application/src/identity/commands/login_user.rs @@ -1,10 +1,10 @@ -use std::sync::Arc; use domain::{ entities::User, errors::DomainError, ports::{PasswordHasher, TokenIssuer, UserRepository}, value_objects::Email, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct LoginUserCommand { @@ -24,14 +24,24 @@ impl LoginUserHandler { hasher: Arc, issuer: Arc, ) -> Self { - Self { repo, hasher, issuer } + Self { + repo, + hasher, + issuer, + } } pub async fn execute(&self, cmd: LoginUserCommand) -> Result<(User, String), DomainError> { let email = Email::new(&cmd.email)?; - let user = self.repo.find_by_email(&email).await? + let user = self + .repo + .find_by_email(&email) + .await? .ok_or_else(|| DomainError::Unauthorized("Invalid credentials".to_string()))?; - let valid = self.hasher.verify(&cmd.password, &user.password_hash).await?; + let valid = self + .hasher + .verify(&cmd.password, &user.password_hash) + .await?; if !valid { return Err(DomainError::Unauthorized("Invalid credentials".to_string())); } diff --git a/crates/application/src/identity/commands/mod.rs b/crates/application/src/identity/commands/mod.rs index 4ece247..2e2b705 100644 --- a/crates/application/src/identity/commands/mod.rs +++ b/crates/application/src/identity/commands/mod.rs @@ -1,5 +1,5 @@ -pub mod register_user; pub mod login_user; +pub mod register_user; -pub use register_user::{RegisterUserCommand, RegisterUserHandler}; pub use login_user::{LoginUserCommand, LoginUserHandler}; +pub use register_user::{RegisterUserCommand, RegisterUserHandler}; diff --git a/crates/application/src/identity/commands/register_user.rs b/crates/application/src/identity/commands/register_user.rs index 41e01c2..e2ec439 100644 --- a/crates/application/src/identity/commands/register_user.rs +++ b/crates/application/src/identity/commands/register_user.rs @@ -1,10 +1,10 @@ -use std::sync::Arc; use domain::{ entities::User, errors::DomainError, ports::{PasswordHasher, UserRepository}, value_objects::Email, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct RegisterUserCommand { @@ -25,17 +25,32 @@ impl RegisterUserHandler { pub async fn execute(&self, cmd: RegisterUserCommand) -> Result { if cmd.username.is_empty() { - return Err(DomainError::Validation("Username must not be empty".to_string())); + return Err(DomainError::Validation( + "Username must not be empty".to_string(), + )); } if cmd.password.len() < 8 { - return Err(DomainError::Validation("Password must be at least 8 characters".to_string())); + return Err(DomainError::Validation( + "Password must be at least 8 characters".to_string(), + )); } let email = Email::new(&cmd.email)?; if self.user_repo.find_by_email(&email).await?.is_some() { - return Err(DomainError::Conflict(format!("Email {} is already registered", email.as_str()))); + return Err(DomainError::Conflict(format!( + "Email {} is already registered", + email.as_str() + ))); } - if self.user_repo.find_by_username(&cmd.username).await?.is_some() { - return Err(DomainError::Conflict(format!("Username {} is already taken", cmd.username))); + if self + .user_repo + .find_by_username(&cmd.username) + .await? + .is_some() + { + return Err(DomainError::Conflict(format!( + "Username {} is already taken", + cmd.username + ))); } let hash = self.hasher.hash(&cmd.password).await?; let user = User::new(&cmd.username, email, hash); diff --git a/crates/application/src/identity/mod.rs b/crates/application/src/identity/mod.rs index 00c5710..1e5adf2 100644 --- a/crates/application/src/identity/mod.rs +++ b/crates/application/src/identity/mod.rs @@ -1,5 +1,5 @@ pub mod commands; pub mod queries; -pub use commands::{RegisterUserCommand, RegisterUserHandler, LoginUserCommand, LoginUserHandler}; -pub use queries::{GetProfileQuery, GetProfileHandler}; +pub use commands::{LoginUserCommand, LoginUserHandler, RegisterUserCommand, RegisterUserHandler}; +pub use queries::{GetProfileHandler, GetProfileQuery}; diff --git a/crates/application/src/identity/queries/get_profile.rs b/crates/application/src/identity/queries/get_profile.rs index 2d7bdce..7973507 100644 --- a/crates/application/src/identity/queries/get_profile.rs +++ b/crates/application/src/identity/queries/get_profile.rs @@ -1,5 +1,5 @@ -use std::sync::Arc; use domain::{entities::User, errors::DomainError, ports::UserRepository, value_objects::SystemId}; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct GetProfileQuery { @@ -16,7 +16,9 @@ impl GetProfileHandler { } pub async fn execute(&self, query: GetProfileQuery) -> Result { - self.repo.find_by_id(&query.user_id).await? + self.repo + .find_by_id(&query.user_id) + .await? .ok_or_else(|| DomainError::NotFound(format!("User {} not found", query.user_id))) } } diff --git a/crates/application/src/identity/queries/mod.rs b/crates/application/src/identity/queries/mod.rs index 8855048..6773ee3 100644 --- a/crates/application/src/identity/queries/mod.rs +++ b/crates/application/src/identity/queries/mod.rs @@ -1,3 +1,3 @@ pub mod get_profile; -pub use get_profile::{GetProfileQuery, GetProfileHandler}; +pub use get_profile::{GetProfileHandler, GetProfileQuery}; diff --git a/crates/application/src/lib.rs b/crates/application/src/lib.rs index 514d8fe..fc352d7 100644 --- a/crates/application/src/lib.rs +++ b/crates/application/src/lib.rs @@ -1,8 +1,8 @@ +pub mod catalog; pub mod identity; pub mod organization; -pub mod storage; -pub mod catalog; +pub mod processing; pub mod sharing; pub mod sidecar; -pub mod processing; +pub mod storage; pub mod testing; diff --git a/crates/application/src/organization/commands/create_album.rs b/crates/application/src/organization/commands/create_album.rs index 67b2e0c..f4b9baa 100644 --- a/crates/application/src/organization/commands/create_album.rs +++ b/crates/application/src/organization/commands/create_album.rs @@ -1,10 +1,7 @@ -use std::sync::Arc; use domain::{ - entities::Album, - errors::DomainError, - ports::AlbumRepository, - value_objects::SystemId, + entities::Album, errors::DomainError, ports::AlbumRepository, value_objects::SystemId, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct CreateAlbumCommand { @@ -23,7 +20,9 @@ impl CreateAlbumHandler { pub async fn execute(&self, cmd: CreateAlbumCommand) -> Result { if cmd.title.is_empty() { - return Err(DomainError::Validation("Album title must not be empty".to_string())); + return Err(DomainError::Validation( + "Album title must not be empty".to_string(), + )); } let album = Album::new(&cmd.title, cmd.creator_id); self.album_repo.save(&album).await?; diff --git a/crates/application/src/organization/commands/manage_album_entries.rs b/crates/application/src/organization/commands/manage_album_entries.rs index a585fce..75fa96b 100644 --- a/crates/application/src/organization/commands/manage_album_entries.rs +++ b/crates/application/src/organization/commands/manage_album_entries.rs @@ -1,10 +1,7 @@ -use std::sync::Arc; use domain::{ - entities::Album, - errors::DomainError, - ports::AlbumRepository, - value_objects::SystemId, + entities::Album, errors::DomainError, ports::AlbumRepository, value_objects::SystemId, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub enum AlbumAction { @@ -29,7 +26,10 @@ impl ManageAlbumEntriesHandler { } pub async fn execute(&self, cmd: ManageAlbumEntriesCommand) -> Result { - let mut album = self.album_repo.find_by_id(&cmd.album_id).await? + let mut album = self + .album_repo + .find_by_id(&cmd.album_id) + .await? .ok_or_else(|| DomainError::NotFound(format!("Album {} not found", cmd.album_id)))?; match cmd.action { diff --git a/crates/application/src/organization/commands/tag_asset.rs b/crates/application/src/organization/commands/tag_asset.rs index c282042..bf24aee 100644 --- a/crates/application/src/organization/commands/tag_asset.rs +++ b/crates/application/src/organization/commands/tag_asset.rs @@ -1,10 +1,10 @@ -use std::sync::Arc; use domain::{ entities::{AssetTag, Tag}, errors::DomainError, ports::{AssetRepository, TagRepository}, value_objects::SystemId, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct TagAssetCommand { @@ -20,11 +20,16 @@ pub struct TagAssetHandler { impl TagAssetHandler { pub fn new(asset_repo: Arc, tag_repo: Arc) -> Self { - Self { asset_repo, tag_repo } + Self { + asset_repo, + tag_repo, + } } pub async fn execute(&self, cmd: TagAssetCommand) -> Result<(Tag, AssetTag), DomainError> { - self.asset_repo.find_by_id(&cmd.asset_id).await? + self.asset_repo + .find_by_id(&cmd.asset_id) + .await? .ok_or_else(|| DomainError::NotFound(format!("Asset {} not found", cmd.asset_id)))?; let tag = match self.tag_repo.find_by_name(&cmd.tag_name).await? { diff --git a/crates/application/src/organization/mod.rs b/crates/application/src/organization/mod.rs index 463b68b..38f701e 100644 --- a/crates/application/src/organization/mod.rs +++ b/crates/application/src/organization/mod.rs @@ -1,7 +1,7 @@ pub mod commands; pub mod queries; -pub use commands::{CreateAlbumCommand, CreateAlbumHandler}; pub use commands::{AlbumAction, ManageAlbumEntriesCommand, ManageAlbumEntriesHandler}; +pub use commands::{CreateAlbumCommand, CreateAlbumHandler}; pub use commands::{TagAssetCommand, TagAssetHandler}; -pub use queries::get_album::{GetAlbumQuery, GetAlbumHandler}; +pub use queries::get_album::{GetAlbumHandler, GetAlbumQuery}; diff --git a/crates/application/src/organization/queries/get_album.rs b/crates/application/src/organization/queries/get_album.rs index 7d0a6c2..6c1e32c 100644 --- a/crates/application/src/organization/queries/get_album.rs +++ b/crates/application/src/organization/queries/get_album.rs @@ -1,10 +1,7 @@ -use std::sync::Arc; use domain::{ - entities::Album, - errors::DomainError, - ports::AlbumRepository, - value_objects::SystemId, + entities::Album, errors::DomainError, ports::AlbumRepository, value_objects::SystemId, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct GetAlbumQuery { @@ -21,7 +18,9 @@ impl GetAlbumHandler { } pub async fn execute(&self, query: GetAlbumQuery) -> Result { - self.album_repo.find_by_id(&query.album_id).await? + self.album_repo + .find_by_id(&query.album_id) + .await? .ok_or_else(|| DomainError::NotFound(format!("Album {} not found", query.album_id))) } } diff --git a/crates/application/src/processing/commands/complete_job.rs b/crates/application/src/processing/commands/complete_job.rs index 141dd94..a208d16 100644 --- a/crates/application/src/processing/commands/complete_job.rs +++ b/crates/application/src/processing/commands/complete_job.rs @@ -1,4 +1,3 @@ -use std::sync::Arc; use domain::{ entities::Job, errors::DomainError, @@ -6,6 +5,7 @@ use domain::{ ports::{EventPublisher, JobBatchRepository, JobRepository}, value_objects::{DateTimeStamp, StructuredData, SystemId}, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct CompleteJobCommand { @@ -25,24 +25,35 @@ impl CompleteJobHandler { batch_repo: Arc, event_pub: Arc, ) -> Self { - Self { job_repo, batch_repo, event_pub } + Self { + job_repo, + batch_repo, + event_pub, + } } pub async fn execute(&self, cmd: CompleteJobCommand) -> Result { - let mut job = self.job_repo.find_by_id(&cmd.job_id).await? + let mut job = self + .job_repo + .find_by_id(&cmd.job_id) + .await? .ok_or_else(|| DomainError::NotFound(format!("Job {} not found", cmd.job_id)))?; job.complete(cmd.result); self.job_repo.save(&job).await?; if let Some(ref batch_id) = job.batch_id { - let mut batch = self.batch_repo.find_by_id(batch_id).await? - .ok_or_else(|| DomainError::NotFound(format!("Batch {} not found", batch_id)))?; + let mut batch = + self.batch_repo.find_by_id(batch_id).await?.ok_or_else(|| { + DomainError::NotFound(format!("Batch {} not found", batch_id)) + })?; batch.record_completion(); self.batch_repo.save(&batch).await?; } - self.event_pub.publish(DomainEvent::JobCompleted { - job_id: job.job_id, - timestamp: DateTimeStamp::now(), - }).await?; + self.event_pub + .publish(DomainEvent::JobCompleted { + job_id: job.job_id, + timestamp: DateTimeStamp::now(), + }) + .await?; Ok(job) } } diff --git a/crates/application/src/processing/commands/configure_pipeline.rs b/crates/application/src/processing/commands/configure_pipeline.rs index b81bf8c..bc2e9ee 100644 --- a/crates/application/src/processing/commands/configure_pipeline.rs +++ b/crates/application/src/processing/commands/configure_pipeline.rs @@ -1,10 +1,10 @@ -use std::sync::Arc; use domain::{ entities::ProcessingPipeline, errors::DomainError, ports::{PipelineRepository, PluginRepository}, value_objects::{StructuredData, SystemId}, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct PipelineStepConfig { @@ -28,13 +28,23 @@ impl ConfigurePipelineHandler { pipeline_repo: Arc, plugin_repo: Arc, ) -> Self { - Self { pipeline_repo, plugin_repo } + Self { + pipeline_repo, + plugin_repo, + } } - pub async fn execute(&self, cmd: ConfigurePipelineCommand) -> Result { + pub async fn execute( + &self, + cmd: ConfigurePipelineCommand, + ) -> Result { for step in &cmd.steps { - self.plugin_repo.find_by_id(&step.plugin_id).await? - .ok_or_else(|| DomainError::NotFound(format!("Plugin {} not found", step.plugin_id)))?; + self.plugin_repo + .find_by_id(&step.plugin_id) + .await? + .ok_or_else(|| { + DomainError::NotFound(format!("Plugin {} not found", step.plugin_id)) + })?; } let mut pipeline = ProcessingPipeline::new(cmd.trigger_event); for step in cmd.steps { diff --git a/crates/application/src/processing/commands/enqueue_job.rs b/crates/application/src/processing/commands/enqueue_job.rs index 4d957d9..9e6b4bc 100644 --- a/crates/application/src/processing/commands/enqueue_job.rs +++ b/crates/application/src/processing/commands/enqueue_job.rs @@ -1,4 +1,3 @@ -use std::sync::Arc; use domain::{ entities::{Job, JobType}, errors::DomainError, @@ -6,6 +5,7 @@ use domain::{ ports::{EventPublisher, JobRepository}, value_objects::{DateTimeStamp, StructuredData, SystemId}, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct EnqueueJobCommand { @@ -23,7 +23,10 @@ pub struct EnqueueJobHandler { impl EnqueueJobHandler { pub fn new(job_repo: Arc, event_pub: Arc) -> Self { - Self { job_repo, event_pub } + Self { + job_repo, + event_pub, + } } pub async fn execute(&self, cmd: EnqueueJobCommand) -> Result { @@ -35,11 +38,13 @@ impl EnqueueJobHandler { job = job.with_batch(id); } self.job_repo.save(&job).await?; - self.event_pub.publish(DomainEvent::JobEnqueued { - job_id: job.job_id, - job_type: format!("{:?}", cmd.job_type), - timestamp: DateTimeStamp::now(), - }).await?; + self.event_pub + .publish(DomainEvent::JobEnqueued { + job_id: job.job_id, + job_type: format!("{:?}", cmd.job_type), + timestamp: DateTimeStamp::now(), + }) + .await?; Ok(job) } } diff --git a/crates/application/src/processing/commands/fail_job.rs b/crates/application/src/processing/commands/fail_job.rs index a1d3875..e3c3c30 100644 --- a/crates/application/src/processing/commands/fail_job.rs +++ b/crates/application/src/processing/commands/fail_job.rs @@ -1,4 +1,3 @@ -use std::sync::Arc; use domain::{ entities::{Job, JobStatus}, errors::DomainError, @@ -6,6 +5,7 @@ use domain::{ ports::{EventPublisher, JobBatchRepository, JobRepository}, value_objects::{DateTimeStamp, SystemId}, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct FailJobCommand { @@ -25,32 +25,44 @@ impl FailJobHandler { batch_repo: Arc, event_pub: Arc, ) -> Self { - Self { job_repo, batch_repo, event_pub } + Self { + job_repo, + batch_repo, + event_pub, + } } pub async fn execute(&self, cmd: FailJobCommand) -> Result { - let mut job = self.job_repo.find_by_id(&cmd.job_id).await? + let mut job = self + .job_repo + .find_by_id(&cmd.job_id) + .await? .ok_or_else(|| DomainError::NotFound(format!("Job {} not found", cmd.job_id)))?; job.fail(&cmd.error); self.job_repo.save(&job).await?; if job.status == JobStatus::Failed { if let Some(ref batch_id) = job.batch_id { - let mut batch = self.batch_repo.find_by_id(batch_id).await? - .ok_or_else(|| DomainError::NotFound(format!("Batch {} not found", batch_id)))?; + let mut batch = self.batch_repo.find_by_id(batch_id).await?.ok_or_else(|| { + DomainError::NotFound(format!("Batch {} not found", batch_id)) + })?; batch.record_failure(); self.batch_repo.save(&batch).await?; } - self.event_pub.publish(DomainEvent::JobFailed { - job_id: job.job_id, - error: cmd.error, - timestamp: DateTimeStamp::now(), - }).await?; + self.event_pub + .publish(DomainEvent::JobFailed { + job_id: job.job_id, + error: cmd.error, + timestamp: DateTimeStamp::now(), + }) + .await?; } else if job.status == JobStatus::Queued { - self.event_pub.publish(DomainEvent::JobEnqueued { - job_id: job.job_id, - job_type: format!("{:?}", job.job_type), - timestamp: DateTimeStamp::now(), - }).await?; + self.event_pub + .publish(DomainEvent::JobEnqueued { + job_id: job.job_id, + job_type: format!("{:?}", job.job_type), + timestamp: DateTimeStamp::now(), + }) + .await?; } Ok(job) } diff --git a/crates/application/src/processing/commands/manage_plugin.rs b/crates/application/src/processing/commands/manage_plugin.rs index 60dd21e..71a78bd 100644 --- a/crates/application/src/processing/commands/manage_plugin.rs +++ b/crates/application/src/processing/commands/manage_plugin.rs @@ -1,10 +1,10 @@ -use std::sync::Arc; use domain::{ entities::{Plugin, PluginType}, errors::DomainError, ports::PluginRepository, value_objects::{StructuredData, SystemId}, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub enum PluginAction { @@ -34,25 +34,37 @@ impl ManagePluginHandler { pub async fn execute(&self, cmd: ManagePluginCommand) -> Result { match cmd.action { - PluginAction::Create { name, plugin_type, config } => { + PluginAction::Create { + name, + plugin_type, + config, + } => { let mut plugin = Plugin::new(name, plugin_type); plugin.configuration = config; self.plugin_repo.save(&plugin).await?; Ok(plugin) } PluginAction::Enable => { - let id = cmd.plugin_id - .ok_or_else(|| DomainError::Validation("plugin_id required for Enable".into()))?; - let mut plugin = self.plugin_repo.find_by_id(&id).await? + let id = cmd.plugin_id.ok_or_else(|| { + DomainError::Validation("plugin_id required for Enable".into()) + })?; + let mut plugin = self + .plugin_repo + .find_by_id(&id) + .await? .ok_or_else(|| DomainError::NotFound(format!("Plugin {} not found", id)))?; plugin.enable(); self.plugin_repo.save(&plugin).await?; Ok(plugin) } PluginAction::Disable => { - let id = cmd.plugin_id - .ok_or_else(|| DomainError::Validation("plugin_id required for Disable".into()))?; - let mut plugin = self.plugin_repo.find_by_id(&id).await? + let id = cmd.plugin_id.ok_or_else(|| { + DomainError::Validation("plugin_id required for Disable".into()) + })?; + let mut plugin = self + .plugin_repo + .find_by_id(&id) + .await? .ok_or_else(|| DomainError::NotFound(format!("Plugin {} not found", id)))?; plugin.disable(); self.plugin_repo.save(&plugin).await?; diff --git a/crates/application/src/processing/commands/mod.rs b/crates/application/src/processing/commands/mod.rs index 9c2ae73..398ccc0 100644 --- a/crates/application/src/processing/commands/mod.rs +++ b/crates/application/src/processing/commands/mod.rs @@ -1,6 +1,6 @@ -pub mod enqueue_job; -pub mod start_job; pub mod complete_job; +pub mod configure_pipeline; +pub mod enqueue_job; pub mod fail_job; pub mod manage_plugin; -pub mod configure_pipeline; +pub mod start_job; diff --git a/crates/application/src/processing/commands/start_job.rs b/crates/application/src/processing/commands/start_job.rs index 8b8dffa..d5f0298 100644 --- a/crates/application/src/processing/commands/start_job.rs +++ b/crates/application/src/processing/commands/start_job.rs @@ -1,10 +1,5 @@ +use domain::{entities::Job, errors::DomainError, ports::JobRepository, value_objects::SystemId}; use std::sync::Arc; -use domain::{ - entities::Job, - errors::DomainError, - ports::JobRepository, - value_objects::SystemId, -}; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct StartJobCommand { @@ -21,7 +16,10 @@ impl StartJobHandler { } pub async fn execute(&self, cmd: StartJobCommand) -> Result { - let mut job = self.job_repo.find_by_id(&cmd.job_id).await? + let mut job = self + .job_repo + .find_by_id(&cmd.job_id) + .await? .ok_or_else(|| DomainError::NotFound(format!("Job {} not found", cmd.job_id)))?; job.start()?; self.job_repo.save(&job).await?; diff --git a/crates/application/src/processing/mod.rs b/crates/application/src/processing/mod.rs index 0c8495a..0372381 100644 --- a/crates/application/src/processing/mod.rs +++ b/crates/application/src/processing/mod.rs @@ -1,10 +1,14 @@ pub mod commands; pub mod queries; -pub use commands::enqueue_job::{EnqueueJobCommand, EnqueueJobHandler}; -pub use commands::start_job::{StartJobCommand, StartJobHandler}; pub use commands::complete_job::{CompleteJobCommand, CompleteJobHandler}; +pub use commands::configure_pipeline::{ + ConfigurePipelineCommand, ConfigurePipelineHandler, PipelineStepConfig, +}; +pub use commands::enqueue_job::{EnqueueJobCommand, EnqueueJobHandler}; pub use commands::fail_job::{FailJobCommand, FailJobHandler}; pub use commands::manage_plugin::{ManagePluginCommand, ManagePluginHandler, PluginAction}; -pub use commands::configure_pipeline::{ConfigurePipelineCommand, ConfigurePipelineHandler, PipelineStepConfig}; -pub use queries::report_batch_progress::{ReportBatchProgressQuery, ReportBatchProgressHandler, BatchProgress}; +pub use commands::start_job::{StartJobCommand, StartJobHandler}; +pub use queries::report_batch_progress::{ + BatchProgress, ReportBatchProgressHandler, ReportBatchProgressQuery, +}; diff --git a/crates/application/src/processing/queries/report_batch_progress.rs b/crates/application/src/processing/queries/report_batch_progress.rs index 4b22924..1f72900 100644 --- a/crates/application/src/processing/queries/report_batch_progress.rs +++ b/crates/application/src/processing/queries/report_batch_progress.rs @@ -1,10 +1,10 @@ -use std::sync::Arc; use domain::{ entities::{Job, JobBatch}, errors::DomainError, ports::{JobBatchRepository, JobRepository}, value_objects::SystemId, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct ReportBatchProgressQuery { @@ -24,11 +24,20 @@ pub struct ReportBatchProgressHandler { impl ReportBatchProgressHandler { pub fn new(batch_repo: Arc, job_repo: Arc) -> Self { - Self { batch_repo, job_repo } + Self { + batch_repo, + job_repo, + } } - pub async fn execute(&self, query: ReportBatchProgressQuery) -> Result { - let batch = self.batch_repo.find_by_id(&query.batch_id).await? + pub async fn execute( + &self, + query: ReportBatchProgressQuery, + ) -> Result { + let batch = self + .batch_repo + .find_by_id(&query.batch_id) + .await? .ok_or_else(|| DomainError::NotFound(format!("Batch {} not found", query.batch_id)))?; let jobs = self.job_repo.find_by_batch(&query.batch_id).await?; Ok(BatchProgress { batch, jobs }) diff --git a/crates/application/src/sharing/commands/generate_share_link.rs b/crates/application/src/sharing/commands/generate_share_link.rs index 01741a7..623ab0b 100644 --- a/crates/application/src/sharing/commands/generate_share_link.rs +++ b/crates/application/src/sharing/commands/generate_share_link.rs @@ -1,10 +1,10 @@ -use std::sync::Arc; use domain::{ entities::{LinkAccessLevel, ScopeType, ShareLink, ShareScope, ShareableType}, errors::DomainError, ports::ShareRepository, value_objects::{DateTimeStamp, SystemId}, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct GenerateShareLinkCommand { @@ -25,8 +25,16 @@ impl GenerateShareLinkHandler { Self { share_repo } } - pub async fn execute(&self, cmd: GenerateShareLinkCommand) -> Result<(ShareScope, ShareLink), DomainError> { - let scope = ShareScope::new(ScopeType::Link, cmd.shareable_type, cmd.shareable_id, cmd.created_by); + pub async fn execute( + &self, + cmd: GenerateShareLinkCommand, + ) -> Result<(ShareScope, ShareLink), DomainError> { + let scope = ShareScope::new( + ScopeType::Link, + cmd.shareable_type, + cmd.shareable_id, + cmd.created_by, + ); let token = uuid::Uuid::new_v4().to_string(); let mut link = ShareLink::new(scope.scope_id, token, cmd.access_level); link.expires_at = cmd.expires_at; diff --git a/crates/application/src/sharing/commands/mod.rs b/crates/application/src/sharing/commands/mod.rs index 5f0428e..0e9f8ad 100644 --- a/crates/application/src/sharing/commands/mod.rs +++ b/crates/application/src/sharing/commands/mod.rs @@ -1,7 +1,7 @@ -pub mod share_resource; pub mod generate_share_link; pub mod revoke_share; +pub mod share_resource; -pub use share_resource::{ShareResourceCommand, ShareResourceHandler}; pub use generate_share_link::{GenerateShareLinkCommand, GenerateShareLinkHandler}; pub use revoke_share::{RevokeShareCommand, RevokeShareHandler}; +pub use share_resource::{ShareResourceCommand, ShareResourceHandler}; diff --git a/crates/application/src/sharing/commands/revoke_share.rs b/crates/application/src/sharing/commands/revoke_share.rs index 85c991e..41261ea 100644 --- a/crates/application/src/sharing/commands/revoke_share.rs +++ b/crates/application/src/sharing/commands/revoke_share.rs @@ -1,10 +1,10 @@ -use std::sync::Arc; use domain::{ errors::DomainError, events::DomainEvent, ports::{EventPublisher, ShareRepository}, value_objects::{DateTimeStamp, SystemId}, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct RevokeShareCommand { @@ -19,20 +19,29 @@ pub struct RevokeShareHandler { impl RevokeShareHandler { pub fn new(share_repo: Arc, event_pub: Arc) -> Self { - Self { share_repo, event_pub } + Self { + share_repo, + event_pub, + } } pub async fn execute(&self, cmd: RevokeShareCommand) -> Result<(), DomainError> { - self.share_repo.find_scope_by_id(&cmd.scope_id).await? - .ok_or_else(|| DomainError::NotFound(format!("Share scope {} not found", cmd.scope_id)))?; + self.share_repo + .find_scope_by_id(&cmd.scope_id) + .await? + .ok_or_else(|| { + DomainError::NotFound(format!("Share scope {} not found", cmd.scope_id)) + })?; self.share_repo.delete_scope(&cmd.scope_id).await?; - self.event_pub.publish(DomainEvent::ShareRevoked { - scope_id: cmd.scope_id, - revoked_by: cmd.revoked_by, - timestamp: DateTimeStamp::now(), - }).await?; + self.event_pub + .publish(DomainEvent::ShareRevoked { + scope_id: cmd.scope_id, + revoked_by: cmd.revoked_by, + timestamp: DateTimeStamp::now(), + }) + .await?; Ok(()) } diff --git a/crates/application/src/sharing/commands/share_resource.rs b/crates/application/src/sharing/commands/share_resource.rs index 56d6f47..1771251 100644 --- a/crates/application/src/sharing/commands/share_resource.rs +++ b/crates/application/src/sharing/commands/share_resource.rs @@ -1,4 +1,3 @@ -use std::sync::Arc; use domain::{ entities::{ScopeType, ShareScope, ShareTarget, ShareableType, TargetType}, errors::DomainError, @@ -6,6 +5,7 @@ use domain::{ ports::{EventPublisher, ShareRepository}, value_objects::{DateTimeStamp, SystemId}, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct ShareResourceCommand { @@ -24,27 +24,40 @@ pub struct ShareResourceHandler { impl ShareResourceHandler { pub fn new(share_repo: Arc, event_pub: Arc) -> Self { - Self { share_repo, event_pub } + Self { + share_repo, + event_pub, + } } - pub async fn execute(&self, cmd: ShareResourceCommand) -> Result<(ShareScope, ShareTarget), DomainError> { + pub async fn execute( + &self, + cmd: ShareResourceCommand, + ) -> Result<(ShareScope, ShareTarget), DomainError> { let scope_type = match cmd.target_type { TargetType::User => ScopeType::User, TargetType::Group => ScopeType::Group, }; - let scope = ShareScope::new(scope_type, cmd.shareable_type, cmd.shareable_id, cmd.created_by); + let scope = ShareScope::new( + scope_type, + cmd.shareable_type, + cmd.shareable_id, + cmd.created_by, + ); let target = ShareTarget::new(scope.scope_id, cmd.target_type, cmd.target_id, cmd.role_id); self.share_repo.save_scope(&scope).await?; self.share_repo.save_target(&target).await?; - self.event_pub.publish(DomainEvent::ShareCreated { - scope_id: scope.scope_id, - shareable_id: cmd.shareable_id, - created_by: cmd.created_by, - timestamp: DateTimeStamp::now(), - }).await?; + self.event_pub + .publish(DomainEvent::ShareCreated { + scope_id: scope.scope_id, + shareable_id: cmd.shareable_id, + created_by: cmd.created_by, + timestamp: DateTimeStamp::now(), + }) + .await?; Ok((scope, target)) } diff --git a/crates/application/src/sharing/mod.rs b/crates/application/src/sharing/mod.rs index b619155..ee1a282 100644 --- a/crates/application/src/sharing/mod.rs +++ b/crates/application/src/sharing/mod.rs @@ -1,7 +1,7 @@ pub mod commands; pub mod queries; -pub use commands::{ShareResourceCommand, ShareResourceHandler}; pub use commands::{GenerateShareLinkCommand, GenerateShareLinkHandler}; pub use commands::{RevokeShareCommand, RevokeShareHandler}; -pub use queries::access_shared_resource::{AccessSharedResourceQuery, AccessSharedResourceHandler}; +pub use commands::{ShareResourceCommand, ShareResourceHandler}; +pub use queries::access_shared_resource::{AccessSharedResourceHandler, AccessSharedResourceQuery}; diff --git a/crates/application/src/sharing/queries/access_shared_resource.rs b/crates/application/src/sharing/queries/access_shared_resource.rs index d7cb64c..da991e3 100644 --- a/crates/application/src/sharing/queries/access_shared_resource.rs +++ b/crates/application/src/sharing/queries/access_shared_resource.rs @@ -1,9 +1,9 @@ -use std::sync::Arc; use domain::{ entities::{LinkAccessLevel, ShareScope}, errors::DomainError, ports::ShareRepository, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct AccessSharedResourceQuery { @@ -19,18 +19,29 @@ impl AccessSharedResourceHandler { Self { share_repo } } - pub async fn execute(&self, query: AccessSharedResourceQuery) -> Result<(ShareScope, LinkAccessLevel), DomainError> { - let mut link = self.share_repo.find_link_by_token(&query.token).await? + pub async fn execute( + &self, + query: AccessSharedResourceQuery, + ) -> Result<(ShareScope, LinkAccessLevel), DomainError> { + let mut link = self + .share_repo + .find_link_by_token(&query.token) + .await? .ok_or_else(|| DomainError::NotFound("Share link not found".to_string()))?; if !link.is_valid() { - return Err(DomainError::Forbidden("Link expired or exhausted".to_string())); + return Err(DomainError::Forbidden( + "Link expired or exhausted".to_string(), + )); } link.record_use(); self.share_repo.save_link(&link).await?; - let scope = self.share_repo.find_scope_by_id(&link.scope_id).await? + let scope = self + .share_repo + .find_scope_by_id(&link.scope_id) + .await? .ok_or_else(|| DomainError::NotFound("Share scope not found".to_string()))?; Ok((scope, link.access_level)) diff --git a/crates/application/src/sidecar/commands/detect_external_changes.rs b/crates/application/src/sidecar/commands/detect_external_changes.rs index 50e9990..a900dad 100644 --- a/crates/application/src/sidecar/commands/detect_external_changes.rs +++ b/crates/application/src/sidecar/commands/detect_external_changes.rs @@ -1,10 +1,10 @@ -use std::sync::Arc; +use crate::sidecar::hash_helper::hash_structured_data; use domain::{ entities::SyncStatus, errors::DomainError, ports::{SidecarRepository, SidecarWriterPort}, }; -use crate::sidecar::hash_helper::hash_structured_data; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct DetectExternalChangesCommand; @@ -19,7 +19,10 @@ impl DetectExternalChangesHandler { sidecar_repo: Arc, writer: Arc, ) -> Self { - Self { sidecar_repo, writer } + Self { + sidecar_repo, + writer, + } } pub async fn execute(&self, _cmd: DetectExternalChangesCommand) -> Result { diff --git a/crates/application/src/sidecar/commands/export_sidecar.rs b/crates/application/src/sidecar/commands/export_sidecar.rs index a6c9d92..796ade4 100644 --- a/crates/application/src/sidecar/commands/export_sidecar.rs +++ b/crates/application/src/sidecar/commands/export_sidecar.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use crate::sidecar::hash_helper::hash_structured_data; use domain::{ catalog::services::resolve_metadata, entities::SidecarRecord, @@ -6,7 +6,7 @@ use domain::{ ports::{AssetMetadataRepository, SidecarRepository, SidecarWriterPort}, value_objects::SystemId, }; -use crate::sidecar::hash_helper::hash_structured_data; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct ExportSidecarCommand { @@ -25,7 +25,11 @@ impl ExportSidecarHandler { sidecar_repo: Arc, writer: Arc, ) -> Self { - Self { metadata_repo, sidecar_repo, writer } + Self { + metadata_repo, + sidecar_repo, + writer, + } } pub async fn execute(&self, cmd: ExportSidecarCommand) -> Result { @@ -37,7 +41,9 @@ impl ExportSidecarHandler { None => SidecarRecord::new(cmd.asset_id, format!("sidecars/{}.xmp", cmd.asset_id)), }; - self.writer.write_sidecar(&resolved, &record.sidecar_storage_path).await?; + self.writer + .write_sidecar(&resolved, &record.sidecar_storage_path) + .await?; let hash = hash_structured_data(&resolved); record.mark_synced(hash); diff --git a/crates/application/src/sidecar/commands/full_export.rs b/crates/application/src/sidecar/commands/full_export.rs index cae8718..6e5d449 100644 --- a/crates/application/src/sidecar/commands/full_export.rs +++ b/crates/application/src/sidecar/commands/full_export.rs @@ -1,12 +1,12 @@ -use std::sync::Arc; +use crate::sidecar::hash_helper::hash_structured_data; use domain::{ catalog::services::resolve_metadata, entities::SidecarRecord, errors::DomainError, - ports::{AssetRepository, AssetMetadataRepository, SidecarRepository, SidecarWriterPort}, + ports::{AssetMetadataRepository, AssetRepository, SidecarRepository, SidecarWriterPort}, value_objects::SystemId, }; -use crate::sidecar::hash_helper::hash_structured_data; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct FullExportCommand { @@ -27,11 +27,19 @@ impl FullExportHandler { sidecar_repo: Arc, writer: Arc, ) -> Self { - Self { asset_repo, metadata_repo, sidecar_repo, writer } + Self { + asset_repo, + metadata_repo, + sidecar_repo, + writer, + } } pub async fn execute(&self, cmd: FullExportCommand) -> Result { - let assets = self.asset_repo.find_by_owner(&cmd.owner_id, u32::MAX, 0).await?; + let assets = self + .asset_repo + .find_by_owner(&cmd.owner_id, u32::MAX, 0) + .await?; let mut count = 0u32; for asset in &assets { @@ -40,10 +48,14 @@ impl FullExportHandler { let mut record = match self.sidecar_repo.find_by_asset(&asset.asset_id).await? { Some(r) => r, - None => SidecarRecord::new(asset.asset_id, format!("sidecars/{}.xmp", asset.asset_id)), + None => { + SidecarRecord::new(asset.asset_id, format!("sidecars/{}.xmp", asset.asset_id)) + } }; - self.writer.write_sidecar(&resolved, &record.sidecar_storage_path).await?; + self.writer + .write_sidecar(&resolved, &record.sidecar_storage_path) + .await?; let hash = hash_structured_data(&resolved); record.mark_synced(hash); self.sidecar_repo.save(&record).await?; diff --git a/crates/application/src/sidecar/commands/full_import.rs b/crates/application/src/sidecar/commands/full_import.rs index 31ead80..a8a2279 100644 --- a/crates/application/src/sidecar/commands/full_import.rs +++ b/crates/application/src/sidecar/commands/full_import.rs @@ -1,12 +1,12 @@ -use std::sync::Arc; +use crate::sidecar::hash_helper::hash_structured_data; use domain::{ catalog::entities::{AssetMetadata, MetadataSource}, entities::SidecarRecord, errors::DomainError, - ports::{AssetRepository, AssetMetadataRepository, SidecarRepository, SidecarWriterPort}, + ports::{AssetMetadataRepository, AssetRepository, SidecarRepository, SidecarWriterPort}, value_objects::SystemId, }; -use crate::sidecar::hash_helper::hash_structured_data; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct FullImportCommand { @@ -27,11 +27,19 @@ impl FullImportHandler { sidecar_repo: Arc, writer: Arc, ) -> Self { - Self { asset_repo, metadata_repo, sidecar_repo, writer } + Self { + asset_repo, + metadata_repo, + sidecar_repo, + writer, + } } pub async fn execute(&self, cmd: FullImportCommand) -> Result { - let assets = self.asset_repo.find_by_owner(&cmd.owner_id, u32::MAX, 0).await?; + let assets = self + .asset_repo + .find_by_owner(&cmd.owner_id, u32::MAX, 0) + .await?; let mut count = 0u32; for asset in &assets { @@ -45,7 +53,11 @@ impl FullImportHandler { match self.writer.read_sidecar(&record.sidecar_storage_path).await { Ok(data) => { - let metadata = AssetMetadata::new(asset.asset_id, MetadataSource::ExifExtracted, data.clone()); + let metadata = AssetMetadata::new( + asset.asset_id, + MetadataSource::ExifExtracted, + data.clone(), + ); self.metadata_repo.save(&metadata).await?; let hash = hash_structured_data(&data); let mut record = record; diff --git a/crates/application/src/sidecar/commands/import_sidecar.rs b/crates/application/src/sidecar/commands/import_sidecar.rs index 2d66ca7..d20b0c1 100644 --- a/crates/application/src/sidecar/commands/import_sidecar.rs +++ b/crates/application/src/sidecar/commands/import_sidecar.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use crate::sidecar::hash_helper::hash_structured_data; use domain::{ catalog::entities::{AssetMetadata, MetadataSource}, entities::SyncStatus, @@ -6,7 +6,7 @@ use domain::{ ports::{AssetMetadataRepository, SidecarRepository, SidecarWriterPort}, value_objects::SystemId, }; -use crate::sidecar::hash_helper::hash_structured_data; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct ImportSidecarCommand { @@ -25,21 +25,35 @@ impl ImportSidecarHandler { writer: Arc, metadata_repo: Arc, ) -> Self { - Self { sidecar_repo, writer, metadata_repo } + Self { + sidecar_repo, + writer, + metadata_repo, + } } pub async fn execute(&self, cmd: ImportSidecarCommand) -> 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)))?; + 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::PendingRead { - return Err(DomainError::Validation( - format!("Sidecar is not pending read (status: {:?})", record.sync_status), - )); + return Err(DomainError::Validation(format!( + "Sidecar is not pending read (status: {:?})", + record.sync_status + ))); } - let data = self.writer.read_sidecar(&record.sidecar_storage_path).await?; - let metadata = AssetMetadata::new(cmd.asset_id, MetadataSource::ExifExtracted, data.clone()); + 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); diff --git a/crates/application/src/sidecar/commands/mod.rs b/crates/application/src/sidecar/commands/mod.rs index f468171..3f7fc33 100644 --- a/crates/application/src/sidecar/commands/mod.rs +++ b/crates/application/src/sidecar/commands/mod.rs @@ -1,6 +1,6 @@ -pub mod export_sidecar; pub mod detect_external_changes; -pub mod import_sidecar; -pub mod resolve_conflict; +pub mod export_sidecar; pub mod full_export; pub mod full_import; +pub mod import_sidecar; +pub mod resolve_conflict; diff --git a/crates/application/src/sidecar/commands/resolve_conflict.rs b/crates/application/src/sidecar/commands/resolve_conflict.rs index fe2f8ef..9e781fe 100644 --- a/crates/application/src/sidecar/commands/resolve_conflict.rs +++ b/crates/application/src/sidecar/commands/resolve_conflict.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use crate::sidecar::hash_helper::hash_structured_data; use domain::{ catalog::entities::{AssetMetadata, MetadataSource}, catalog::services::resolve_metadata, @@ -7,7 +7,7 @@ use domain::{ ports::{AssetMetadataRepository, SidecarRepository, SidecarWriterPort}, value_objects::SystemId, }; -use crate::sidecar::hash_helper::hash_structured_data; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct ResolveConflictCommand { @@ -27,36 +27,54 @@ impl ResolveConflictHandler { writer: Arc, metadata_repo: Arc, ) -> Self { - Self { sidecar_repo, writer, metadata_repo } + 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)))?; + 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), - )); + 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?; + 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()); + 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())); + return Err(DomainError::Validation( + "Manual resolution required".to_string(), + )); } } diff --git a/crates/application/src/sidecar/hash_helper.rs b/crates/application/src/sidecar/hash_helper.rs index ac2847b..bd38db0 100644 --- a/crates/application/src/sidecar/hash_helper.rs +++ b/crates/application/src/sidecar/hash_helper.rs @@ -1,5 +1,5 @@ use domain::value_objects::{Checksum, StructuredData}; -use sha2::{Sha256, Digest}; +use sha2::{Digest, Sha256}; pub fn hash_structured_data(data: &StructuredData) -> Checksum { let json = serde_json::to_string(data).unwrap_or_default(); diff --git a/crates/application/src/sidecar/mod.rs b/crates/application/src/sidecar/mod.rs index 444b792..b5fcfc8 100644 --- a/crates/application/src/sidecar/mod.rs +++ b/crates/application/src/sidecar/mod.rs @@ -1,9 +1,11 @@ pub mod commands; pub mod hash_helper; +pub use commands::detect_external_changes::{ + DetectExternalChangesCommand, DetectExternalChangesHandler, +}; pub use commands::export_sidecar::{ExportSidecarCommand, ExportSidecarHandler}; -pub use commands::detect_external_changes::{DetectExternalChangesCommand, DetectExternalChangesHandler}; -pub use commands::import_sidecar::{ImportSidecarCommand, ImportSidecarHandler}; -pub use commands::resolve_conflict::{ResolveConflictCommand, ResolveConflictHandler}; pub use commands::full_export::{FullExportCommand, FullExportHandler}; pub use commands::full_import::{FullImportCommand, FullImportHandler}; +pub use commands::import_sidecar::{ImportSidecarCommand, ImportSidecarHandler}; +pub use commands::resolve_conflict::{ResolveConflictCommand, ResolveConflictHandler}; diff --git a/crates/application/src/storage/commands/ingest_asset.rs b/crates/application/src/storage/commands/ingest_asset.rs index eb63007..8993db3 100644 --- a/crates/application/src/storage/commands/ingest_asset.rs +++ b/crates/application/src/storage/commands/ingest_asset.rs @@ -1,15 +1,17 @@ -use std::sync::Arc; use bytes::Bytes; use domain::{ - entities::{Asset, AssetType, IngestSession, IngestStatus, SourceReference, UsageLedgerEntry, UsageType}, + entities::{ + Asset, AssetType, IngestSession, IngestStatus, SourceReference, UsageLedgerEntry, UsageType, + }, errors::DomainError, events::DomainEvent, ports::{ - AssetRepository, EventPublisher, FileStoragePort, - IngestSessionRepository, LibraryPathRepository, QuotaRepository, UsageLedgerRepository, + AssetRepository, EventPublisher, FileStoragePort, IngestSessionRepository, + LibraryPathRepository, QuotaRepository, UsageLedgerRepository, }, value_objects::{Checksum, DateTimeStamp, SystemId}, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct IngestAssetCommand { @@ -43,25 +45,53 @@ impl IngestAssetHandler { file_storage: Arc, event_pub: Arc, ) -> Self { - Self { ingest_repo, path_repo, quota_repo, ledger_repo, asset_repo, file_storage, event_pub } + Self { + ingest_repo, + path_repo, + quota_repo, + ledger_repo, + asset_repo, + file_storage, + event_pub, + } } - pub async fn execute(&self, cmd: IngestAssetCommand) -> Result<(Asset, IngestSession), DomainError> { + pub async fn execute( + &self, + cmd: IngestAssetCommand, + ) -> Result<(Asset, IngestSession), DomainError> { let checksum = Checksum::new(&cmd.checksum)?; - let path = self.path_repo.find_by_id(&cmd.target_path_id).await? - .ok_or_else(|| DomainError::NotFound(format!("Library path {} not found", cmd.target_path_id)))?; + let path = self + .path_repo + .find_by_id(&cmd.target_path_id) + .await? + .ok_or_else(|| { + DomainError::NotFound(format!("Library path {} not found", cmd.target_path_id)) + })?; if !path.is_ingest_destination { - return Err(DomainError::Validation("Target path is not an ingest destination".to_string())); + return Err(DomainError::Validation( + "Target path is not an ingest destination".to_string(), + )); } if let Some(quota) = self.quota_repo.find_by_owner(&cmd.uploader_id).await? { - let current = self.ledger_repo.sum_usage(&cmd.uploader_id, UsageType::StorageBytes, None).await?; - let result = domain::storage::services::check_quota("a, UsageType::StorageBytes, current, cmd.file_size); + let current = self + .ledger_repo + .sum_usage(&cmd.uploader_id, UsageType::StorageBytes, None) + .await?; + let result = domain::storage::services::check_quota( + "a, + UsageType::StorageBytes, + current, + cmd.file_size, + ); if !result.allowed { return Err(DomainError::QuotaExceeded(format!( - "Storage quota exceeded: {} / {} bytes", current + cmd.file_size, result.limit + "Storage quota exceeded: {} / {} bytes", + current + cmd.file_size, + result.limit ))); } } @@ -75,10 +105,16 @@ impl IngestAssetHandler { ); let storage_path = format!("{}/{}", path.relative_path, cmd.filename); - self.file_storage.store_file(&storage_path, cmd.data).await?; + self.file_storage + .store_file(&storage_path, cmd.data) + .await?; let mime_type = mime_type_from_filename(&cmd.filename); - let asset_type = if mime_type.starts_with("video") { AssetType::Video } else { AssetType::Image }; + let asset_type = if mime_type.starts_with("video") { + AssetType::Video + } else { + AssetType::Image + }; let asset = Asset::new( SourceReference { @@ -105,11 +141,13 @@ impl IngestAssetHandler { ); self.ledger_repo.record(&entry).await?; - self.event_pub.publish(DomainEvent::AssetIngested { - asset_id: asset.asset_id, - owner_user_id: cmd.uploader_id, - timestamp: DateTimeStamp::now(), - }).await?; + self.event_pub + .publish(DomainEvent::AssetIngested { + asset_id: asset.asset_id, + owner_user_id: cmd.uploader_id, + timestamp: DateTimeStamp::now(), + }) + .await?; Ok((asset, session)) } diff --git a/crates/application/src/storage/commands/mod.rs b/crates/application/src/storage/commands/mod.rs index 5e7f5ef..855b5c7 100644 --- a/crates/application/src/storage/commands/mod.rs +++ b/crates/application/src/storage/commands/mod.rs @@ -1,3 +1,3 @@ -pub mod register_volume; -pub mod register_library_path; pub mod ingest_asset; +pub mod register_library_path; +pub mod register_volume; diff --git a/crates/application/src/storage/commands/register_library_path.rs b/crates/application/src/storage/commands/register_library_path.rs index 1abca06..4f89159 100644 --- a/crates/application/src/storage/commands/register_library_path.rs +++ b/crates/application/src/storage/commands/register_library_path.rs @@ -1,10 +1,10 @@ -use std::sync::Arc; use domain::{ entities::LibraryPath, errors::DomainError, ports::{LibraryPathRepository, StorageVolumeRepository}, value_objects::SystemId, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct RegisterLibraryPathCommand { @@ -24,11 +24,19 @@ impl RegisterLibraryPathHandler { volume_repo: Arc, path_repo: Arc, ) -> Self { - Self { volume_repo, path_repo } + Self { + volume_repo, + path_repo, + } } - pub async fn execute(&self, cmd: RegisterLibraryPathCommand) -> Result { - self.volume_repo.find_by_id(&cmd.volume_id).await? + pub async fn execute( + &self, + cmd: RegisterLibraryPathCommand, + ) -> Result { + self.volume_repo + .find_by_id(&cmd.volume_id) + .await? .ok_or_else(|| DomainError::NotFound(format!("Volume {} not found", cmd.volume_id)))?; let path = LibraryPath::new_user_owned( diff --git a/crates/application/src/storage/commands/register_volume.rs b/crates/application/src/storage/commands/register_volume.rs index 5b6e1f6..03d0388 100644 --- a/crates/application/src/storage/commands/register_volume.rs +++ b/crates/application/src/storage/commands/register_volume.rs @@ -1,9 +1,5 @@ +use domain::{entities::StorageVolume, errors::DomainError, ports::StorageVolumeRepository}; use std::sync::Arc; -use domain::{ - entities::StorageVolume, - errors::DomainError, - ports::StorageVolumeRepository, -}; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct RegisterVolumeCommand { @@ -23,7 +19,9 @@ impl RegisterVolumeHandler { pub async fn execute(&self, cmd: RegisterVolumeCommand) -> Result { if cmd.volume_name.is_empty() { - return Err(DomainError::Validation("Volume name must not be empty".to_string())); + return Err(DomainError::Validation( + "Volume name must not be empty".to_string(), + )); } let volume = StorageVolume::new(cmd.volume_name, cmd.uri_prefix, cmd.is_writable); self.volume_repo.save(&volume).await?; diff --git a/crates/application/src/storage/mod.rs b/crates/application/src/storage/mod.rs index 1b7b2f0..9952e8c 100644 --- a/crates/application/src/storage/mod.rs +++ b/crates/application/src/storage/mod.rs @@ -1,7 +1,7 @@ pub mod commands; pub mod queries; -pub use commands::register_volume::{RegisterVolumeCommand, RegisterVolumeHandler}; -pub use commands::register_library_path::{RegisterLibraryPathCommand, RegisterLibraryPathHandler}; pub use commands::ingest_asset::{IngestAssetCommand, IngestAssetHandler}; -pub use queries::check_quota::{CheckQuotaQuery, CheckQuotaHandler}; +pub use commands::register_library_path::{RegisterLibraryPathCommand, RegisterLibraryPathHandler}; +pub use commands::register_volume::{RegisterVolumeCommand, RegisterVolumeHandler}; +pub use queries::check_quota::{CheckQuotaHandler, CheckQuotaQuery}; diff --git a/crates/application/src/storage/queries/check_quota.rs b/crates/application/src/storage/queries/check_quota.rs index 31faa27..a29cc52 100644 --- a/crates/application/src/storage/queries/check_quota.rs +++ b/crates/application/src/storage/queries/check_quota.rs @@ -1,11 +1,11 @@ -use std::sync::Arc; use domain::{ entities::UsageType, errors::DomainError, ports::{QuotaRepository, UsageLedgerRepository}, - storage::services::{check_quota, QuotaCheckResult}, + storage::services::{QuotaCheckResult, check_quota}, value_objects::SystemId, }; +use std::sync::Arc; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct CheckQuotaQuery { @@ -24,7 +24,10 @@ impl CheckQuotaHandler { quota_repo: Arc, ledger_repo: Arc, ) -> Self { - Self { quota_repo, ledger_repo } + Self { + quota_repo, + ledger_repo, + } } pub async fn execute(&self, query: CheckQuotaQuery) -> Result { @@ -39,7 +42,15 @@ impl CheckQuotaHandler { }); }; - let current = self.ledger_repo.sum_usage(&query.user_id, query.usage_type, None).await?; - Ok(check_quota("a, query.usage_type, current, query.requested_amount)) + let current = self + .ledger_repo + .sum_usage(&query.user_id, query.usage_type, None) + .await?; + Ok(check_quota( + "a, + query.usage_type, + current, + query.requested_amount, + )) } } diff --git a/crates/application/src/testing/fakes.rs b/crates/application/src/testing/fakes.rs index 3dea0c0..7832e99 100644 --- a/crates/application/src/testing/fakes.rs +++ b/crates/application/src/testing/fakes.rs @@ -1,13 +1,15 @@ -use std::collections::HashMap; use async_trait::async_trait; use bytes::Bytes; -use tokio::sync::Mutex; use domain::{ errors::DomainError, events::DomainEvent, - ports::{EventPublisher, FileStoragePort, FileEntry, PasswordHasher, TokenIssuer, SidecarWriterPort}, + ports::{ + EventPublisher, FileEntry, FileStoragePort, PasswordHasher, SidecarWriterPort, TokenIssuer, + }, value_objects::{PasswordHash, StructuredData, SystemId}, }; +use std::collections::HashMap; +use tokio::sync::Mutex; // --- StubEventPublisher --- @@ -17,7 +19,9 @@ pub struct StubEventPublisher { impl StubEventPublisher { pub fn new() -> Self { - Self { events: Mutex::new(Vec::new()) } + Self { + events: Mutex::new(Vec::new()), + } } pub async fn published(&self) -> Vec { @@ -26,7 +30,9 @@ impl StubEventPublisher { } impl Default for StubEventPublisher { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } #[async_trait] @@ -45,12 +51,16 @@ pub struct InMemoryFileStorage { impl InMemoryFileStorage { pub fn new() -> Self { - Self { files: Mutex::new(HashMap::new()) } + Self { + files: Mutex::new(HashMap::new()), + } } } impl Default for InMemoryFileStorage { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } #[async_trait] @@ -61,7 +71,11 @@ impl FileStoragePort for InMemoryFileStorage { } async fn read_file(&self, path: &str) -> Result { - self.files.lock().await.get(path).cloned() + self.files + .lock() + .await + .get(path) + .cloned() .ok_or_else(|| DomainError::NotFound(format!("File not found: {path}"))) } @@ -72,8 +86,13 @@ impl FileStoragePort for InMemoryFileStorage { async fn list_directory(&self, path: &str) -> Result, DomainError> { let files = self.files.lock().await; - let prefix = if path.ends_with('/') { path.to_string() } else { format!("{path}/") }; - Ok(files.keys() + let prefix = if path.ends_with('/') { + path.to_string() + } else { + format!("{path}/") + }; + Ok(files + .keys() .filter(|k| k.starts_with(&prefix)) .map(|k| FileEntry { path: k.clone(), @@ -98,7 +117,9 @@ pub struct StubSidecarWriter; #[async_trait] impl SidecarWriterPort for StubSidecarWriter { - fn format_name(&self) -> &str { "stub" } + fn format_name(&self) -> &str { + "stub" + } async fn write_sidecar(&self, _data: &StructuredData, _path: &str) -> Result<(), DomainError> { Ok(()) @@ -117,7 +138,9 @@ pub struct InMemorySidecarWriter { impl InMemorySidecarWriter { pub fn new() -> Self { - Self { data: Mutex::new(HashMap::new()) } + Self { + data: Mutex::new(HashMap::new()), + } } pub async fn get(&self, path: &str) -> Option { @@ -126,20 +149,31 @@ impl InMemorySidecarWriter { } impl Default for InMemorySidecarWriter { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } #[async_trait] impl SidecarWriterPort for InMemorySidecarWriter { - fn format_name(&self) -> &str { "in-memory" } + fn format_name(&self) -> &str { + "in-memory" + } async fn write_sidecar(&self, data: &StructuredData, path: &str) -> Result<(), DomainError> { - self.data.lock().await.insert(path.to_string(), data.clone()); + self.data + .lock() + .await + .insert(path.to_string(), data.clone()); Ok(()) } async fn read_sidecar(&self, path: &str) -> Result { - self.data.lock().await.get(path).cloned() + self.data + .lock() + .await + .get(path) + .cloned() .ok_or_else(|| DomainError::NotFound(format!("Sidecar not found: {path}"))) } } @@ -168,9 +202,9 @@ impl TokenIssuer for StubTokenIssuer { Ok(format!("token:{user_id}")) } async fn verify(&self, token: &str) -> Result<(SystemId, String), DomainError> { - let id_str = token.strip_prefix("token:").ok_or_else(|| { - DomainError::Unauthorized("Invalid stub token".to_string()) - })?; + let id_str = token + .strip_prefix("token:") + .ok_or_else(|| DomainError::Unauthorized("Invalid stub token".to_string()))?; let uuid = uuid::Uuid::parse_str(id_str) .map_err(|_| DomainError::Unauthorized("Bad UUID in stub token".to_string()))?; Ok((SystemId::from_uuid(uuid), "user".to_string())) diff --git a/crates/application/src/testing/repositories.rs b/crates/application/src/testing/repositories.rs index 289d00b..fd46ba1 100644 --- a/crates/application/src/testing/repositories.rs +++ b/crates/application/src/testing/repositories.rs @@ -1,25 +1,23 @@ -use std::collections::HashMap; use async_trait::async_trait; -use tokio::sync::Mutex; use domain::{ entities::{ - Album, Asset, AssetMetadata, AssetTag, DuplicateGroup, DuplicateStatus, - Group, IngestSession, InviteCode, Job, JobBatch, JobStatus, LibraryPath, - MetadataSource, Plugin, ProcessingPipeline, QuotaDefinition, Role, - ShareLink, ShareScope, ShareTarget, SidecarRecord, SyncStatus, - StorageVolume, Tag, UsageLedgerEntry, UsageType, User, + Album, Asset, AssetMetadata, AssetTag, DuplicateGroup, DuplicateStatus, Group, + IngestSession, InviteCode, Job, JobBatch, JobStatus, LibraryPath, MetadataSource, Plugin, + ProcessingPipeline, QuotaDefinition, Role, ShareLink, ShareScope, ShareTarget, + SidecarRecord, StorageVolume, SyncStatus, Tag, UsageLedgerEntry, UsageType, User, }, errors::DomainError, ports::{ - AlbumRepository, AssetMetadataRepository, AssetRepository, - DuplicateRepository, GroupRepository, IngestSessionRepository, - JobBatchRepository, JobRepository, LibraryPathRepository, - PipelineRepository, PluginRepository, QuotaRepository, - RoleRepository, ShareRepository, SidecarRepository, StorageVolumeRepository, - TagRepository, UsageLedgerRepository, UserRepository, + AlbumRepository, AssetMetadataRepository, AssetRepository, DuplicateRepository, + GroupRepository, IngestSessionRepository, JobBatchRepository, JobRepository, + LibraryPathRepository, PipelineRepository, PluginRepository, QuotaRepository, + RoleRepository, ShareRepository, SidecarRepository, StorageVolumeRepository, TagRepository, + UsageLedgerRepository, UserRepository, }, value_objects::{Checksum, DateTimeStamp, Email, SystemId}, }; +use std::collections::HashMap; +use tokio::sync::Mutex; // --- InMemoryUserRepository --- @@ -29,7 +27,9 @@ pub struct InMemoryUserRepository { impl InMemoryUserRepository { pub fn new() -> Self { - Self { users: Mutex::new(HashMap::new()) } + Self { + users: Mutex::new(HashMap::new()), + } } pub async fn all(&self) -> Vec { @@ -38,7 +38,9 @@ impl InMemoryUserRepository { } impl Default for InMemoryUserRepository { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } #[async_trait] @@ -48,19 +50,30 @@ impl UserRepository for InMemoryUserRepository { } async fn find_by_email(&self, email: &Email) -> Result, DomainError> { - Ok(self.users.lock().await.values() + Ok(self + .users + .lock() + .await + .values() .find(|u| u.email.as_str() == email.as_str()) .cloned()) } async fn find_by_username(&self, username: &str) -> Result, DomainError> { - Ok(self.users.lock().await.values() + Ok(self + .users + .lock() + .await + .values() .find(|u| u.username == username) .cloned()) } async fn save(&self, user: &User) -> Result<(), DomainError> { - self.users.lock().await.insert(user.id.to_string(), user.clone()); + self.users + .lock() + .await + .insert(user.id.to_string(), user.clone()); Ok(()) } @@ -78,12 +91,16 @@ pub struct InMemoryAssetRepository { impl InMemoryAssetRepository { pub fn new() -> Self { - Self { data: Mutex::new(HashMap::new()) } + Self { + data: Mutex::new(HashMap::new()), + } } } impl Default for InMemoryAssetRepository { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } #[async_trait] @@ -93,22 +110,42 @@ impl AssetRepository for InMemoryAssetRepository { } async fn find_by_checksum(&self, checksum: &Checksum) -> Result, DomainError> { - Ok(self.data.lock().await.values() + Ok(self + .data + .lock() + .await + .values() .filter(|a| &a.source_reference.checksum == checksum) .cloned() .collect()) } - async fn find_by_owner(&self, owner_id: &SystemId, limit: u32, offset: u32) -> Result, DomainError> { - let all: Vec = self.data.lock().await.values() + async fn find_by_owner( + &self, + owner_id: &SystemId, + limit: u32, + offset: u32, + ) -> Result, DomainError> { + let all: Vec = self + .data + .lock() + .await + .values() .filter(|a| &a.owner_user_id == owner_id) .cloned() .collect(); - Ok(all.into_iter().skip(offset as usize).take(limit as usize).collect()) + Ok(all + .into_iter() + .skip(offset as usize) + .take(limit as usize) + .collect()) } async fn save(&self, asset: &Asset) -> Result<(), DomainError> { - self.data.lock().await.insert(asset.asset_id.to_string(), asset.clone()); + self.data + .lock() + .await + .insert(asset.asset_id.to_string(), asset.clone()); Ok(()) } @@ -126,12 +163,16 @@ pub struct InMemoryAlbumRepository { impl InMemoryAlbumRepository { pub fn new() -> Self { - Self { data: Mutex::new(HashMap::new()) } + Self { + data: Mutex::new(HashMap::new()), + } } } impl Default for InMemoryAlbumRepository { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } #[async_trait] @@ -141,14 +182,21 @@ impl AlbumRepository for InMemoryAlbumRepository { } async fn find_by_creator(&self, creator_id: &SystemId) -> Result, DomainError> { - Ok(self.data.lock().await.values() + Ok(self + .data + .lock() + .await + .values() .filter(|a| &a.creator_user_id == creator_id) .cloned() .collect()) } async fn save(&self, album: &Album) -> Result<(), DomainError> { - self.data.lock().await.insert(album.album_id.to_string(), album.clone()); + self.data + .lock() + .await + .insert(album.album_id.to_string(), album.clone()); Ok(()) } @@ -166,12 +214,16 @@ pub struct InMemoryJobRepository { impl InMemoryJobRepository { pub fn new() -> Self { - Self { data: Mutex::new(HashMap::new()) } + Self { + data: Mutex::new(HashMap::new()), + } } } impl Default for InMemoryJobRepository { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } #[async_trait] @@ -182,21 +234,29 @@ impl JobRepository for InMemoryJobRepository { async fn find_next_queued(&self) -> Result, DomainError> { let data = self.data.lock().await; - Ok(data.values() + Ok(data + .values() .filter(|j| j.status == JobStatus::Queued) .max_by_key(|j| j.priority) .cloned()) } async fn find_by_batch(&self, batch_id: &SystemId) -> Result, DomainError> { - Ok(self.data.lock().await.values() + Ok(self + .data + .lock() + .await + .values() .filter(|j| j.batch_id.as_ref() == Some(batch_id)) .cloned() .collect()) } async fn save(&self, job: &Job) -> Result<(), DomainError> { - self.data.lock().await.insert(job.job_id.to_string(), job.clone()); + self.data + .lock() + .await + .insert(job.job_id.to_string(), job.clone()); Ok(()) } } @@ -209,12 +269,16 @@ pub struct InMemoryRoleRepository { impl InMemoryRoleRepository { pub fn new() -> Self { - Self { data: Mutex::new(HashMap::new()) } + Self { + data: Mutex::new(HashMap::new()), + } } } impl Default for InMemoryRoleRepository { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } #[async_trait] @@ -224,20 +288,31 @@ impl RoleRepository for InMemoryRoleRepository { } async fn find_by_name(&self, name: &str) -> Result, DomainError> { - Ok(self.data.lock().await.values() + Ok(self + .data + .lock() + .await + .values() .find(|r| r.name == name) .cloned()) } async fn find_defaults(&self) -> Result, DomainError> { - Ok(self.data.lock().await.values() + Ok(self + .data + .lock() + .await + .values() .filter(|r| r.is_system_default) .cloned() .collect()) } async fn save(&self, role: &Role) -> Result<(), DomainError> { - self.data.lock().await.insert(role.role_id.to_string(), role.clone()); + self.data + .lock() + .await + .insert(role.role_id.to_string(), role.clone()); Ok(()) } @@ -255,12 +330,16 @@ pub struct InMemoryGroupRepository { impl InMemoryGroupRepository { pub fn new() -> Self { - Self { data: Mutex::new(HashMap::new()) } + Self { + data: Mutex::new(HashMap::new()), + } } } impl Default for InMemoryGroupRepository { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } #[async_trait] @@ -270,14 +349,21 @@ impl GroupRepository for InMemoryGroupRepository { } async fn find_by_user(&self, user_id: &SystemId) -> Result, DomainError> { - Ok(self.data.lock().await.values() + Ok(self + .data + .lock() + .await + .values() .filter(|g| g.is_member(user_id)) .cloned() .collect()) } async fn save(&self, group: &Group) -> Result<(), DomainError> { - self.data.lock().await.insert(group.group_id.to_string(), group.clone()); + self.data + .lock() + .await + .insert(group.group_id.to_string(), group.clone()); Ok(()) } @@ -295,12 +381,16 @@ pub struct InMemoryStorageVolumeRepository { impl InMemoryStorageVolumeRepository { pub fn new() -> Self { - Self { data: Mutex::new(HashMap::new()) } + Self { + data: Mutex::new(HashMap::new()), + } } } impl Default for InMemoryStorageVolumeRepository { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } #[async_trait] @@ -314,7 +404,10 @@ impl StorageVolumeRepository for InMemoryStorageVolumeRepository { } async fn save(&self, volume: &StorageVolume) -> Result<(), DomainError> { - self.data.lock().await.insert(volume.volume_id.to_string(), volume.clone()); + self.data + .lock() + .await + .insert(volume.volume_id.to_string(), volume.clone()); Ok(()) } @@ -332,12 +425,16 @@ pub struct InMemoryLibraryPathRepository { impl InMemoryLibraryPathRepository { pub fn new() -> Self { - Self { data: Mutex::new(HashMap::new()) } + Self { + data: Mutex::new(HashMap::new()), + } } } impl Default for InMemoryLibraryPathRepository { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } #[async_trait] @@ -347,21 +444,35 @@ impl LibraryPathRepository for InMemoryLibraryPathRepository { } async fn find_by_volume(&self, volume_id: &SystemId) -> Result, DomainError> { - Ok(self.data.lock().await.values() + Ok(self + .data + .lock() + .await + .values() .filter(|p| &p.volume_id == volume_id) .cloned() .collect()) } - async fn find_ingest_destinations(&self, owner_id: &SystemId) -> Result, DomainError> { - Ok(self.data.lock().await.values() + async fn find_ingest_destinations( + &self, + owner_id: &SystemId, + ) -> Result, DomainError> { + Ok(self + .data + .lock() + .await + .values() .filter(|p| p.is_ingest_destination && p.designated_owner_id.as_ref() == Some(owner_id)) .cloned() .collect()) } async fn save(&self, path: &LibraryPath) -> Result<(), DomainError> { - self.data.lock().await.insert(path.path_id.to_string(), path.clone()); + self.data + .lock() + .await + .insert(path.path_id.to_string(), path.clone()); Ok(()) } @@ -379,12 +490,16 @@ pub struct InMemoryIngestSessionRepository { impl InMemoryIngestSessionRepository { pub fn new() -> Self { - Self { data: Mutex::new(HashMap::new()) } + Self { + data: Mutex::new(HashMap::new()), + } } } impl Default for InMemoryIngestSessionRepository { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } #[async_trait] @@ -394,14 +509,21 @@ impl IngestSessionRepository for InMemoryIngestSessionRepository { } async fn find_by_user(&self, user_id: &SystemId) -> Result, DomainError> { - Ok(self.data.lock().await.values() + Ok(self + .data + .lock() + .await + .values() .filter(|s| &s.uploader_user_id == user_id) .cloned() .collect()) } async fn save(&self, session: &IngestSession) -> Result<(), DomainError> { - self.data.lock().await.insert(session.session_id.to_string(), session.clone()); + self.data + .lock() + .await + .insert(session.session_id.to_string(), session.clone()); Ok(()) } } @@ -414,24 +536,38 @@ pub struct InMemoryQuotaRepository { impl InMemoryQuotaRepository { pub fn new() -> Self { - Self { data: Mutex::new(HashMap::new()) } + Self { + data: Mutex::new(HashMap::new()), + } } } impl Default for InMemoryQuotaRepository { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } #[async_trait] impl QuotaRepository for InMemoryQuotaRepository { - async fn find_by_owner(&self, owner_id: &SystemId) -> Result, DomainError> { - Ok(self.data.lock().await.values() + async fn find_by_owner( + &self, + owner_id: &SystemId, + ) -> Result, DomainError> { + Ok(self + .data + .lock() + .await + .values() .find(|q| &q.owner_scope == owner_id) .cloned()) } async fn save(&self, quota: &QuotaDefinition) -> Result<(), DomainError> { - self.data.lock().await.insert(quota.quota_id.to_string(), quota.clone()); + self.data + .lock() + .await + .insert(quota.quota_id.to_string(), quota.clone()); Ok(()) } @@ -449,12 +585,16 @@ pub struct InMemoryUsageLedgerRepository { impl InMemoryUsageLedgerRepository { pub fn new() -> Self { - Self { entries: Mutex::new(Vec::new()) } + Self { + entries: Mutex::new(Vec::new()), + } } } impl Default for InMemoryUsageLedgerRepository { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } #[async_trait] @@ -471,7 +611,8 @@ impl UsageLedgerRepository for InMemoryUsageLedgerRepository { since: Option, ) -> Result { let entries = self.entries.lock().await; - let total = entries.iter() + let total = entries + .iter() .filter(|e| &e.user_id == user_id && e.usage_type == usage_type) .filter(|e| match &since { Some(ts) => &e.timestamp >= ts, @@ -491,7 +632,9 @@ pub struct InMemoryAssetMetadataRepository { impl InMemoryAssetMetadataRepository { pub fn new() -> Self { - Self { data: Mutex::new(HashMap::new()) } + Self { + data: Mutex::new(HashMap::new()), + } } fn key(asset_id: &SystemId, source: MetadataSource) -> String { @@ -500,21 +643,36 @@ impl InMemoryAssetMetadataRepository { } impl Default for InMemoryAssetMetadataRepository { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } #[async_trait] impl AssetMetadataRepository for InMemoryAssetMetadataRepository { async fn find_by_asset(&self, asset_id: &SystemId) -> Result, DomainError> { let prefix = format!("{asset_id}:"); - Ok(self.data.lock().await.iter() + Ok(self + .data + .lock() + .await + .iter() .filter(|(k, _)| k.starts_with(&prefix)) .map(|(_, v)| v.clone()) .collect()) } - async fn find_by_asset_and_source(&self, asset_id: &SystemId, source: MetadataSource) -> Result, DomainError> { - Ok(self.data.lock().await.get(&Self::key(asset_id, source)).cloned()) + async fn find_by_asset_and_source( + &self, + asset_id: &SystemId, + source: MetadataSource, + ) -> Result, DomainError> { + Ok(self + .data + .lock() + .await + .get(&Self::key(asset_id, source)) + .cloned()) } async fn save(&self, metadata: &AssetMetadata) -> Result<(), DomainError> { @@ -523,7 +681,11 @@ impl AssetMetadataRepository for InMemoryAssetMetadataRepository { Ok(()) } - async fn delete_by_asset_and_source(&self, asset_id: &SystemId, source: MetadataSource) -> Result<(), DomainError> { + async fn delete_by_asset_and_source( + &self, + asset_id: &SystemId, + source: MetadataSource, + ) -> Result<(), DomainError> { self.data.lock().await.remove(&Self::key(asset_id, source)); Ok(()) } @@ -550,13 +712,18 @@ impl InMemoryShareRepository { } impl Default for InMemoryShareRepository { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } #[async_trait] impl ShareRepository for InMemoryShareRepository { async fn save_scope(&self, scope: &ShareScope) -> Result<(), DomainError> { - self.scopes.lock().await.insert(scope.scope_id.to_string(), scope.clone()); + self.scopes + .lock() + .await + .insert(scope.scope_id.to_string(), scope.clone()); Ok(()) } @@ -564,8 +731,15 @@ impl ShareRepository for InMemoryShareRepository { Ok(self.scopes.lock().await.get(&id.to_string()).cloned()) } - async fn find_scopes_for_resource(&self, resource_id: &SystemId) -> Result, DomainError> { - Ok(self.scopes.lock().await.values() + async fn find_scopes_for_resource( + &self, + resource_id: &SystemId, + ) -> Result, DomainError> { + Ok(self + .scopes + .lock() + .await + .values() .filter(|s| &s.shareable_id == resource_id) .cloned() .collect()) @@ -582,22 +756,39 @@ impl ShareRepository for InMemoryShareRepository { Ok(()) } - async fn find_targets_for_scope(&self, scope_id: &SystemId) -> Result, DomainError> { - Ok(self.targets.lock().await.values() + async fn find_targets_for_scope( + &self, + scope_id: &SystemId, + ) -> Result, DomainError> { + Ok(self + .targets + .lock() + .await + .values() .filter(|t| &t.scope_id == scope_id) .cloned() .collect()) } - async fn find_targets_for_user(&self, user_id: &SystemId) -> Result, DomainError> { - Ok(self.targets.lock().await.values() + async fn find_targets_for_user( + &self, + user_id: &SystemId, + ) -> Result, DomainError> { + Ok(self + .targets + .lock() + .await + .values() .filter(|t| &t.target_id == user_id) .cloned() .collect()) } async fn save_link(&self, link: &ShareLink) -> Result<(), DomainError> { - self.links.lock().await.insert(link.token.clone(), link.clone()); + self.links + .lock() + .await + .insert(link.token.clone(), link.clone()); Ok(()) } @@ -606,7 +797,10 @@ impl ShareRepository for InMemoryShareRepository { } async fn save_invite(&self, invite: &InviteCode) -> Result<(), DomainError> { - self.invites.lock().await.insert(invite.code_id.to_string(), invite.clone()); + self.invites + .lock() + .await + .insert(invite.code_id.to_string(), invite.clone()); Ok(()) } @@ -632,7 +826,9 @@ impl InMemoryTagRepository { } impl Default for InMemoryTagRepository { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } #[async_trait] @@ -642,17 +838,26 @@ impl TagRepository for InMemoryTagRepository { } async fn find_by_name(&self, name: &str) -> Result, DomainError> { - Ok(self.tags.lock().await.values() + Ok(self + .tags + .lock() + .await + .values() .find(|t| t.name == name) .cloned()) } - async fn find_tags_for_asset(&self, asset_id: &SystemId) -> Result, DomainError> { + async fn find_tags_for_asset( + &self, + asset_id: &SystemId, + ) -> Result, DomainError> { let asset_tags = self.asset_tags.lock().await; let tags = self.tags.lock().await; let mut result = Vec::new(); for at in asset_tags.values() { - if &at.asset_id == asset_id && let Some(tag) = tags.get(&at.tag_id.to_string()) { + if &at.asset_id == asset_id + && let Some(tag) = tags.get(&at.tag_id.to_string()) + { result.push((tag.clone(), at.clone())); } } @@ -660,7 +865,10 @@ impl TagRepository for InMemoryTagRepository { } async fn save_tag(&self, tag: &Tag) -> Result<(), DomainError> { - self.tags.lock().await.insert(tag.tag_id.to_string(), tag.clone()); + self.tags + .lock() + .await + .insert(tag.tag_id.to_string(), tag.clone()); Ok(()) } @@ -670,7 +878,11 @@ impl TagRepository for InMemoryTagRepository { Ok(()) } - async fn remove_asset_tag(&self, asset_id: &SystemId, tag_id: &SystemId) -> Result<(), DomainError> { + async fn remove_asset_tag( + &self, + asset_id: &SystemId, + tag_id: &SystemId, + ) -> Result<(), DomainError> { let key = format!("{asset_id}:{tag_id}"); self.asset_tags.lock().await.remove(&key); Ok(()) @@ -685,12 +897,16 @@ pub struct InMemoryDuplicateRepository { impl InMemoryDuplicateRepository { pub fn new() -> Self { - Self { data: Mutex::new(HashMap::new()) } + Self { + data: Mutex::new(HashMap::new()), + } } } impl Default for InMemoryDuplicateRepository { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } #[async_trait] @@ -700,21 +916,32 @@ impl DuplicateRepository for InMemoryDuplicateRepository { } async fn find_unresolved(&self) -> Result, DomainError> { - Ok(self.data.lock().await.values() + Ok(self + .data + .lock() + .await + .values() .filter(|g| g.status == DuplicateStatus::Unresolved) .cloned() .collect()) } async fn find_by_asset(&self, asset_id: &SystemId) -> Result, DomainError> { - Ok(self.data.lock().await.values() + Ok(self + .data + .lock() + .await + .values() .filter(|g| g.candidates.iter().any(|c| &c.asset_id == asset_id)) .cloned() .collect()) } async fn save(&self, group: &DuplicateGroup) -> Result<(), DomainError> { - self.data.lock().await.insert(group.group_id.to_string(), group.clone()); + self.data + .lock() + .await + .insert(group.group_id.to_string(), group.clone()); Ok(()) } } @@ -727,29 +954,43 @@ pub struct InMemorySidecarRepository { impl InMemorySidecarRepository { pub fn new() -> Self { - Self { data: Mutex::new(HashMap::new()) } + Self { + data: Mutex::new(HashMap::new()), + } } } impl Default for InMemorySidecarRepository { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } #[async_trait] impl SidecarRepository for InMemorySidecarRepository { - async fn find_by_asset(&self, asset_id: &SystemId) -> Result, DomainError> { + async fn find_by_asset( + &self, + asset_id: &SystemId, + ) -> Result, DomainError> { Ok(self.data.lock().await.get(&asset_id.to_string()).cloned()) } async fn find_by_status(&self, status: SyncStatus) -> Result, DomainError> { - Ok(self.data.lock().await.values() + 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()); + self.data + .lock() + .await + .insert(record.asset_id.to_string(), record.clone()); Ok(()) } @@ -767,12 +1008,16 @@ pub struct InMemoryJobBatchRepository { impl InMemoryJobBatchRepository { pub fn new() -> Self { - Self { data: Mutex::new(HashMap::new()) } + Self { + data: Mutex::new(HashMap::new()), + } } } impl Default for InMemoryJobBatchRepository { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } #[async_trait] @@ -782,7 +1027,10 @@ impl JobBatchRepository for InMemoryJobBatchRepository { } async fn save(&self, batch: &JobBatch) -> Result<(), DomainError> { - self.data.lock().await.insert(batch.batch_id.to_string(), batch.clone()); + self.data + .lock() + .await + .insert(batch.batch_id.to_string(), batch.clone()); Ok(()) } } @@ -795,12 +1043,16 @@ pub struct InMemoryPluginRepository { impl InMemoryPluginRepository { pub fn new() -> Self { - Self { data: Mutex::new(HashMap::new()) } + Self { + data: Mutex::new(HashMap::new()), + } } } impl Default for InMemoryPluginRepository { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } #[async_trait] @@ -810,14 +1062,21 @@ impl PluginRepository for InMemoryPluginRepository { } async fn find_enabled(&self) -> Result, DomainError> { - Ok(self.data.lock().await.values() + 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()); + self.data + .lock() + .await + .insert(plugin.plugin_id.to_string(), plugin.clone()); Ok(()) } } @@ -830,12 +1089,16 @@ pub struct InMemoryPipelineRepository { impl InMemoryPipelineRepository { pub fn new() -> Self { - Self { data: Mutex::new(HashMap::new()) } + Self { + data: Mutex::new(HashMap::new()), + } } } impl Default for InMemoryPipelineRepository { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } #[async_trait] @@ -845,14 +1108,21 @@ impl PipelineRepository for InMemoryPipelineRepository { } async fn find_by_trigger(&self, event: &str) -> Result, DomainError> { - Ok(self.data.lock().await.values() + 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()); + self.data + .lock() + .await + .insert(pipeline.pipeline_id.to_string(), pipeline.clone()); Ok(()) } } diff --git a/crates/application/tests/app_tests.rs b/crates/application/tests/app_tests.rs index a98bce2..145b6b9 100644 --- a/crates/application/tests/app_tests.rs +++ b/crates/application/tests/app_tests.rs @@ -1,7 +1,7 @@ +mod catalog; mod identity; mod organization; -mod storage; -mod catalog; +mod processing; mod sharing; mod sidecar; -mod processing; +mod storage; diff --git a/crates/application/tests/catalog/commands/register_asset.rs b/crates/application/tests/catalog/commands/register_asset.rs index 26d2698..e46805c 100644 --- a/crates/application/tests/catalog/commands/register_asset.rs +++ b/crates/application/tests/catalog/commands/register_asset.rs @@ -1,8 +1,10 @@ -use std::sync::Arc; use application::catalog::{RegisterAssetCommand, RegisterAssetHandler}; -use application::testing::{InMemoryAssetRepository, InMemoryDuplicateRepository, StubEventPublisher}; +use application::testing::{ + InMemoryAssetRepository, InMemoryDuplicateRepository, StubEventPublisher, +}; use domain::catalog::entities::AssetType; use domain::value_objects::SystemId; +use std::sync::Arc; fn valid_checksum() -> String { "a".repeat(64) @@ -14,24 +16,23 @@ async fn registers_asset() { let dup_repo = Arc::new(InMemoryDuplicateRepository::new()); let events = Arc::new(StubEventPublisher::new()); - let handler = RegisterAssetHandler::new( - asset_repo.clone(), - dup_repo.clone(), - events.clone(), - ); + let handler = RegisterAssetHandler::new(asset_repo.clone(), dup_repo.clone(), events.clone()); let owner = SystemId::new(); let volume = SystemId::new(); - let (asset, dup) = handler.execute(RegisterAssetCommand { - volume_id: volume, - relative_path: "photos/img.jpg".into(), - checksum: valid_checksum(), - asset_type: AssetType::Image, - mime_type: "image/jpeg".into(), - file_size: 1024, - owner_id: owner, - }).await.unwrap(); + let (asset, dup) = handler + .execute(RegisterAssetCommand { + volume_id: volume, + relative_path: "photos/img.jpg".into(), + checksum: valid_checksum(), + asset_type: AssetType::Image, + mime_type: "image/jpeg".into(), + file_size: 1024, + owner_id: owner, + }) + .await + .unwrap(); assert_eq!(asset.mime_type, "image/jpeg"); assert_eq!(asset.file_size, 1024); @@ -46,37 +47,39 @@ async fn flags_duplicate_when_checksum_exists() { let dup_repo = Arc::new(InMemoryDuplicateRepository::new()); let events = Arc::new(StubEventPublisher::new()); - let handler = RegisterAssetHandler::new( - asset_repo.clone(), - dup_repo.clone(), - events.clone(), - ); + let handler = RegisterAssetHandler::new(asset_repo.clone(), dup_repo.clone(), events.clone()); let owner = SystemId::new(); let volume = SystemId::new(); let checksum = valid_checksum(); // First asset - let (first, _) = handler.execute(RegisterAssetCommand { - volume_id: volume, - relative_path: "photos/img1.jpg".into(), - checksum: checksum.clone(), - asset_type: AssetType::Image, - mime_type: "image/jpeg".into(), - file_size: 1024, - owner_id: owner, - }).await.unwrap(); + let (first, _) = handler + .execute(RegisterAssetCommand { + volume_id: volume, + relative_path: "photos/img1.jpg".into(), + checksum: checksum.clone(), + asset_type: AssetType::Image, + mime_type: "image/jpeg".into(), + file_size: 1024, + owner_id: owner, + }) + .await + .unwrap(); // Second asset with same checksum - let (second, dup) = handler.execute(RegisterAssetCommand { - volume_id: volume, - relative_path: "photos/img2.jpg".into(), - checksum, - asset_type: AssetType::Image, - mime_type: "image/jpeg".into(), - file_size: 1024, - owner_id: owner, - }).await.unwrap(); + let (second, dup) = handler + .execute(RegisterAssetCommand { + volume_id: volume, + relative_path: "photos/img2.jpg".into(), + checksum, + asset_type: AssetType::Image, + mime_type: "image/jpeg".into(), + file_size: 1024, + owner_id: owner, + }) + .await + .unwrap(); let group = dup.expect("should flag duplicate"); let candidate_ids: Vec<_> = group.candidates.iter().map(|c| c.asset_id).collect(); diff --git a/crates/application/tests/catalog/commands/update_metadata.rs b/crates/application/tests/catalog/commands/update_metadata.rs index f1e9dcd..14bd556 100644 --- a/crates/application/tests/catalog/commands/update_metadata.rs +++ b/crates/application/tests/catalog/commands/update_metadata.rs @@ -1,9 +1,11 @@ -use std::sync::Arc; use application::catalog::{UpdateMetadataCommand, UpdateMetadataHandler}; -use application::testing::{InMemoryAssetRepository, InMemoryAssetMetadataRepository, StubEventPublisher}; -use domain::catalog::entities::{Asset, AssetType, SourceReference, MetadataSource}; +use application::testing::{ + InMemoryAssetMetadataRepository, InMemoryAssetRepository, StubEventPublisher, +}; +use domain::catalog::entities::{Asset, AssetType, MetadataSource, SourceReference}; use domain::errors::DomainError; use domain::value_objects::{Checksum, MetadataValue, StructuredData, SystemId}; +use std::sync::Arc; async fn seed_asset(repo: &InMemoryAssetRepository) -> Asset { let source = SourceReference { @@ -11,7 +13,13 @@ async fn seed_asset(repo: &InMemoryAssetRepository) -> Asset { relative_path: "photos/img.jpg".into(), checksum: Checksum::new("a".repeat(64)).unwrap(), }; - let asset = Asset::new(source, AssetType::Image, "image/jpeg", 1024, SystemId::new()); + let asset = Asset::new( + source, + AssetType::Image, + "image/jpeg", + 1024, + SystemId::new(), + ); repo.save(&asset).await.unwrap(); asset } @@ -26,20 +34,19 @@ async fn updates_metadata() { let asset = seed_asset(&asset_repo).await; - let handler = UpdateMetadataHandler::new( - asset_repo.clone(), - meta_repo.clone(), - events.clone(), - ); + let handler = UpdateMetadataHandler::new(asset_repo.clone(), meta_repo.clone(), events.clone()); let mut data = StructuredData::new(); data.insert("title", MetadataValue::String("Sunset".into())); - let result = handler.execute(UpdateMetadataCommand { - asset_id: asset.asset_id, - user_id: SystemId::new(), - data, - }).await.unwrap(); + let result = handler + .execute(UpdateMetadataCommand { + asset_id: asset.asset_id, + user_id: SystemId::new(), + data, + }) + .await + .unwrap(); assert_eq!(result.metadata_source, MetadataSource::UserEdited); assert_eq!(result.data.get_string("title"), Some("Sunset")); @@ -54,11 +61,13 @@ async fn rejects_nonexistent_asset() { let handler = UpdateMetadataHandler::new(asset_repo, meta_repo, events); - let result = handler.execute(UpdateMetadataCommand { - asset_id: SystemId::new(), - user_id: SystemId::new(), - data: StructuredData::new(), - }).await; + let result = handler + .execute(UpdateMetadataCommand { + asset_id: SystemId::new(), + user_id: SystemId::new(), + data: StructuredData::new(), + }) + .await; assert!(matches!(result, Err(DomainError::NotFound(_)))); } diff --git a/crates/application/tests/catalog/queries/get_asset.rs b/crates/application/tests/catalog/queries/get_asset.rs index e705b76..c815e5d 100644 --- a/crates/application/tests/catalog/queries/get_asset.rs +++ b/crates/application/tests/catalog/queries/get_asset.rs @@ -1,10 +1,10 @@ -use std::sync::Arc; -use application::catalog::{GetAssetQuery, GetAssetHandler}; -use application::testing::{InMemoryAssetRepository, InMemoryAssetMetadataRepository}; +use application::catalog::{GetAssetHandler, GetAssetQuery}; +use application::testing::{InMemoryAssetMetadataRepository, InMemoryAssetRepository}; use domain::catalog::entities::{Asset, AssetMetadata, AssetType, MetadataSource, SourceReference}; use domain::errors::DomainError; -use domain::ports::{AssetRepository, AssetMetadataRepository}; +use domain::ports::{AssetMetadataRepository, AssetRepository}; use domain::value_objects::{Checksum, MetadataValue, StructuredData, SystemId}; +use std::sync::Arc; #[tokio::test] async fn returns_asset_with_resolved_metadata() { @@ -16,7 +16,13 @@ async fn returns_asset_with_resolved_metadata() { relative_path: "photos/img.jpg".into(), checksum: Checksum::new("a".repeat(64)).unwrap(), }; - let asset = Asset::new(source, AssetType::Image, "image/jpeg", 1024, SystemId::new()); + let asset = Asset::new( + source, + AssetType::Image, + "image/jpeg", + 1024, + SystemId::new(), + ); asset_repo.save(&asset).await.unwrap(); // Add exif layer @@ -33,9 +39,12 @@ async fn returns_asset_with_resolved_metadata() { meta_repo.save(&user_meta).await.unwrap(); let handler = GetAssetHandler::new(asset_repo, meta_repo); - let (returned, resolved) = handler.execute(GetAssetQuery { - asset_id: asset.asset_id, - }).await.unwrap(); + let (returned, resolved) = handler + .execute(GetAssetQuery { + asset_id: asset.asset_id, + }) + .await + .unwrap(); assert_eq!(returned.asset_id, asset.asset_id); // UserEdited overrides ExifExtracted @@ -50,9 +59,11 @@ async fn rejects_nonexistent() { let meta_repo = Arc::new(InMemoryAssetMetadataRepository::new()); let handler = GetAssetHandler::new(asset_repo, meta_repo); - let result = handler.execute(GetAssetQuery { - asset_id: SystemId::new(), - }).await; + let result = handler + .execute(GetAssetQuery { + asset_id: SystemId::new(), + }) + .await; assert!(matches!(result, Err(DomainError::NotFound(_)))); } diff --git a/crates/application/tests/catalog/queries/get_timeline.rs b/crates/application/tests/catalog/queries/get_timeline.rs index e8ecbb6..e39009e 100644 --- a/crates/application/tests/catalog/queries/get_timeline.rs +++ b/crates/application/tests/catalog/queries/get_timeline.rs @@ -1,9 +1,9 @@ -use std::sync::Arc; -use application::catalog::{GetTimelineQuery, GetTimelineHandler}; -use application::testing::{InMemoryAssetRepository, InMemoryAssetMetadataRepository}; +use application::catalog::{GetTimelineHandler, GetTimelineQuery}; +use application::testing::{InMemoryAssetMetadataRepository, InMemoryAssetRepository}; use domain::catalog::entities::{Asset, AssetType, SourceReference}; use domain::ports::AssetRepository; use domain::value_objects::{Checksum, SystemId}; +use std::sync::Arc; async fn seed_assets(repo: &InMemoryAssetRepository, owner: SystemId, count: usize) { for i in 0..count { @@ -28,11 +28,14 @@ async fn returns_paginated_assets() { let handler = GetTimelineHandler::new(asset_repo, meta_repo); - let page = handler.execute(GetTimelineQuery { - owner_id: owner, - limit: 3, - offset: 0, - }).await.unwrap(); + let page = handler + .execute(GetTimelineQuery { + owner_id: owner, + limit: 3, + offset: 0, + }) + .await + .unwrap(); assert_eq!(page.len(), 3); } @@ -44,11 +47,14 @@ async fn returns_empty_for_no_assets() { let handler = GetTimelineHandler::new(asset_repo, meta_repo); - let page = handler.execute(GetTimelineQuery { - owner_id: SystemId::new(), - limit: 10, - offset: 0, - }).await.unwrap(); + let page = handler + .execute(GetTimelineQuery { + owner_id: SystemId::new(), + limit: 10, + offset: 0, + }) + .await + .unwrap(); assert!(page.is_empty()); } diff --git a/crates/application/tests/catalog/queries/mod.rs b/crates/application/tests/catalog/queries/mod.rs index 80aa28a..4955b19 100644 --- a/crates/application/tests/catalog/queries/mod.rs +++ b/crates/application/tests/catalog/queries/mod.rs @@ -1,2 +1,2 @@ -mod get_timeline; mod get_asset; +mod get_timeline; diff --git a/crates/application/tests/identity/commands/register_user.rs b/crates/application/tests/identity/commands/register_user.rs index 4a35be9..050cc47 100644 --- a/crates/application/tests/identity/commands/register_user.rs +++ b/crates/application/tests/identity/commands/register_user.rs @@ -1,7 +1,7 @@ -use std::sync::Arc; -use application::testing::{InMemoryUserRepository, StubPasswordHasher}; use application::identity::{RegisterUserCommand, RegisterUserHandler}; +use application::testing::{InMemoryUserRepository, StubPasswordHasher}; use domain::errors::DomainError; +use std::sync::Arc; #[tokio::test] async fn registers_new_user() { @@ -22,16 +22,21 @@ async fn registers_new_user() { async fn rejects_duplicate_email() { let repo = Arc::new(InMemoryUserRepository::new()); let handler = RegisterUserHandler::new(repo.clone(), Arc::new(StubPasswordHasher)); - handler.execute(RegisterUserCommand { - username: "user1".into(), - email: "test@example.com".into(), - password: "password123".into(), - }).await.unwrap(); - let result = handler.execute(RegisterUserCommand { - username: "user2".into(), - email: "test@example.com".into(), - password: "different1".into(), - }).await; + handler + .execute(RegisterUserCommand { + username: "user1".into(), + email: "test@example.com".into(), + password: "password123".into(), + }) + .await + .unwrap(); + let result = handler + .execute(RegisterUserCommand { + username: "user2".into(), + email: "test@example.com".into(), + password: "different1".into(), + }) + .await; assert!(matches!(result, Err(DomainError::Conflict(_)))); } @@ -39,16 +44,21 @@ async fn rejects_duplicate_email() { async fn rejects_duplicate_username() { let repo = Arc::new(InMemoryUserRepository::new()); let handler = RegisterUserHandler::new(repo.clone(), Arc::new(StubPasswordHasher)); - handler.execute(RegisterUserCommand { - username: "sameuser".into(), - email: "a@example.com".into(), - password: "password123".into(), - }).await.unwrap(); - let result = handler.execute(RegisterUserCommand { - username: "sameuser".into(), - email: "b@example.com".into(), - password: "password123".into(), - }).await; + handler + .execute(RegisterUserCommand { + username: "sameuser".into(), + email: "a@example.com".into(), + password: "password123".into(), + }) + .await + .unwrap(); + let result = handler + .execute(RegisterUserCommand { + username: "sameuser".into(), + email: "b@example.com".into(), + password: "password123".into(), + }) + .await; assert!(matches!(result, Err(DomainError::Conflict(_)))); } @@ -56,11 +66,13 @@ async fn rejects_duplicate_username() { async fn rejects_short_password() { let repo = Arc::new(InMemoryUserRepository::new()); let handler = RegisterUserHandler::new(repo, Arc::new(StubPasswordHasher)); - let result = handler.execute(RegisterUserCommand { - username: "user".into(), - email: "test@example.com".into(), - password: "short".into(), - }).await; + let result = handler + .execute(RegisterUserCommand { + username: "user".into(), + email: "test@example.com".into(), + password: "short".into(), + }) + .await; assert!(matches!(result, Err(DomainError::Validation(_)))); } @@ -68,10 +80,12 @@ async fn rejects_short_password() { async fn rejects_empty_username() { let repo = Arc::new(InMemoryUserRepository::new()); let handler = RegisterUserHandler::new(repo, Arc::new(StubPasswordHasher)); - let result = handler.execute(RegisterUserCommand { - username: "".into(), - email: "test@example.com".into(), - password: "password123".into(), - }).await; + let result = handler + .execute(RegisterUserCommand { + username: "".into(), + email: "test@example.com".into(), + password: "password123".into(), + }) + .await; assert!(matches!(result, Err(DomainError::Validation(_)))); } diff --git a/crates/application/tests/identity/queries/get_profile.rs b/crates/application/tests/identity/queries/get_profile.rs index bdcab1f..c792d43 100644 --- a/crates/application/tests/identity/queries/get_profile.rs +++ b/crates/application/tests/identity/queries/get_profile.rs @@ -1,21 +1,29 @@ -use std::sync::Arc; +use application::identity::{ + GetProfileHandler, GetProfileQuery, RegisterUserCommand, RegisterUserHandler, +}; use application::testing::{InMemoryUserRepository, StubPasswordHasher}; -use application::identity::{RegisterUserCommand, RegisterUserHandler, GetProfileQuery, GetProfileHandler}; use domain::errors::DomainError; use domain::value_objects::SystemId; +use std::sync::Arc; #[tokio::test] async fn returns_existing_user() { let repo = Arc::new(InMemoryUserRepository::new()); let reg = RegisterUserHandler::new(repo.clone(), Arc::new(StubPasswordHasher)); - let user = reg.execute(RegisterUserCommand { - username: "alice".into(), - email: "alice@example.com".into(), - password: "password123".into(), - }).await.unwrap(); + let user = reg + .execute(RegisterUserCommand { + username: "alice".into(), + email: "alice@example.com".into(), + password: "password123".into(), + }) + .await + .unwrap(); let handler = GetProfileHandler::new(repo); - let found = handler.execute(GetProfileQuery { user_id: user.id }).await.unwrap(); + let found = handler + .execute(GetProfileQuery { user_id: user.id }) + .await + .unwrap(); assert_eq!(found.username, "alice"); } @@ -23,6 +31,10 @@ async fn returns_existing_user() { async fn returns_not_found_for_missing_user() { let repo = Arc::new(InMemoryUserRepository::new()); let handler = GetProfileHandler::new(repo); - let result = handler.execute(GetProfileQuery { user_id: SystemId::new() }).await; + let result = handler + .execute(GetProfileQuery { + user_id: SystemId::new(), + }) + .await; assert!(matches!(result, Err(DomainError::NotFound(_)))); } diff --git a/crates/application/tests/organization/commands/create_album.rs b/crates/application/tests/organization/commands/create_album.rs index 859f381..a87ad8e 100644 --- a/crates/application/tests/organization/commands/create_album.rs +++ b/crates/application/tests/organization/commands/create_album.rs @@ -1,18 +1,21 @@ -use std::sync::Arc; -use application::testing::InMemoryAlbumRepository; use application::organization::{CreateAlbumCommand, CreateAlbumHandler}; +use application::testing::InMemoryAlbumRepository; use domain::errors::DomainError; use domain::value_objects::SystemId; +use std::sync::Arc; #[tokio::test] async fn creates_album() { let repo = Arc::new(InMemoryAlbumRepository::new()); let handler = CreateAlbumHandler::new(repo); let creator = SystemId::new(); - let album = handler.execute(CreateAlbumCommand { - title: "Vacation 2024".into(), - creator_id: creator, - }).await.unwrap(); + let album = handler + .execute(CreateAlbumCommand { + title: "Vacation 2024".into(), + creator_id: creator, + }) + .await + .unwrap(); assert_eq!(album.title, "Vacation 2024"); assert_eq!(album.creator_user_id, creator); assert_eq!(album.asset_count(), 0); @@ -22,9 +25,11 @@ async fn creates_album() { async fn rejects_empty_title() { let repo = Arc::new(InMemoryAlbumRepository::new()); let handler = CreateAlbumHandler::new(repo); - let result = handler.execute(CreateAlbumCommand { - title: "".into(), - creator_id: SystemId::new(), - }).await; + let result = handler + .execute(CreateAlbumCommand { + title: "".into(), + creator_id: SystemId::new(), + }) + .await; assert!(matches!(result, Err(DomainError::Validation(_)))); } diff --git a/crates/application/tests/organization/commands/manage_album_entries.rs b/crates/application/tests/organization/commands/manage_album_entries.rs index 41ca869..d2563f9 100644 --- a/crates/application/tests/organization/commands/manage_album_entries.rs +++ b/crates/application/tests/organization/commands/manage_album_entries.rs @@ -1,18 +1,21 @@ -use std::sync::Arc; -use application::testing::InMemoryAlbumRepository; use application::organization::{ - AlbumAction, CreateAlbumCommand, CreateAlbumHandler, - ManageAlbumEntriesCommand, ManageAlbumEntriesHandler, + AlbumAction, CreateAlbumCommand, CreateAlbumHandler, ManageAlbumEntriesCommand, + ManageAlbumEntriesHandler, }; +use application::testing::InMemoryAlbumRepository; use domain::errors::DomainError; use domain::value_objects::SystemId; +use std::sync::Arc; async fn setup_album(repo: &Arc, creator: SystemId) -> SystemId { let handler = CreateAlbumHandler::new(repo.clone()); - let album = handler.execute(CreateAlbumCommand { - title: "Test Album".into(), - creator_id: creator, - }).await.unwrap(); + let album = handler + .execute(CreateAlbumCommand { + title: "Test Album".into(), + creator_id: creator, + }) + .await + .unwrap(); album.album_id } @@ -24,11 +27,14 @@ async fn adds_asset_to_album() { let asset_id = SystemId::new(); let handler = ManageAlbumEntriesHandler::new(repo.clone()); - let album = handler.execute(ManageAlbumEntriesCommand { - album_id, - action: AlbumAction::Add { asset_id }, - user_id: user, - }).await.unwrap(); + let album = handler + .execute(ManageAlbumEntriesCommand { + album_id, + action: AlbumAction::Add { asset_id }, + user_id: user, + }) + .await + .unwrap(); assert_eq!(album.asset_count(), 1); assert_eq!(album.entries[0].asset_id, asset_id); @@ -42,17 +48,23 @@ async fn removes_asset_from_album() { let asset_id = SystemId::new(); let handler = ManageAlbumEntriesHandler::new(repo.clone()); - handler.execute(ManageAlbumEntriesCommand { - album_id, - action: AlbumAction::Add { asset_id }, - user_id: user, - }).await.unwrap(); + handler + .execute(ManageAlbumEntriesCommand { + album_id, + action: AlbumAction::Add { asset_id }, + user_id: user, + }) + .await + .unwrap(); - let album = handler.execute(ManageAlbumEntriesCommand { - album_id, - action: AlbumAction::Remove { asset_id }, - user_id: user, - }).await.unwrap(); + let album = handler + .execute(ManageAlbumEntriesCommand { + album_id, + action: AlbumAction::Remove { asset_id }, + user_id: user, + }) + .await + .unwrap(); assert_eq!(album.asset_count(), 0); } @@ -61,11 +73,15 @@ async fn removes_asset_from_album() { async fn rejects_nonexistent_album() { let repo = Arc::new(InMemoryAlbumRepository::new()); let handler = ManageAlbumEntriesHandler::new(repo); - let result = handler.execute(ManageAlbumEntriesCommand { - album_id: SystemId::new(), - action: AlbumAction::Add { asset_id: SystemId::new() }, - user_id: SystemId::new(), - }).await; + let result = handler + .execute(ManageAlbumEntriesCommand { + album_id: SystemId::new(), + action: AlbumAction::Add { + asset_id: SystemId::new(), + }, + user_id: SystemId::new(), + }) + .await; assert!(matches!(result, Err(DomainError::NotFound(_)))); } @@ -77,16 +93,21 @@ async fn rejects_duplicate_add() { let asset_id = SystemId::new(); let handler = ManageAlbumEntriesHandler::new(repo.clone()); - handler.execute(ManageAlbumEntriesCommand { - album_id, - action: AlbumAction::Add { asset_id }, - user_id: user, - }).await.unwrap(); + handler + .execute(ManageAlbumEntriesCommand { + album_id, + action: AlbumAction::Add { asset_id }, + user_id: user, + }) + .await + .unwrap(); - let result = handler.execute(ManageAlbumEntriesCommand { - album_id, - action: AlbumAction::Add { asset_id }, - user_id: user, - }).await; + let result = handler + .execute(ManageAlbumEntriesCommand { + album_id, + action: AlbumAction::Add { asset_id }, + user_id: user, + }) + .await; assert!(matches!(result, Err(DomainError::Conflict(_)))); } diff --git a/crates/application/tests/organization/commands/tag_asset.rs b/crates/application/tests/organization/commands/tag_asset.rs index 63e8ba7..667674b 100644 --- a/crates/application/tests/organization/commands/tag_asset.rs +++ b/crates/application/tests/organization/commands/tag_asset.rs @@ -1,10 +1,10 @@ -use std::sync::Arc; -use application::testing::{InMemoryAssetRepository, InMemoryTagRepository}; use application::organization::{TagAssetCommand, TagAssetHandler}; +use application::testing::{InMemoryAssetRepository, InMemoryTagRepository}; use domain::entities::{Asset, AssetType, SourceReference}; use domain::errors::DomainError; use domain::ports::{AssetRepository, TagRepository}; use domain::value_objects::{Checksum, SystemId}; +use std::sync::Arc; async fn seed_asset(repo: &Arc) -> SystemId { let owner = SystemId::new(); @@ -32,11 +32,14 @@ async fn tags_asset_creates_new_tag() { let user = SystemId::new(); let handler = TagAssetHandler::new(asset_repo, tag_repo); - let (tag, asset_tag) = handler.execute(TagAssetCommand { - asset_id, - tag_name: "sunset".into(), - user_id: user, - }).await.unwrap(); + let (tag, asset_tag) = handler + .execute(TagAssetCommand { + asset_id, + tag_name: "sunset".into(), + user_id: user, + }) + .await + .unwrap(); assert_eq!(tag.name, "sunset"); assert_eq!(asset_tag.asset_id, asset_id); @@ -57,11 +60,14 @@ async fn reuses_existing_tag() { tag_repo.save_tag(&existing).await.unwrap(); let handler = TagAssetHandler::new(asset_repo, tag_repo); - let (tag, _) = handler.execute(TagAssetCommand { - asset_id, - tag_name: "sunset".into(), - user_id: user, - }).await.unwrap(); + let (tag, _) = handler + .execute(TagAssetCommand { + asset_id, + tag_name: "sunset".into(), + user_id: user, + }) + .await + .unwrap(); assert_eq!(tag.tag_id, existing_id); } @@ -72,10 +78,12 @@ async fn rejects_nonexistent_asset() { let tag_repo = Arc::new(InMemoryTagRepository::new()); let handler = TagAssetHandler::new(asset_repo, tag_repo); - let result = handler.execute(TagAssetCommand { - asset_id: SystemId::new(), - tag_name: "sunset".into(), - user_id: SystemId::new(), - }).await; + let result = handler + .execute(TagAssetCommand { + asset_id: SystemId::new(), + tag_name: "sunset".into(), + user_id: SystemId::new(), + }) + .await; assert!(matches!(result, Err(DomainError::NotFound(_)))); } diff --git a/crates/application/tests/organization/queries/get_album.rs b/crates/application/tests/organization/queries/get_album.rs index d2029b0..191cd51 100644 --- a/crates/application/tests/organization/queries/get_album.rs +++ b/crates/application/tests/organization/queries/get_album.rs @@ -1,11 +1,10 @@ -use std::sync::Arc; -use application::testing::InMemoryAlbumRepository; use application::organization::{ - CreateAlbumCommand, CreateAlbumHandler, - GetAlbumQuery, GetAlbumHandler, + CreateAlbumCommand, CreateAlbumHandler, GetAlbumHandler, GetAlbumQuery, }; +use application::testing::InMemoryAlbumRepository; use domain::errors::DomainError; use domain::value_objects::SystemId; +use std::sync::Arc; #[tokio::test] async fn returns_album() { @@ -13,15 +12,21 @@ async fn returns_album() { let creator = SystemId::new(); let create_handler = CreateAlbumHandler::new(repo.clone()); - let album = create_handler.execute(CreateAlbumCommand { - title: "My Album".into(), - creator_id: creator, - }).await.unwrap(); + let album = create_handler + .execute(CreateAlbumCommand { + title: "My Album".into(), + creator_id: creator, + }) + .await + .unwrap(); let query_handler = GetAlbumHandler::new(repo); - let found = query_handler.execute(GetAlbumQuery { - album_id: album.album_id, - }).await.unwrap(); + let found = query_handler + .execute(GetAlbumQuery { + album_id: album.album_id, + }) + .await + .unwrap(); assert_eq!(found.album_id, album.album_id); assert_eq!(found.title, "My Album"); @@ -31,8 +36,10 @@ async fn returns_album() { async fn rejects_nonexistent() { let repo = Arc::new(InMemoryAlbumRepository::new()); let handler = GetAlbumHandler::new(repo); - let result = handler.execute(GetAlbumQuery { - album_id: SystemId::new(), - }).await; + let result = handler + .execute(GetAlbumQuery { + album_id: SystemId::new(), + }) + .await; assert!(matches!(result, Err(DomainError::NotFound(_)))); } diff --git a/crates/application/tests/processing/commands/complete_job.rs b/crates/application/tests/processing/commands/complete_job.rs index 46bc060..7d7fbd8 100644 --- a/crates/application/tests/processing/commands/complete_job.rs +++ b/crates/application/tests/processing/commands/complete_job.rs @@ -1,10 +1,10 @@ -use std::sync::Arc; -use application::testing::{InMemoryJobBatchRepository, InMemoryJobRepository, StubEventPublisher}; use application::processing::{CompleteJobCommand, CompleteJobHandler}; +use application::testing::{InMemoryJobBatchRepository, InMemoryJobRepository, StubEventPublisher}; use domain::entities::{Job, JobBatch, JobStatus, JobType}; use domain::events::DomainEvent; use domain::ports::{JobBatchRepository, JobRepository}; use domain::value_objects::StructuredData; +use std::sync::Arc; #[tokio::test] async fn completes_job() { @@ -18,10 +18,13 @@ async fn completes_job() { job_repo.save(&job).await.unwrap(); let handler = CompleteJobHandler::new(job_repo.clone(), batch_repo.clone(), event_pub.clone()); - let result = handler.execute(CompleteJobCommand { - job_id, - result: StructuredData::new(), - }).await.unwrap(); + let result = handler + .execute(CompleteJobCommand { + job_id, + result: StructuredData::new(), + }) + .await + .unwrap(); assert_eq!(result.status, JobStatus::Completed); assert!(result.result_data.is_some()); @@ -37,17 +40,19 @@ async fn completes_job_and_updates_batch() { let batch_id = batch.batch_id; batch_repo.save(&batch).await.unwrap(); - let mut job = Job::new(JobType::ExtractMetadata, 5, StructuredData::new()) - .with_batch(batch_id); + let mut job = Job::new(JobType::ExtractMetadata, 5, StructuredData::new()).with_batch(batch_id); job.start().unwrap(); let job_id = job.job_id; job_repo.save(&job).await.unwrap(); let handler = CompleteJobHandler::new(job_repo.clone(), batch_repo.clone(), event_pub.clone()); - handler.execute(CompleteJobCommand { - job_id, - result: StructuredData::new(), - }).await.unwrap(); + handler + .execute(CompleteJobCommand { + job_id, + result: StructuredData::new(), + }) + .await + .unwrap(); let updated_batch = batch_repo.find_by_id(&batch_id).await.unwrap().unwrap(); assert_eq!(updated_batch.completed_count, 1); @@ -65,10 +70,13 @@ async fn publishes_event() { job_repo.save(&job).await.unwrap(); let handler = CompleteJobHandler::new(job_repo.clone(), batch_repo.clone(), event_pub.clone()); - handler.execute(CompleteJobCommand { - job_id, - result: StructuredData::new(), - }).await.unwrap(); + handler + .execute(CompleteJobCommand { + job_id, + result: StructuredData::new(), + }) + .await + .unwrap(); let events = event_pub.published().await; assert_eq!(events.len(), 1); diff --git a/crates/application/tests/processing/commands/configure_pipeline.rs b/crates/application/tests/processing/commands/configure_pipeline.rs index ad6f580..b39668e 100644 --- a/crates/application/tests/processing/commands/configure_pipeline.rs +++ b/crates/application/tests/processing/commands/configure_pipeline.rs @@ -1,10 +1,12 @@ -use std::sync::Arc; +use application::processing::{ + ConfigurePipelineCommand, ConfigurePipelineHandler, PipelineStepConfig, +}; use application::testing::{InMemoryPipelineRepository, InMemoryPluginRepository}; -use application::processing::{ConfigurePipelineCommand, ConfigurePipelineHandler, PipelineStepConfig}; use domain::entities::{Plugin, PluginType}; use domain::errors::DomainError; use domain::ports::PluginRepository; use domain::value_objects::{StructuredData, SystemId}; +use std::sync::Arc; #[tokio::test] async fn creates_pipeline() { @@ -19,13 +21,22 @@ async fn creates_pipeline() { plugin_repo.save(&p2).await.unwrap(); let handler = ConfigurePipelineHandler::new(pipeline_repo.clone(), plugin_repo.clone()); - let pipeline = handler.execute(ConfigurePipelineCommand { - trigger_event: "asset.ingested".into(), - steps: vec![ - PipelineStepConfig { plugin_id: p1_id, config: StructuredData::new() }, - PipelineStepConfig { plugin_id: p2_id, config: StructuredData::new() }, - ], - }).await.unwrap(); + let pipeline = handler + .execute(ConfigurePipelineCommand { + trigger_event: "asset.ingested".into(), + steps: vec![ + PipelineStepConfig { + plugin_id: p1_id, + config: StructuredData::new(), + }, + PipelineStepConfig { + plugin_id: p2_id, + config: StructuredData::new(), + }, + ], + }) + .await + .unwrap(); assert_eq!(pipeline.trigger_event, "asset.ingested"); assert_eq!(pipeline.steps.len(), 2); @@ -37,12 +48,15 @@ async fn rejects_nonexistent_plugin() { let plugin_repo = Arc::new(InMemoryPluginRepository::new()); let handler = ConfigurePipelineHandler::new(pipeline_repo.clone(), plugin_repo.clone()); - let result = handler.execute(ConfigurePipelineCommand { - trigger_event: "asset.ingested".into(), - steps: vec![ - PipelineStepConfig { plugin_id: SystemId::new(), config: StructuredData::new() }, - ], - }).await; + let result = handler + .execute(ConfigurePipelineCommand { + trigger_event: "asset.ingested".into(), + steps: vec![PipelineStepConfig { + plugin_id: SystemId::new(), + config: StructuredData::new(), + }], + }) + .await; assert!(matches!(result, Err(DomainError::NotFound(_)))); } diff --git a/crates/application/tests/processing/commands/enqueue_job.rs b/crates/application/tests/processing/commands/enqueue_job.rs index a3607f5..eaa252b 100644 --- a/crates/application/tests/processing/commands/enqueue_job.rs +++ b/crates/application/tests/processing/commands/enqueue_job.rs @@ -1,9 +1,9 @@ -use std::sync::Arc; -use application::testing::{InMemoryJobRepository, StubEventPublisher}; use application::processing::{EnqueueJobCommand, EnqueueJobHandler}; +use application::testing::{InMemoryJobRepository, StubEventPublisher}; use domain::entities::{JobStatus, JobType}; use domain::events::DomainEvent; use domain::value_objects::{StructuredData, SystemId}; +use std::sync::Arc; #[tokio::test] async fn enqueues_job() { @@ -11,13 +11,16 @@ async fn enqueues_job() { let event_pub = Arc::new(StubEventPublisher::new()); let handler = EnqueueJobHandler::new(job_repo.clone(), event_pub.clone()); - let job = handler.execute(EnqueueJobCommand { - job_type: JobType::ExtractMetadata, - priority: 5, - payload: StructuredData::new(), - target_asset_id: None, - batch_id: None, - }).await.unwrap(); + let job = handler + .execute(EnqueueJobCommand { + job_type: JobType::ExtractMetadata, + priority: 5, + payload: StructuredData::new(), + target_asset_id: None, + batch_id: None, + }) + .await + .unwrap(); assert_eq!(job.status, JobStatus::Queued); assert_eq!(job.priority, 5); @@ -33,13 +36,16 @@ async fn enqueues_with_target_and_batch() { let target = SystemId::new(); let batch = SystemId::new(); - let job = handler.execute(EnqueueJobCommand { - job_type: JobType::GenerateDerivative, - priority: 10, - payload: StructuredData::new(), - target_asset_id: Some(target), - batch_id: Some(batch), - }).await.unwrap(); + let job = handler + .execute(EnqueueJobCommand { + job_type: JobType::GenerateDerivative, + priority: 10, + payload: StructuredData::new(), + target_asset_id: Some(target), + batch_id: Some(batch), + }) + .await + .unwrap(); assert_eq!(job.target_asset_id, Some(target)); assert_eq!(job.batch_id, Some(batch)); @@ -51,13 +57,16 @@ async fn publishes_event() { let event_pub = Arc::new(StubEventPublisher::new()); let handler = EnqueueJobHandler::new(job_repo.clone(), event_pub.clone()); - let job = handler.execute(EnqueueJobCommand { - job_type: JobType::ScanDirectory, - priority: 1, - payload: StructuredData::new(), - target_asset_id: None, - batch_id: None, - }).await.unwrap(); + let job = handler + .execute(EnqueueJobCommand { + job_type: JobType::ScanDirectory, + priority: 1, + payload: StructuredData::new(), + target_asset_id: None, + batch_id: None, + }) + .await + .unwrap(); let events = event_pub.published().await; assert_eq!(events.len(), 1); diff --git a/crates/application/tests/processing/commands/fail_job.rs b/crates/application/tests/processing/commands/fail_job.rs index 9670c27..38d195f 100644 --- a/crates/application/tests/processing/commands/fail_job.rs +++ b/crates/application/tests/processing/commands/fail_job.rs @@ -1,10 +1,10 @@ -use std::sync::Arc; -use application::testing::{InMemoryJobBatchRepository, InMemoryJobRepository, StubEventPublisher}; use application::processing::{FailJobCommand, FailJobHandler}; +use application::testing::{InMemoryJobBatchRepository, InMemoryJobRepository, StubEventPublisher}; use domain::entities::{Job, JobBatch, JobStatus, JobType}; use domain::events::DomainEvent; use domain::ports::{JobBatchRepository, JobRepository}; use domain::value_objects::StructuredData; +use std::sync::Arc; fn make_handler( job_repo: Arc, @@ -26,10 +26,13 @@ async fn retries_on_failure() { job_repo.save(&job).await.unwrap(); let handler = make_handler(job_repo.clone(), batch_repo.clone(), event_pub.clone()); - let result = handler.execute(FailJobCommand { - job_id, - error: "transient error".into(), - }).await.unwrap(); + let result = handler + .execute(FailJobCommand { + job_id, + error: "transient error".into(), + }) + .await + .unwrap(); assert_eq!(result.status, JobStatus::Queued); assert_eq!(result.retry_count, 1); @@ -54,10 +57,13 @@ async fn fails_permanently_after_max_retries() { job_repo.save(&job).await.unwrap(); let handler = make_handler(job_repo.clone(), batch_repo.clone(), event_pub.clone()); - let result = handler.execute(FailJobCommand { - job_id, - error: "fatal".into(), - }).await.unwrap(); + let result = handler + .execute(FailJobCommand { + job_id, + error: "fatal".into(), + }) + .await + .unwrap(); assert_eq!(result.status, JobStatus::Failed); assert_eq!(result.retry_count, 3); @@ -76,8 +82,7 @@ async fn updates_batch_on_permanent_failure() { let batch_id = batch.batch_id; batch_repo.save(&batch).await.unwrap(); - let mut job = Job::new(JobType::ExtractMetadata, 5, StructuredData::new()) - .with_batch(batch_id); + let mut job = Job::new(JobType::ExtractMetadata, 5, StructuredData::new()).with_batch(batch_id); // Exhaust retries job.fail("err1"); job.fail("err2"); @@ -85,10 +90,13 @@ async fn updates_batch_on_permanent_failure() { job_repo.save(&job).await.unwrap(); let handler = make_handler(job_repo.clone(), batch_repo.clone(), event_pub.clone()); - handler.execute(FailJobCommand { - job_id, - error: "permanent failure".into(), - }).await.unwrap(); + handler + .execute(FailJobCommand { + job_id, + error: "permanent failure".into(), + }) + .await + .unwrap(); let updated_batch = batch_repo.find_by_id(&batch_id).await.unwrap().unwrap(); assert_eq!(updated_batch.failed_count, 1); diff --git a/crates/application/tests/processing/commands/manage_plugin.rs b/crates/application/tests/processing/commands/manage_plugin.rs index 98ada9a..5da8a65 100644 --- a/crates/application/tests/processing/commands/manage_plugin.rs +++ b/crates/application/tests/processing/commands/manage_plugin.rs @@ -1,23 +1,26 @@ -use std::sync::Arc; -use application::testing::InMemoryPluginRepository; use application::processing::{ManagePluginCommand, ManagePluginHandler, PluginAction}; +use application::testing::InMemoryPluginRepository; use domain::entities::{Plugin, PluginType}; use domain::ports::PluginRepository; use domain::value_objects::StructuredData; +use std::sync::Arc; #[tokio::test] async fn creates_plugin() { let plugin_repo = Arc::new(InMemoryPluginRepository::new()); let handler = ManagePluginHandler::new(plugin_repo.clone()); - let plugin = handler.execute(ManagePluginCommand { - plugin_id: None, - action: PluginAction::Create { - name: "EXIF Extractor".into(), - plugin_type: PluginType::MediaProcessor, - config: StructuredData::new(), - }, - }).await.unwrap(); + let plugin = handler + .execute(ManagePluginCommand { + plugin_id: None, + action: PluginAction::Create { + name: "EXIF Extractor".into(), + plugin_type: PluginType::MediaProcessor, + config: StructuredData::new(), + }, + }) + .await + .unwrap(); assert_eq!(plugin.name, "EXIF Extractor"); assert_eq!(plugin.plugin_type, PluginType::MediaProcessor); @@ -36,10 +39,13 @@ async fn enables_plugin() { plugin_repo.save(&plugin).await.unwrap(); let handler = ManagePluginHandler::new(plugin_repo.clone()); - let result = handler.execute(ManagePluginCommand { - plugin_id: Some(plugin_id), - action: PluginAction::Enable, - }).await.unwrap(); + let result = handler + .execute(ManagePluginCommand { + plugin_id: Some(plugin_id), + action: PluginAction::Enable, + }) + .await + .unwrap(); assert!(result.is_enabled); } @@ -52,10 +58,13 @@ async fn disables_plugin() { plugin_repo.save(&plugin).await.unwrap(); let handler = ManagePluginHandler::new(plugin_repo.clone()); - let result = handler.execute(ManagePluginCommand { - plugin_id: Some(plugin_id), - action: PluginAction::Disable, - }).await.unwrap(); + let result = handler + .execute(ManagePluginCommand { + plugin_id: Some(plugin_id), + action: PluginAction::Disable, + }) + .await + .unwrap(); assert!(!result.is_enabled); } diff --git a/crates/application/tests/processing/commands/mod.rs b/crates/application/tests/processing/commands/mod.rs index c0ca8cf..a701da5 100644 --- a/crates/application/tests/processing/commands/mod.rs +++ b/crates/application/tests/processing/commands/mod.rs @@ -1,6 +1,6 @@ -mod enqueue_job; -mod start_job; mod complete_job; +mod configure_pipeline; +mod enqueue_job; mod fail_job; mod manage_plugin; -mod configure_pipeline; +mod start_job; diff --git a/crates/application/tests/processing/commands/start_job.rs b/crates/application/tests/processing/commands/start_job.rs index 3ef12a3..17930cf 100644 --- a/crates/application/tests/processing/commands/start_job.rs +++ b/crates/application/tests/processing/commands/start_job.rs @@ -1,10 +1,10 @@ -use std::sync::Arc; -use application::testing::InMemoryJobRepository; use application::processing::{StartJobCommand, StartJobHandler}; +use application::testing::InMemoryJobRepository; use domain::entities::{Job, JobStatus, JobType}; use domain::errors::DomainError; use domain::ports::JobRepository; use domain::value_objects::StructuredData; +use std::sync::Arc; #[tokio::test] async fn starts_queued_job() { diff --git a/crates/application/tests/processing/queries/report_batch_progress.rs b/crates/application/tests/processing/queries/report_batch_progress.rs index 01ce26c..bda504a 100644 --- a/crates/application/tests/processing/queries/report_batch_progress.rs +++ b/crates/application/tests/processing/queries/report_batch_progress.rs @@ -1,10 +1,10 @@ -use std::sync::Arc; +use application::processing::{ReportBatchProgressHandler, ReportBatchProgressQuery}; use application::testing::{InMemoryJobBatchRepository, InMemoryJobRepository}; -use application::processing::{ReportBatchProgressQuery, ReportBatchProgressHandler}; use domain::entities::{Job, JobBatch, JobType}; use domain::errors::DomainError; use domain::ports::{JobBatchRepository, JobRepository}; use domain::value_objects::{StructuredData, SystemId}; +use std::sync::Arc; #[tokio::test] async fn returns_progress() { @@ -21,7 +21,10 @@ async fn returns_progress() { job_repo.save(&j2).await.unwrap(); let handler = ReportBatchProgressHandler::new(batch_repo.clone(), job_repo.clone()); - let progress = handler.execute(ReportBatchProgressQuery { batch_id }).await.unwrap(); + let progress = handler + .execute(ReportBatchProgressQuery { batch_id }) + .await + .unwrap(); assert_eq!(progress.batch.batch_id, batch_id); assert_eq!(progress.jobs.len(), 2); @@ -33,9 +36,11 @@ async fn rejects_nonexistent_batch() { let job_repo = Arc::new(InMemoryJobRepository::new()); let handler = ReportBatchProgressHandler::new(batch_repo.clone(), job_repo.clone()); - let result = handler.execute(ReportBatchProgressQuery { - batch_id: SystemId::new(), - }).await; + let result = handler + .execute(ReportBatchProgressQuery { + batch_id: SystemId::new(), + }) + .await; assert!(matches!(result, Err(DomainError::NotFound(_)))); } diff --git a/crates/application/tests/sharing/commands/generate_share_link.rs b/crates/application/tests/sharing/commands/generate_share_link.rs index c6390ac..9f30e8b 100644 --- a/crates/application/tests/sharing/commands/generate_share_link.rs +++ b/crates/application/tests/sharing/commands/generate_share_link.rs @@ -1,22 +1,25 @@ -use std::sync::Arc; -use application::testing::InMemoryShareRepository; use application::sharing::{GenerateShareLinkCommand, GenerateShareLinkHandler}; +use application::testing::InMemoryShareRepository; use domain::entities::{LinkAccessLevel, ScopeType, ShareableType}; use domain::value_objects::{DateTimeStamp, SystemId}; +use std::sync::Arc; #[tokio::test] async fn generates_link() { let share_repo = Arc::new(InMemoryShareRepository::new()); let handler = GenerateShareLinkHandler::new(share_repo); - let (scope, link) = handler.execute(GenerateShareLinkCommand { - shareable_type: ShareableType::Album, - shareable_id: SystemId::new(), - access_level: LinkAccessLevel::ViewOnly, - created_by: SystemId::new(), - expires_at: None, - max_uses: None, - }).await.unwrap(); + let (scope, link) = handler + .execute(GenerateShareLinkCommand { + shareable_type: ShareableType::Album, + shareable_id: SystemId::new(), + access_level: LinkAccessLevel::ViewOnly, + created_by: SystemId::new(), + expires_at: None, + max_uses: None, + }) + .await + .unwrap(); assert_eq!(scope.scope_type, ScopeType::Link); assert!(!link.token.is_empty()); @@ -31,14 +34,17 @@ async fn generates_link_with_expiry_and_max_uses() { let handler = GenerateShareLinkHandler::new(share_repo); let expiry = DateTimeStamp::now(); - let (_, link) = handler.execute(GenerateShareLinkCommand { - shareable_type: ShareableType::Collection, - shareable_id: SystemId::new(), - access_level: LinkAccessLevel::LimitedSearch, - created_by: SystemId::new(), - expires_at: Some(expiry), - max_uses: Some(10), - }).await.unwrap(); + let (_, link) = handler + .execute(GenerateShareLinkCommand { + shareable_type: ShareableType::Collection, + shareable_id: SystemId::new(), + access_level: LinkAccessLevel::LimitedSearch, + created_by: SystemId::new(), + expires_at: Some(expiry), + max_uses: Some(10), + }) + .await + .unwrap(); assert!(link.expires_at.is_some()); assert_eq!(link.max_uses, Some(10)); diff --git a/crates/application/tests/sharing/commands/mod.rs b/crates/application/tests/sharing/commands/mod.rs index 7f54552..37a6f3b 100644 --- a/crates/application/tests/sharing/commands/mod.rs +++ b/crates/application/tests/sharing/commands/mod.rs @@ -1,3 +1,3 @@ -mod share_resource; mod generate_share_link; mod revoke_share; +mod share_resource; diff --git a/crates/application/tests/sharing/commands/revoke_share.rs b/crates/application/tests/sharing/commands/revoke_share.rs index dc11f8e..df99be5 100644 --- a/crates/application/tests/sharing/commands/revoke_share.rs +++ b/crates/application/tests/sharing/commands/revoke_share.rs @@ -1,12 +1,11 @@ -use std::sync::Arc; -use application::testing::{InMemoryShareRepository, StubEventPublisher}; use application::sharing::{ - GenerateShareLinkCommand, GenerateShareLinkHandler, - RevokeShareCommand, RevokeShareHandler, + GenerateShareLinkCommand, GenerateShareLinkHandler, RevokeShareCommand, RevokeShareHandler, }; +use application::testing::{InMemoryShareRepository, StubEventPublisher}; use domain::entities::{LinkAccessLevel, ShareableType}; use domain::errors::DomainError; use domain::value_objects::SystemId; +use std::sync::Arc; #[tokio::test] async fn revokes_share() { @@ -15,20 +14,26 @@ async fn revokes_share() { // Create a scope first via generate_share_link let gen_handler = GenerateShareLinkHandler::new(share_repo.clone()); - let (scope, _) = gen_handler.execute(GenerateShareLinkCommand { - shareable_type: ShareableType::Album, - shareable_id: SystemId::new(), - access_level: LinkAccessLevel::ViewOnly, - created_by: SystemId::new(), - expires_at: None, - max_uses: None, - }).await.unwrap(); + let (scope, _) = gen_handler + .execute(GenerateShareLinkCommand { + shareable_type: ShareableType::Album, + shareable_id: SystemId::new(), + access_level: LinkAccessLevel::ViewOnly, + created_by: SystemId::new(), + expires_at: None, + max_uses: None, + }) + .await + .unwrap(); let handler = RevokeShareHandler::new(share_repo, event_pub.clone()); - handler.execute(RevokeShareCommand { - scope_id: scope.scope_id, - revoked_by: SystemId::new(), - }).await.unwrap(); + handler + .execute(RevokeShareCommand { + scope_id: scope.scope_id, + revoked_by: SystemId::new(), + }) + .await + .unwrap(); let events = event_pub.published().await; assert_eq!(events.len(), 1); @@ -40,9 +45,11 @@ async fn rejects_nonexistent_scope() { let event_pub = Arc::new(StubEventPublisher::new()); let handler = RevokeShareHandler::new(share_repo, event_pub); - let result = handler.execute(RevokeShareCommand { - scope_id: SystemId::new(), - revoked_by: SystemId::new(), - }).await; + let result = handler + .execute(RevokeShareCommand { + scope_id: SystemId::new(), + revoked_by: SystemId::new(), + }) + .await; assert!(matches!(result, Err(DomainError::NotFound(_)))); } diff --git a/crates/application/tests/sharing/commands/share_resource.rs b/crates/application/tests/sharing/commands/share_resource.rs index dbb3e0f..41c354e 100644 --- a/crates/application/tests/sharing/commands/share_resource.rs +++ b/crates/application/tests/sharing/commands/share_resource.rs @@ -1,8 +1,8 @@ -use std::sync::Arc; -use application::testing::{InMemoryShareRepository, StubEventPublisher}; use application::sharing::{ShareResourceCommand, ShareResourceHandler}; +use application::testing::{InMemoryShareRepository, StubEventPublisher}; use domain::entities::{ScopeType, ShareableType, TargetType}; use domain::value_objects::SystemId; +use std::sync::Arc; #[tokio::test] async fn shares_with_user() { @@ -10,14 +10,17 @@ async fn shares_with_user() { let event_pub = Arc::new(StubEventPublisher::new()); let handler = ShareResourceHandler::new(share_repo, event_pub.clone()); - let (scope, target) = handler.execute(ShareResourceCommand { - shareable_type: ShareableType::Album, - shareable_id: SystemId::new(), - target_type: TargetType::User, - target_id: SystemId::new(), - role_id: SystemId::new(), - created_by: SystemId::new(), - }).await.unwrap(); + let (scope, target) = handler + .execute(ShareResourceCommand { + shareable_type: ShareableType::Album, + shareable_id: SystemId::new(), + target_type: TargetType::User, + target_id: SystemId::new(), + role_id: SystemId::new(), + created_by: SystemId::new(), + }) + .await + .unwrap(); assert_eq!(scope.scope_type, ScopeType::User); assert_eq!(target.target_type, TargetType::User); @@ -33,14 +36,17 @@ async fn shares_with_group() { let event_pub = Arc::new(StubEventPublisher::new()); let handler = ShareResourceHandler::new(share_repo, event_pub.clone()); - let (scope, target) = handler.execute(ShareResourceCommand { - shareable_type: ShareableType::Asset, - shareable_id: SystemId::new(), - target_type: TargetType::Group, - target_id: SystemId::new(), - role_id: SystemId::new(), - created_by: SystemId::new(), - }).await.unwrap(); + let (scope, target) = handler + .execute(ShareResourceCommand { + shareable_type: ShareableType::Asset, + shareable_id: SystemId::new(), + target_type: TargetType::Group, + target_id: SystemId::new(), + role_id: SystemId::new(), + created_by: SystemId::new(), + }) + .await + .unwrap(); assert_eq!(scope.scope_type, ScopeType::Group); assert_eq!(target.target_type, TargetType::Group); diff --git a/crates/application/tests/sharing/queries/access_shared_resource.rs b/crates/application/tests/sharing/queries/access_shared_resource.rs index 5d49c5b..1bb0f07 100644 --- a/crates/application/tests/sharing/queries/access_shared_resource.rs +++ b/crates/application/tests/sharing/queries/access_shared_resource.rs @@ -1,13 +1,13 @@ -use std::sync::Arc; -use application::testing::InMemoryShareRepository; use application::sharing::{ - AccessSharedResourceQuery, AccessSharedResourceHandler, - GenerateShareLinkCommand, GenerateShareLinkHandler, + AccessSharedResourceHandler, AccessSharedResourceQuery, GenerateShareLinkCommand, + GenerateShareLinkHandler, }; +use application::testing::InMemoryShareRepository; use chrono::{DateTime, Utc}; use domain::entities::{LinkAccessLevel, ShareableType}; use domain::errors::DomainError; use domain::value_objects::{DateTimeStamp, SystemId}; +use std::sync::Arc; async fn create_link( repo: &Arc, @@ -15,14 +15,17 @@ async fn create_link( max_uses: Option, ) -> String { let handler = GenerateShareLinkHandler::new(repo.clone()); - let (_, link) = handler.execute(GenerateShareLinkCommand { - shareable_type: ShareableType::Album, - shareable_id: SystemId::new(), - access_level: LinkAccessLevel::ViewOnly, - created_by: SystemId::new(), - expires_at, - max_uses, - }).await.unwrap(); + let (_, link) = handler + .execute(GenerateShareLinkCommand { + shareable_type: ShareableType::Album, + shareable_id: SystemId::new(), + access_level: LinkAccessLevel::ViewOnly, + created_by: SystemId::new(), + expires_at, + max_uses, + }) + .await + .unwrap(); link.token } @@ -32,9 +35,10 @@ async fn valid_link_returns_scope() { let token = create_link(&repo, None, None).await; let handler = AccessSharedResourceHandler::new(repo); - let (scope, access_level) = handler.execute(AccessSharedResourceQuery { - token, - }).await.unwrap(); + let (scope, access_level) = handler + .execute(AccessSharedResourceQuery { token }) + .await + .unwrap(); assert_eq!(access_level, LinkAccessLevel::ViewOnly); assert_eq!(scope.shareable_type, ShareableType::Album); @@ -59,7 +63,12 @@ async fn exhausted_link_rejected() { // Use it once let handler = AccessSharedResourceHandler::new(repo.clone()); - handler.execute(AccessSharedResourceQuery { token: token.clone() }).await.unwrap(); + handler + .execute(AccessSharedResourceQuery { + token: token.clone(), + }) + .await + .unwrap(); // Second use should fail let result = handler.execute(AccessSharedResourceQuery { token }).await; diff --git a/crates/application/tests/sidecar/commands/detect_external_changes.rs b/crates/application/tests/sidecar/commands/detect_external_changes.rs index 7b752b6..63806b4 100644 --- a/crates/application/tests/sidecar/commands/detect_external_changes.rs +++ b/crates/application/tests/sidecar/commands/detect_external_changes.rs @@ -1,10 +1,10 @@ -use std::sync::Arc; -use application::sidecar::{DetectExternalChangesCommand, DetectExternalChangesHandler}; use application::sidecar::hash_helper::hash_structured_data; +use application::sidecar::{DetectExternalChangesCommand, DetectExternalChangesHandler}; use application::testing::{InMemorySidecarRepository, InMemorySidecarWriter}; use domain::entities::{SidecarRecord, SyncStatus}; use domain::ports::{SidecarRepository, SidecarWriterPort}; use domain::value_objects::{MetadataValue, StructuredData, SystemId}; +use std::sync::Arc; #[tokio::test] async fn detects_changed_sidecar() { @@ -31,7 +31,11 @@ async fn detects_changed_sidecar() { let changed = handler.execute(DetectExternalChangesCommand).await.unwrap(); assert_eq!(changed, 1); - let updated = sidecar_repo.find_by_asset(&asset_id).await.unwrap().unwrap(); + let updated = sidecar_repo + .find_by_asset(&asset_id) + .await + .unwrap() + .unwrap(); assert_eq!(updated.sync_status, SyncStatus::PendingRead); } @@ -58,6 +62,10 @@ async fn ignores_unchanged_sidecar() { let changed = handler.execute(DetectExternalChangesCommand).await.unwrap(); assert_eq!(changed, 0); - let updated = sidecar_repo.find_by_asset(&asset_id).await.unwrap().unwrap(); + let updated = sidecar_repo + .find_by_asset(&asset_id) + .await + .unwrap() + .unwrap(); assert_eq!(updated.sync_status, SyncStatus::InSync); } diff --git a/crates/application/tests/sidecar/commands/export_sidecar.rs b/crates/application/tests/sidecar/commands/export_sidecar.rs index 62c760b..2f6af88 100644 --- a/crates/application/tests/sidecar/commands/export_sidecar.rs +++ b/crates/application/tests/sidecar/commands/export_sidecar.rs @@ -1,10 +1,12 @@ -use std::sync::Arc; use application::sidecar::{ExportSidecarCommand, ExportSidecarHandler}; -use application::testing::{InMemoryAssetMetadataRepository, InMemorySidecarRepository, InMemorySidecarWriter}; +use application::testing::{ + InMemoryAssetMetadataRepository, InMemorySidecarRepository, InMemorySidecarWriter, +}; use domain::catalog::entities::{AssetMetadata, MetadataSource}; use domain::entities::SyncStatus; use domain::ports::SidecarRepository; use domain::value_objects::{MetadataValue, StructuredData, SystemId}; +use std::sync::Arc; #[tokio::test] async fn exports_sidecar_marks_in_sync() { @@ -20,7 +22,10 @@ async fn exports_sidecar_marks_in_sync() { meta_repo.save(&metadata).await.unwrap(); let handler = ExportSidecarHandler::new(meta_repo, sidecar_repo.clone(), writer.clone()); - let record = handler.execute(ExportSidecarCommand { asset_id }).await.unwrap(); + let record = handler + .execute(ExportSidecarCommand { asset_id }) + .await + .unwrap(); assert_eq!(record.sync_status, SyncStatus::InSync); assert!(record.last_known_file_hash.is_some()); @@ -39,7 +44,10 @@ async fn creates_new_record_if_none_exists() { let asset_id = SystemId::new(); let handler = ExportSidecarHandler::new(meta_repo, sidecar_repo.clone(), writer); - let record = handler.execute(ExportSidecarCommand { asset_id }).await.unwrap(); + let record = handler + .execute(ExportSidecarCommand { asset_id }) + .await + .unwrap(); assert_eq!(record.asset_id, asset_id); assert_eq!(record.sync_status, SyncStatus::InSync); diff --git a/crates/application/tests/sidecar/commands/full_export.rs b/crates/application/tests/sidecar/commands/full_export.rs index 136e51d..9bbb969 100644 --- a/crates/application/tests/sidecar/commands/full_export.rs +++ b/crates/application/tests/sidecar/commands/full_export.rs @@ -1,12 +1,12 @@ -use std::sync::Arc; use application::sidecar::{FullExportCommand, FullExportHandler}; use application::testing::{ - InMemoryAssetRepository, InMemoryAssetMetadataRepository, - InMemorySidecarRepository, InMemorySidecarWriter, + InMemoryAssetMetadataRepository, InMemoryAssetRepository, InMemorySidecarRepository, + InMemorySidecarWriter, }; use domain::catalog::entities::{Asset, AssetMetadata, AssetType, MetadataSource, SourceReference}; use domain::ports::AssetRepository; use domain::value_objects::{Checksum, MetadataValue, StructuredData, SystemId}; +use std::sync::Arc; fn make_asset(owner: SystemId) -> Asset { let source = SourceReference { @@ -33,10 +33,20 @@ async fn exports_all_user_assets() { let mut data = StructuredData::new(); data.insert("title", MetadataValue::String("Sunset".into())); use domain::ports::AssetMetadataRepository; - meta_repo.save(&AssetMetadata::new(a1.asset_id, MetadataSource::UserEdited, data)).await.unwrap(); + meta_repo + .save(&AssetMetadata::new( + a1.asset_id, + MetadataSource::UserEdited, + data, + )) + .await + .unwrap(); let handler = FullExportHandler::new(asset_repo, meta_repo, sidecar_repo, writer.clone()); - let count = handler.execute(FullExportCommand { owner_id: owner }).await.unwrap(); + let count = handler + .execute(FullExportCommand { owner_id: owner }) + .await + .unwrap(); assert_eq!(count, 2); diff --git a/crates/application/tests/sidecar/commands/full_import.rs b/crates/application/tests/sidecar/commands/full_import.rs index 9ff0a0f..d4815b8 100644 --- a/crates/application/tests/sidecar/commands/full_import.rs +++ b/crates/application/tests/sidecar/commands/full_import.rs @@ -1,13 +1,15 @@ -use std::sync::Arc; use application::sidecar::{FullImportCommand, FullImportHandler}; use application::testing::{ - InMemoryAssetRepository, InMemoryAssetMetadataRepository, - InMemorySidecarRepository, InMemorySidecarWriter, + InMemoryAssetMetadataRepository, InMemoryAssetRepository, InMemorySidecarRepository, + InMemorySidecarWriter, }; use domain::catalog::entities::{Asset, AssetType, MetadataSource, SourceReference}; use domain::entities::SidecarRecord; -use domain::ports::{AssetMetadataRepository, AssetRepository, SidecarRepository, SidecarWriterPort}; +use domain::ports::{ + AssetMetadataRepository, AssetRepository, SidecarRepository, SidecarWriterPort, +}; use domain::value_objects::{Checksum, MetadataValue, StructuredData, SystemId}; +use std::sync::Arc; fn make_asset(owner: SystemId) -> Asset { let source = SourceReference { @@ -38,10 +40,16 @@ async fn imports_from_existing_sidecars() { writer.write_sidecar(&data, &path).await.unwrap(); let handler = FullImportHandler::new(asset_repo, meta_repo.clone(), sidecar_repo, writer); - let count = handler.execute(FullImportCommand { owner_id: owner }).await.unwrap(); + let count = handler + .execute(FullImportCommand { owner_id: owner }) + .await + .unwrap(); assert_eq!(count, 1); - let imported = meta_repo.find_by_asset_and_source(&asset.asset_id, MetadataSource::ExifExtracted).await.unwrap(); + let imported = meta_repo + .find_by_asset_and_source(&asset.asset_id, MetadataSource::ExifExtracted) + .await + .unwrap(); assert!(imported.is_some()); assert_eq!(imported.unwrap().data.get_string("lens"), Some("50mm")); } @@ -59,7 +67,10 @@ async fn skips_missing_sidecars() { // No sidecar record, no sidecar file let handler = FullImportHandler::new(asset_repo, meta_repo, sidecar_repo, writer); - let count = handler.execute(FullImportCommand { owner_id: owner }).await.unwrap(); + let count = handler + .execute(FullImportCommand { owner_id: owner }) + .await + .unwrap(); assert_eq!(count, 0); } diff --git a/crates/application/tests/sidecar/commands/import_sidecar.rs b/crates/application/tests/sidecar/commands/import_sidecar.rs index dc2e608..788d7dd 100644 --- a/crates/application/tests/sidecar/commands/import_sidecar.rs +++ b/crates/application/tests/sidecar/commands/import_sidecar.rs @@ -1,11 +1,13 @@ -use std::sync::Arc; use application::sidecar::{ImportSidecarCommand, ImportSidecarHandler}; -use application::testing::{InMemoryAssetMetadataRepository, InMemorySidecarRepository, InMemorySidecarWriter}; +use application::testing::{ + InMemoryAssetMetadataRepository, InMemorySidecarRepository, InMemorySidecarWriter, +}; use domain::catalog::entities::MetadataSource; use domain::entities::{SidecarRecord, SyncStatus}; use domain::errors::DomainError; use domain::ports::{SidecarRepository, SidecarWriterPort}; use domain::value_objects::{MetadataValue, StructuredData, SystemId}; +use std::sync::Arc; #[tokio::test] async fn imports_pending_read_sidecar() { @@ -27,12 +29,19 @@ async fn imports_pending_read_sidecar() { writer.write_sidecar(&data, &path).await.unwrap(); let handler = ImportSidecarHandler::new(sidecar_repo.clone(), writer, meta_repo); - let metadata = handler.execute(ImportSidecarCommand { asset_id }).await.unwrap(); + let metadata = handler + .execute(ImportSidecarCommand { asset_id }) + .await + .unwrap(); assert_eq!(metadata.metadata_source, MetadataSource::ExifExtracted); assert_eq!(metadata.data.get_string("camera"), Some("Canon")); - let updated = sidecar_repo.find_by_asset(&asset_id).await.unwrap().unwrap(); + let updated = sidecar_repo + .find_by_asset(&asset_id) + .await + .unwrap() + .unwrap(); assert_eq!(updated.sync_status, SyncStatus::InSync); } diff --git a/crates/application/tests/sidecar/commands/mod.rs b/crates/application/tests/sidecar/commands/mod.rs index c403682..e95274f 100644 --- a/crates/application/tests/sidecar/commands/mod.rs +++ b/crates/application/tests/sidecar/commands/mod.rs @@ -1,6 +1,6 @@ -mod export_sidecar; mod detect_external_changes; -mod import_sidecar; -mod resolve_conflict; +mod export_sidecar; mod full_export; mod full_import; +mod import_sidecar; +mod resolve_conflict; diff --git a/crates/application/tests/sidecar/commands/resolve_conflict.rs b/crates/application/tests/sidecar/commands/resolve_conflict.rs index 733dd7d..c336a7d 100644 --- a/crates/application/tests/sidecar/commands/resolve_conflict.rs +++ b/crates/application/tests/sidecar/commands/resolve_conflict.rs @@ -1,11 +1,13 @@ -use std::sync::Arc; use application::sidecar::{ResolveConflictCommand, ResolveConflictHandler}; -use application::testing::{InMemoryAssetMetadataRepository, InMemorySidecarRepository, InMemorySidecarWriter}; +use application::testing::{ + InMemoryAssetMetadataRepository, InMemorySidecarRepository, InMemorySidecarWriter, +}; use domain::catalog::entities::{AssetMetadata, MetadataSource}; use domain::entities::{ConflictPolicy, SidecarRecord, SyncStatus}; use domain::errors::DomainError; use domain::ports::{AssetMetadataRepository, SidecarRepository, SidecarWriterPort}; use domain::value_objects::{MetadataValue, StructuredData, SystemId}; +use std::sync::Arc; fn conflict_record(asset_id: SystemId, path: &str) -> SidecarRecord { let mut r = SidecarRecord::new(asset_id, path); @@ -22,17 +24,30 @@ async fn db_wins_re_exports() { let asset_id = SystemId::new(); let path = format!("sidecars/{}.xmp", asset_id); - sidecar_repo.save(&conflict_record(asset_id, &path)).await.unwrap(); + sidecar_repo + .save(&conflict_record(asset_id, &path)) + .await + .unwrap(); let mut data = StructuredData::new(); data.insert("title", MetadataValue::String("DB Value".into())); - meta_repo.save(&AssetMetadata::new(asset_id, MetadataSource::UserEdited, data)).await.unwrap(); + meta_repo + .save(&AssetMetadata::new( + asset_id, + MetadataSource::UserEdited, + data, + )) + .await + .unwrap(); let handler = ResolveConflictHandler::new(sidecar_repo.clone(), writer.clone(), meta_repo); - let record = handler.execute(ResolveConflictCommand { - asset_id, - policy: ConflictPolicy::DbWins, - }).await.unwrap(); + let record = handler + .execute(ResolveConflictCommand { + asset_id, + policy: ConflictPolicy::DbWins, + }) + .await + .unwrap(); assert_eq!(record.sync_status, SyncStatus::InSync); let written = writer.get(&path).await.unwrap(); @@ -48,22 +63,34 @@ async fn file_wins_re_imports() { let asset_id = SystemId::new(); let path = format!("sidecars/{}.xmp", asset_id); - sidecar_repo.save(&conflict_record(asset_id, &path)).await.unwrap(); + sidecar_repo + .save(&conflict_record(asset_id, &path)) + .await + .unwrap(); let mut file_data = StructuredData::new(); file_data.insert("title", MetadataValue::String("File Value".into())); writer.write_sidecar(&file_data, &path).await.unwrap(); let handler = ResolveConflictHandler::new(sidecar_repo.clone(), writer, meta_repo.clone()); - let record = handler.execute(ResolveConflictCommand { - asset_id, - policy: ConflictPolicy::FileWins, - }).await.unwrap(); + let record = handler + .execute(ResolveConflictCommand { + asset_id, + policy: ConflictPolicy::FileWins, + }) + .await + .unwrap(); assert_eq!(record.sync_status, SyncStatus::InSync); - let imported = meta_repo.find_by_asset_and_source(&asset_id, MetadataSource::ExifExtracted).await.unwrap(); + let imported = meta_repo + .find_by_asset_and_source(&asset_id, MetadataSource::ExifExtracted) + .await + .unwrap(); assert!(imported.is_some()); - assert_eq!(imported.unwrap().data.get_string("title"), Some("File Value")); + assert_eq!( + imported.unwrap().data.get_string("title"), + Some("File Value") + ); } #[tokio::test] @@ -73,13 +100,18 @@ async fn user_decision_returns_error() { let meta_repo = Arc::new(InMemoryAssetMetadataRepository::new()); let asset_id = SystemId::new(); - sidecar_repo.save(&conflict_record(asset_id, "sidecars/x.xmp")).await.unwrap(); + sidecar_repo + .save(&conflict_record(asset_id, "sidecars/x.xmp")) + .await + .unwrap(); let handler = ResolveConflictHandler::new(sidecar_repo, writer, meta_repo); - let result = handler.execute(ResolveConflictCommand { - asset_id, - policy: ConflictPolicy::RequireUserDecision, - }).await; + let result = handler + .execute(ResolveConflictCommand { + asset_id, + policy: ConflictPolicy::RequireUserDecision, + }) + .await; assert!(matches!(result, Err(DomainError::Validation(msg)) if msg.contains("Manual"))); } diff --git a/crates/application/tests/storage/commands/ingest_asset.rs b/crates/application/tests/storage/commands/ingest_asset.rs index 90ed05c..1122fc9 100644 --- a/crates/application/tests/storage/commands/ingest_asset.rs +++ b/crates/application/tests/storage/commands/ingest_asset.rs @@ -1,20 +1,18 @@ -use std::sync::Arc; -use bytes::Bytes; -use application::testing::{ - InMemoryAssetRepository, InMemoryIngestSessionRepository, - InMemoryLibraryPathRepository, InMemoryQuotaRepository, - InMemoryStorageVolumeRepository, InMemoryUsageLedgerRepository, - InMemoryFileStorage, StubEventPublisher, -}; use application::storage::{ - IngestAssetCommand, IngestAssetHandler, + IngestAssetCommand, IngestAssetHandler, RegisterLibraryPathCommand, RegisterLibraryPathHandler, RegisterVolumeCommand, RegisterVolumeHandler, - RegisterLibraryPathCommand, RegisterLibraryPathHandler, }; +use application::testing::{ + InMemoryAssetRepository, InMemoryFileStorage, InMemoryIngestSessionRepository, + InMemoryLibraryPathRepository, InMemoryQuotaRepository, InMemoryStorageVolumeRepository, + InMemoryUsageLedgerRepository, StubEventPublisher, +}; +use bytes::Bytes; use domain::entities::{IngestStatus, QuotaDefinition, TimePeriod, UsageType}; use domain::errors::DomainError; use domain::ports::QuotaRepository; use domain::value_objects::SystemId; +use std::sync::Arc; struct Harness { ingest_repo: Arc, @@ -55,19 +53,26 @@ impl Harness { async fn setup_volume_and_path(&self, owner: SystemId) -> SystemId { let vol_handler = RegisterVolumeHandler::new(self.vol_repo.clone()); - let vol = vol_handler.execute(RegisterVolumeCommand { - volume_name: "main".into(), - uri_prefix: "file:///data".into(), - is_writable: true, - }).await.unwrap(); + let vol = vol_handler + .execute(RegisterVolumeCommand { + volume_name: "main".into(), + uri_prefix: "file:///data".into(), + is_writable: true, + }) + .await + .unwrap(); - let path_handler = RegisterLibraryPathHandler::new(self.vol_repo.clone(), self.path_repo.clone()); - let path = path_handler.execute(RegisterLibraryPathCommand { - volume_id: vol.volume_id, - relative_path: "photos/inbox".into(), - owner_id: owner, - is_ingest_destination: true, - }).await.unwrap(); + let path_handler = + RegisterLibraryPathHandler::new(self.vol_repo.clone(), self.path_repo.clone()); + let path = path_handler + .execute(RegisterLibraryPathCommand { + volume_id: vol.volume_id, + relative_path: "photos/inbox".into(), + owner_id: owner, + is_ingest_destination: true, + }) + .await + .unwrap(); path.path_id } } @@ -83,15 +88,18 @@ async fn ingests_successfully() { let path_id = h.setup_volume_and_path(user).await; let handler = h.ingest_handler(); - let (asset, session) = handler.execute(IngestAssetCommand { - uploader_id: user, - client_device_id: "iphone-1".into(), - filename: "photo.jpg".into(), - checksum: valid_checksum(), - target_path_id: path_id, - file_size: 1024, - data: Bytes::from(vec![0u8; 1024]), - }).await.unwrap(); + let (asset, session) = handler + .execute(IngestAssetCommand { + uploader_id: user, + client_device_id: "iphone-1".into(), + filename: "photo.jpg".into(), + checksum: valid_checksum(), + target_path_id: path_id, + file_size: 1024, + data: Bytes::from(vec![0u8; 1024]), + }) + .await + .unwrap(); assert_eq!(asset.mime_type, "image/jpeg"); assert_eq!(asset.file_size, 1024); @@ -111,15 +119,17 @@ async fn rejects_quota_exceeded() { h.quota_repo.save("a).await.unwrap(); let handler = h.ingest_handler(); - let result = handler.execute(IngestAssetCommand { - uploader_id: user, - client_device_id: "iphone-1".into(), - filename: "big.jpg".into(), - checksum: valid_checksum(), - target_path_id: path_id, - file_size: 1024, - data: Bytes::from(vec![0u8; 1024]), - }).await; + let result = handler + .execute(IngestAssetCommand { + uploader_id: user, + client_device_id: "iphone-1".into(), + filename: "big.jpg".into(), + checksum: valid_checksum(), + target_path_id: path_id, + file_size: 1024, + data: Bytes::from(vec![0u8; 1024]), + }) + .await; assert!(matches!(result, Err(DomainError::QuotaExceeded(_)))); } @@ -131,15 +141,17 @@ async fn rejects_invalid_checksum() { let path_id = h.setup_volume_and_path(user).await; let handler = h.ingest_handler(); - let result = handler.execute(IngestAssetCommand { - uploader_id: user, - client_device_id: "iphone-1".into(), - filename: "photo.jpg".into(), - checksum: "tooshort".into(), - target_path_id: path_id, - file_size: 1024, - data: Bytes::from(vec![0u8; 1024]), - }).await; + let result = handler + .execute(IngestAssetCommand { + uploader_id: user, + client_device_id: "iphone-1".into(), + filename: "photo.jpg".into(), + checksum: "tooshort".into(), + target_path_id: path_id, + file_size: 1024, + data: Bytes::from(vec![0u8; 1024]), + }) + .await; assert!(matches!(result, Err(DomainError::Validation(_)))); } @@ -151,11 +163,14 @@ async fn rejects_non_ingest_path() { // Create volume + non-ingest path directly let vol_handler = RegisterVolumeHandler::new(h.vol_repo.clone()); - let vol = vol_handler.execute(RegisterVolumeCommand { - volume_name: "main".into(), - uri_prefix: "file:///data".into(), - is_writable: true, - }).await.unwrap(); + let vol = vol_handler + .execute(RegisterVolumeCommand { + volume_name: "main".into(), + uri_prefix: "file:///data".into(), + is_writable: true, + }) + .await + .unwrap(); let path = domain::entities::LibraryPath::new_user_owned( vol.volume_id, @@ -167,15 +182,17 @@ async fn rejects_non_ingest_path() { h.path_repo.save(&path).await.unwrap(); let handler = h.ingest_handler(); - let result = handler.execute(IngestAssetCommand { - uploader_id: user, - client_device_id: "iphone-1".into(), - filename: "photo.jpg".into(), - checksum: valid_checksum(), - target_path_id: path.path_id, - file_size: 1024, - data: Bytes::from(vec![0u8; 1024]), - }).await; + let result = handler + .execute(IngestAssetCommand { + uploader_id: user, + client_device_id: "iphone-1".into(), + filename: "photo.jpg".into(), + checksum: valid_checksum(), + target_path_id: path.path_id, + file_size: 1024, + data: Bytes::from(vec![0u8; 1024]), + }) + .await; assert!(matches!(result, Err(DomainError::Validation(_)))); } diff --git a/crates/application/tests/storage/commands/mod.rs b/crates/application/tests/storage/commands/mod.rs index c15fb27..0cf8fd8 100644 --- a/crates/application/tests/storage/commands/mod.rs +++ b/crates/application/tests/storage/commands/mod.rs @@ -1,3 +1,3 @@ -mod register_volume; -mod register_library_path; mod ingest_asset; +mod register_library_path; +mod register_volume; diff --git a/crates/application/tests/storage/commands/register_library_path.rs b/crates/application/tests/storage/commands/register_library_path.rs index e02635f..82d354e 100644 --- a/crates/application/tests/storage/commands/register_library_path.rs +++ b/crates/application/tests/storage/commands/register_library_path.rs @@ -1,8 +1,11 @@ -use std::sync::Arc; -use application::testing::{InMemoryStorageVolumeRepository, InMemoryLibraryPathRepository}; -use application::storage::{RegisterVolumeCommand, RegisterVolumeHandler, RegisterLibraryPathCommand, RegisterLibraryPathHandler}; +use application::storage::{ + RegisterLibraryPathCommand, RegisterLibraryPathHandler, RegisterVolumeCommand, + RegisterVolumeHandler, +}; +use application::testing::{InMemoryLibraryPathRepository, InMemoryStorageVolumeRepository}; use domain::errors::DomainError; use domain::value_objects::SystemId; +use std::sync::Arc; #[tokio::test] async fn creates_path() { @@ -10,20 +13,26 @@ async fn creates_path() { let path_repo = Arc::new(InMemoryLibraryPathRepository::new()); let vol_handler = RegisterVolumeHandler::new(vol_repo.clone()); - let vol = vol_handler.execute(RegisterVolumeCommand { - volume_name: "main".into(), - uri_prefix: "file:///data".into(), - is_writable: true, - }).await.unwrap(); + let vol = vol_handler + .execute(RegisterVolumeCommand { + volume_name: "main".into(), + uri_prefix: "file:///data".into(), + is_writable: true, + }) + .await + .unwrap(); let handler = RegisterLibraryPathHandler::new(vol_repo, path_repo); let owner = SystemId::new(); - let path = handler.execute(RegisterLibraryPathCommand { - volume_id: vol.volume_id, - relative_path: "photos/inbox".into(), - owner_id: owner, - is_ingest_destination: true, - }).await.unwrap(); + let path = handler + .execute(RegisterLibraryPathCommand { + volume_id: vol.volume_id, + relative_path: "photos/inbox".into(), + owner_id: owner, + is_ingest_destination: true, + }) + .await + .unwrap(); assert_eq!(path.volume_id, vol.volume_id); assert_eq!(path.relative_path, "photos/inbox"); @@ -37,11 +46,13 @@ async fn rejects_nonexistent_volume() { let path_repo = Arc::new(InMemoryLibraryPathRepository::new()); let handler = RegisterLibraryPathHandler::new(vol_repo, path_repo); - let result = handler.execute(RegisterLibraryPathCommand { - volume_id: SystemId::new(), - relative_path: "photos/inbox".into(), - owner_id: SystemId::new(), - is_ingest_destination: true, - }).await; + let result = handler + .execute(RegisterLibraryPathCommand { + volume_id: SystemId::new(), + relative_path: "photos/inbox".into(), + owner_id: SystemId::new(), + is_ingest_destination: true, + }) + .await; assert!(matches!(result, Err(DomainError::NotFound(_)))); } diff --git a/crates/application/tests/storage/commands/register_volume.rs b/crates/application/tests/storage/commands/register_volume.rs index 602fa53..d6baee5 100644 --- a/crates/application/tests/storage/commands/register_volume.rs +++ b/crates/application/tests/storage/commands/register_volume.rs @@ -1,17 +1,20 @@ -use std::sync::Arc; -use application::testing::InMemoryStorageVolumeRepository; use application::storage::{RegisterVolumeCommand, RegisterVolumeHandler}; +use application::testing::InMemoryStorageVolumeRepository; use domain::errors::DomainError; +use std::sync::Arc; #[tokio::test] async fn creates_volume() { let repo = Arc::new(InMemoryStorageVolumeRepository::new()); let handler = RegisterVolumeHandler::new(repo); - let vol = handler.execute(RegisterVolumeCommand { - volume_name: "primary".into(), - uri_prefix: "file:///data".into(), - is_writable: true, - }).await.unwrap(); + let vol = handler + .execute(RegisterVolumeCommand { + volume_name: "primary".into(), + uri_prefix: "file:///data".into(), + is_writable: true, + }) + .await + .unwrap(); assert_eq!(vol.volume_name, "primary"); assert_eq!(vol.uri_prefix, "file:///data"); assert!(vol.is_writable); @@ -21,10 +24,12 @@ async fn creates_volume() { async fn rejects_empty_name() { let repo = Arc::new(InMemoryStorageVolumeRepository::new()); let handler = RegisterVolumeHandler::new(repo); - let result = handler.execute(RegisterVolumeCommand { - volume_name: "".into(), - uri_prefix: "file:///data".into(), - is_writable: true, - }).await; + let result = handler + .execute(RegisterVolumeCommand { + volume_name: "".into(), + uri_prefix: "file:///data".into(), + is_writable: true, + }) + .await; assert!(matches!(result, Err(DomainError::Validation(_)))); } diff --git a/crates/application/tests/storage/queries/check_quota.rs b/crates/application/tests/storage/queries/check_quota.rs index b3a1b45..37cac6e 100644 --- a/crates/application/tests/storage/queries/check_quota.rs +++ b/crates/application/tests/storage/queries/check_quota.rs @@ -1,9 +1,9 @@ -use std::sync::Arc; +use application::storage::{CheckQuotaHandler, CheckQuotaQuery}; use application::testing::{InMemoryQuotaRepository, InMemoryUsageLedgerRepository}; -use application::storage::{CheckQuotaQuery, CheckQuotaHandler}; use domain::entities::{QuotaDefinition, TimePeriod, UsageLedgerEntry, UsageType}; use domain::ports::UsageLedgerRepository; use domain::value_objects::SystemId; +use std::sync::Arc; #[tokio::test] async fn returns_allowed() { @@ -17,11 +17,14 @@ async fn returns_allowed() { quota_repo.save("a).await.unwrap(); let handler = CheckQuotaHandler::new(quota_repo, ledger_repo); - let result = handler.execute(CheckQuotaQuery { - user_id: user, - usage_type: UsageType::StorageBytes, - requested_amount: 5_000, - }).await.unwrap(); + let result = handler + .execute(CheckQuotaQuery { + user_id: user, + usage_type: UsageType::StorageBytes, + requested_amount: 5_000, + }) + .await + .unwrap(); assert!(result.allowed); assert_eq!(result.limit, 10_000); @@ -44,11 +47,14 @@ async fn returns_denied() { ledger_repo.record(&entry).await.unwrap(); let handler = CheckQuotaHandler::new(quota_repo, ledger_repo); - let result = handler.execute(CheckQuotaQuery { - user_id: user, - usage_type: UsageType::StorageBytes, - requested_amount: 200, - }).await.unwrap(); + let result = handler + .execute(CheckQuotaQuery { + user_id: user, + usage_type: UsageType::StorageBytes, + requested_amount: 200, + }) + .await + .unwrap(); assert!(!result.allowed); assert_eq!(result.current_usage, 900); @@ -60,11 +66,14 @@ async fn returns_unlimited_when_no_quota() { let ledger_repo = Arc::new(InMemoryUsageLedgerRepository::new()); let handler = CheckQuotaHandler::new(quota_repo, ledger_repo); - let result = handler.execute(CheckQuotaQuery { - user_id: SystemId::new(), - usage_type: UsageType::StorageBytes, - requested_amount: 999_999, - }).await.unwrap(); + let result = handler + .execute(CheckQuotaQuery { + user_id: SystemId::new(), + usage_type: UsageType::StorageBytes, + requested_amount: 999_999, + }) + .await + .unwrap(); assert!(result.allowed); assert!(result.is_unlimited); diff --git a/crates/bootstrap/src/factory.rs b/crates/bootstrap/src/factory.rs index 75d7d01..231ee71 100644 --- a/crates/bootstrap/src/factory.rs +++ b/crates/bootstrap/src/factory.rs @@ -1,18 +1,19 @@ -use std::sync::Arc; use anyhow::Result; use axum::Router; use axum::http::HeaderValue; -use tower_http::{cors::{Any, CorsLayer}, trace::TraceLayer}; +use std::sync::Arc; +use tower_http::{ + cors::{Any, CorsLayer}, + trace::TraceLayer, +}; use adapters_auth::{BcryptPasswordHasher, JwtTokenIssuer}; - -use adapters_postgres::{connect, run_migrations, PostgresUserRepository}; - +use adapters_postgres::{PostgresUserRepository, connect, run_migrations}; use adapters_storage::{ObjectStorageAdapter, StorageConfig, build_store}; -use application::identity::{RegisterUserHandler, LoginUserHandler, GetProfileHandler}; +use application::identity::{GetProfileHandler, LoginUserHandler, RegisterUserHandler}; use presentation::{routes::app_router, state::AppState}; use crate::config::Config; @@ -21,28 +22,36 @@ pub async fn build_app(config: &Config) -> Result { let pool = connect(&config.database_url).await?; run_migrations(&pool).await?; - - let user_repo = Arc::new(PostgresUserRepository::new(pool)); let hasher = Arc::new(BcryptPasswordHasher); let issuer = Arc::new(JwtTokenIssuer::new(&config.jwt_secret)); let register_handler = Arc::new(RegisterUserHandler::new(user_repo.clone(), hasher.clone())); - let login_handler = Arc::new(LoginUserHandler::new(user_repo.clone(), hasher, issuer.clone())); + let login_handler = Arc::new(LoginUserHandler::new( + user_repo.clone(), + hasher, + issuer.clone(), + )); let get_profile_handler = Arc::new(GetProfileHandler::new(user_repo)); - let storage_cfg = StorageConfig::from_env()?; let store = build_store(&storage_cfg)?; let storage = Arc::new(ObjectStorageAdapter::new(store, &storage_cfg.prefix)?); - - let state = AppState::new(register_handler, login_handler, get_profile_handler, issuer, storage); + let state = AppState::new( + register_handler, + login_handler, + get_profile_handler, + issuer, + storage, + ); let cors = CorsLayer::new() .allow_origin( - config.cors_allowed_origins.iter() + config + .cors_allowed_origins + .iter() .filter_map(|o| o.parse::().ok()) .collect::>(), ) diff --git a/crates/bootstrap/src/lib.rs b/crates/bootstrap/src/lib.rs index e69de29..8b13789 100644 --- a/crates/bootstrap/src/lib.rs +++ b/crates/bootstrap/src/lib.rs @@ -0,0 +1 @@ + diff --git a/crates/domain/src/catalog/entities.rs b/crates/domain/src/catalog/entities.rs index b06b614..64aecde 100644 --- a/crates/domain/src/catalog/entities.rs +++ b/crates/domain/src/catalog/entities.rs @@ -56,7 +56,9 @@ impl Asset { // --- AssetMetadata --- -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)] +#[derive( + Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, +)] pub enum MetadataSource { ExifExtracted, AiGenerated, @@ -133,7 +135,11 @@ impl AssetStack { } } - pub fn add_member(&mut self, asset_id: SystemId, role: StackMemberRole) -> Result<(), DomainError> { + pub fn add_member( + &mut self, + asset_id: SystemId, + role: StackMemberRole, + ) -> Result<(), DomainError> { if self.members.iter().any(|m| m.asset_id == asset_id) { return Err(DomainError::Conflict( "Asset already exists in stack".to_string(), @@ -179,7 +185,11 @@ pub struct DerivativeAsset { } impl DerivativeAsset { - pub fn new_pending(parent: SystemId, profile: DerivativeProfile, path: impl Into) -> Self { + pub fn new_pending( + parent: SystemId, + profile: DerivativeProfile, + path: impl Into, + ) -> Self { Self { derivative_id: SystemId::new(), parent_asset_id: parent, @@ -239,8 +249,14 @@ impl DuplicateGroup { detection_method: DetectionMethod::ExactHash, status: DuplicateStatus::Unresolved, candidates: vec![ - DuplicateCandidate { asset_id: asset_a, similarity_score: 1.0 }, - DuplicateCandidate { asset_id: asset_b, similarity_score: 1.0 }, + DuplicateCandidate { + asset_id: asset_a, + similarity_score: 1.0, + }, + DuplicateCandidate { + asset_id: asset_b, + similarity_score: 1.0, + }, ], } } diff --git a/crates/domain/src/catalog/ports.rs b/crates/domain/src/catalog/ports.rs index aec9251..ba870d2 100644 --- a/crates/domain/src/catalog/ports.rs +++ b/crates/domain/src/catalog/ports.rs @@ -1,10 +1,10 @@ -use async_trait::async_trait; +use super::entities::{ + Asset, AssetMetadata, AssetStack, DerivativeAsset, DerivativeProfile, DuplicateGroup, + MetadataSource, +}; use crate::common::errors::DomainError; use crate::common::value_objects::{Checksum, SystemId}; -use super::entities::{ - Asset, AssetMetadata, AssetStack, DerivativeAsset, DerivativeProfile, - DuplicateGroup, MetadataSource, -}; +use async_trait::async_trait; // --- AssetRepository --- @@ -12,7 +12,12 @@ use super::entities::{ pub trait AssetRepository: Send + Sync { async fn find_by_id(&self, id: &SystemId) -> Result, DomainError>; async fn find_by_checksum(&self, checksum: &Checksum) -> Result, DomainError>; - async fn find_by_owner(&self, owner_id: &SystemId, limit: u32, offset: u32) -> Result, DomainError>; + async fn find_by_owner( + &self, + owner_id: &SystemId, + limit: u32, + offset: u32, + ) -> Result, DomainError>; async fn save(&self, asset: &Asset) -> Result<(), DomainError>; async fn delete(&self, id: &SystemId) -> Result<(), DomainError>; } @@ -22,9 +27,17 @@ pub trait AssetRepository: Send + Sync { #[async_trait] pub trait AssetMetadataRepository: Send + Sync { async fn find_by_asset(&self, asset_id: &SystemId) -> Result, DomainError>; - async fn find_by_asset_and_source(&self, asset_id: &SystemId, source: MetadataSource) -> Result, DomainError>; + async fn find_by_asset_and_source( + &self, + asset_id: &SystemId, + source: MetadataSource, + ) -> Result, DomainError>; async fn save(&self, metadata: &AssetMetadata) -> Result<(), DomainError>; - async fn delete_by_asset_and_source(&self, asset_id: &SystemId, source: MetadataSource) -> Result<(), DomainError>; + async fn delete_by_asset_and_source( + &self, + asset_id: &SystemId, + source: MetadataSource, + ) -> Result<(), DomainError>; } // --- AssetStackRepository --- @@ -41,8 +54,13 @@ pub trait AssetStackRepository: Send + Sync { #[async_trait] pub trait DerivativeRepository: Send + Sync { - async fn find_by_asset(&self, asset_id: &SystemId) -> Result, DomainError>; - async fn find_by_asset_and_profile(&self, asset_id: &SystemId, profile: DerivativeProfile) -> Result, DomainError>; + async fn find_by_asset(&self, asset_id: &SystemId) + -> Result, DomainError>; + async fn find_by_asset_and_profile( + &self, + asset_id: &SystemId, + profile: DerivativeProfile, + ) -> Result, DomainError>; async fn save(&self, derivative: &DerivativeAsset) -> Result<(), DomainError>; async fn delete(&self, id: &SystemId) -> Result<(), DomainError>; } diff --git a/crates/domain/src/common/ports.rs b/crates/domain/src/common/ports.rs index e841119..a72cee8 100644 --- a/crates/domain/src/common/ports.rs +++ b/crates/domain/src/common/ports.rs @@ -1,6 +1,6 @@ -use async_trait::async_trait; use crate::common::errors::DomainError; use crate::common::events::DomainEvent; +use async_trait::async_trait; #[async_trait] pub trait EventPublisher: Send + Sync { diff --git a/crates/domain/src/common/value_objects/checksum.rs b/crates/domain/src/common/value_objects/checksum.rs index f4071df..0dd9ad6 100644 --- a/crates/domain/src/common/value_objects/checksum.rs +++ b/crates/domain/src/common/value_objects/checksum.rs @@ -7,9 +7,10 @@ impl Checksum { pub fn new(hex: impl Into) -> Result { let hex = hex.into().to_lowercase(); if hex.len() != 64 { - return Err(DomainError::Validation( - format!("Checksum must be 64 hex characters, got {}", hex.len()), - )); + return Err(DomainError::Validation(format!( + "Checksum must be 64 hex characters, got {}", + hex.len() + ))); } if !hex.chars().all(|c| c.is_ascii_hexdigit()) { return Err(DomainError::Validation( @@ -19,7 +20,9 @@ impl Checksum { Ok(Self(hex)) } - pub fn as_str(&self) -> &str { &self.0 } + pub fn as_str(&self) -> &str { + &self.0 + } } impl std::fmt::Display for Checksum { diff --git a/crates/domain/src/common/value_objects/date_time_stamp.rs b/crates/domain/src/common/value_objects/date_time_stamp.rs index 506139e..4f5cfd8 100644 --- a/crates/domain/src/common/value_objects/date_time_stamp.rs +++ b/crates/domain/src/common/value_objects/date_time_stamp.rs @@ -1,12 +1,20 @@ use chrono::{DateTime, Utc}; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)] +#[derive( + Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, +)] pub struct DateTimeStamp(DateTime); impl DateTimeStamp { - pub fn now() -> Self { Self(Utc::now()) } - pub fn from_datetime(dt: DateTime) -> Self { Self(dt) } - pub fn as_datetime(&self) -> &DateTime { &self.0 } + pub fn now() -> Self { + Self(Utc::now()) + } + pub fn from_datetime(dt: DateTime) -> Self { + Self(dt) + } + pub fn as_datetime(&self) -> &DateTime { + &self.0 + } } impl std::fmt::Display for DateTimeStamp { @@ -16,5 +24,7 @@ impl std::fmt::Display for DateTimeStamp { } impl From> for DateTimeStamp { - fn from(dt: DateTime) -> Self { Self(dt) } + fn from(dt: DateTime) -> Self { + Self(dt) + } } diff --git a/crates/domain/src/common/value_objects/email.rs b/crates/domain/src/common/value_objects/email.rs index 4e99011..c4dd9fb 100644 --- a/crates/domain/src/common/value_objects/email.rs +++ b/crates/domain/src/common/value_objects/email.rs @@ -12,7 +12,9 @@ impl Email { Ok(Self(value)) } - pub fn as_str(&self) -> &str { &self.0 } + pub fn as_str(&self) -> &str { + &self.0 + } } impl std::fmt::Display for Email { diff --git a/crates/domain/src/common/value_objects/filter_criteria.rs b/crates/domain/src/common/value_objects/filter_criteria.rs index 8a99278..e19e198 100644 --- a/crates/domain/src/common/value_objects/filter_criteria.rs +++ b/crates/domain/src/common/value_objects/filter_criteria.rs @@ -33,7 +33,11 @@ impl FilterCriteria { Self::Or(conditions) } - pub fn condition(field: impl Into, op: FilterOperator, value: serde_json::Value) -> Self { + pub fn condition( + field: impl Into, + op: FilterOperator, + value: serde_json::Value, + ) -> Self { Self::Condition(FilterCondition { field: field.into(), op, diff --git a/crates/domain/src/common/value_objects/password.rs b/crates/domain/src/common/value_objects/password.rs index 072a3c7..a59f3cb 100644 --- a/crates/domain/src/common/value_objects/password.rs +++ b/crates/domain/src/common/value_objects/password.rs @@ -9,6 +9,10 @@ impl std::fmt::Debug for PasswordHash { } impl PasswordHash { - pub fn from_hash(hash: String) -> Self { Self(hash) } - pub fn as_str(&self) -> &str { &self.0 } + pub fn from_hash(hash: String) -> Self { + Self(hash) + } + pub fn as_str(&self) -> &str { + &self.0 + } } diff --git a/crates/domain/src/common/value_objects/structured_data.rs b/crates/domain/src/common/value_objects/structured_data.rs index 0730b37..9eb276f 100644 --- a/crates/domain/src/common/value_objects/structured_data.rs +++ b/crates/domain/src/common/value_objects/structured_data.rs @@ -14,7 +14,9 @@ pub enum MetadataValue { pub struct StructuredData(HashMap); impl StructuredData { - pub fn new() -> Self { Self(HashMap::new()) } + pub fn new() -> Self { + Self(HashMap::new()) + } pub fn insert(&mut self, key: impl Into, value: MetadataValue) { self.0.insert(key.into(), value); @@ -35,8 +37,12 @@ impl StructuredData { self.0.keys() } - pub fn is_empty(&self) -> bool { self.0.is_empty() } - pub fn len(&self) -> usize { self.0.len() } + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + pub fn len(&self) -> usize { + self.0.len() + } pub fn merge_from(&mut self, other: StructuredData) { self.0.extend(other.0); @@ -46,9 +52,13 @@ impl StructuredData { self.0.remove(key) } - pub fn inner(&self) -> &HashMap { &self.0 } + pub fn inner(&self) -> &HashMap { + &self.0 + } } impl Default for StructuredData { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } diff --git a/crates/domain/src/common/value_objects/system_id.rs b/crates/domain/src/common/value_objects/system_id.rs index 126a691..b7ab2f0 100644 --- a/crates/domain/src/common/value_objects/system_id.rs +++ b/crates/domain/src/common/value_objects/system_id.rs @@ -4,13 +4,21 @@ use uuid::Uuid; pub struct SystemId(Uuid); impl SystemId { - pub fn new() -> Self { Self(Uuid::new_v4()) } - pub fn from_uuid(id: Uuid) -> Self { Self(id) } - pub fn as_uuid(&self) -> &Uuid { &self.0 } + pub fn new() -> Self { + Self(Uuid::new_v4()) + } + pub fn from_uuid(id: Uuid) -> Self { + Self(id) + } + pub fn as_uuid(&self) -> &Uuid { + &self.0 + } } impl Default for SystemId { - fn default() -> Self { Self::new() } + fn default() -> Self { + Self::new() + } } impl std::fmt::Display for SystemId { @@ -20,5 +28,7 @@ impl std::fmt::Display for SystemId { } impl From for SystemId { - fn from(id: Uuid) -> Self { Self(id) } + fn from(id: Uuid) -> Self { + Self(id) + } } diff --git a/crates/domain/src/identity/entities.rs b/crates/domain/src/identity/entities.rs index 664df4e..a94526e 100644 --- a/crates/domain/src/identity/entities.rs +++ b/crates/domain/src/identity/entities.rs @@ -1,7 +1,7 @@ -use std::collections::HashSet; -use chrono::{DateTime, Utc}; use crate::common::errors::DomainError; use crate::common::value_objects::{Email, PasswordHash, SystemId}; +use chrono::{DateTime, Utc}; +use std::collections::HashSet; // --- Permission --- @@ -36,7 +36,10 @@ pub struct Permission { impl Permission { pub fn new(action: PermissionAction, resource_type: ResourceType) -> Self { - Self { action, resource_type } + Self { + action, + resource_type, + } } } @@ -49,18 +52,39 @@ pub fn viewer_permissions() -> HashSet { pub fn contributor_permissions() -> HashSet { let mut perms = viewer_permissions(); - perms.insert(Permission::new(PermissionAction::WriteMetadata, ResourceType::Global)); + perms.insert(Permission::new( + PermissionAction::WriteMetadata, + ResourceType::Global, + )); perms } pub fn admin_permissions() -> HashSet { let mut perms = contributor_permissions(); - perms.insert(Permission::new(PermissionAction::DeleteAsset, ResourceType::Global)); - perms.insert(Permission::new(PermissionAction::ManageAccess, ResourceType::Global)); - perms.insert(Permission::new(PermissionAction::ManageUsers, ResourceType::Global)); - perms.insert(Permission::new(PermissionAction::ManageSystem, ResourceType::Global)); - perms.insert(Permission::new(PermissionAction::ReadLocation, ResourceType::Global)); - perms.insert(Permission::new(PermissionAction::ReadPerson, ResourceType::Global)); + perms.insert(Permission::new( + PermissionAction::DeleteAsset, + ResourceType::Global, + )); + perms.insert(Permission::new( + PermissionAction::ManageAccess, + ResourceType::Global, + )); + perms.insert(Permission::new( + PermissionAction::ManageUsers, + ResourceType::Global, + )); + perms.insert(Permission::new( + PermissionAction::ManageSystem, + ResourceType::Global, + )); + perms.insert(Permission::new( + PermissionAction::ReadLocation, + ResourceType::Global, + )); + perms.insert(Permission::new( + PermissionAction::ReadPerson, + ResourceType::Global, + )); perms } @@ -75,7 +99,11 @@ pub struct Role { } impl Role { - pub fn new(name: impl Into, permissions: HashSet, is_system_default: bool) -> Self { + pub fn new( + name: impl Into, + permissions: HashSet, + is_system_default: bool, + ) -> Self { Self { role_id: SystemId::new(), name: name.into(), @@ -85,7 +113,8 @@ impl Role { } pub fn has_permission(&self, action: PermissionAction, resource_type: ResourceType) -> bool { - self.permissions.contains(&Permission::new(action, resource_type)) + self.permissions + .contains(&Permission::new(action, resource_type)) } } @@ -136,7 +165,9 @@ impl Group { pub fn add_member(&mut self, user_id: SystemId) -> Result<(), DomainError> { if self.members.contains(&user_id) { - return Err(DomainError::Conflict(format!("User {user_id} is already a member"))); + return Err(DomainError::Conflict(format!( + "User {user_id} is already a member" + ))); } self.members.insert(user_id); Ok(()) @@ -144,10 +175,14 @@ impl Group { pub fn remove_member(&mut self, user_id: SystemId) -> Result<(), DomainError> { if user_id == self.owner_user_id { - return Err(DomainError::Validation("Cannot remove the group owner".to_string())); + return Err(DomainError::Validation( + "Cannot remove the group owner".to_string(), + )); } if !self.members.remove(&user_id) { - return Err(DomainError::NotFound(format!("User {user_id} is not a member"))); + return Err(DomainError::NotFound(format!( + "User {user_id} is not a member" + ))); } Ok(()) } diff --git a/crates/domain/src/identity/ports.rs b/crates/domain/src/identity/ports.rs index 151818d..fe9a4d4 100644 --- a/crates/domain/src/identity/ports.rs +++ b/crates/domain/src/identity/ports.rs @@ -1,7 +1,7 @@ -use async_trait::async_trait; +use super::entities::{Group, Role, User}; use crate::common::errors::DomainError; use crate::common::value_objects::{Email, PasswordHash, SystemId}; -use super::entities::{Group, Role, User}; +use async_trait::async_trait; // --- UserRepository --- diff --git a/crates/domain/src/identity/services.rs b/crates/domain/src/identity/services.rs index cf5d418..e509f5e 100644 --- a/crates/domain/src/identity/services.rs +++ b/crates/domain/src/identity/services.rs @@ -1,5 +1,5 @@ -use std::collections::HashSet; use super::entities::{Permission, PermissionAction, ResourceType, Role}; +use std::collections::HashSet; pub struct PermissionChecker; @@ -16,6 +16,9 @@ impl PermissionChecker { } pub fn effective_permissions(roles: &[Role]) -> HashSet { - roles.iter().flat_map(|r| r.permissions.iter().copied()).collect() + roles + .iter() + .flat_map(|r| r.permissions.iter().copied()) + .collect() } } diff --git a/crates/domain/src/lib.rs b/crates/domain/src/lib.rs index 72f35c6..70c7dbd 100644 --- a/crates/domain/src/lib.rs +++ b/crates/domain/src/lib.rs @@ -1,11 +1,11 @@ +pub mod catalog; pub mod common; pub mod identity; -pub mod storage; -pub mod catalog; pub mod organization; +pub mod processing; pub mod sharing; pub mod sidecar; -pub mod processing; +pub mod storage; // Facade — old import paths still work pub mod errors { @@ -18,31 +18,31 @@ pub mod value_objects { pub use crate::common::value_objects::*; } pub mod entities { - pub use crate::identity::entities::*; - pub use crate::storage::entities::*; pub use crate::catalog::entities::*; + pub use crate::identity::entities::*; pub use crate::organization::entities::*; + pub use crate::processing::entities::*; pub use crate::sharing::entities::*; pub use crate::sidecar::entities::*; - pub use crate::processing::entities::*; + pub use crate::storage::entities::*; // Sub-module alias for `domain::entities::permission::` imports pub mod permission { pub use crate::identity::entities::{ - Permission, PermissionAction, ResourceType, - viewer_permissions, contributor_permissions, admin_permissions, + Permission, PermissionAction, ResourceType, admin_permissions, contributor_permissions, + viewer_permissions, }; } } pub mod ports { + pub use crate::catalog::ports::*; pub use crate::common::ports::*; pub use crate::identity::ports::*; - pub use crate::storage::ports::*; - pub use crate::catalog::ports::*; pub use crate::organization::ports::*; + pub use crate::processing::ports::*; pub use crate::sharing::ports::*; pub use crate::sidecar::ports::*; - pub use crate::processing::ports::*; + pub use crate::storage::ports::*; } pub mod services { pub mod permission_service { diff --git a/crates/domain/src/organization/entities.rs b/crates/domain/src/organization/entities.rs index 5c87709..870e8a3 100644 --- a/crates/domain/src/organization/entities.rs +++ b/crates/domain/src/organization/entities.rs @@ -43,7 +43,8 @@ impl Album { if self.entries.iter().any(|e| e.asset_id == asset_id) { return Err(DomainError::Conflict("Asset already in album".to_string())); } - let next_order = self.entries.iter().map(|e| e.sort_order).max().unwrap_or(0) + if self.entries.is_empty() { 0 } else { 1 }; + let next_order = self.entries.iter().map(|e| e.sort_order).max().unwrap_or(0) + + if self.entries.is_empty() { 0 } else { 1 }; self.entries.push(AlbumEntry { asset_id, sort_order: next_order, @@ -54,7 +55,10 @@ impl Album { } pub fn remove_asset(&mut self, asset_id: &SystemId) -> Result<(), DomainError> { - let idx = self.entries.iter().position(|e| &e.asset_id == asset_id) + let idx = self + .entries + .iter() + .position(|e| &e.asset_id == asset_id) .ok_or_else(|| DomainError::NotFound("Asset not in album".to_string()))?; self.entries.remove(idx); Ok(()) diff --git a/crates/domain/src/organization/ports.rs b/crates/domain/src/organization/ports.rs index 739e64c..5499d58 100644 --- a/crates/domain/src/organization/ports.rs +++ b/crates/domain/src/organization/ports.rs @@ -1,7 +1,7 @@ -use async_trait::async_trait; +use super::entities::{Album, AssetTag, Collection, Tag}; use crate::common::errors::DomainError; use crate::common::value_objects::SystemId; -use super::entities::{Album, AssetTag, Collection, Tag}; +use async_trait::async_trait; // --- AlbumRepository --- @@ -19,10 +19,17 @@ pub trait AlbumRepository: Send + Sync { pub trait TagRepository: Send + Sync { async fn find_by_id(&self, id: &SystemId) -> Result, DomainError>; async fn find_by_name(&self, name: &str) -> Result, DomainError>; - async fn find_tags_for_asset(&self, asset_id: &SystemId) -> Result, DomainError>; + async fn find_tags_for_asset( + &self, + asset_id: &SystemId, + ) -> Result, DomainError>; async fn save_tag(&self, tag: &Tag) -> Result<(), DomainError>; async fn save_asset_tag(&self, asset_tag: &AssetTag) -> Result<(), DomainError>; - async fn remove_asset_tag(&self, asset_id: &SystemId, tag_id: &SystemId) -> Result<(), DomainError>; + async fn remove_asset_tag( + &self, + asset_id: &SystemId, + tag_id: &SystemId, + ) -> Result<(), DomainError>; } // --- CollectionRepository --- diff --git a/crates/domain/src/processing/entities.rs b/crates/domain/src/processing/entities.rs index 1561bf6..30ec37b 100644 --- a/crates/domain/src/processing/entities.rs +++ b/crates/domain/src/processing/entities.rs @@ -88,9 +88,10 @@ impl Job { pub fn start(&mut self) -> Result<(), DomainError> { if self.status != JobStatus::Queued { - return Err(DomainError::Conflict( - format!("Job can only start from Queued, currently {:?}", self.status), - )); + return Err(DomainError::Conflict(format!( + "Job can only start from Queued, currently {:?}", + self.status + ))); } self.status = JobStatus::Processing; self.started_at = Some(DateTimeStamp::now()); diff --git a/crates/domain/src/processing/ports.rs b/crates/domain/src/processing/ports.rs index 51c0494..283e770 100644 --- a/crates/domain/src/processing/ports.rs +++ b/crates/domain/src/processing/ports.rs @@ -1,7 +1,7 @@ -use async_trait::async_trait; +use super::entities::{Job, JobBatch, Plugin, ProcessingPipeline}; use crate::common::errors::DomainError; use crate::common::value_objects::SystemId; -use super::entities::{Job, JobBatch, Plugin, ProcessingPipeline}; +use async_trait::async_trait; // --- JobRepository --- diff --git a/crates/domain/src/sharing/entities.rs b/crates/domain/src/sharing/entities.rs index cff34f4..23455ef 100644 --- a/crates/domain/src/sharing/entities.rs +++ b/crates/domain/src/sharing/entities.rs @@ -1,5 +1,5 @@ -use chrono::Utc; use crate::common::value_objects::{DateTimeStamp, StructuredData, SystemId}; +use chrono::Utc; // --- ShareScope --- @@ -74,8 +74,18 @@ pub struct ShareTarget { } impl ShareTarget { - pub fn new(scope_id: SystemId, target_type: TargetType, target_id: SystemId, role_id: SystemId) -> Self { - Self { scope_id, target_type, target_id, role_id } + pub fn new( + scope_id: SystemId, + target_type: TargetType, + target_id: SystemId, + role_id: SystemId, + ) -> Self { + Self { + scope_id, + target_type, + target_id, + role_id, + } } } @@ -99,7 +109,11 @@ pub struct ShareLink { } impl ShareLink { - pub fn new(scope_id: SystemId, token: impl Into, access_level: LinkAccessLevel) -> Self { + pub fn new( + scope_id: SystemId, + token: impl Into, + access_level: LinkAccessLevel, + ) -> Self { Self { scope_id, token: token.into(), @@ -115,7 +129,10 @@ impl ShareLink { if !self.is_active { return false; } - if self.expires_at.is_some_and(|exp| exp.as_datetime() < &Utc::now()) { + if self + .expires_at + .is_some_and(|exp| exp.as_datetime() < &Utc::now()) + { return false; } if self.max_uses.is_some_and(|max| self.use_count >= max) { @@ -160,7 +177,10 @@ impl InviteCode { } pub fn is_valid(&self) -> bool { - if self.expires_at.is_some_and(|exp| exp.as_datetime() < &Utc::now()) { + if self + .expires_at + .is_some_and(|exp| exp.as_datetime() < &Utc::now()) + { return false; } if self.max_uses.is_some_and(|max| self.use_count >= max) { diff --git a/crates/domain/src/sharing/ports.rs b/crates/domain/src/sharing/ports.rs index 7a12921..71300c3 100644 --- a/crates/domain/src/sharing/ports.rs +++ b/crates/domain/src/sharing/ports.rs @@ -1,7 +1,7 @@ -use async_trait::async_trait; +use super::entities::{InviteCode, ShareLink, ShareScope, ShareTarget, VisibilityFilter}; use crate::common::errors::DomainError; use crate::common::value_objects::SystemId; -use super::entities::{InviteCode, ShareLink, ShareScope, ShareTarget, VisibilityFilter}; +use async_trait::async_trait; // --- ShareRepository --- @@ -9,12 +9,21 @@ use super::entities::{InviteCode, ShareLink, ShareScope, ShareTarget, Visibility pub trait ShareRepository: Send + Sync { async fn save_scope(&self, scope: &ShareScope) -> Result<(), DomainError>; async fn find_scope_by_id(&self, id: &SystemId) -> Result, DomainError>; - async fn find_scopes_for_resource(&self, resource_id: &SystemId) -> Result, DomainError>; + async fn find_scopes_for_resource( + &self, + resource_id: &SystemId, + ) -> Result, DomainError>; async fn delete_scope(&self, id: &SystemId) -> Result<(), DomainError>; async fn save_target(&self, target: &ShareTarget) -> Result<(), DomainError>; - async fn find_targets_for_scope(&self, scope_id: &SystemId) -> Result, DomainError>; - async fn find_targets_for_user(&self, user_id: &SystemId) -> Result, DomainError>; + async fn find_targets_for_scope( + &self, + scope_id: &SystemId, + ) -> Result, DomainError>; + async fn find_targets_for_user( + &self, + user_id: &SystemId, + ) -> Result, DomainError>; async fn save_link(&self, link: &ShareLink) -> Result<(), DomainError>; async fn find_link_by_token(&self, token: &str) -> Result, DomainError>; @@ -27,7 +36,11 @@ pub trait ShareRepository: Send + Sync { #[async_trait] pub trait VisibilityFilterRepository: Send + Sync { - async fn find_by_scope_and_role(&self, scope_id: &SystemId, role_id: &SystemId) -> Result, DomainError>; + async fn find_by_scope_and_role( + &self, + scope_id: &SystemId, + role_id: &SystemId, + ) -> Result, DomainError>; async fn save(&self, filter: &VisibilityFilter) -> Result<(), DomainError>; async fn delete(&self, id: &SystemId) -> Result<(), DomainError>; } diff --git a/crates/domain/src/sidecar/ports.rs b/crates/domain/src/sidecar/ports.rs index aa577e5..cd5d7b5 100644 --- a/crates/domain/src/sidecar/ports.rs +++ b/crates/domain/src/sidecar/ports.rs @@ -1,13 +1,16 @@ -use async_trait::async_trait; +use super::entities::{SidecarRecord, SyncStatus}; use crate::common::errors::DomainError; use crate::common::value_objects::{StructuredData, SystemId}; -use super::entities::{SidecarRecord, SyncStatus}; +use async_trait::async_trait; // --- SidecarRepository --- #[async_trait] pub trait SidecarRepository: Send + Sync { - async fn find_by_asset(&self, asset_id: &SystemId) -> Result, DomainError>; + async fn find_by_asset( + &self, + asset_id: &SystemId, + ) -> Result, DomainError>; async fn find_by_status(&self, status: SyncStatus) -> Result, DomainError>; async fn save(&self, record: &SidecarRecord) -> Result<(), DomainError>; async fn delete(&self, asset_id: &SystemId) -> Result<(), DomainError>; diff --git a/crates/domain/src/storage/ports.rs b/crates/domain/src/storage/ports.rs index 2d9ca5a..e7fad5b 100644 --- a/crates/domain/src/storage/ports.rs +++ b/crates/domain/src/storage/ports.rs @@ -1,12 +1,11 @@ +use super::entities::{ + IngestSession, LibraryPath, QuotaDefinition, StorageVolume, UsageLedgerEntry, UsageType, +}; +use crate::common::errors::DomainError; +use crate::common::value_objects::{DateTimeStamp, SystemId}; use async_trait::async_trait; use bytes::Bytes; use futures::stream::{self, BoxStream, StreamExt}; -use crate::common::errors::DomainError; -use crate::common::value_objects::{DateTimeStamp, SystemId}; -use super::entities::{ - IngestSession, LibraryPath, QuotaDefinition, StorageVolume, - UsageLedgerEntry, UsageType, -}; // --- StorageVolumeRepository --- @@ -24,7 +23,10 @@ pub trait StorageVolumeRepository: Send + Sync { pub trait LibraryPathRepository: Send + Sync { async fn find_by_id(&self, id: &SystemId) -> Result, DomainError>; async fn find_by_volume(&self, volume_id: &SystemId) -> Result, DomainError>; - async fn find_ingest_destinations(&self, owner_id: &SystemId) -> Result, DomainError>; + async fn find_ingest_destinations( + &self, + owner_id: &SystemId, + ) -> Result, DomainError>; async fn save(&self, path: &LibraryPath) -> Result<(), DomainError>; async fn delete(&self, id: &SystemId) -> Result<(), DomainError>; } @@ -42,7 +44,10 @@ pub trait IngestSessionRepository: Send + Sync { #[async_trait] pub trait QuotaRepository: Send + Sync { - async fn find_by_owner(&self, owner_id: &SystemId) -> Result, DomainError>; + async fn find_by_owner( + &self, + owner_id: &SystemId, + ) -> Result, DomainError>; async fn save(&self, quota: &QuotaDefinition) -> Result<(), DomainError>; async fn delete(&self, id: &SystemId) -> Result<(), DomainError>; } @@ -114,7 +119,8 @@ pub trait StorageWriter: Send + Sync { /// Convenience: stores an in-memory buffer at `key`. Wraps `put`. async fn put_bytes(&self, key: &str, data: Bytes) -> Result<(), DomainError> { - self.put(key, Box::pin(stream::once(async move { Ok(data) }))).await + self.put(key, Box::pin(stream::once(async move { Ok(data) }))) + .await } } diff --git a/crates/domain/src/storage/services.rs b/crates/domain/src/storage/services.rs index 63d1714..bb51065 100644 --- a/crates/domain/src/storage/services.rs +++ b/crates/domain/src/storage/services.rs @@ -1,6 +1,6 @@ -use chrono::{Datelike, NaiveDate, TimeZone, Utc}; use super::entities::{QuotaDefinition, TimePeriod, UsageType}; use crate::common::value_objects::DateTimeStamp; +use chrono::{Datelike, NaiveDate, TimeZone, Utc}; pub struct QuotaCheckResult { pub allowed: bool, diff --git a/crates/domain/tests/catalog/entities.rs b/crates/domain/tests/catalog/entities.rs index d0a3039..89fd5f1 100644 --- a/crates/domain/tests/catalog/entities.rs +++ b/crates/domain/tests/catalog/entities.rs @@ -1,7 +1,7 @@ use domain::entities::{ Asset, AssetMetadata, AssetStack, AssetType, DerivativeAsset, DerivativeProfile, - DetectionMethod, DuplicateGroup, DuplicateStatus, GenerationStatus, - MetadataSource, SourceReference, StackMemberRole, StackType, + DetectionMethod, DuplicateGroup, DuplicateStatus, GenerationStatus, MetadataSource, + SourceReference, StackMemberRole, StackType, }; use domain::errors::DomainError; use domain::value_objects::{Checksum, MetadataValue, StructuredData, SystemId}; @@ -63,7 +63,9 @@ fn new_stack_contains_primary() { fn add_motion_clip() { let mut stack = AssetStack::new(StackType::LivePhoto, SystemId::new(), SystemId::new()); let clip_id = SystemId::new(); - stack.add_member(clip_id.clone(), StackMemberRole::MotionClip).unwrap(); + stack + .add_member(clip_id.clone(), StackMemberRole::MotionClip) + .unwrap(); assert_eq!(stack.members.len(), 2); assert_eq!(stack.members[1].asset_id, clip_id); assert_eq!(stack.members[1].sort_order, 1); diff --git a/crates/domain/tests/catalog/services.rs b/crates/domain/tests/catalog/services.rs index 8dae9df..f7e362d 100644 --- a/crates/domain/tests/catalog/services.rs +++ b/crates/domain/tests/catalog/services.rs @@ -20,7 +20,10 @@ fn user_edited_overrides_exif() { #[test] fn ai_overrides_exif_but_not_user() { - let exif = layer(MetadataSource::ExifExtracted, &[("desc", "raw"), ("camera", "Nikon")]); + let exif = layer( + MetadataSource::ExifExtracted, + &[("desc", "raw"), ("camera", "Nikon")], + ); let ai = layer(MetadataSource::AiGenerated, &[("desc", "ai-desc")]); let user = layer(MetadataSource::UserEdited, &[("desc", "user-desc")]); let result = resolve_metadata(&[exif, ai, user]); diff --git a/crates/domain/tests/common/mod.rs b/crates/domain/tests/common/mod.rs index f185018..abe1467 100644 --- a/crates/domain/tests/common/mod.rs +++ b/crates/domain/tests/common/mod.rs @@ -1,2 +1,2 @@ -mod value_objects; mod events; +mod value_objects; diff --git a/crates/domain/tests/common/value_objects/date_time_stamp.rs b/crates/domain/tests/common/value_objects/date_time_stamp.rs index ce0e9a9..b0819cd 100644 --- a/crates/domain/tests/common/value_objects/date_time_stamp.rs +++ b/crates/domain/tests/common/value_objects/date_time_stamp.rs @@ -13,10 +13,14 @@ fn now_is_recent() { #[test] fn ordering() { let a = DateTimeStamp::from_datetime( - chrono::DateTime::parse_from_rfc3339("2024-01-01T00:00:00Z").unwrap().into(), + chrono::DateTime::parse_from_rfc3339("2024-01-01T00:00:00Z") + .unwrap() + .into(), ); let b = DateTimeStamp::from_datetime( - chrono::DateTime::parse_from_rfc3339("2025-01-01T00:00:00Z").unwrap().into(), + chrono::DateTime::parse_from_rfc3339("2025-01-01T00:00:00Z") + .unwrap() + .into(), ); assert!(a < b); } diff --git a/crates/domain/tests/domain_tests.rs b/crates/domain/tests/domain_tests.rs index f58ea75..203e262 100644 --- a/crates/domain/tests/domain_tests.rs +++ b/crates/domain/tests/domain_tests.rs @@ -1,8 +1,8 @@ +mod catalog; mod common; mod identity; -mod storage; -mod catalog; mod organization; +mod processing; mod sharing; mod sidecar; -mod processing; +mod storage; diff --git a/crates/domain/tests/identity/entities.rs b/crates/domain/tests/identity/entities.rs index ed3518f..4e39c99 100644 --- a/crates/domain/tests/identity/entities.rs +++ b/crates/domain/tests/identity/entities.rs @@ -1,6 +1,6 @@ use domain::entities::permission::{ - admin_permissions, contributor_permissions, viewer_permissions, - Permission, PermissionAction, ResourceType, + Permission, PermissionAction, ResourceType, admin_permissions, contributor_permissions, + viewer_permissions, }; use domain::entities::{Group, Role, User}; use domain::errors::DomainError; @@ -36,8 +36,16 @@ fn role_checks_permission() { #[test] fn creates_user_with_unique_id() { - let a = User::new("alice", Email::new("a@example.com").unwrap(), PasswordHash::from_hash("h".into())); - let b = User::new("bob", Email::new("b@example.com").unwrap(), PasswordHash::from_hash("h".into())); + let a = User::new( + "alice", + Email::new("a@example.com").unwrap(), + PasswordHash::from_hash("h".into()), + ); + let b = User::new( + "bob", + Email::new("b@example.com").unwrap(), + PasswordHash::from_hash("h".into()), + ); assert_ne!(a.id, b.id); assert_eq!(a.username, "alice"); assert_eq!(b.username, "bob"); diff --git a/crates/domain/tests/identity/services.rs b/crates/domain/tests/identity/services.rs index 2d4d2f6..2dc29f8 100644 --- a/crates/domain/tests/identity/services.rs +++ b/crates/domain/tests/identity/services.rs @@ -1,5 +1,5 @@ -use domain::entities::{Permission, PermissionAction, ResourceType, Role}; use domain::entities::permission::{admin_permissions, viewer_permissions}; +use domain::entities::{Permission, PermissionAction, ResourceType, Role}; use domain::services::permission_service::PermissionChecker; #[test] @@ -24,8 +24,24 @@ fn viewer_cannot_delete() { #[test] fn roles_additive() { - let r1 = Role::new("r1", [Permission::new(PermissionAction::ReadAsset, ResourceType::Global)].into(), false); - let r2 = Role::new("r2", [Permission::new(PermissionAction::WriteMetadata, ResourceType::Global)].into(), false); + let r1 = Role::new( + "r1", + [Permission::new( + PermissionAction::ReadAsset, + ResourceType::Global, + )] + .into(), + false, + ); + let r2 = Role::new( + "r2", + [Permission::new( + PermissionAction::WriteMetadata, + ResourceType::Global, + )] + .into(), + false, + ); let eff = PermissionChecker::effective_permissions(&[r1, r2]); assert_eq!(eff.len(), 2); } diff --git a/crates/domain/tests/sharing/entities.rs b/crates/domain/tests/sharing/entities.rs index a1fe7e6..10ffbb1 100644 --- a/crates/domain/tests/sharing/entities.rs +++ b/crates/domain/tests/sharing/entities.rs @@ -6,14 +6,26 @@ use domain::value_objects::{DateTimeStamp, SystemId}; #[test] fn not_expired_when_no_expiry() { - let scope = ShareScope::new(ScopeType::Link, ShareableType::Album, SystemId::new(), SystemId::new()); + let scope = ShareScope::new( + ScopeType::Link, + ShareableType::Album, + SystemId::new(), + SystemId::new(), + ); assert!(!scope.is_expired()); } #[test] fn expired_when_past() { - let mut scope = ShareScope::new(ScopeType::Link, ShareableType::Album, SystemId::new(), SystemId::new()); - scope.expires_at = Some(DateTimeStamp::from_datetime(Utc::now() - Duration::hours(1))); + let mut scope = ShareScope::new( + ScopeType::Link, + ShareableType::Album, + SystemId::new(), + SystemId::new(), + ); + scope.expires_at = Some(DateTimeStamp::from_datetime( + Utc::now() - Duration::hours(1), + )); assert!(scope.is_expired()); } diff --git a/crates/domain/tests/storage/entities.rs b/crates/domain/tests/storage/entities.rs index d243b37..a230568 100644 --- a/crates/domain/tests/storage/entities.rs +++ b/crates/domain/tests/storage/entities.rs @@ -1,6 +1,6 @@ use domain::entities::{ - IngestSession, IngestStatus, LibraryPath, OwnershipPolicy, - QuotaDefinition, StorageVolume, TimePeriod, UsageType, + IngestSession, IngestStatus, LibraryPath, OwnershipPolicy, QuotaDefinition, StorageVolume, + TimePeriod, UsageType, }; use domain::errors::DomainError; use domain::value_objects::{Checksum, SystemId}; @@ -63,7 +63,11 @@ fn invalid_transition_rejected() { #[test] fn can_fail_from_any_non_terminal() { - for target in [IngestStatus::Uploading, IngestStatus::AwaitingProcessing, IngestStatus::Processing] { + for target in [ + IngestStatus::Uploading, + IngestStatus::AwaitingProcessing, + IngestStatus::Processing, + ] { let mut s = make_session(); if target == IngestStatus::AwaitingProcessing || target == IngestStatus::Processing { s.advance_to(IngestStatus::AwaitingProcessing).unwrap(); diff --git a/crates/presentation/src/errors.rs b/crates/presentation/src/errors.rs index 10bfa31..4f508f0 100644 --- a/crates/presentation/src/errors.rs +++ b/crates/presentation/src/errors.rs @@ -1,11 +1,17 @@ -use axum::{http::StatusCode, response::{IntoResponse, Response}, Json}; +use axum::{ + Json, + http::StatusCode, + response::{IntoResponse, Response}, +}; use domain::errors::DomainError; use serde_json::json; pub struct AppError(DomainError); impl From for AppError { - fn from(e: DomainError) -> Self { Self(e) } + fn from(e: DomainError) -> Self { + Self(e) + } } impl IntoResponse for AppError { @@ -19,7 +25,10 @@ impl IntoResponse for AppError { DomainError::QuotaExceeded(msg) => (StatusCode::PAYLOAD_TOO_LARGE, msg.clone()), DomainError::Internal(msg) => { tracing::error!("Internal error: {msg}"); - (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error".to_string()) + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Internal server error".to_string(), + ) } }; (status, Json(json!({ "error": message }))).into_response() diff --git a/crates/presentation/src/extractors/auth.rs b/crates/presentation/src/extractors/auth.rs index 22a4dfd..4fe2a42 100644 --- a/crates/presentation/src/extractors/auth.rs +++ b/crates/presentation/src/extractors/auth.rs @@ -1,12 +1,12 @@ +use crate::state::AppState; use axum::{ - extract::FromRequestParts, - http::{request::Parts, StatusCode}, - response::{IntoResponse, Response}, Json, + extract::FromRequestParts, + http::{StatusCode, request::Parts}, + response::{IntoResponse, Response}, }; use domain::value_objects::SystemId; use serde_json::json; -use crate::state::AppState; pub struct JwtClaims { pub user_id: SystemId, @@ -16,21 +16,36 @@ pub struct JwtClaims { impl FromRequestParts for JwtClaims { type Rejection = Response; - async fn from_request_parts(parts: &mut Parts, state: &AppState) -> Result { + async fn from_request_parts( + parts: &mut Parts, + state: &AppState, + ) -> Result { let auth_header = parts .headers .get(axum::http::header::AUTHORIZATION) .and_then(|v| v.to_str().ok()) .ok_or_else(|| { - (StatusCode::UNAUTHORIZED, Json(json!({ "error": "Missing Authorization header" }))).into_response() + ( + StatusCode::UNAUTHORIZED, + Json(json!({ "error": "Missing Authorization header" })), + ) + .into_response() })?; let token = auth_header.strip_prefix("Bearer ").ok_or_else(|| { - (StatusCode::UNAUTHORIZED, Json(json!({ "error": "Invalid Authorization format" }))).into_response() + ( + StatusCode::UNAUTHORIZED, + Json(json!({ "error": "Invalid Authorization format" })), + ) + .into_response() })?; let (user_id, role) = state.token_issuer.verify(token).await.map_err(|_| { - (StatusCode::UNAUTHORIZED, Json(json!({ "error": "Invalid or expired token" }))).into_response() + ( + StatusCode::UNAUTHORIZED, + Json(json!({ "error": "Invalid or expired token" })), + ) + .into_response() })?; Ok(JwtClaims { user_id, role }) diff --git a/crates/presentation/src/extractors/json.rs b/crates/presentation/src/extractors/json.rs index 3beaa54..9e35ea6 100644 --- a/crates/presentation/src/extractors/json.rs +++ b/crates/presentation/src/extractors/json.rs @@ -1,8 +1,8 @@ use axum::{ - extract::{rejection::JsonRejection, FromRequest, Request}, + Json, + extract::{FromRequest, Request, rejection::JsonRejection}, http::StatusCode, response::{IntoResponse, Response}, - Json, }; use serde::de::DeserializeOwned; use serde_json::json; @@ -22,7 +22,11 @@ where .await .map(|Json(value)| ValidatedJson(value)) .map_err(|rejection| { - (StatusCode::UNPROCESSABLE_ENTITY, Json(json!({ "error": rejection.body_text() }))).into_response() + ( + StatusCode::UNPROCESSABLE_ENTITY, + Json(json!({ "error": rejection.body_text() })), + ) + .into_response() }) } } diff --git a/crates/presentation/src/handlers/auth.rs b/crates/presentation/src/handlers/auth.rs index 2dc98a0..9fd96bd 100644 --- a/crates/presentation/src/handlers/auth.rs +++ b/crates/presentation/src/handlers/auth.rs @@ -1,10 +1,14 @@ -use axum::{extract::State, http::StatusCode, Json}; +use crate::{ + errors::AppError, + extractors::{JwtClaims, ValidatedJson}, + state::AppState, +}; use api_types::{ requests::{LoginRequest, RegisterRequest}, responses::{AuthResponse, UserResponse}, }; -use application::identity::{RegisterUserCommand, LoginUserCommand, GetProfileQuery}; -use crate::{errors::AppError, extractors::{JwtClaims, ValidatedJson}, state::AppState}; +use application::identity::{GetProfileQuery, LoginUserCommand, RegisterUserCommand}; +use axum::{Json, extract::State, http::StatusCode}; #[utoipa::path( post, path = "/api/v1/auth/register", @@ -25,8 +29,18 @@ pub async fn register( password: req.password, }; let user = state.register_handler.execute(cmd).await?; - let token = state.token_issuer.issue(&user.id, "user").await.map_err(AppError::from)?; - Ok((StatusCode::CREATED, Json(AuthResponse { token, user: UserResponse::from_domain(&user) }))) + let token = state + .token_issuer + .issue(&user.id, "user") + .await + .map_err(AppError::from)?; + Ok(( + StatusCode::CREATED, + Json(AuthResponse { + token, + user: UserResponse::from_domain(&user), + }), + )) } #[utoipa::path( @@ -46,7 +60,10 @@ pub async fn login( password: req.password, }; let (user, token) = state.login_handler.execute(cmd).await?; - Ok(Json(AuthResponse { token, user: UserResponse::from_domain(&user) })) + Ok(Json(AuthResponse { + token, + user: UserResponse::from_domain(&user), + })) } #[utoipa::path( @@ -61,7 +78,9 @@ pub async fn me( State(state): State, claims: JwtClaims, ) -> Result, AppError> { - let query = GetProfileQuery { user_id: claims.user_id }; + let query = GetProfileQuery { + user_id: claims.user_id, + }; let user = state.get_profile_handler.execute(query).await?; Ok(Json(UserResponse::from_domain(&user))) } diff --git a/crates/presentation/src/handlers/health.rs b/crates/presentation/src/handlers/health.rs index db3a40e..62cb885 100644 --- a/crates/presentation/src/handlers/health.rs +++ b/crates/presentation/src/handlers/health.rs @@ -1,4 +1,4 @@ -use axum::{http::StatusCode, Json}; +use axum::{Json, http::StatusCode}; use serde_json::json; #[utoipa::path(get, path = "/health", responses((status = 200, description = "Service is healthy")))] diff --git a/crates/presentation/src/openapi/mod.rs b/crates/presentation/src/openapi/mod.rs index f8cf261..0183eb7 100644 --- a/crates/presentation/src/openapi/mod.rs +++ b/crates/presentation/src/openapi/mod.rs @@ -1,7 +1,10 @@ -use utoipa::{openapi::security::{Http, HttpAuthScheme, SecurityScheme}, Modify, OpenApi}; -use utoipa_scalar::{Scalar, Servable}; -use axum::Router; use crate::state::AppState; +use axum::Router; +use utoipa::{ + Modify, OpenApi, + openapi::security::{Http, HttpAuthScheme, SecurityScheme}, +}; +use utoipa_scalar::{Scalar, Servable}; #[derive(OpenApi)] #[openapi( @@ -37,5 +40,8 @@ impl Modify for SecurityAddon { pub fn openapi_router() -> Router { Router::new() .merge(Scalar::with_url("/scalar", ApiDoc::openapi())) - .route("/api-docs/openapi.json", axum::routing::get(|| async { axum::Json(ApiDoc::openapi()) })) + .route( + "/api-docs/openapi.json", + axum::routing::get(|| async { axum::Json(ApiDoc::openapi()) }), + ) } diff --git a/crates/presentation/src/routes.rs b/crates/presentation/src/routes.rs index 8946e08..1b50871 100644 --- a/crates/presentation/src/routes.rs +++ b/crates/presentation/src/routes.rs @@ -1,5 +1,12 @@ -use axum::{routing::{get, post}, Router}; -use crate::{handlers::{auth, health}, openapi::openapi_router, state::AppState}; +use crate::{ + handlers::{auth, health}, + openapi::openapi_router, + state::AppState, +}; +use axum::{ + Router, + routing::{get, post}, +}; pub fn api_v1_router() -> Router { Router::new() diff --git a/crates/presentation/src/state.rs b/crates/presentation/src/state.rs index 16a4365..2b730f8 100644 --- a/crates/presentation/src/state.rs +++ b/crates/presentation/src/state.rs @@ -1,9 +1,8 @@ +use application::identity::{GetProfileHandler, LoginUserHandler, RegisterUserHandler}; use std::sync::Arc; -use application::identity::{RegisterUserHandler, LoginUserHandler, GetProfileHandler}; use domain::ports::{StoragePort, TokenIssuer}; - #[derive(Clone)] pub struct AppState { pub register_handler: Arc, @@ -21,6 +20,12 @@ impl AppState { token_issuer: Arc, storage: Arc, ) -> Self { - Self { register_handler, login_handler, get_profile_handler, token_issuer, storage } + Self { + register_handler, + login_handler, + get_profile_handler, + token_issuer, + storage, + } } } diff --git a/crates/worker/src/jobs/example.rs b/crates/worker/src/jobs/example.rs index ba9784b..013a3b6 100644 --- a/crates/worker/src/jobs/example.rs +++ b/crates/worker/src/jobs/example.rs @@ -1,12 +1,14 @@ +use crate::job::Job; use async_trait::async_trait; use tracing::info; -use crate::job::Job; pub struct ExampleJob; #[async_trait] impl Job for ExampleJob { - fn name(&self) -> &str { "example" } + fn name(&self) -> &str { + "example" + } async fn run(&self) -> anyhow::Result<()> { info!("example job ran — replace with real work"); Ok(()) diff --git a/crates/worker/src/main.rs b/crates/worker/src/main.rs index d0244f9..89c4dc4 100644 --- a/crates/worker/src/main.rs +++ b/crates/worker/src/main.rs @@ -14,8 +14,7 @@ use runner::JobRunner; async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt() .with_env_filter( - tracing_subscriber::EnvFilter::from_default_env() - .add_directive("worker=info".parse()?), + tracing_subscriber::EnvFilter::from_default_env().add_directive("worker=info".parse()?), ) .init(); diff --git a/crates/worker/src/runner.rs b/crates/worker/src/runner.rs index 23b6bc5..7f533bc 100644 --- a/crates/worker/src/runner.rs +++ b/crates/worker/src/runner.rs @@ -1,14 +1,16 @@ +use crate::job::Job; use std::sync::Arc; use std::time::Duration; use tracing::{error, info}; -use crate::job::Job; pub struct JobRunner { jobs: Vec<(Arc, Duration)>, } impl JobRunner { - pub fn new() -> Self { Self { jobs: vec![] } } + pub fn new() -> Self { + Self { jobs: vec![] } + } pub fn register(mut self, job: Arc, interval: Duration) -> Self { self.jobs.push((job, interval)); @@ -16,19 +18,29 @@ impl JobRunner { } pub async fn run(self) { - let handles: Vec<_> = self.jobs.into_iter().map(|(job, interval)| { - tokio::spawn(async move { - loop { - info!(job = job.name(), "running job"); - if let Err(e) = job.run().await { - error!(job = job.name(), error = %e, "job failed"); + let handles: Vec<_> = self + .jobs + .into_iter() + .map(|(job, interval)| { + tokio::spawn(async move { + loop { + info!(job = job.name(), "running job"); + if let Err(e) = job.run().await { + error!(job = job.name(), error = %e, "job failed"); + } + tokio::time::sleep(interval).await; } - tokio::time::sleep(interval).await; - } + }) }) - }).collect(); - for handle in handles { let _ = handle.await; } + .collect(); + for handle in handles { + let _ = handle.await; + } } } -impl Default for JobRunner { fn default() -> Self { Self::new() } } +impl Default for JobRunner { + fn default() -> Self { + Self::new() + } +}