kill LibraryItem, unify to MediaItem; decouple schedule engine from providers
ADR-0001: MediaItem absorbs LibraryItem fields (provider_id, external_id, collection_name, collection_type, synced_at, role/MediaRole). LibraryItem + LibraryItemRow deleted. All ports/adapters/tests updated. ADR-0002: schedule engine takes LibraryQuery instead of IProviderRegistry. Algorithmic blocks query library via search(), manual blocks via get_by_id(). get_stream_url removed from engine; provider_registry moved to ScheduleDeps for playback-time stream URL resolution in application layer. BlockContent provider_id field removed (meaningless when querying library).
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use domain::models::LibraryItem;
|
||||
use domain::models::MediaItem;
|
||||
use domain::DomainResult;
|
||||
|
||||
use super::deps::LibraryQueryDeps;
|
||||
@@ -7,7 +7,7 @@ use super::queries::GetItemQuery;
|
||||
pub async fn execute(
|
||||
deps: &LibraryQueryDeps,
|
||||
query: GetItemQuery,
|
||||
) -> DomainResult<Option<LibraryItem>> {
|
||||
) -> DomainResult<Option<MediaItem>> {
|
||||
deps.library_query.get_by_id(&query.item_id).await
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use domain::DomainResult;
|
||||
use domain::models::LibraryItem;
|
||||
use domain::models::MediaItem;
|
||||
use domain::value_objects::LibrarySearchFilter;
|
||||
|
||||
use super::deps::LibraryQueryDeps;
|
||||
@@ -9,7 +9,7 @@ use super::queries::SearchItemsQuery;
|
||||
pub async fn execute(
|
||||
deps: &LibraryQueryDeps,
|
||||
query: SearchItemsQuery,
|
||||
) -> DomainResult<(Vec<LibraryItem>, u32)> {
|
||||
) -> DomainResult<(Vec<MediaItem>, u32)> {
|
||||
let content_type = query
|
||||
.content_type
|
||||
.as_deref()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use domain::models::LibraryItem;
|
||||
use domain::models::MediaItem;
|
||||
use domain::value_objects::ContentType;
|
||||
|
||||
use crate::library::get_item;
|
||||
@@ -8,11 +8,11 @@ use crate::library::queries::GetItemQuery;
|
||||
mod helpers;
|
||||
|
||||
fn seed_item(repo: &std::sync::Arc<domain::testing::InMemoryLibraryRepository>) {
|
||||
let item = LibraryItem::new("test", "m1", "Die Hard", ContentType::Movie, 7800, "2026-01-01");
|
||||
let item = MediaItem::new_library("test", "m1", "Die Hard", ContentType::Movie, 7800, "2026-01-01");
|
||||
repo.items
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(item.id().to_string(), item);
|
||||
.insert(item.id().value().to_string(), item);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use domain::models::{LibraryItem, LibraryItemRow};
|
||||
use domain::value_objects::ContentType;
|
||||
use domain::models::{MediaItem, MediaItemRow};
|
||||
use domain::value_objects::{ContentType, MediaItemId, MediaRole};
|
||||
|
||||
use crate::library::list_collections;
|
||||
use crate::library::queries::ListCollectionsQuery;
|
||||
@@ -10,13 +10,14 @@ mod helpers;
|
||||
fn seed_with_collections(repo: &std::sync::Arc<domain::testing::InMemoryLibraryRepository>) {
|
||||
let mut store = repo.items.lock().unwrap();
|
||||
|
||||
let item = LibraryItem::from_persistence(LibraryItemRow {
|
||||
id: "test::m1".into(),
|
||||
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,
|
||||
@@ -27,17 +28,19 @@ fn seed_with_collections(repo: &std::sync::Arc<domain::testing::InMemoryLibraryR
|
||||
collection_name: Some("Movies".into()),
|
||||
collection_type: Some("movies".into()),
|
||||
thumbnail_url: None,
|
||||
synced_at: "2026-01-01".into(),
|
||||
synced_at: Some("2026-01-01".into()),
|
||||
role: MediaRole::default(),
|
||||
});
|
||||
store.insert(item.id().to_string(), item);
|
||||
store.insert(item.id().value().to_string(), item);
|
||||
|
||||
let item2 = LibraryItem::from_persistence(LibraryItemRow {
|
||||
id: "test::e1".into(),
|
||||
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),
|
||||
@@ -48,9 +51,10 @@ fn seed_with_collections(repo: &std::sync::Arc<domain::testing::InMemoryLibraryR
|
||||
collection_name: Some("TV Shows".into()),
|
||||
collection_type: Some("tvshows".into()),
|
||||
thumbnail_url: None,
|
||||
synced_at: "2026-01-01".into(),
|
||||
synced_at: Some("2026-01-01".into()),
|
||||
role: MediaRole::default(),
|
||||
});
|
||||
store.insert(item2.id().to_string(), item2);
|
||||
store.insert(item2.id().value().to_string(), item2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use domain::models::{LibraryItem, LibraryItemRow};
|
||||
use domain::value_objects::ContentType;
|
||||
use domain::models::{MediaItem, MediaItemRow};
|
||||
use domain::value_objects::{ContentType, MediaItemId, MediaRole};
|
||||
|
||||
use crate::library::list_genres;
|
||||
use crate::library::queries::ListGenresQuery;
|
||||
@@ -10,13 +10,14 @@ mod helpers;
|
||||
fn seed_with_genres(repo: &std::sync::Arc<domain::testing::InMemoryLibraryRepository>) {
|
||||
let mut store = repo.items.lock().unwrap();
|
||||
|
||||
let item1 = LibraryItem::from_persistence(LibraryItemRow {
|
||||
id: "test::m1".into(),
|
||||
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,
|
||||
@@ -27,15 +28,17 @@ fn seed_with_genres(repo: &std::sync::Arc<domain::testing::InMemoryLibraryReposi
|
||||
collection_name: None,
|
||||
collection_type: None,
|
||||
thumbnail_url: None,
|
||||
synced_at: "2026-01-01".into(),
|
||||
synced_at: Some("2026-01-01".into()),
|
||||
role: MediaRole::default(),
|
||||
});
|
||||
let item2 = LibraryItem::from_persistence(LibraryItemRow {
|
||||
id: "test::m2".into(),
|
||||
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,
|
||||
@@ -46,11 +49,12 @@ fn seed_with_genres(repo: &std::sync::Arc<domain::testing::InMemoryLibraryReposi
|
||||
collection_name: None,
|
||||
collection_type: None,
|
||||
thumbnail_url: None,
|
||||
synced_at: "2026-01-01".into(),
|
||||
synced_at: Some("2026-01-01".into()),
|
||||
role: MediaRole::default(),
|
||||
});
|
||||
|
||||
store.insert(item1.id().to_string(), item1);
|
||||
store.insert(item2.id().to_string(), item2);
|
||||
store.insert(item1.id().value().to_string(), item1);
|
||||
store.insert(item2.id().value().to_string(), item2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use domain::models::{LibraryItem, LibraryItemRow};
|
||||
use domain::value_objects::ContentType;
|
||||
use domain::models::{MediaItem, MediaItemRow};
|
||||
use domain::value_objects::{ContentType, MediaItemId, MediaRole};
|
||||
|
||||
use crate::library::list_seasons;
|
||||
use crate::library::queries::ListSeasonsQuery;
|
||||
@@ -11,13 +11,14 @@ fn seed_episodes(repo: &std::sync::Arc<domain::testing::InMemoryLibraryRepositor
|
||||
let mut store = repo.items.lock().unwrap();
|
||||
|
||||
for (i, season) in [1u32, 1, 2, 2, 2, 3].iter().enumerate() {
|
||||
let item = LibraryItem::from_persistence(LibraryItemRow {
|
||||
id: format!("test::e{i}"),
|
||||
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),
|
||||
@@ -28,9 +29,10 @@ fn seed_episodes(repo: &std::sync::Arc<domain::testing::InMemoryLibraryRepositor
|
||||
collection_name: None,
|
||||
collection_type: None,
|
||||
thumbnail_url: None,
|
||||
synced_at: "2026-01-01".into(),
|
||||
synced_at: Some("2026-01-01".into()),
|
||||
role: MediaRole::default(),
|
||||
});
|
||||
store.insert(item.id().to_string(), item);
|
||||
store.insert(item.id().value().to_string(), item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use domain::models::{LibraryItem, LibraryItemRow};
|
||||
use domain::value_objects::ContentType;
|
||||
use domain::models::{MediaItem, MediaItemRow};
|
||||
use domain::value_objects::{ContentType, MediaItemId, MediaRole};
|
||||
|
||||
use crate::library::list_shows;
|
||||
use crate::library::queries::ListShowsQuery;
|
||||
@@ -20,13 +20,14 @@ fn seed_episodes(repo: &std::sync::Arc<domain::testing::InMemoryLibraryRepositor
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let item = LibraryItem::from_persistence(LibraryItemRow {
|
||||
id: format!("test::e{i}"),
|
||||
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),
|
||||
@@ -37,9 +38,10 @@ fn seed_episodes(repo: &std::sync::Arc<domain::testing::InMemoryLibraryRepositor
|
||||
collection_name: None,
|
||||
collection_type: None,
|
||||
thumbnail_url: None,
|
||||
synced_at: "2026-01-01".into(),
|
||||
synced_at: Some("2026-01-01".into()),
|
||||
role: MediaRole::default(),
|
||||
});
|
||||
store.insert(item.id().to_string(), item);
|
||||
store.insert(item.id().value().to_string(), item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use domain::models::{LibraryItem, LibraryItemRow};
|
||||
use domain::value_objects::ContentType;
|
||||
use domain::models::{MediaItem, MediaItemRow};
|
||||
use domain::value_objects::{ContentType, MediaItemId, MediaRole};
|
||||
|
||||
use crate::library::queries::SearchItemsQuery;
|
||||
use crate::library::search;
|
||||
@@ -10,25 +10,26 @@ mod helpers;
|
||||
fn seed_items(repo: &std::sync::Arc<domain::testing::InMemoryLibraryRepository>) {
|
||||
let mut store = repo.items.lock().unwrap();
|
||||
let items = vec![
|
||||
LibraryItem::new("test", "m1", "Die Hard", ContentType::Movie, 7800, "2026-01-01"),
|
||||
LibraryItem::new("test", "m2", "Alien", ContentType::Movie, 7020, "2026-01-01"),
|
||||
LibraryItem::new("test", "e1", "BB S01E01", ContentType::Episode, 2700, "2026-01-01"),
|
||||
MediaItem::new_library("test", "m1", "Die Hard", ContentType::Movie, 7800, "2026-01-01"),
|
||||
MediaItem::new_library("test", "m2", "Alien", ContentType::Movie, 7020, "2026-01-01"),
|
||||
MediaItem::new_library("test", "e1", "BB S01E01", ContentType::Episode, 2700, "2026-01-01"),
|
||||
];
|
||||
for item in items {
|
||||
store.insert(item.id().to_string(), item);
|
||||
store.insert(item.id().value().to_string(), item);
|
||||
}
|
||||
}
|
||||
|
||||
fn seed_items_with_genres(repo: &std::sync::Arc<domain::testing::InMemoryLibraryRepository>) {
|
||||
let mut store = repo.items.lock().unwrap();
|
||||
|
||||
let action = LibraryItem::from_persistence(LibraryItemRow {
|
||||
id: "test::m1".into(),
|
||||
let action = 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,
|
||||
@@ -39,15 +40,17 @@ fn seed_items_with_genres(repo: &std::sync::Arc<domain::testing::InMemoryLibrary
|
||||
collection_name: None,
|
||||
collection_type: None,
|
||||
thumbnail_url: None,
|
||||
synced_at: "2026-01-01".into(),
|
||||
synced_at: Some("2026-01-01".into()),
|
||||
role: MediaRole::default(),
|
||||
});
|
||||
let scifi = LibraryItem::from_persistence(LibraryItemRow {
|
||||
id: "test::m2".into(),
|
||||
let scifi = 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,
|
||||
@@ -58,15 +61,17 @@ fn seed_items_with_genres(repo: &std::sync::Arc<domain::testing::InMemoryLibrary
|
||||
collection_name: None,
|
||||
collection_type: None,
|
||||
thumbnail_url: None,
|
||||
synced_at: "2026-01-01".into(),
|
||||
synced_at: Some("2026-01-01".into()),
|
||||
role: MediaRole::default(),
|
||||
});
|
||||
let comedy = LibraryItem::from_persistence(LibraryItemRow {
|
||||
id: "test::m3".into(),
|
||||
let comedy = MediaItem::from_persistence(MediaItemRow {
|
||||
id: MediaItemId::new("test::m3"),
|
||||
provider_id: "test".into(),
|
||||
external_id: "m3".into(),
|
||||
title: "Airplane!".into(),
|
||||
content_type: ContentType::Movie,
|
||||
duration_secs: 5280,
|
||||
description: None,
|
||||
series_name: None,
|
||||
season_number: None,
|
||||
episode_number: None,
|
||||
@@ -77,12 +82,13 @@ fn seed_items_with_genres(repo: &std::sync::Arc<domain::testing::InMemoryLibrary
|
||||
collection_name: None,
|
||||
collection_type: None,
|
||||
thumbnail_url: None,
|
||||
synced_at: "2026-01-01".into(),
|
||||
synced_at: Some("2026-01-01".into()),
|
||||
role: MediaRole::default(),
|
||||
});
|
||||
|
||||
store.insert(action.id().to_string(), action);
|
||||
store.insert(scifi.id().to_string(), scifi);
|
||||
store.insert(comedy.id().to_string(), comedy);
|
||||
store.insert(action.id().value().to_string(), action);
|
||||
store.insert(scifi.id().value().to_string(), scifi);
|
||||
store.insert(comedy.id().value().to_string(), comedy);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
Reference in New Issue
Block a user