diff --git a/crates/application/src/admin/activity_log.rs b/crates/application/src/admin/activity_log.rs deleted file mode 100644 index 6c5d3c3..0000000 --- a/crates/application/src/admin/activity_log.rs +++ /dev/null @@ -1,12 +0,0 @@ -use domain::models::ActivityEvent; -use domain::DomainResult; - -use super::deps::AdminDeps; -use super::queries::GetActivityLogQuery; - -pub async fn execute( - deps: &AdminDeps, - query: GetActivityLogQuery, -) -> DomainResult> { - deps.activity_query.recent(query.limit).await -} diff --git a/crates/application/src/admin/get_settings.rs b/crates/application/src/admin/get_settings.rs deleted file mode 100644 index 53086eb..0000000 --- a/crates/application/src/admin/get_settings.rs +++ /dev/null @@ -1,15 +0,0 @@ -use domain::DomainResult; - -use super::deps::AdminDeps; -use super::queries::GetSettingsQuery; - -pub async fn execute( - deps: &AdminDeps, - _query: GetSettingsQuery, -) -> DomainResult> { - deps.settings_repo.get_all().await -} - -#[cfg(test)] -#[path = "tests/get_settings.rs"] -mod tests; diff --git a/crates/application/src/admin/mod.rs b/crates/application/src/admin/mod.rs index a94b6f7..dcfd064 100644 --- a/crates/application/src/admin/mod.rs +++ b/crates/application/src/admin/mod.rs @@ -1,10 +1,6 @@ -pub mod activity_log; pub mod commands; pub mod deps; -pub mod get_settings; -pub mod queries; pub mod update_settings; pub use commands::UpdateSettingsCommand; pub use deps::AdminDeps; -pub use queries::{GetActivityLogQuery, GetSettingsQuery}; diff --git a/crates/application/src/admin/queries.rs b/crates/application/src/admin/queries.rs deleted file mode 100644 index d151c85..0000000 --- a/crates/application/src/admin/queries.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub struct GetSettingsQuery; - -pub struct GetActivityLogQuery { - pub limit: u32, -} diff --git a/crates/application/src/admin/tests/get_settings.rs b/crates/application/src/admin/tests/get_settings.rs deleted file mode 100644 index 1f509a1..0000000 --- a/crates/application/src/admin/tests/get_settings.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::sync::Arc; - -use domain::testing::{InMemoryActivityLog, InMemoryAppSettings}; - -use crate::admin::commands::UpdateSettingsCommand; -use crate::admin::deps::AdminDeps; -use crate::admin::queries::GetSettingsQuery; -use crate::admin::{get_settings, update_settings}; - -fn make_deps() -> AdminDeps { - AdminDeps { - settings_repo: Arc::new(InMemoryAppSettings::new()), - activity_query: Arc::new(InMemoryActivityLog::new()), - } -} - -#[tokio::test] -async fn get_empty_settings() { - let deps = make_deps(); - let settings = get_settings::execute(&deps, GetSettingsQuery).await.unwrap(); - assert!(settings.is_empty()); -} - -#[tokio::test] -async fn get_returns_stored_settings() { - let deps = make_deps(); - - update_settings::execute( - &deps, - UpdateSettingsCommand { - settings: vec![ - ("a".into(), "1".into()), - ("b".into(), "2".into()), - ], - }, - ) - .await - .unwrap(); - - let settings = get_settings::execute(&deps, GetSettingsQuery).await.unwrap(); - assert_eq!(settings.len(), 2); - - let keys: Vec<&str> = settings.iter().map(|(k, _)| k.as_str()).collect(); - assert!(keys.contains(&"a")); - assert!(keys.contains(&"b")); -} diff --git a/crates/application/src/channels/deps.rs b/crates/application/src/channels/deps.rs index 3eec997..7fdaefa 100644 --- a/crates/application/src/channels/deps.rs +++ b/crates/application/src/channels/deps.rs @@ -7,7 +7,3 @@ pub struct ChannelCommandDeps { pub channel_query: Arc, pub event_publisher: Arc, } - -pub struct ChannelQueryDeps { - pub channel_query: Arc, -} diff --git a/crates/application/src/channels/get.rs b/crates/application/src/channels/get.rs deleted file mode 100644 index 51adda9..0000000 --- a/crates/application/src/channels/get.rs +++ /dev/null @@ -1,13 +0,0 @@ -use domain::models::Channel; -use domain::DomainResult; - -use super::deps::ChannelQueryDeps; -use super::queries::GetChannelQuery; - -pub async fn execute(deps: &ChannelQueryDeps, query: GetChannelQuery) -> DomainResult> { - deps.channel_query.find_by_id(query.channel_id).await -} - -#[cfg(test)] -#[path = "tests/get.rs"] -mod tests; diff --git a/crates/application/src/channels/list.rs b/crates/application/src/channels/list.rs deleted file mode 100644 index 06bce5e..0000000 --- a/crates/application/src/channels/list.rs +++ /dev/null @@ -1,13 +0,0 @@ -use domain::models::Channel; -use domain::DomainResult; - -use super::deps::ChannelQueryDeps; -use super::queries::ListChannelsQuery; - -pub async fn execute(deps: &ChannelQueryDeps, _query: ListChannelsQuery) -> DomainResult> { - deps.channel_query.find_all().await -} - -#[cfg(test)] -#[path = "tests/list.rs"] -mod tests; diff --git a/crates/application/src/channels/list_by_owner.rs b/crates/application/src/channels/list_by_owner.rs deleted file mode 100644 index e4bba83..0000000 --- a/crates/application/src/channels/list_by_owner.rs +++ /dev/null @@ -1,13 +0,0 @@ -use domain::models::Channel; -use domain::DomainResult; - -use super::deps::ChannelQueryDeps; -use super::queries::ListByOwnerQuery; - -pub async fn execute(deps: &ChannelQueryDeps, query: ListByOwnerQuery) -> DomainResult> { - deps.channel_query.find_by_owner(query.owner_id).await -} - -#[cfg(test)] -#[path = "tests/list_by_owner.rs"] -mod tests; diff --git a/crates/application/src/channels/mod.rs b/crates/application/src/channels/mod.rs index 41d5bfe..140af88 100644 --- a/crates/application/src/channels/mod.rs +++ b/crates/application/src/channels/mod.rs @@ -2,15 +2,10 @@ pub mod commands; pub mod create; pub mod delete; pub mod deps; -pub mod get; -pub mod list; -pub mod list_by_owner; -pub mod queries; pub mod update; pub use commands::{CreateChannelCommand, DeleteChannelCommand, UpdateChannelCommand}; -pub use deps::{ChannelCommandDeps, ChannelQueryDeps}; -pub use queries::{GetChannelQuery, ListByOwnerQuery, ListChannelsQuery}; +pub use deps::ChannelCommandDeps; use domain::models::Channel; use domain::value_objects::{ChannelId, UserId}; diff --git a/crates/application/src/channels/queries.rs b/crates/application/src/channels/queries.rs deleted file mode 100644 index 618892c..0000000 --- a/crates/application/src/channels/queries.rs +++ /dev/null @@ -1,11 +0,0 @@ -use domain::value_objects::{ChannelId, UserId}; - -pub struct GetChannelQuery { - pub channel_id: ChannelId, -} - -pub struct ListChannelsQuery; - -pub struct ListByOwnerQuery { - pub owner_id: UserId, -} diff --git a/crates/application/src/channels/tests/get.rs b/crates/application/src/channels/tests/get.rs deleted file mode 100644 index 06f099c..0000000 --- a/crates/application/src/channels/tests/get.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::sync::Arc; - -use domain::testing::{InMemoryChannelRepository, NoopEventPublisher}; -use domain::value_objects::{ChannelId, UserId}; - -use crate::channels::commands::CreateChannelCommand; -use crate::channels::deps::{ChannelCommandDeps, ChannelQueryDeps}; -use crate::channels::queries::GetChannelQuery; -use crate::channels::{create, get}; - -fn make_deps() -> (ChannelCommandDeps, ChannelQueryDeps) { - let repo = Arc::new(InMemoryChannelRepository::new()); - let cmd_deps = ChannelCommandDeps { - channel_command: repo.clone(), - channel_query: repo.clone(), - event_publisher: Arc::new(NoopEventPublisher::new()), - }; - let query_deps = ChannelQueryDeps { - channel_query: repo, - }; - (cmd_deps, query_deps) -} - -#[tokio::test] -async fn get_existing_channel() { - let (cmd_deps, query_deps) = make_deps(); - - let channel = create::execute( - &cmd_deps, - CreateChannelCommand { - owner_id: UserId::generate(), - name: "Findable".into(), - timezone: "UTC".into(), - }, - ) - .await - .unwrap(); - - let found = get::execute( - &query_deps, - GetChannelQuery { - channel_id: channel.id(), - }, - ) - .await - .unwrap(); - - assert!(found.is_some()); - assert_eq!(found.unwrap().name(), "Findable"); -} - -#[tokio::test] -async fn get_nonexistent_returns_none() { - let (_, query_deps) = make_deps(); - - let found = get::execute( - &query_deps, - GetChannelQuery { - channel_id: ChannelId::generate(), - }, - ) - .await - .unwrap(); - - assert!(found.is_none()); -} diff --git a/crates/application/src/channels/tests/list.rs b/crates/application/src/channels/tests/list.rs deleted file mode 100644 index e5391b2..0000000 --- a/crates/application/src/channels/tests/list.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::sync::Arc; - -use domain::testing::{InMemoryChannelRepository, NoopEventPublisher}; -use domain::value_objects::UserId; - -use crate::channels::commands::CreateChannelCommand; -use crate::channels::deps::{ChannelCommandDeps, ChannelQueryDeps}; -use crate::channels::queries::ListChannelsQuery; -use crate::channels::{create, list}; - -fn make_deps() -> (ChannelCommandDeps, ChannelQueryDeps) { - let repo = Arc::new(InMemoryChannelRepository::new()); - let cmd_deps = ChannelCommandDeps { - channel_command: repo.clone(), - channel_query: repo.clone(), - event_publisher: Arc::new(NoopEventPublisher::new()), - }; - let query_deps = ChannelQueryDeps { - channel_query: repo, - }; - (cmd_deps, query_deps) -} - -#[tokio::test] -async fn list_empty_returns_empty() { - let (_, query_deps) = make_deps(); - - let channels = list::execute(&query_deps, ListChannelsQuery).await.unwrap(); - assert!(channels.is_empty()); -} - -#[tokio::test] -async fn list_returns_all_channels() { - let (cmd_deps, query_deps) = make_deps(); - - for name in ["A", "B", "C"] { - create::execute( - &cmd_deps, - CreateChannelCommand { - owner_id: UserId::generate(), - name: name.into(), - timezone: "UTC".into(), - }, - ) - .await - .unwrap(); - } - - let channels = list::execute(&query_deps, ListChannelsQuery).await.unwrap(); - assert_eq!(channels.len(), 3); -} diff --git a/crates/application/src/channels/tests/list_by_owner.rs b/crates/application/src/channels/tests/list_by_owner.rs deleted file mode 100644 index bd9641d..0000000 --- a/crates/application/src/channels/tests/list_by_owner.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::sync::Arc; - -use domain::testing::{InMemoryChannelRepository, NoopEventPublisher}; -use domain::value_objects::UserId; - -use crate::channels::commands::CreateChannelCommand; -use crate::channels::deps::{ChannelCommandDeps, ChannelQueryDeps}; -use crate::channels::queries::ListByOwnerQuery; -use crate::channels::{create, list_by_owner}; - -fn make_deps() -> (ChannelCommandDeps, ChannelQueryDeps) { - let repo = Arc::new(InMemoryChannelRepository::new()); - let cmd_deps = ChannelCommandDeps { - channel_command: repo.clone(), - channel_query: repo.clone(), - event_publisher: Arc::new(NoopEventPublisher::new()), - }; - let query_deps = ChannelQueryDeps { - channel_query: repo, - }; - (cmd_deps, query_deps) -} - -#[tokio::test] -async fn filters_by_owner() { - let (cmd_deps, query_deps) = make_deps(); - let alice = UserId::generate(); - let bob = UserId::generate(); - - // Alice: 2 channels - for name in ["Alice-1", "Alice-2"] { - create::execute( - &cmd_deps, - CreateChannelCommand { - owner_id: alice, - name: name.into(), - timezone: "UTC".into(), - }, - ) - .await - .unwrap(); - } - - // Bob: 1 channel - create::execute( - &cmd_deps, - CreateChannelCommand { - owner_id: bob, - name: "Bob-1".into(), - timezone: "UTC".into(), - }, - ) - .await - .unwrap(); - - let alice_channels = list_by_owner::execute( - &query_deps, - ListByOwnerQuery { - owner_id: alice, - }, - ) - .await - .unwrap(); - - assert_eq!(alice_channels.len(), 2); - assert!(alice_channels.iter().all(|c| c.owner_id() == alice)); -} - -#[tokio::test] -async fn no_channels_returns_empty() { - let (_, query_deps) = make_deps(); - - let channels = list_by_owner::execute( - &query_deps, - ListByOwnerQuery { - owner_id: UserId::generate(), - }, - ) - .await - .unwrap(); - - assert!(channels.is_empty()); -} diff --git a/crates/application/src/config_snapshots/commands.rs b/crates/application/src/config_snapshots/commands.rs index 560277c..b3c7061 100644 --- a/crates/application/src/config_snapshots/commands.rs +++ b/crates/application/src/config_snapshots/commands.rs @@ -5,12 +5,6 @@ pub struct SaveSnapshotCommand { pub label: Option, } -pub struct PatchLabelCommand { - pub channel_id: ChannelId, - pub snapshot_id: SnapshotId, - pub label: Option, -} - pub struct RestoreSnapshotCommand { pub channel_id: ChannelId, pub snapshot_id: SnapshotId, diff --git a/crates/application/src/config_snapshots/get.rs b/crates/application/src/config_snapshots/get.rs deleted file mode 100644 index d6a7425..0000000 --- a/crates/application/src/config_snapshots/get.rs +++ /dev/null @@ -1,14 +0,0 @@ -use domain::models::ChannelConfigSnapshot; -use domain::DomainResult; - -use super::deps::ConfigSnapshotDeps; -use super::queries::GetSnapshotQuery; - -pub async fn execute( - deps: &ConfigSnapshotDeps, - query: GetSnapshotQuery, -) -> DomainResult> { - deps.channel_query - .get_config_snapshot(query.channel_id, query.snapshot_id) - .await -} diff --git a/crates/application/src/config_snapshots/list.rs b/crates/application/src/config_snapshots/list.rs deleted file mode 100644 index f28272a..0000000 --- a/crates/application/src/config_snapshots/list.rs +++ /dev/null @@ -1,16 +0,0 @@ -use domain::models::ChannelConfigSnapshot; -use domain::DomainResult; - -use super::deps::ConfigSnapshotDeps; -use super::queries::ListSnapshotsQuery; - -pub async fn execute( - deps: &ConfigSnapshotDeps, - query: ListSnapshotsQuery, -) -> DomainResult> { - deps.channel_query.list_config_snapshots(query.channel_id).await -} - -#[cfg(test)] -#[path = "tests/list.rs"] -mod tests; diff --git a/crates/application/src/config_snapshots/mod.rs b/crates/application/src/config_snapshots/mod.rs index 9a90607..76447ed 100644 --- a/crates/application/src/config_snapshots/mod.rs +++ b/crates/application/src/config_snapshots/mod.rs @@ -1,12 +1,7 @@ pub mod commands; pub mod deps; -pub mod get; -pub mod list; -pub mod patch_label; -pub mod queries; pub mod restore; pub mod save; -pub use commands::{PatchLabelCommand, RestoreSnapshotCommand, SaveSnapshotCommand}; +pub use commands::{RestoreSnapshotCommand, SaveSnapshotCommand}; pub use deps::ConfigSnapshotDeps; -pub use queries::{GetSnapshotQuery, ListSnapshotsQuery}; diff --git a/crates/application/src/config_snapshots/patch_label.rs b/crates/application/src/config_snapshots/patch_label.rs deleted file mode 100644 index cc97ee8..0000000 --- a/crates/application/src/config_snapshots/patch_label.rs +++ /dev/null @@ -1,14 +0,0 @@ -use domain::models::ChannelConfigSnapshot; -use domain::DomainResult; - -use super::commands::PatchLabelCommand; -use super::deps::ConfigSnapshotDeps; - -pub async fn execute( - deps: &ConfigSnapshotDeps, - cmd: PatchLabelCommand, -) -> DomainResult> { - deps.channel_command - .patch_config_snapshot_label(cmd.channel_id, cmd.snapshot_id, cmd.label) - .await -} diff --git a/crates/application/src/config_snapshots/queries.rs b/crates/application/src/config_snapshots/queries.rs deleted file mode 100644 index 13bc7f4..0000000 --- a/crates/application/src/config_snapshots/queries.rs +++ /dev/null @@ -1,10 +0,0 @@ -use domain::value_objects::{ChannelId, SnapshotId}; - -pub struct ListSnapshotsQuery { - pub channel_id: ChannelId, -} - -pub struct GetSnapshotQuery { - pub channel_id: ChannelId, - pub snapshot_id: SnapshotId, -} diff --git a/crates/application/src/config_snapshots/tests/list.rs b/crates/application/src/config_snapshots/tests/list.rs deleted file mode 100644 index 067ae58..0000000 --- a/crates/application/src/config_snapshots/tests/list.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::sync::Arc; - -use domain::models::Channel; -use domain::testing::InMemoryChannelRepository; -use domain::value_objects::UserId; - -use crate::config_snapshots::commands::SaveSnapshotCommand; -use crate::config_snapshots::deps::ConfigSnapshotDeps; -use crate::config_snapshots::queries::ListSnapshotsQuery; -use crate::config_snapshots::{list, save}; - -fn make_deps() -> (ConfigSnapshotDeps, Arc) { - let repo = Arc::new(InMemoryChannelRepository::new()); - let deps = ConfigSnapshotDeps { - channel_command: repo.clone(), - channel_query: repo.clone(), - }; - (deps, repo) -} - -async fn seed_channel(repo: &InMemoryChannelRepository) -> Channel { - let channel = Channel::new(UserId::generate(), "Test Channel", "UTC"); - repo.channels - .lock() - .unwrap() - .insert(channel.id(), channel.clone()); - channel -} - -#[tokio::test] -async fn list_empty() { - let (deps, repo) = make_deps(); - let channel = seed_channel(&repo).await; - - let snaps = list::execute( - &deps, - ListSnapshotsQuery { - channel_id: channel.id(), - }, - ) - .await - .unwrap(); - - assert!(snaps.is_empty()); -} - -#[tokio::test] -async fn list_returns_saved_snapshots() { - let (deps, repo) = make_deps(); - let channel = seed_channel(&repo).await; - - for label in ["first", "second"] { - save::execute( - &deps, - SaveSnapshotCommand { - channel_id: channel.id(), - label: Some(label.into()), - }, - ) - .await - .unwrap(); - } - - let snaps = list::execute( - &deps, - ListSnapshotsQuery { - channel_id: channel.id(), - }, - ) - .await - .unwrap(); - - assert_eq!(snaps.len(), 2); - // Newest first - assert_eq!(snaps[0].version_num(), 2); - assert_eq!(snaps[1].version_num(), 1); -} diff --git a/crates/application/src/library/deps.rs b/crates/application/src/library/deps.rs index 9d82ec1..b350daa 100644 --- a/crates/application/src/library/deps.rs +++ b/crates/application/src/library/deps.rs @@ -9,7 +9,3 @@ pub struct LibraryCommandDeps { pub provider_registry: Arc, pub event_publisher: Arc, } - -pub struct LibraryQueryDeps { - pub library_query: Arc, -} diff --git a/crates/application/src/library/get_item.rs b/crates/application/src/library/get_item.rs deleted file mode 100644 index 604e5bd..0000000 --- a/crates/application/src/library/get_item.rs +++ /dev/null @@ -1,16 +0,0 @@ -use domain::models::MediaItem; -use domain::DomainResult; - -use super::deps::LibraryQueryDeps; -use super::queries::GetItemQuery; - -pub async fn execute( - deps: &LibraryQueryDeps, - query: GetItemQuery, -) -> DomainResult> { - deps.library_query.get_by_id(&query.item_id).await -} - -#[cfg(test)] -#[path = "tests/get_item.rs"] -mod tests; diff --git a/crates/application/src/library/get_sync_status.rs b/crates/application/src/library/get_sync_status.rs deleted file mode 100644 index 5316470..0000000 --- a/crates/application/src/library/get_sync_status.rs +++ /dev/null @@ -1,16 +0,0 @@ -use domain::models::LibrarySyncLogEntry; -use domain::DomainResult; - -use super::deps::LibraryQueryDeps; -use super::queries::GetSyncStatusQuery; - -pub async fn execute( - deps: &LibraryQueryDeps, - _query: GetSyncStatusQuery, -) -> DomainResult> { - deps.library_query.latest_sync_status().await -} - -#[cfg(test)] -#[path = "tests/get_sync_status.rs"] -mod tests; diff --git a/crates/application/src/library/list_collections.rs b/crates/application/src/library/list_collections.rs deleted file mode 100644 index 0e80314..0000000 --- a/crates/application/src/library/list_collections.rs +++ /dev/null @@ -1,18 +0,0 @@ -use domain::models::LibraryCollection; -use domain::DomainResult; - -use super::deps::LibraryQueryDeps; -use super::queries::ListCollectionsQuery; - -pub async fn execute( - deps: &LibraryQueryDeps, - query: ListCollectionsQuery, -) -> DomainResult> { - deps.library_query - .list_collections(query.provider_id.as_deref()) - .await -} - -#[cfg(test)] -#[path = "tests/list_collections.rs"] -mod tests; diff --git a/crates/application/src/library/list_genres.rs b/crates/application/src/library/list_genres.rs deleted file mode 100644 index fb982df..0000000 --- a/crates/application/src/library/list_genres.rs +++ /dev/null @@ -1,21 +0,0 @@ -use domain::DomainResult; - -use super::deps::LibraryQueryDeps; -use super::parse_content_type; -use super::queries::ListGenresQuery; - -pub async fn execute(deps: &LibraryQueryDeps, query: ListGenresQuery) -> DomainResult> { - let content_type = query - .content_type - .as_deref() - .map(parse_content_type) - .transpose()?; - - deps.library_query - .list_genres(content_type.as_ref(), query.provider_id.as_deref()) - .await -} - -#[cfg(test)] -#[path = "tests/list_genres.rs"] -mod tests; diff --git a/crates/application/src/library/list_seasons.rs b/crates/application/src/library/list_seasons.rs deleted file mode 100644 index 5a13a7b..0000000 --- a/crates/application/src/library/list_seasons.rs +++ /dev/null @@ -1,18 +0,0 @@ -use domain::models::SeasonSummary; -use domain::DomainResult; - -use super::deps::LibraryQueryDeps; -use super::queries::ListSeasonsQuery; - -pub async fn execute( - deps: &LibraryQueryDeps, - query: ListSeasonsQuery, -) -> DomainResult> { - deps.library_query - .list_seasons(&query.series_name, query.provider_id.as_deref()) - .await -} - -#[cfg(test)] -#[path = "tests/list_seasons.rs"] -mod tests; diff --git a/crates/application/src/library/list_shows.rs b/crates/application/src/library/list_shows.rs deleted file mode 100644 index e026c66..0000000 --- a/crates/application/src/library/list_shows.rs +++ /dev/null @@ -1,22 +0,0 @@ -use domain::models::ShowSummary; -use domain::DomainResult; - -use super::deps::LibraryQueryDeps; -use super::queries::ListShowsQuery; - -pub async fn execute( - deps: &LibraryQueryDeps, - query: ListShowsQuery, -) -> DomainResult> { - deps.library_query - .list_shows( - query.provider_id.as_deref(), - query.search_term.as_deref(), - &query.genres, - ) - .await -} - -#[cfg(test)] -#[path = "tests/list_shows.rs"] -mod tests; diff --git a/crates/application/src/library/mod.rs b/crates/application/src/library/mod.rs index 7df4d95..156532e 100644 --- a/crates/application/src/library/mod.rs +++ b/crates/application/src/library/mod.rs @@ -1,26 +1,17 @@ pub mod commands; pub mod deps; -pub mod get_item; -pub mod get_sync_status; -pub mod list_collections; -pub mod list_genres; -pub mod list_seasons; -pub mod list_shows; pub mod queries; pub mod search; pub mod sync; pub use commands::TriggerSyncCommand; -pub use deps::{LibraryCommandDeps, LibraryQueryDeps}; -pub use queries::{ - GetItemQuery, GetSyncStatusQuery, ListCollectionsQuery, ListGenresQuery, ListSeasonsQuery, - ListShowsQuery, SearchItemsQuery, -}; +pub use deps::LibraryCommandDeps; +pub use queries::SearchItemsQuery; use domain::errors::{DomainError, DomainResult}; use domain::value_objects::ContentType; -pub(crate) fn parse_content_type(s: &str) -> DomainResult { +pub fn parse_content_type(s: &str) -> DomainResult { match s { "movie" => Ok(ContentType::Movie), "episode" => Ok(ContentType::Episode), diff --git a/crates/application/src/library/queries.rs b/crates/application/src/library/queries.rs index 1d21761..100d923 100644 --- a/crates/application/src/library/queries.rs +++ b/crates/application/src/library/queries.rs @@ -10,29 +10,3 @@ pub struct SearchItemsQuery { pub offset: u32, pub limit: u32, } - -pub struct ListCollectionsQuery { - pub provider_id: Option, -} - -pub struct ListShowsQuery { - pub provider_id: Option, - pub search_term: Option, - pub genres: Vec, -} - -pub struct ListSeasonsQuery { - pub series_name: String, - pub provider_id: Option, -} - -pub struct ListGenresQuery { - pub content_type: Option, - pub provider_id: Option, -} - -pub struct GetItemQuery { - pub item_id: String, -} - -pub struct GetSyncStatusQuery; diff --git a/crates/application/src/library/search.rs b/crates/application/src/library/search.rs index 79d5d6a..172a0b3 100644 --- a/crates/application/src/library/search.rs +++ b/crates/application/src/library/search.rs @@ -2,12 +2,12 @@ use domain::DomainResult; use domain::models::MediaItem; use domain::value_objects::LibrarySearchFilter; -use super::deps::LibraryQueryDeps; +use super::deps::LibraryCommandDeps; use super::parse_content_type; use super::queries::SearchItemsQuery; pub async fn execute( - deps: &LibraryQueryDeps, + deps: &LibraryCommandDeps, query: SearchItemsQuery, ) -> DomainResult<(Vec, u32)> { let content_type = query diff --git a/crates/application/src/library/tests/get_item.rs b/crates/application/src/library/tests/get_item.rs deleted file mode 100644 index 6921c81..0000000 --- a/crates/application/src/library/tests/get_item.rs +++ /dev/null @@ -1,50 +0,0 @@ -use domain::models::MediaItem; -use domain::value_objects::ContentType; - -use crate::library::get_item; -use crate::library::queries::GetItemQuery; - -#[path = "helpers.rs"] -mod helpers; - -fn seed_item(repo: &std::sync::Arc) { - let item = MediaItem::new_library("test", "m1", "Die Hard", ContentType::Movie, 7800, "2026-01-01"); - repo.items - .lock() - .unwrap() - .insert(item.id().value().to_string(), item); -} - -#[tokio::test] -async fn get_existing_item() { - let (_cmd, query, repo) = helpers::make_deps(); - seed_item(&repo); - - let item = get_item::execute( - &query, - GetItemQuery { - item_id: "test::m1".into(), - }, - ) - .await - .unwrap(); - - assert!(item.is_some()); - assert_eq!(item.unwrap().title(), "Die Hard"); -} - -#[tokio::test] -async fn get_nonexistent_item_returns_none() { - let (_cmd, query, _repo) = helpers::make_deps(); - - let item = get_item::execute( - &query, - GetItemQuery { - item_id: "test::missing".into(), - }, - ) - .await - .unwrap(); - - assert!(item.is_none()); -} diff --git a/crates/application/src/library/tests/get_sync_status.rs b/crates/application/src/library/tests/get_sync_status.rs deleted file mode 100644 index c81b32f..0000000 --- a/crates/application/src/library/tests/get_sync_status.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::library::get_sync_status; -use crate::library::queries::GetSyncStatusQuery; - -#[path = "helpers.rs"] -mod helpers; - -#[tokio::test] -async fn get_sync_status_empty() { - let (_cmd, query, _repo) = helpers::make_deps(); - - let entries = get_sync_status::execute(&query, GetSyncStatusQuery) - .await - .unwrap(); - - assert!(entries.is_empty()); -} - -#[tokio::test] -async fn get_sync_status_after_sync() { - let (cmd, query, _repo) = helpers::make_deps(); - - // Start a sync log entry - let log_id = domain::ports::LibraryCommand::log_sync_start(&*cmd.library_command, "test") - .await - .unwrap(); - - // Finish it - let result = domain::models::LibrarySyncResult::new("test", 42, 500); - domain::ports::LibraryCommand::log_sync_finish(&*cmd.library_command, log_id, &result) - .await - .unwrap(); - - let entries = get_sync_status::execute(&query, GetSyncStatusQuery) - .await - .unwrap(); - - assert_eq!(entries.len(), 1); - assert_eq!(entries[0].provider_id(), "test"); - assert_eq!(entries[0].items_found(), 42); - assert_eq!(entries[0].status(), "success"); -} diff --git a/crates/application/src/library/tests/helpers.rs b/crates/application/src/library/tests/helpers.rs index e6017b4..f9eb20d 100644 --- a/crates/application/src/library/tests/helpers.rs +++ b/crates/application/src/library/tests/helpers.rs @@ -11,9 +11,8 @@ use domain::ports::{ use domain::testing::{InMemoryLibraryRepository, NoopEventPublisher, NoopLibrarySync}; use domain::value_objects::{ContentType, MediaFilter, MediaItemId}; -use crate::library::deps::{LibraryCommandDeps, LibraryQueryDeps}; +use crate::library::deps::LibraryCommandDeps; -/// Minimal IProviderRegistry for library tests. pub(crate) struct TestProviderRegistry; #[async_trait] @@ -83,24 +82,14 @@ impl IProviderRegistry for TestProviderRegistry { } } -/// Build LibraryCommandDeps and LibraryQueryDeps backed by InMemory repos. -/// -/// Returns deps plus the underlying repo for seeding test data. -pub(crate) fn make_deps() -> ( - LibraryCommandDeps, - LibraryQueryDeps, - Arc, -) { +pub(crate) fn make_deps() -> (LibraryCommandDeps, Arc) { let repo = Arc::new(InMemoryLibraryRepository::new()); - let cmd_deps = LibraryCommandDeps { + let deps = LibraryCommandDeps { library_command: repo.clone(), library_query: repo.clone(), library_sync: Arc::new(NoopLibrarySync::new()), provider_registry: Arc::new(TestProviderRegistry), event_publisher: Arc::new(NoopEventPublisher::new()), }; - let query_deps = LibraryQueryDeps { - library_query: repo.clone(), - }; - (cmd_deps, query_deps, repo) + (deps, repo) } diff --git a/crates/application/src/library/tests/list_collections.rs b/crates/application/src/library/tests/list_collections.rs deleted file mode 100644 index df284fe..0000000 --- a/crates/application/src/library/tests/list_collections.rs +++ /dev/null @@ -1,87 +0,0 @@ -use domain::models::{MediaItem, MediaItemRow}; -use domain::value_objects::{ContentType, MediaItemId, MediaRole}; - -use crate::library::list_collections; -use crate::library::queries::ListCollectionsQuery; - -#[path = "helpers.rs"] -mod helpers; - -fn seed_with_collections(repo: &std::sync::Arc) { - let mut store = repo.items.lock().unwrap(); - - let item = MediaItem::from_persistence(MediaItemRow { - id: MediaItemId::new("test::m1"), - provider_id: "test".into(), - external_id: "m1".into(), - title: "Die Hard".into(), - content_type: ContentType::Movie, - duration_secs: 7800, - description: None, - series_name: None, - season_number: None, - episode_number: None, - year: None, - genres: vec![], - tags: vec![], - collection_id: Some("col-1".into()), - collection_name: Some("Movies".into()), - collection_type: Some("movies".into()), - thumbnail_url: None, - synced_at: Some("2026-01-01".into()), - role: MediaRole::default(), - }); - store.insert(item.id().value().to_string(), item); - - let item2 = MediaItem::from_persistence(MediaItemRow { - id: MediaItemId::new("test::e1"), - provider_id: "test".into(), - external_id: "e1".into(), - title: "BB S01E01".into(), - content_type: ContentType::Episode, - duration_secs: 2700, - description: None, - series_name: Some("Breaking Bad".into()), - season_number: Some(1), - episode_number: Some(1), - year: None, - genres: vec![], - tags: vec![], - collection_id: Some("col-2".into()), - collection_name: Some("TV Shows".into()), - collection_type: Some("tvshows".into()), - thumbnail_url: None, - synced_at: Some("2026-01-01".into()), - role: MediaRole::default(), - }); - store.insert(item2.id().value().to_string(), item2); -} - -#[tokio::test] -async fn list_collections_returns_distinct() { - let (_cmd, query, repo) = helpers::make_deps(); - seed_with_collections(&repo); - - let cols = list_collections::execute( - &query, - ListCollectionsQuery { provider_id: None }, - ) - .await - .unwrap(); - - assert_eq!(cols.len(), 2); -} - -#[tokio::test] -async fn list_collections_empty_library() { - let (_cmd, query, _repo) = helpers::make_deps(); - - let cols = list_collections::execute( - &query, - ListCollectionsQuery { provider_id: None }, - ) - .await - .unwrap(); - - assert!(cols.is_empty()); -} diff --git a/crates/application/src/library/tests/list_genres.rs b/crates/application/src/library/tests/list_genres.rs deleted file mode 100644 index b56398c..0000000 --- a/crates/application/src/library/tests/list_genres.rs +++ /dev/null @@ -1,112 +0,0 @@ -use domain::models::{MediaItem, MediaItemRow}; -use domain::value_objects::{ContentType, MediaItemId, MediaRole}; - -use crate::library::list_genres; -use crate::library::queries::ListGenresQuery; - -#[path = "helpers.rs"] -mod helpers; - -fn seed_with_genres(repo: &std::sync::Arc) { - let mut store = repo.items.lock().unwrap(); - - let item1 = MediaItem::from_persistence(MediaItemRow { - id: MediaItemId::new("test::m1"), - provider_id: "test".into(), - external_id: "m1".into(), - title: "Die Hard".into(), - content_type: ContentType::Movie, - duration_secs: 7800, - description: None, - series_name: None, - season_number: None, - episode_number: None, - year: None, - genres: vec!["Action".into(), "Thriller".into()], - tags: vec![], - collection_id: None, - collection_name: None, - collection_type: None, - thumbnail_url: None, - synced_at: Some("2026-01-01".into()), - role: MediaRole::default(), - }); - let item2 = MediaItem::from_persistence(MediaItemRow { - id: MediaItemId::new("test::m2"), - provider_id: "test".into(), - external_id: "m2".into(), - title: "Alien".into(), - content_type: ContentType::Movie, - duration_secs: 7020, - description: None, - series_name: None, - season_number: None, - episode_number: None, - year: None, - genres: vec!["Sci-Fi".into(), "Action".into()], - tags: vec![], - collection_id: None, - collection_name: None, - collection_type: None, - thumbnail_url: None, - synced_at: Some("2026-01-01".into()), - role: MediaRole::default(), - }); - - store.insert(item1.id().value().to_string(), item1); - store.insert(item2.id().value().to_string(), item2); -} - -#[tokio::test] -async fn list_genres_returns_unique() { - let (_cmd, query, repo) = helpers::make_deps(); - seed_with_genres(&repo); - - let genres = list_genres::execute( - &query, - ListGenresQuery { - content_type: None, - provider_id: None, - }, - ) - .await - .unwrap(); - - assert_eq!(genres.len(), 3); // Action, Sci-Fi, Thriller (deduped) - assert!(genres.contains(&"Action".to_string())); - assert!(genres.contains(&"Sci-Fi".to_string())); - assert!(genres.contains(&"Thriller".to_string())); -} - -#[tokio::test] -async fn list_genres_empty_library() { - let (_cmd, query, _repo) = helpers::make_deps(); - - let genres = list_genres::execute( - &query, - ListGenresQuery { - content_type: None, - provider_id: None, - }, - ) - .await - .unwrap(); - - assert!(genres.is_empty()); -} - -#[tokio::test] -async fn list_genres_invalid_content_type_errors() { - let (_cmd, query, _repo) = helpers::make_deps(); - - let result = list_genres::execute( - &query, - ListGenresQuery { - content_type: Some("invalid".into()), - provider_id: None, - }, - ) - .await; - - assert!(result.is_err()); -} diff --git a/crates/application/src/library/tests/list_seasons.rs b/crates/application/src/library/tests/list_seasons.rs deleted file mode 100644 index 3e54b25..0000000 --- a/crates/application/src/library/tests/list_seasons.rs +++ /dev/null @@ -1,79 +0,0 @@ -use domain::models::{MediaItem, MediaItemRow}; -use domain::value_objects::{ContentType, MediaItemId, MediaRole}; - -use crate::library::list_seasons; -use crate::library::queries::ListSeasonsQuery; - -#[path = "helpers.rs"] -mod helpers; - -fn seed_episodes(repo: &std::sync::Arc) { - let mut store = repo.items.lock().unwrap(); - - for (i, season) in [1u32, 1, 2, 2, 2, 3].iter().enumerate() { - let item = MediaItem::from_persistence(MediaItemRow { - id: MediaItemId::new(format!("test::e{i}")), - provider_id: "test".into(), - external_id: format!("e{i}"), - title: format!("BB S{season:02}E{:02}", i + 1), - content_type: ContentType::Episode, - duration_secs: 2700, - description: None, - series_name: Some("Breaking Bad".into()), - season_number: Some(*season), - episode_number: Some(i as u32 + 1), - year: None, - genres: vec![], - tags: vec![], - collection_id: None, - collection_name: None, - collection_type: None, - thumbnail_url: None, - synced_at: Some("2026-01-01".into()), - role: MediaRole::default(), - }); - store.insert(item.id().value().to_string(), item); - } -} - -#[tokio::test] -async fn list_seasons_for_series() { - let (_cmd, query, repo) = helpers::make_deps(); - seed_episodes(&repo); - - let seasons = list_seasons::execute( - &query, - ListSeasonsQuery { - series_name: "Breaking Bad".into(), - provider_id: None, - }, - ) - .await - .unwrap(); - - assert_eq!(seasons.len(), 3); - // Sorted by season_number - assert_eq!(seasons[0].season_number(), 1); - assert_eq!(seasons[0].episode_count(), 2); - assert_eq!(seasons[1].season_number(), 2); - assert_eq!(seasons[1].episode_count(), 3); - assert_eq!(seasons[2].season_number(), 3); - assert_eq!(seasons[2].episode_count(), 1); -} - -#[tokio::test] -async fn list_seasons_unknown_series() { - let (_cmd, query, _repo) = helpers::make_deps(); - - let seasons = list_seasons::execute( - &query, - ListSeasonsQuery { - series_name: "Nonexistent".into(), - provider_id: None, - }, - ) - .await - .unwrap(); - - assert!(seasons.is_empty()); -} diff --git a/crates/application/src/library/tests/list_shows.rs b/crates/application/src/library/tests/list_shows.rs deleted file mode 100644 index 01a6611..0000000 --- a/crates/application/src/library/tests/list_shows.rs +++ /dev/null @@ -1,103 +0,0 @@ -use domain::models::{MediaItem, MediaItemRow}; -use domain::value_objects::{ContentType, MediaItemId, MediaRole}; - -use crate::library::list_shows; -use crate::library::queries::ListShowsQuery; - -#[path = "helpers.rs"] -mod helpers; - -fn seed_episodes(repo: &std::sync::Arc) { - let mut store = repo.items.lock().unwrap(); - - for (i, (series, season)) in [ - ("Breaking Bad", 1u32), - ("Breaking Bad", 1), - ("Breaking Bad", 2), - ("The Wire", 1), - ("The Wire", 1), - ] - .iter() - .enumerate() - { - let item = MediaItem::from_persistence(MediaItemRow { - id: MediaItemId::new(format!("test::e{i}")), - provider_id: "test".into(), - external_id: format!("e{i}"), - title: format!("{series} S{season:02}E{i:02}"), - content_type: ContentType::Episode, - duration_secs: 2700, - description: None, - series_name: Some(series.to_string()), - season_number: Some(*season), - episode_number: Some(i as u32 + 1), - year: None, - genres: vec![], - tags: vec![], - collection_id: None, - collection_name: None, - collection_type: None, - thumbnail_url: None, - synced_at: Some("2026-01-01".into()), - role: MediaRole::default(), - }); - store.insert(item.id().value().to_string(), item); - } -} - -#[tokio::test] -async fn list_shows_returns_summaries() { - let (_cmd, query, repo) = helpers::make_deps(); - seed_episodes(&repo); - - let shows = list_shows::execute( - &query, - ListShowsQuery { - provider_id: None, - search_term: None, - genres: vec![], - }, - ) - .await - .unwrap(); - - assert_eq!(shows.len(), 2); -} - -#[tokio::test] -async fn list_shows_with_search_term() { - let (_cmd, query, repo) = helpers::make_deps(); - seed_episodes(&repo); - - let shows = list_shows::execute( - &query, - ListShowsQuery { - provider_id: None, - search_term: Some("breaking".into()), - genres: vec![], - }, - ) - .await - .unwrap(); - - assert_eq!(shows.len(), 1); - assert_eq!(shows[0].series_name(), "Breaking Bad"); -} - -#[tokio::test] -async fn list_shows_empty() { - let (_cmd, query, _repo) = helpers::make_deps(); - - let shows = list_shows::execute( - &query, - ListShowsQuery { - provider_id: None, - search_term: None, - genres: vec![], - }, - ) - .await - .unwrap(); - - assert!(shows.is_empty()); -} diff --git a/crates/application/src/library/tests/search.rs b/crates/application/src/library/tests/search.rs index 35070e3..a63b75c 100644 --- a/crates/application/src/library/tests/search.rs +++ b/crates/application/src/library/tests/search.rs @@ -93,11 +93,11 @@ fn seed_items_with_genres(repo: &std::sync::Arc DomainResult<()> { - deps.provider_config_command.delete(&cmd.id).await -} diff --git a/crates/application/src/providers/get.rs b/crates/application/src/providers/get.rs deleted file mode 100644 index fd7b7b1..0000000 --- a/crates/application/src/providers/get.rs +++ /dev/null @@ -1,12 +0,0 @@ -use domain::models::ProviderConfigRow; -use domain::DomainResult; - -use super::deps::ProviderDeps; -use super::queries::GetProviderQuery; - -pub async fn execute( - deps: &ProviderDeps, - query: GetProviderQuery, -) -> DomainResult> { - deps.provider_config_query.get_by_id(&query.id).await -} diff --git a/crates/application/src/providers/list.rs b/crates/application/src/providers/list.rs deleted file mode 100644 index ded566a..0000000 --- a/crates/application/src/providers/list.rs +++ /dev/null @@ -1,16 +0,0 @@ -use domain::models::ProviderConfigRow; -use domain::DomainResult; - -use super::deps::ProviderDeps; -use super::queries::ListProvidersQuery; - -pub async fn execute( - deps: &ProviderDeps, - _query: ListProvidersQuery, -) -> DomainResult> { - deps.provider_config_query.get_all().await -} - -#[cfg(test)] -#[path = "tests/list.rs"] -mod tests; diff --git a/crates/application/src/providers/mod.rs b/crates/application/src/providers/mod.rs index d4abf10..67b2637 100644 --- a/crates/application/src/providers/mod.rs +++ b/crates/application/src/providers/mod.rs @@ -1,11 +1,6 @@ pub mod commands; -pub mod delete; pub mod deps; -pub mod get; -pub mod list; -pub mod queries; pub mod upsert; -pub use commands::{DeleteProviderCommand, UpsertProviderCommand}; +pub use commands::UpsertProviderCommand; pub use deps::ProviderDeps; -pub use queries::{GetProviderQuery, ListProvidersQuery}; diff --git a/crates/application/src/providers/queries.rs b/crates/application/src/providers/queries.rs deleted file mode 100644 index dc86b5c..0000000 --- a/crates/application/src/providers/queries.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub struct ListProvidersQuery; - -pub struct GetProviderQuery { - pub id: String, -} diff --git a/crates/application/src/providers/tests/list.rs b/crates/application/src/providers/tests/list.rs deleted file mode 100644 index 1d11f5f..0000000 --- a/crates/application/src/providers/tests/list.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::sync::Arc; - -use domain::testing::InMemoryProviderConfig; - -use crate::providers::commands::UpsertProviderCommand; -use crate::providers::deps::ProviderDeps; -use crate::providers::queries::ListProvidersQuery; -use crate::providers::{list, upsert}; - -fn make_deps() -> ProviderDeps { - let repo = Arc::new(InMemoryProviderConfig::new()); - ProviderDeps { - provider_config_command: repo.clone(), - provider_config_query: repo, - } -} - -#[tokio::test] -async fn list_empty() { - let deps = make_deps(); - let providers = list::execute(&deps, ListProvidersQuery).await.unwrap(); - assert!(providers.is_empty()); -} - -#[tokio::test] -async fn list_returns_all_providers() { - let deps = make_deps(); - - for id in ["jf-1", "local-1"] { - upsert::execute( - &deps, - UpsertProviderCommand { - id: id.into(), - provider_type: "jellyfin".into(), - config: serde_json::json!({}), - enabled: true, - }, - ) - .await - .unwrap(); - } - - let providers = list::execute(&deps, ListProvidersQuery).await.unwrap(); - assert_eq!(providers.len(), 2); -} diff --git a/crates/application/src/schedule/commands.rs b/crates/application/src/schedule/commands.rs index e1cd14a..dc501ca 100644 --- a/crates/application/src/schedule/commands.rs +++ b/crates/application/src/schedule/commands.rs @@ -3,8 +3,3 @@ use uuid::Uuid; pub struct GenerateScheduleCommand { pub channel_id: Uuid, } - -pub struct DeleteSchedulesAfterCommand { - pub channel_id: Uuid, - pub target_generation: u32, -} diff --git a/crates/application/src/schedule/delete_after.rs b/crates/application/src/schedule/delete_after.rs deleted file mode 100644 index 223e06d..0000000 --- a/crates/application/src/schedule/delete_after.rs +++ /dev/null @@ -1,19 +0,0 @@ -use domain::value_objects::ChannelId; -use domain::DomainResult; - -use super::commands::DeleteSchedulesAfterCommand; -use super::deps::ScheduleDeps; - -pub async fn execute( - deps: &ScheduleDeps, - cmd: DeleteSchedulesAfterCommand, -) -> DomainResult<()> { - let channel_id = ChannelId::from(cmd.channel_id); - deps.schedule_command - .delete_schedules_after(channel_id, cmd.target_generation) - .await -} - -#[cfg(test)] -#[path = "tests/delete_after.rs"] -mod tests; diff --git a/crates/application/src/schedule/get_active.rs b/crates/application/src/schedule/get_active.rs deleted file mode 100644 index b8a7284..0000000 --- a/crates/application/src/schedule/get_active.rs +++ /dev/null @@ -1,20 +0,0 @@ -use chrono::Utc; - -use domain::models::GeneratedSchedule; -use domain::value_objects::ChannelId; -use domain::DomainResult; - -use super::deps::ScheduleDeps; -use super::queries::GetActiveScheduleQuery; - -pub async fn execute( - deps: &ScheduleDeps, - query: GetActiveScheduleQuery, -) -> DomainResult> { - let channel_id = ChannelId::from(query.channel_id); - deps.schedule_query.find_active(channel_id, Utc::now()).await -} - -#[cfg(test)] -#[path = "tests/get_active.rs"] -mod tests; diff --git a/crates/application/src/schedule/list_history.rs b/crates/application/src/schedule/list_history.rs deleted file mode 100644 index 794d83e..0000000 --- a/crates/application/src/schedule/list_history.rs +++ /dev/null @@ -1,20 +0,0 @@ -use domain::models::GeneratedSchedule; -use domain::value_objects::ChannelId; -use domain::DomainResult; - -use super::deps::ScheduleDeps; -use super::queries::ListHistoryQuery; - -pub async fn execute( - deps: &ScheduleDeps, - query: ListHistoryQuery, -) -> DomainResult> { - let channel_id = ChannelId::from(query.channel_id); - deps.schedule_query - .list_schedule_history(channel_id) - .await -} - -#[cfg(test)] -#[path = "tests/list_history.rs"] -mod tests; diff --git a/crates/application/src/schedule/mod.rs b/crates/application/src/schedule/mod.rs index 49753d3..0140824 100644 --- a/crates/application/src/schedule/mod.rs +++ b/crates/application/src/schedule/mod.rs @@ -1,17 +1,11 @@ pub mod commands; -pub mod delete_after; pub mod deps; pub mod generate; -pub mod get_active; pub mod get_current_broadcast; pub mod get_epg; pub mod get_stream_url; -pub mod list_history; pub mod queries; -pub use commands::{DeleteSchedulesAfterCommand, GenerateScheduleCommand}; +pub use commands::GenerateScheduleCommand; pub use deps::ScheduleDeps; -pub use queries::{ - GetActiveScheduleQuery, GetCurrentBroadcastQuery, GetEpgQuery, GetStreamUrlQuery, - ListHistoryQuery, -}; +pub use queries::{GetCurrentBroadcastQuery, GetEpgQuery, GetStreamUrlQuery}; diff --git a/crates/application/src/schedule/queries.rs b/crates/application/src/schedule/queries.rs index 984871a..591e4fd 100644 --- a/crates/application/src/schedule/queries.rs +++ b/crates/application/src/schedule/queries.rs @@ -1,9 +1,5 @@ use uuid::Uuid; -pub struct GetActiveScheduleQuery { - pub channel_id: Uuid, -} - pub struct GetCurrentBroadcastQuery { pub channel_id: Uuid, } @@ -15,7 +11,3 @@ pub struct GetEpgQuery { pub struct GetStreamUrlQuery { pub channel_id: Uuid, } - -pub struct ListHistoryQuery { - pub channel_id: Uuid, -} diff --git a/crates/application/src/schedule/tests/delete_after.rs b/crates/application/src/schedule/tests/delete_after.rs deleted file mode 100644 index e6a0e47..0000000 --- a/crates/application/src/schedule/tests/delete_after.rs +++ /dev/null @@ -1,64 +0,0 @@ -use domain::models::{Channel, GeneratedSchedule}; -use domain::value_objects::UserId; - -use crate::schedule::commands::DeleteSchedulesAfterCommand; -use crate::schedule::delete_after; -use crate::schedule::queries::ListHistoryQuery; -use crate::schedule::list_history; - -#[path = "helpers.rs"] -mod helpers; -use helpers::make_schedule_deps; - -#[tokio::test] -async fn delete_after_removes_later_generations() { - let (deps, channel_repo, schedule_repo) = make_schedule_deps(); - - let channel = Channel::new(UserId::generate(), "Cleanup", "UTC"); - let channel_id = channel.id(); - channel_repo - .channels - .lock() - .unwrap() - .insert(channel_id, channel); - - // Manually insert schedules with different generations. - let now = chrono::Utc::now(); - for generation in 1..=3 { - let sched = GeneratedSchedule::new( - channel_id, - now, - now + chrono::Duration::hours(24), - generation, - vec![], - ); - schedule_repo - .schedules - .lock() - .unwrap() - .insert(sched.id(), sched); - } - - // Delete generations > 1. - delete_after::execute( - &deps, - DeleteSchedulesAfterCommand { - channel_id: channel_id.value(), - target_generation: 1, - }, - ) - .await - .unwrap(); - - let remaining = list_history::execute( - &deps, - ListHistoryQuery { - channel_id: channel_id.value(), - }, - ) - .await - .unwrap(); - - assert_eq!(remaining.len(), 1); - assert_eq!(remaining[0].generation(), 1); -} diff --git a/crates/application/src/schedule/tests/get_active.rs b/crates/application/src/schedule/tests/get_active.rs deleted file mode 100644 index 85752bd..0000000 --- a/crates/application/src/schedule/tests/get_active.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::schedule::get_active; -use crate::schedule::queries::GetActiveScheduleQuery; - -#[path = "helpers.rs"] -mod helpers; -use helpers::make_schedule_deps; - -#[tokio::test] -async fn returns_none_when_no_schedule_exists() { - let (deps, _, _) = make_schedule_deps(); - - let result = get_active::execute( - &deps, - GetActiveScheduleQuery { - channel_id: uuid::Uuid::new_v4(), - }, - ) - .await - .unwrap(); - - assert!(result.is_none()); -} diff --git a/crates/application/src/schedule/tests/list_history.rs b/crates/application/src/schedule/tests/list_history.rs deleted file mode 100644 index 23d6097..0000000 --- a/crates/application/src/schedule/tests/list_history.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::schedule::list_history; -use crate::schedule::queries::ListHistoryQuery; - -#[path = "helpers.rs"] -mod helpers; -use helpers::make_schedule_deps; - -#[tokio::test] -async fn returns_empty_for_new_channel() { - let (deps, _, _) = make_schedule_deps(); - - let result = list_history::execute( - &deps, - ListHistoryQuery { - channel_id: uuid::Uuid::new_v4(), - }, - ) - .await - .unwrap(); - - assert!(result.is_empty()); -} diff --git a/crates/mcp/src/main.rs b/crates/mcp/src/main.rs index b54a784..649a2ea 100644 --- a/crates/mcp/src/main.rs +++ b/crates/mcp/src/main.rs @@ -60,10 +60,6 @@ async fn main() -> anyhow::Result<()> { event_publisher: event_publisher.clone(), }); - let channel_query_deps = Arc::new(application::channels::ChannelQueryDeps { - channel_query: wire.channel_query.clone(), - }); - let schedule_deps = Arc::new(application::schedule::ScheduleDeps { schedule_engine, channel_query: wire.channel_query.clone(), @@ -73,15 +69,24 @@ async fn main() -> anyhow::Result<()> { provider_registry: provider_registry.clone(), }); - let library_query_deps = Arc::new(application::library::LibraryQueryDeps { + let library_sync: Arc = + Arc::new(NoopLibrarySync); + + let library_command_deps = Arc::new(application::library::LibraryCommandDeps { + library_command: wire.library_command.clone(), library_query: wire.library_query.clone(), + library_sync, + provider_registry: provider_registry.clone(), + event_publisher: event_publisher.clone(), }); let server = KTvMcpServer { channel_cmd_deps, - channel_query_deps, + channel_query: wire.channel_query.clone(), schedule_deps, - library_query_deps, + schedule_query: wire.schedule_query.clone(), + library_query: wire.library_query.clone(), + library_command_deps, owner_id, }; @@ -103,6 +108,7 @@ struct WireOutput { channel_query: Arc, schedule_command: Arc, schedule_query: Arc, + library_command: Arc, library_query: Arc, } @@ -116,6 +122,7 @@ fn wire_repositories(pool: &DbPool) -> anyhow::Result { channel_query: w.channel_query, schedule_command: w.schedule_command, schedule_query: w.schedule_query, + library_command: w.library_command, library_query: w.library_query, }) } @@ -325,3 +332,16 @@ impl IProviderRegistry for SimpleProviderRegistry { provider.list_genres(content_type).await } } + +struct NoopLibrarySync; + +#[async_trait::async_trait] +impl domain::ports::LibrarySyncAdapter for NoopLibrarySync { + async fn sync_provider( + &self, + _provider: &dyn IMediaProvider, + provider_id: &str, + ) -> domain::LibrarySyncResult { + domain::LibrarySyncResult::with_error(provider_id, 0, "MCP does not support sync") + } +} diff --git a/crates/mcp/src/server.rs b/crates/mcp/src/server.rs index b7249db..465b054 100644 --- a/crates/mcp/src/server.rs +++ b/crates/mcp/src/server.rs @@ -1,8 +1,8 @@ use std::sync::Arc; use application::{ - channels::{ChannelCommandDeps, ChannelQueryDeps}, - library::LibraryQueryDeps, + channels::ChannelCommandDeps, + library::LibraryCommandDeps, schedule::ScheduleDeps, }; use rmcp::{ @@ -21,9 +21,11 @@ const SERVER_NAME: &str = "k-tv-mcp"; #[derive(Clone)] pub struct KTvMcpServer { pub channel_cmd_deps: Arc, - pub channel_query_deps: Arc, + pub channel_query: Arc, pub schedule_deps: Arc, - pub library_query_deps: Arc, + pub schedule_query: Arc, + pub library_query: Arc, + pub library_command_deps: Arc, pub owner_id: Uuid, } @@ -80,13 +82,13 @@ fn parse_uuid(s: &str) -> Result { impl KTvMcpServer { #[tool(description = "List all channels owned by the configured user")] async fn list_channels(&self) -> String { - channels::list_channels(&self.channel_query_deps, self.owner_id).await + channels::list_channels(&self.channel_query, self.owner_id).await } #[tool(description = "Get a channel by UUID")] async fn get_channel(&self, #[tool(aggr)] p: GetChannelParams) -> String { match parse_uuid(&p.id) { - Ok(id) => channels::get_channel(&self.channel_query_deps, id).await, + Ok(id) => channels::get_channel(&self.channel_query, id).await, Err(e) => e, } } @@ -143,7 +145,7 @@ impl KTvMcpServer { #[tool(description = "Get the currently active schedule for a channel (returns null if none)")] async fn get_active_schedule(&self, #[tool(aggr)] p: ChannelIdParam) -> String { match parse_uuid(&p.channel_id) { - Ok(id) => schedule::get_active_schedule(&self.schedule_deps, id).await, + Ok(id) => schedule::get_active_schedule(&self.schedule_query, id).await, Err(e) => e, } } @@ -160,14 +162,14 @@ impl KTvMcpServer { #[tool(description = "List media collections/libraries available in the library")] async fn list_collections(&self) -> String { - library::list_collections(&self.library_query_deps).await + library::list_collections(&self.library_query).await } #[tool( description = "List genres available in the library, optionally filtered by content type (movie/episode/short)" )] async fn list_genres(&self, #[tool(aggr)] p: ListGenresParams) -> String { - library::list_genres(&self.library_query_deps, p.content_type).await + library::list_genres(&self.library_query, p.content_type).await } #[tool( @@ -175,7 +177,7 @@ impl KTvMcpServer { )] async fn search_media(&self, #[tool(aggr)] p: SearchMediaParams) -> String { library::search_media( - &self.library_query_deps, + &self.library_command_deps, p.content_type, p.genres.unwrap_or_default(), p.search_term, diff --git a/crates/mcp/src/tools/channels.rs b/crates/mcp/src/tools/channels.rs index 17e8be2..407209f 100644 --- a/crates/mcp/src/tools/channels.rs +++ b/crates/mcp/src/tools/channels.rs @@ -1,31 +1,27 @@ use std::sync::Arc; use application::channels::{ - ChannelCommandDeps, ChannelQueryDeps, CreateChannelCommand, DeleteChannelCommand, - GetChannelQuery, ListByOwnerQuery, UpdateChannelCommand, + ChannelCommandDeps, CreateChannelCommand, DeleteChannelCommand, UpdateChannelCommand, }; use uuid::Uuid; use crate::error::{domain_err, ok_json}; pub async fn list_channels( - query_deps: &Arc, + channel_query: &Arc, owner_id: Uuid, ) -> String { - let query = ListByOwnerQuery { - owner_id: owner_id.into(), - }; - match application::channels::list_by_owner::execute(query_deps, query).await { + match channel_query.find_by_owner(owner_id.into()).await { Ok(channels) => ok_json(&channels), Err(e) => domain_err(e), } } -pub async fn get_channel(query_deps: &Arc, id: Uuid) -> String { - let query = GetChannelQuery { - channel_id: id.into(), - }; - match application::channels::get::execute(query_deps, query).await { +pub async fn get_channel( + channel_query: &Arc, + id: Uuid, +) -> String { + match channel_query.find_by_id(id.into()).await { Ok(Some(channel)) => ok_json(&channel), Ok(None) => serde_json::json!({"error": "Channel not found"}).to_string(), Err(e) => domain_err(e), diff --git a/crates/mcp/src/tools/library.rs b/crates/mcp/src/tools/library.rs index 440c597..281019b 100644 --- a/crates/mcp/src/tools/library.rs +++ b/crates/mcp/src/tools/library.rs @@ -1,8 +1,6 @@ use std::sync::Arc; -use application::library::{ - LibraryQueryDeps, ListCollectionsQuery, ListGenresQuery, SearchItemsQuery, -}; +use application::library::SearchItemsQuery; use serde::Serialize; use crate::error::{domain_err, ok_json}; @@ -48,9 +46,8 @@ fn content_type_to_str(ct: &domain::ContentType) -> &'static str { } } -pub async fn list_collections(deps: &Arc) -> String { - let query = ListCollectionsQuery { provider_id: None }; - match application::library::list_collections::execute(deps, query).await { +pub async fn list_collections(library_query: &Arc) -> String { + match library_query.list_collections(None).await { Ok(cols) => { let dtos: Vec = cols .into_iter() @@ -66,19 +63,26 @@ pub async fn list_collections(deps: &Arc) -> String { } } -pub async fn list_genres(deps: &Arc, content_type: Option) -> String { - let query = ListGenresQuery { - content_type, - provider_id: None, +pub async fn list_genres( + library_query: &Arc, + content_type: Option, +) -> String { + let ct = match content_type + .as_deref() + .map(application::library::parse_content_type) + .transpose() + { + Ok(ct) => ct, + Err(e) => return domain_err(e), }; - match application::library::list_genres::execute(deps, query).await { + match library_query.list_genres(ct.as_ref(), None).await { Ok(genres) => ok_json(&genres), Err(e) => domain_err(e), } } pub async fn search_media( - deps: &Arc, + library_command_deps: &Arc, content_type: Option, genres: Vec, search_term: Option, @@ -97,7 +101,7 @@ pub async fn search_media( offset: 0, limit: DEFAULT_SEARCH_LIMIT, }; - match application::library::search::execute(deps, query).await { + match application::library::search::execute(library_command_deps, query).await { Ok((items, total)) => { let dtos: Vec = items .into_iter() diff --git a/crates/mcp/src/tools/schedule.rs b/crates/mcp/src/tools/schedule.rs index 0fdc2da..abc9c32 100644 --- a/crates/mcp/src/tools/schedule.rs +++ b/crates/mcp/src/tools/schedule.rs @@ -1,9 +1,11 @@ use std::sync::Arc; use application::schedule::{ - GenerateScheduleCommand, GetActiveScheduleQuery, GetCurrentBroadcastQuery, ScheduleDeps, + GenerateScheduleCommand, GetCurrentBroadcastQuery, ScheduleDeps, }; +use chrono::Utc; use domain::ScheduledSlot; +use domain::value_objects::ChannelId; use serde::Serialize; use uuid::Uuid; @@ -23,9 +25,12 @@ pub async fn generate_schedule(deps: &Arc, channel_id: Uuid) -> St } } -pub async fn get_active_schedule(deps: &Arc, channel_id: Uuid) -> String { - let query = GetActiveScheduleQuery { channel_id }; - match application::schedule::get_active::execute(deps, query).await { +pub async fn get_active_schedule( + schedule_query: &Arc, + channel_id: Uuid, +) -> String { + let cid = ChannelId::from(channel_id); + match schedule_query.find_active(cid, Utc::now()).await { Ok(Some(schedule)) => ok_json(&schedule), Ok(None) => "null".to_string(), Err(e) => domain_err(e), diff --git a/crates/presentation/src/factory.rs b/crates/presentation/src/factory.rs index aedfc38..d519c83 100644 --- a/crates/presentation/src/factory.rs +++ b/crates/presentation/src/factory.rs @@ -3,11 +3,11 @@ use std::sync::Arc; use application::{ admin::AdminDeps, auth::AuthDeps, - channels::{ChannelCommandDeps, ChannelQueryDeps}, + channels::ChannelCommandDeps, config::ConfigDeps, config_snapshots::ConfigSnapshotDeps, iptv::IptvDeps, - library::{LibraryCommandDeps, LibraryQueryDeps}, + library::LibraryCommandDeps, providers::ProviderDeps, schedule::ScheduleDeps, }; @@ -73,10 +73,6 @@ pub async fn build_app_state(config: Config) -> anyhow::Result { event_publisher: event_publisher.clone(), }); - let channel_query_deps = Arc::new(ChannelQueryDeps { - channel_query: wire_output.channel_query.clone(), - }); - let config_snapshot_deps = Arc::new(ConfigSnapshotDeps { channel_command: wire_output.channel_command.clone(), channel_query: wire_output.channel_query.clone(), @@ -99,10 +95,6 @@ pub async fn build_app_state(config: Config) -> anyhow::Result { event_publisher: event_publisher.clone(), }); - let library_query_deps = Arc::new(LibraryQueryDeps { - library_query: wire_output.library_query.clone(), - }); - let admin_deps = Arc::new(AdminDeps { settings_repo: wire_output.settings.clone(), activity_query: wire_output.activity_query.clone(), @@ -163,19 +155,23 @@ pub async fn build_app_state(config: Config) -> anyhow::Result { Ok(AppState { auth_deps, channel_command_deps, - channel_query_deps, config_snapshot_deps, config_deps, schedule_deps, library_command_deps, - library_query_deps, admin_deps, iptv_deps, provider_deps, + channel_query: wire_output.channel_query.clone(), + library_query: wire_output.library_query.clone(), + schedule_query: wire_output.schedule_query.clone(), + settings_repo: wire_output.settings.clone(), + activity_query: wire_output.activity_query.clone(), + provider_config_query: wire_output.provider_config_query.clone(), + provider_config_command: wire_output.provider_config_command.clone(), #[cfg(feature = "auth-jwt")] jwt_validator, _library_sync: library_sync, - _settings_repo: wire_output.settings, _event_bus: event_bus, config: config_arc, _sync_trigger: sync_tx, diff --git a/crates/presentation/src/handlers/admin.rs b/crates/presentation/src/handlers/admin.rs index ba8bb21..8344396 100644 --- a/crates/presentation/src/handlers/admin.rs +++ b/crates/presentation/src/handlers/admin.rs @@ -3,7 +3,7 @@ use axum::extract::{Query, State}; use std::collections::HashMap; use api_types::{ActivityEventResponse, ActivityLogParams, SettingsResponse}; -use application::admin::{GetActivityLogQuery, UpdateSettingsCommand}; +use application::admin::UpdateSettingsCommand; use crate::errors::AppError; use crate::extractors::AdminUser; @@ -15,8 +15,7 @@ pub async fn get_settings( State(state): State, AdminUser(_user): AdminUser, ) -> Result, AppError> { - let pairs = - application::admin::get_settings::execute(&state.admin_deps, application::admin::GetSettingsQuery).await?; + let pairs = state.settings_repo.get_all().await?; let settings: HashMap = pairs.into_iter().collect(); Ok(Json(SettingsResponse { settings })) } @@ -40,10 +39,8 @@ pub async fn get_activity_log( AdminUser(_user): AdminUser, Query(params): Query, ) -> Result>, AppError> { - let query = GetActivityLogQuery { - limit: params.limit.unwrap_or(DEFAULT_ACTIVITY_LIMIT), - }; - let events = application::admin::activity_log::execute(&state.admin_deps, query).await?; + let limit = params.limit.unwrap_or(DEFAULT_ACTIVITY_LIMIT); + let events = state.activity_query.recent(limit).await?; Ok(Json( events .into_iter() diff --git a/crates/presentation/src/handlers/channels.rs b/crates/presentation/src/handlers/channels.rs index d6d2c17..995eef5 100644 --- a/crates/presentation/src/handlers/channels.rs +++ b/crates/presentation/src/handlers/channels.rs @@ -5,14 +5,8 @@ use api_types::{ ChannelResponse, ConfigSnapshotResponse, CreateChannelRequest, PatchSnapshotRequest, UpdateChannelRequest, }; -use application::channels::{ - CreateChannelCommand, DeleteChannelCommand, GetChannelQuery, ListByOwnerQuery, - ListChannelsQuery, UpdateChannelCommand, -}; -use application::config_snapshots::{ - GetSnapshotQuery, ListSnapshotsQuery, PatchLabelCommand, RestoreSnapshotCommand, - SaveSnapshotCommand, -}; +use application::channels::{CreateChannelCommand, DeleteChannelCommand, UpdateChannelCommand}; +use application::config_snapshots::{RestoreSnapshotCommand, SaveSnapshotCommand}; use domain::DomainError; use crate::errors::AppError; @@ -23,8 +17,7 @@ pub async fn list_channels( State(state): State, CurrentUser(_user): CurrentUser, ) -> Result>, AppError> { - let channels = - application::channels::list::execute(&state.channel_query_deps, ListChannelsQuery).await?; + let channels = state.channel_query.find_all().await?; Ok(Json(channels.into_iter().map(ChannelResponse::from).collect())) } @@ -32,11 +25,7 @@ pub async fn list_my_channels( State(state): State, CurrentUser(user): CurrentUser, ) -> Result>, AppError> { - let query = ListByOwnerQuery { - owner_id: user.id(), - }; - let channels = - application::channels::list_by_owner::execute(&state.channel_query_deps, query).await?; + let channels = state.channel_query.find_by_owner(user.id()).await?; Ok(Json(channels.into_iter().map(ChannelResponse::from).collect())) } @@ -59,10 +48,9 @@ pub async fn get_channel( CurrentUser(_user): CurrentUser, Path(id): Path, ) -> Result, AppError> { - let query = GetChannelQuery { - channel_id: id.into(), - }; - let channel = application::channels::get::execute(&state.channel_query_deps, query) + let channel = state + .channel_query + .find_by_id(id.into()) .await? .ok_or_else(|| AppError(DomainError::NotFound(format!("Channel {id} not found"))))?; Ok(Json(ChannelResponse::from(channel))) @@ -119,10 +107,7 @@ pub async fn list_snapshots( CurrentUser(_user): CurrentUser, Path(id): Path, ) -> Result>, AppError> { - let query = ListSnapshotsQuery { - channel_id: id.into(), - }; - let snaps = application::config_snapshots::list::execute(&state.config_snapshot_deps, query).await?; + let snaps = state.channel_query.list_config_snapshots(id.into()).await?; Ok(Json(snaps.into_iter().map(ConfigSnapshotResponse::from).collect())) } @@ -131,11 +116,9 @@ pub async fn get_snapshot( CurrentUser(_user): CurrentUser, Path((id, snapshot_id)): Path<(uuid::Uuid, uuid::Uuid)>, ) -> Result, AppError> { - let query = GetSnapshotQuery { - channel_id: id.into(), - snapshot_id: snapshot_id.into(), - }; - let snap = application::config_snapshots::get::execute(&state.config_snapshot_deps, query) + let snap = state + .channel_query + .get_config_snapshot(id.into(), snapshot_id.into()) .await? .ok_or_else(|| AppError(DomainError::NotFound("Snapshot not found".into())))?; Ok(Json(ConfigSnapshotResponse::from(snap))) @@ -147,15 +130,12 @@ pub async fn patch_snapshot( Path((id, snapshot_id)): Path<(uuid::Uuid, uuid::Uuid)>, Json(req): Json, ) -> Result, AppError> { - let cmd = PatchLabelCommand { - channel_id: id.into(), - snapshot_id: snapshot_id.into(), - label: req.label, - }; - let snap = - application::config_snapshots::patch_label::execute(&state.config_snapshot_deps, cmd) - .await? - .ok_or_else(|| AppError(DomainError::NotFound("Snapshot not found".into())))?; + let snap = state + .channel_command_deps + .channel_command + .patch_config_snapshot_label(id.into(), snapshot_id.into(), req.label) + .await? + .ok_or_else(|| AppError(DomainError::NotFound("Snapshot not found".into())))?; Ok(Json(ConfigSnapshotResponse::from(snap))) } diff --git a/crates/presentation/src/handlers/library.rs b/crates/presentation/src/handlers/library.rs index c2a98ab..1f79fd1 100644 --- a/crates/presentation/src/handlers/library.rs +++ b/crates/presentation/src/handlers/library.rs @@ -5,10 +5,7 @@ use api_types::{ CollectionResponse, GenresParams, LibraryItemResponse, LibrarySearchParams, PaginatedResponse, ProviderParam, SeasonResponse, SeasonsParams, ShowResponse, ShowsParams, SyncStatusEntry, }; -use application::library::{ - GetItemQuery, GetSyncStatusQuery, ListCollectionsQuery, ListGenresQuery, ListSeasonsQuery, - ListShowsQuery, SearchItemsQuery, TriggerSyncCommand, -}; +use application::library::{SearchItemsQuery, TriggerSyncCommand}; use domain::DomainError; use crate::errors::AppError; @@ -34,7 +31,7 @@ pub async fn search_items( offset: params.offset.unwrap_or(0), limit: params.limit.unwrap_or(DEFAULT_SEARCH_LIMIT), }; - let (items, total) = application::library::search::execute(&state.library_query_deps, query).await?; + let (items, total) = application::library::search::execute(&state.library_command_deps, query).await?; Ok(Json(PaginatedResponse::new( items.into_iter().map(LibraryItemResponse::from).collect(), total as u64, @@ -46,8 +43,9 @@ pub async fn get_item( CurrentUser(_user): CurrentUser, Path(id): Path, ) -> Result, AppError> { - let query = GetItemQuery { item_id: id.clone() }; - let item = application::library::get_item::execute(&state.library_query_deps, query) + let item = state + .library_query + .get_by_id(&id) .await? .ok_or_else(|| AppError(DomainError::NotFound(format!("Library item {id} not found"))))?; Ok(Json(LibraryItemResponse::from(item))) @@ -58,11 +56,10 @@ pub async fn list_collections( CurrentUser(_user): CurrentUser, Query(params): Query, ) -> Result>, AppError> { - let query = ListCollectionsQuery { - provider_id: params.provider, - }; - let collections = - application::library::list_collections::execute(&state.library_query_deps, query).await?; + let collections = state + .library_query + .list_collections(params.provider.as_deref()) + .await?; Ok(Json( collections .into_iter() @@ -76,12 +73,14 @@ pub async fn list_shows( CurrentUser(_user): CurrentUser, Query(params): Query, ) -> Result>, AppError> { - let query = ListShowsQuery { - provider_id: params.provider, - search_term: params.search_term, - genres: params.genres, - }; - let shows = application::library::list_shows::execute(&state.library_query_deps, query).await?; + let shows = state + .library_query + .list_shows( + params.provider.as_deref(), + params.search_term.as_deref(), + ¶ms.genres, + ) + .await?; Ok(Json(shows.into_iter().map(ShowResponse::from).collect())) } @@ -90,12 +89,10 @@ pub async fn list_seasons( CurrentUser(_user): CurrentUser, Query(params): Query, ) -> Result>, AppError> { - let query = ListSeasonsQuery { - series_name: params.series_name, - provider_id: params.provider, - }; - let seasons = - application::library::list_seasons::execute(&state.library_query_deps, query).await?; + let seasons = state + .library_query + .list_seasons(¶ms.series_name, params.provider.as_deref()) + .await?; Ok(Json( seasons.into_iter().map(SeasonResponse::from).collect(), )) @@ -106,12 +103,15 @@ pub async fn list_genres( CurrentUser(_user): CurrentUser, Query(params): Query, ) -> Result>, AppError> { - let query = ListGenresQuery { - content_type: params.content_type, - provider_id: params.provider, - }; - let genres = - application::library::list_genres::execute(&state.library_query_deps, query).await?; + let content_type = params + .content_type + .as_deref() + .map(application::library::parse_content_type) + .transpose()?; + let genres = state + .library_query + .list_genres(content_type.as_ref(), params.provider.as_deref()) + .await?; Ok(Json(genres)) } @@ -119,9 +119,7 @@ pub async fn sync_status( State(state): State, CurrentUser(_user): CurrentUser, ) -> Result>, AppError> { - let entries = - application::library::get_sync_status::execute(&state.library_query_deps, GetSyncStatusQuery) - .await?; + let entries = state.library_query.latest_sync_status().await?; Ok(Json(entries.into_iter().map(SyncStatusEntry::from).collect())) } diff --git a/crates/presentation/src/handlers/providers.rs b/crates/presentation/src/handlers/providers.rs index 67b4131..e972303 100644 --- a/crates/presentation/src/handlers/providers.rs +++ b/crates/presentation/src/handlers/providers.rs @@ -2,9 +2,7 @@ use axum::Json; use axum::extract::{Path, State}; use api_types::{ProviderConfigRequest, ProviderConfigResponse}; -use application::providers::{ - DeleteProviderCommand, GetProviderQuery, ListProvidersQuery, UpsertProviderCommand, -}; +use application::providers::UpsertProviderCommand; use domain::DomainError; use crate::errors::AppError; @@ -15,8 +13,7 @@ pub async fn list_providers( State(state): State, AdminUser(_user): AdminUser, ) -> Result>, AppError> { - let providers = - application::providers::list::execute(&state.provider_deps, ListProvidersQuery).await?; + let providers = state.provider_config_query.get_all().await?; Ok(Json( providers .into_iter() @@ -30,8 +27,9 @@ pub async fn get_provider( AdminUser(_user): AdminUser, Path(id): Path, ) -> Result, AppError> { - let query = GetProviderQuery { id: id.clone() }; - let provider = application::providers::get::execute(&state.provider_deps, query) + let provider = state + .provider_config_query + .get_by_id(&id) .await? .ok_or_else(|| AppError(DomainError::NotFound(format!("Provider {id} not found"))))?; Ok(Json(ProviderConfigResponse::from(provider))) @@ -58,7 +56,6 @@ pub async fn delete_provider( AdminUser(_user): AdminUser, Path(id): Path, ) -> Result { - let cmd = DeleteProviderCommand { id }; - application::providers::delete::execute(&state.provider_deps, cmd).await?; + state.provider_config_command.delete(&id).await?; Ok(axum::http::StatusCode::NO_CONTENT) } diff --git a/crates/presentation/src/handlers/schedule.rs b/crates/presentation/src/handlers/schedule.rs index 2abb0e1..6497f49 100644 --- a/crates/presentation/src/handlers/schedule.rs +++ b/crates/presentation/src/handlers/schedule.rs @@ -2,14 +2,15 @@ use axum::Json; use axum::extract::{Path, State}; use axum::http::StatusCode; use axum::response::IntoResponse; +use chrono::Utc; use api_types::{ CurrentBroadcastResponse, ScheduleHistoryEntry, ScheduleResponse, SlotResponse, }; use application::schedule::{ - GenerateScheduleCommand, GetActiveScheduleQuery, GetCurrentBroadcastQuery, GetEpgQuery, - GetStreamUrlQuery, ListHistoryQuery, + GenerateScheduleCommand, GetCurrentBroadcastQuery, GetEpgQuery, GetStreamUrlQuery, }; +use domain::value_objects::ChannelId; use crate::errors::AppError; use crate::extractors::CurrentUser; @@ -30,8 +31,8 @@ pub async fn get_active_schedule( CurrentUser(_user): CurrentUser, Path(id): Path, ) -> Result { - let query = GetActiveScheduleQuery { channel_id: id }; - match application::schedule::get_active::execute(&state.schedule_deps, query).await? { + let channel_id = ChannelId::from(id); + match state.schedule_query.find_active(channel_id, Utc::now()).await? { Some(schedule) => Ok(Json(ScheduleResponse::from(schedule)).into_response()), None => Ok(StatusCode::NO_CONTENT.into_response()), } @@ -85,9 +86,8 @@ pub async fn list_schedule_history( CurrentUser(_user): CurrentUser, Path(id): Path, ) -> Result>, AppError> { - let query = ListHistoryQuery { channel_id: id }; - let history = - application::schedule::list_history::execute(&state.schedule_deps, query).await?; + let channel_id = ChannelId::from(id); + let history = state.schedule_query.list_schedule_history(channel_id).await?; Ok(Json( history .into_iter() diff --git a/crates/presentation/src/state.rs b/crates/presentation/src/state.rs index 3818b9f..a61d429 100644 --- a/crates/presentation/src/state.rs +++ b/crates/presentation/src/state.rs @@ -3,11 +3,11 @@ use std::sync::Arc; use application::{ admin::AdminDeps, auth::AuthDeps, - channels::{ChannelCommandDeps, ChannelQueryDeps}, + channels::ChannelCommandDeps, config::ConfigDeps, config_snapshots::ConfigSnapshotDeps, iptv::IptvDeps, - library::{LibraryCommandDeps, LibraryQueryDeps}, + library::LibraryCommandDeps, providers::ProviderDeps, schedule::ScheduleDeps, }; @@ -16,21 +16,26 @@ use application::{ pub struct AppState { pub auth_deps: Arc, pub channel_command_deps: Arc, - pub channel_query_deps: Arc, pub config_snapshot_deps: Arc, pub config_deps: Arc, pub schedule_deps: Arc, pub library_command_deps: Arc, - pub library_query_deps: Arc, pub admin_deps: Arc, pub iptv_deps: Arc, pub provider_deps: Arc, + pub channel_query: Arc, + pub library_query: Arc, + pub schedule_query: Arc, + pub settings_repo: Arc, + pub activity_query: Arc, + pub provider_config_query: Arc, + pub provider_config_command: Arc, + #[cfg(feature = "auth-jwt")] pub jwt_validator: Option>, pub _library_sync: Arc, - pub _settings_repo: Arc, pub _event_bus: Arc, pub config: Arc,