refactor(domain): Row structs for from_persistence, ID newtypes, kill clippy.toml

- delete clippy.toml (too-many-arguments-threshold=20 hack)
- ChannelRow/MediaItemRow/LibraryItemRow structs for from_persistence
- SnapshotId/ActivityEventId/PlaybackRecordId newtypes
- DomainError variants use ChannelId/UserId instead of Uuid
- ActivityEvent.channel_id: Option<ChannelId> not Option<Uuid>
- InMemory repos key on newtype IDs
- AlgorithmicParams struct for schedule engine
- update all adapters/application/presentation callers
This commit is contained in:
2026-07-12 05:00:49 +02:00
parent 031cba5cfb
commit c0e685a4ee
65 changed files with 684 additions and 695 deletions

View File

@@ -34,7 +34,7 @@ fn make_deps_with_user(
repo.store
.lock()
.unwrap()
.insert(user.id().value(), user);
.insert(user.id(), user);
let deps = AuthDeps {
user_command: repo.clone(),
@@ -112,7 +112,7 @@ async fn login_fails_for_oidc_only_user() {
repo.store
.lock()
.unwrap()
.insert(user.id().value(), user);
.insert(user.id(), user);
let deps = AuthDeps {
user_command: repo.clone(),

View File

@@ -103,7 +103,7 @@ async fn register_fails_for_duplicate_email() {
repo.store
.lock()
.unwrap()
.insert(existing.id().value(), existing);
.insert(existing.id(), existing);
let result = register::execute(
&deps,

View File

@@ -1,17 +1,15 @@
use uuid::Uuid;
use domain::models::ScheduleConfig;
use domain::value_objects::RecyclePolicy;
use domain::value_objects::{ChannelId, RecyclePolicy, UserId};
pub struct CreateChannelCommand {
pub owner_id: Uuid,
pub owner_id: UserId,
pub name: String,
pub timezone: String,
}
pub struct UpdateChannelCommand {
pub channel_id: Uuid,
pub owner_id: Uuid,
pub channel_id: ChannelId,
pub owner_id: UserId,
pub name: Option<String>,
pub description: Option<Option<String>>,
pub timezone: Option<String>,
@@ -21,6 +19,6 @@ pub struct UpdateChannelCommand {
}
pub struct DeleteChannelCommand {
pub channel_id: Uuid,
pub owner_id: Uuid,
pub channel_id: ChannelId,
pub owner_id: UserId,
}

View File

@@ -1,14 +1,12 @@
use domain::events::DomainEvent;
use domain::models::Channel;
use domain::value_objects::UserId;
use domain::DomainResult;
use super::commands::CreateChannelCommand;
use super::deps::ChannelCommandDeps;
pub async fn execute(deps: &ChannelCommandDeps, cmd: CreateChannelCommand) -> DomainResult<Channel> {
let owner_id = UserId::from(cmd.owner_id);
let channel = Channel::new(owner_id, cmd.name, cmd.timezone);
let channel = Channel::new(cmd.owner_id, cmd.name, cmd.timezone);
deps.channel_command.save(&channel).await?;

View File

@@ -1,5 +1,4 @@
use domain::events::DomainEvent;
use domain::value_objects::{ChannelId, UserId};
use domain::DomainResult;
use super::commands::DeleteChannelCommand;
@@ -7,15 +6,12 @@ use super::deps::ChannelCommandDeps;
use super::find_owned_channel;
pub async fn execute(deps: &ChannelCommandDeps, cmd: DeleteChannelCommand) -> DomainResult<()> {
let channel_id = ChannelId::from(cmd.channel_id);
let owner_id = UserId::from(cmd.owner_id);
find_owned_channel(deps.channel_query.as_ref(), cmd.channel_id, cmd.owner_id).await?;
find_owned_channel(deps.channel_query.as_ref(), channel_id, owner_id, cmd.channel_id).await?;
deps.channel_command.delete(channel_id).await?;
deps.channel_command.delete(cmd.channel_id).await?;
deps.event_publisher
.publish(DomainEvent::ChannelDeleted { channel_id })
.publish(DomainEvent::ChannelDeleted { channel_id: cmd.channel_id })
.await?;
Ok(())

View File

@@ -1,13 +1,11 @@
use domain::models::Channel;
use domain::value_objects::ChannelId;
use domain::DomainResult;
use super::deps::ChannelQueryDeps;
use super::queries::GetChannelQuery;
pub async fn execute(deps: &ChannelQueryDeps, query: GetChannelQuery) -> DomainResult<Option<Channel>> {
let channel_id = ChannelId::from(query.channel_id);
deps.channel_query.find_by_id(channel_id).await
deps.channel_query.find_by_id(query.channel_id).await
}
#[cfg(test)]

View File

@@ -1,13 +1,11 @@
use domain::models::Channel;
use domain::value_objects::UserId;
use domain::DomainResult;
use super::deps::ChannelQueryDeps;
use super::queries::ListByOwnerQuery;
pub async fn execute(deps: &ChannelQueryDeps, query: ListByOwnerQuery) -> DomainResult<Vec<Channel>> {
let owner_id = UserId::from(query.owner_id);
deps.channel_query.find_by_owner(owner_id).await
deps.channel_query.find_by_owner(query.owner_id).await
}
#[cfg(test)]

View File

@@ -22,12 +22,11 @@ pub(crate) async fn find_owned_channel(
query: &dyn domain::ports::ChannelQuery,
channel_id: ChannelId,
owner_id: UserId,
raw_channel_id: uuid::Uuid,
) -> DomainResult<Channel> {
let channel = query
.find_by_id(channel_id)
.await?
.ok_or(DomainError::ChannelNotFound(raw_channel_id))?;
.ok_or(DomainError::ChannelNotFound(channel_id))?;
if channel.owner_id() != owner_id {
return Err(DomainError::forbidden(OWNERSHIP_DENIED));

View File

@@ -1,11 +1,11 @@
use uuid::Uuid;
use domain::value_objects::{ChannelId, UserId};
pub struct GetChannelQuery {
pub channel_id: Uuid,
pub channel_id: ChannelId,
}
pub struct ListChannelsQuery;
pub struct ListByOwnerQuery {
pub owner_id: Uuid,
pub owner_id: UserId,
}

View File

@@ -25,7 +25,7 @@ async fn creates_channel_successfully() {
let channel = create::execute(
&deps,
CreateChannelCommand {
owner_id: owner.value(),
owner_id: owner,
name: "Movie Night".into(),
timezone: "America/New_York".into(),
},
@@ -51,7 +51,7 @@ async fn create_returns_default_config() {
let channel = create::execute(
&deps,
CreateChannelCommand {
owner_id: UserId::generate().value(),
owner_id: UserId::generate(),
name: "Defaults".into(),
timezone: "UTC".into(),
},

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use domain::testing::{InMemoryChannelRepository, NoopEventPublisher};
use domain::value_objects::UserId;
use domain::value_objects::{ChannelId, UserId};
use domain::DomainError;
use crate::channels::commands::{CreateChannelCommand, DeleteChannelCommand};
@@ -26,7 +26,7 @@ async fn deletes_channel_by_owner() {
let channel = create::execute(
&deps,
CreateChannelCommand {
owner_id: owner.value(),
owner_id: owner,
name: "Doomed".into(),
timezone: "UTC".into(),
},
@@ -37,8 +37,8 @@ async fn deletes_channel_by_owner() {
delete::execute(
&deps,
DeleteChannelCommand {
channel_id: channel.id().value(),
owner_id: owner.value(),
channel_id: channel.id(),
owner_id: owner,
},
)
.await
@@ -56,7 +56,7 @@ async fn delete_fails_if_not_owner() {
let channel = create::execute(
&deps,
CreateChannelCommand {
owner_id: owner.value(),
owner_id: owner,
name: "Protected".into(),
timezone: "UTC".into(),
},
@@ -67,8 +67,8 @@ async fn delete_fails_if_not_owner() {
let result = delete::execute(
&deps,
DeleteChannelCommand {
channel_id: channel.id().value(),
owner_id: stranger.value(),
channel_id: channel.id(),
owner_id: stranger,
},
)
.await;
@@ -87,8 +87,8 @@ async fn delete_nonexistent_channel_returns_not_found() {
let result = delete::execute(
&deps,
DeleteChannelCommand {
channel_id: uuid::Uuid::new_v4(),
owner_id: uuid::Uuid::new_v4(),
channel_id: ChannelId::generate(),
owner_id: UserId::generate(),
},
)
.await;

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use domain::testing::{InMemoryChannelRepository, NoopEventPublisher};
use domain::value_objects::UserId;
use domain::value_objects::{ChannelId, UserId};
use crate::channels::commands::CreateChannelCommand;
use crate::channels::deps::{ChannelCommandDeps, ChannelQueryDeps};
@@ -28,7 +28,7 @@ async fn get_existing_channel() {
let channel = create::execute(
&cmd_deps,
CreateChannelCommand {
owner_id: UserId::generate().value(),
owner_id: UserId::generate(),
name: "Findable".into(),
timezone: "UTC".into(),
},
@@ -39,7 +39,7 @@ async fn get_existing_channel() {
let found = get::execute(
&query_deps,
GetChannelQuery {
channel_id: channel.id().value(),
channel_id: channel.id(),
},
)
.await
@@ -56,7 +56,7 @@ async fn get_nonexistent_returns_none() {
let found = get::execute(
&query_deps,
GetChannelQuery {
channel_id: uuid::Uuid::new_v4(),
channel_id: ChannelId::generate(),
},
)
.await

View File

@@ -37,7 +37,7 @@ async fn list_returns_all_channels() {
create::execute(
&cmd_deps,
CreateChannelCommand {
owner_id: UserId::generate().value(),
owner_id: UserId::generate(),
name: name.into(),
timezone: "UTC".into(),
},

View File

@@ -32,7 +32,7 @@ async fn filters_by_owner() {
create::execute(
&cmd_deps,
CreateChannelCommand {
owner_id: alice.value(),
owner_id: alice,
name: name.into(),
timezone: "UTC".into(),
},
@@ -45,7 +45,7 @@ async fn filters_by_owner() {
create::execute(
&cmd_deps,
CreateChannelCommand {
owner_id: bob.value(),
owner_id: bob,
name: "Bob-1".into(),
timezone: "UTC".into(),
},
@@ -56,7 +56,7 @@ async fn filters_by_owner() {
let alice_channels = list_by_owner::execute(
&query_deps,
ListByOwnerQuery {
owner_id: alice.value(),
owner_id: alice,
},
)
.await
@@ -73,7 +73,7 @@ async fn no_channels_returns_empty() {
let channels = list_by_owner::execute(
&query_deps,
ListByOwnerQuery {
owner_id: UserId::generate().value(),
owner_id: UserId::generate(),
},
)
.await

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use domain::testing::{InMemoryChannelRepository, NoopEventPublisher};
use domain::value_objects::UserId;
use domain::value_objects::{ChannelId, UserId};
use domain::DomainError;
use crate::channels::commands::{CreateChannelCommand, UpdateChannelCommand};
@@ -26,7 +26,7 @@ async fn updates_channel_name() {
let channel = create::execute(
&deps,
CreateChannelCommand {
owner_id: owner.value(),
owner_id: owner,
name: "Original".into(),
timezone: "UTC".into(),
},
@@ -37,8 +37,8 @@ async fn updates_channel_name() {
let updated = update::execute(
&deps,
UpdateChannelCommand {
channel_id: channel.id().value(),
owner_id: owner.value(),
channel_id: channel.id(),
owner_id: owner,
name: Some("Renamed".into()),
description: None,
timezone: None,
@@ -63,7 +63,7 @@ async fn update_fails_if_not_owner() {
let channel = create::execute(
&deps,
CreateChannelCommand {
owner_id: owner.value(),
owner_id: owner,
name: "Protected".into(),
timezone: "UTC".into(),
},
@@ -74,8 +74,8 @@ async fn update_fails_if_not_owner() {
let result = update::execute(
&deps,
UpdateChannelCommand {
channel_id: channel.id().value(),
owner_id: stranger.value(),
channel_id: channel.id(),
owner_id: stranger,
name: Some("Hacked".into()),
description: None,
timezone: None,
@@ -100,8 +100,8 @@ async fn update_nonexistent_channel_returns_not_found() {
let result = update::execute(
&deps,
UpdateChannelCommand {
channel_id: uuid::Uuid::new_v4(),
owner_id: uuid::Uuid::new_v4(),
channel_id: ChannelId::generate(),
owner_id: UserId::generate(),
name: Some("Ghost".into()),
description: None,
timezone: None,
@@ -127,7 +127,7 @@ async fn update_config_creates_snapshot() {
let channel = create::execute(
&deps,
CreateChannelCommand {
owner_id: owner.value(),
owner_id: owner,
name: "Snapshotted".into(),
timezone: "UTC".into(),
},
@@ -140,8 +140,8 @@ async fn update_config_creates_snapshot() {
update::execute(
&deps,
UpdateChannelCommand {
channel_id: channel.id().value(),
owner_id: owner.value(),
channel_id: channel.id(),
owner_id: owner,
name: None,
description: None,
timezone: None,
@@ -167,7 +167,7 @@ async fn update_without_config_skips_snapshot() {
let channel = create::execute(
&deps,
CreateChannelCommand {
owner_id: owner.value(),
owner_id: owner,
name: "NoSnapshot".into(),
timezone: "UTC".into(),
},
@@ -179,8 +179,8 @@ async fn update_without_config_skips_snapshot() {
update::execute(
&deps,
UpdateChannelCommand {
channel_id: channel.id().value(),
owner_id: owner.value(),
channel_id: channel.id(),
owner_id: owner,
name: Some("Renamed".into()),
description: None,
timezone: None,
@@ -205,7 +205,7 @@ async fn update_description_clear() {
let channel = create::execute(
&deps,
CreateChannelCommand {
owner_id: owner.value(),
owner_id: owner,
name: "Desc Test".into(),
timezone: "UTC".into(),
},
@@ -217,8 +217,8 @@ async fn update_description_clear() {
let updated = update::execute(
&deps,
UpdateChannelCommand {
channel_id: channel.id().value(),
owner_id: owner.value(),
channel_id: channel.id(),
owner_id: owner,
name: None,
description: Some(Some("A description".into())),
timezone: None,
@@ -235,8 +235,8 @@ async fn update_description_clear() {
let cleared = update::execute(
&deps,
UpdateChannelCommand {
channel_id: channel.id().value(),
owner_id: owner.value(),
channel_id: channel.id(),
owner_id: owner,
name: None,
description: Some(None),
timezone: None,

View File

@@ -1,6 +1,5 @@
use domain::events::DomainEvent;
use domain::models::Channel;
use domain::value_objects::{ChannelId, UserId};
use domain::DomainResult;
use super::commands::UpdateChannelCommand;
@@ -8,16 +7,13 @@ use super::deps::ChannelCommandDeps;
use super::find_owned_channel;
pub async fn execute(deps: &ChannelCommandDeps, cmd: UpdateChannelCommand) -> DomainResult<Channel> {
let channel_id = ChannelId::from(cmd.channel_id);
let owner_id = UserId::from(cmd.owner_id);
let mut channel =
find_owned_channel(deps.channel_query.as_ref(), channel_id, owner_id, cmd.channel_id)
find_owned_channel(deps.channel_query.as_ref(), cmd.channel_id, cmd.owner_id)
.await?;
if cmd.schedule_config.is_some() {
deps.channel_command
.save_config_snapshot(channel_id, channel.schedule_config(), None)
.save_config_snapshot(cmd.channel_id, channel.schedule_config(), None)
.await?;
}

View File

@@ -1,17 +1,17 @@
use uuid::Uuid;
use domain::value_objects::{ChannelId, SnapshotId};
pub struct SaveSnapshotCommand {
pub channel_id: Uuid,
pub channel_id: ChannelId,
pub label: Option<String>,
}
pub struct PatchLabelCommand {
pub channel_id: Uuid,
pub snapshot_id: Uuid,
pub channel_id: ChannelId,
pub snapshot_id: SnapshotId,
pub label: Option<String>,
}
pub struct RestoreSnapshotCommand {
pub channel_id: Uuid,
pub snapshot_id: Uuid,
pub channel_id: ChannelId,
pub snapshot_id: SnapshotId,
}

View File

@@ -1,5 +1,4 @@
use domain::models::ChannelConfigSnapshot;
use domain::value_objects::ChannelId;
use domain::DomainResult;
use super::deps::ConfigSnapshotDeps;
@@ -9,8 +8,7 @@ pub async fn execute(
deps: &ConfigSnapshotDeps,
query: GetSnapshotQuery,
) -> DomainResult<Option<ChannelConfigSnapshot>> {
let channel_id = ChannelId::from(query.channel_id);
deps.channel_query
.get_config_snapshot(channel_id, query.snapshot_id)
.get_config_snapshot(query.channel_id, query.snapshot_id)
.await
}

View File

@@ -1,5 +1,4 @@
use domain::models::ChannelConfigSnapshot;
use domain::value_objects::ChannelId;
use domain::DomainResult;
use super::deps::ConfigSnapshotDeps;
@@ -9,8 +8,7 @@ pub async fn execute(
deps: &ConfigSnapshotDeps,
query: ListSnapshotsQuery,
) -> DomainResult<Vec<ChannelConfigSnapshot>> {
let channel_id = ChannelId::from(query.channel_id);
deps.channel_query.list_config_snapshots(channel_id).await
deps.channel_query.list_config_snapshots(query.channel_id).await
}
#[cfg(test)]

View File

@@ -1,5 +1,4 @@
use domain::models::ChannelConfigSnapshot;
use domain::value_objects::ChannelId;
use domain::DomainResult;
use super::commands::PatchLabelCommand;
@@ -9,9 +8,7 @@ pub async fn execute(
deps: &ConfigSnapshotDeps,
cmd: PatchLabelCommand,
) -> DomainResult<Option<ChannelConfigSnapshot>> {
let channel_id = ChannelId::from(cmd.channel_id);
deps.channel_command
.patch_config_snapshot_label(channel_id, cmd.snapshot_id, cmd.label)
.patch_config_snapshot_label(cmd.channel_id, cmd.snapshot_id, cmd.label)
.await
}

View File

@@ -1,10 +1,10 @@
use uuid::Uuid;
use domain::value_objects::{ChannelId, SnapshotId};
pub struct ListSnapshotsQuery {
pub channel_id: Uuid,
pub channel_id: ChannelId,
}
pub struct GetSnapshotQuery {
pub channel_id: Uuid,
pub snapshot_id: Uuid,
pub channel_id: ChannelId,
pub snapshot_id: SnapshotId,
}

View File

@@ -1,5 +1,4 @@
use domain::models::Channel;
use domain::value_objects::ChannelId;
use domain::{DomainError, DomainResult};
use super::commands::RestoreSnapshotCommand;
@@ -9,11 +8,9 @@ pub async fn execute(
deps: &ConfigSnapshotDeps,
cmd: RestoreSnapshotCommand,
) -> DomainResult<Channel> {
let channel_id = ChannelId::from(cmd.channel_id);
let snapshot = deps
.channel_query
.get_config_snapshot(channel_id, cmd.snapshot_id)
.get_config_snapshot(cmd.channel_id, cmd.snapshot_id)
.await?
.ok_or(DomainError::ValidationError(format!(
"Snapshot {} not found",
@@ -22,12 +19,12 @@ pub async fn execute(
let mut channel = deps
.channel_query
.find_by_id(channel_id)
.find_by_id(cmd.channel_id)
.await?
.ok_or(DomainError::ChannelNotFound(cmd.channel_id))?;
deps.channel_command
.save_config_snapshot(channel_id, channel.schedule_config(), None)
.save_config_snapshot(cmd.channel_id, channel.schedule_config(), None)
.await?;
channel.set_schedule_config(snapshot.config().clone());

View File

@@ -1,5 +1,4 @@
use domain::models::ChannelConfigSnapshot;
use domain::value_objects::ChannelId;
use domain::{DomainError, DomainResult};
use super::commands::SaveSnapshotCommand;
@@ -9,16 +8,14 @@ pub async fn execute(
deps: &ConfigSnapshotDeps,
cmd: SaveSnapshotCommand,
) -> DomainResult<ChannelConfigSnapshot> {
let channel_id = ChannelId::from(cmd.channel_id);
let channel = deps
.channel_query
.find_by_id(channel_id)
.find_by_id(cmd.channel_id)
.await?
.ok_or(DomainError::ChannelNotFound(cmd.channel_id))?;
deps.channel_command
.save_config_snapshot(channel_id, channel.schedule_config(), cmd.label)
.save_config_snapshot(cmd.channel_id, channel.schedule_config(), cmd.label)
.await
}

View File

@@ -23,7 +23,7 @@ async fn seed_channel(repo: &InMemoryChannelRepository) -> Channel {
repo.channels
.lock()
.unwrap()
.insert(channel.id().value(), channel.clone());
.insert(channel.id(), channel.clone());
channel
}
@@ -35,7 +35,7 @@ async fn list_empty() {
let snaps = list::execute(
&deps,
ListSnapshotsQuery {
channel_id: channel.id().value(),
channel_id: channel.id(),
},
)
.await
@@ -53,7 +53,7 @@ async fn list_returns_saved_snapshots() {
save::execute(
&deps,
SaveSnapshotCommand {
channel_id: channel.id().value(),
channel_id: channel.id(),
label: Some(label.into()),
},
)
@@ -64,7 +64,7 @@ async fn list_returns_saved_snapshots() {
let snaps = list::execute(
&deps,
ListSnapshotsQuery {
channel_id: channel.id().value(),
channel_id: channel.id(),
},
)
.await

View File

@@ -22,7 +22,7 @@ async fn seed_channel(repo: &InMemoryChannelRepository) -> Channel {
repo.channels
.lock()
.unwrap()
.insert(channel.id().value(), channel.clone());
.insert(channel.id(), channel.clone());
channel
}
@@ -34,7 +34,7 @@ async fn save_creates_snapshot() {
let snap = save::execute(
&deps,
SaveSnapshotCommand {
channel_id: channel.id().value(),
channel_id: channel.id(),
label: Some("v1".into()),
},
)
@@ -54,7 +54,7 @@ async fn save_increments_version() {
save::execute(
&deps,
SaveSnapshotCommand {
channel_id: channel.id().value(),
channel_id: channel.id(),
label: None,
},
)
@@ -64,7 +64,7 @@ async fn save_increments_version() {
let snap2 = save::execute(
&deps,
SaveSnapshotCommand {
channel_id: channel.id().value(),
channel_id: channel.id(),
label: None,
},
)

View File

@@ -43,7 +43,7 @@ async fn m3u_includes_channels() {
repo.channels
.lock()
.unwrap()
.insert(ch.id().value(), ch.clone());
.insert(ch.id(), ch.clone());
let result = m3u::execute(
&deps,
@@ -68,7 +68,7 @@ async fn m3u_no_token() {
repo.channels
.lock()
.unwrap()
.insert(ch.id().value(), ch);
.insert(ch.id(), ch);
let result = m3u::execute(
&deps,

View File

@@ -1,4 +1,4 @@
use domain::models::LibraryItem;
use domain::models::{LibraryItem, LibraryItemRow};
use domain::value_objects::ContentType;
use crate::library::list_collections;
@@ -10,46 +10,46 @@ 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(
"test::m1".into(),
"test".into(),
"m1".into(),
"Die Hard".into(),
ContentType::Movie,
7800,
None,
None,
None,
None,
vec![],
vec![],
Some("col-1".into()),
Some("Movies".into()),
Some("movies".into()),
None,
"2026-01-01".into(),
);
let item = LibraryItem::from_persistence(LibraryItemRow {
id: "test::m1".into(),
provider_id: "test".into(),
external_id: "m1".into(),
title: "Die Hard".into(),
content_type: ContentType::Movie,
duration_secs: 7800,
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: "2026-01-01".into(),
});
store.insert(item.id().to_string(), item);
let item2 = LibraryItem::from_persistence(
"test::e1".into(),
"test".into(),
"e1".into(),
"BB S01E01".into(),
ContentType::Episode,
2700,
Some("Breaking Bad".into()),
Some(1),
Some(1),
None,
vec![],
vec![],
Some("col-2".into()),
Some("TV Shows".into()),
Some("tvshows".into()),
None,
"2026-01-01".into(),
);
let item2 = LibraryItem::from_persistence(LibraryItemRow {
id: "test::e1".into(),
provider_id: "test".into(),
external_id: "e1".into(),
title: "BB S01E01".into(),
content_type: ContentType::Episode,
duration_secs: 2700,
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: "2026-01-01".into(),
});
store.insert(item2.id().to_string(), item2);
}

View File

@@ -1,4 +1,4 @@
use domain::models::LibraryItem;
use domain::models::{LibraryItem, LibraryItemRow};
use domain::value_objects::ContentType;
use crate::library::list_genres;
@@ -10,44 +10,44 @@ 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(
"test::m1".into(),
"test".into(),
"m1".into(),
"Die Hard".into(),
ContentType::Movie,
7800,
None,
None,
None,
None,
vec!["Action".into(), "Thriller".into()],
vec![],
None,
None,
None,
None,
"2026-01-01".into(),
);
let item2 = LibraryItem::from_persistence(
"test::m2".into(),
"test".into(),
"m2".into(),
"Alien".into(),
ContentType::Movie,
7020,
None,
None,
None,
None,
vec!["Sci-Fi".into(), "Action".into()],
vec![],
None,
None,
None,
None,
"2026-01-01".into(),
);
let item1 = LibraryItem::from_persistence(LibraryItemRow {
id: "test::m1".into(),
provider_id: "test".into(),
external_id: "m1".into(),
title: "Die Hard".into(),
content_type: ContentType::Movie,
duration_secs: 7800,
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: "2026-01-01".into(),
});
let item2 = LibraryItem::from_persistence(LibraryItemRow {
id: "test::m2".into(),
provider_id: "test".into(),
external_id: "m2".into(),
title: "Alien".into(),
content_type: ContentType::Movie,
duration_secs: 7020,
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: "2026-01-01".into(),
});
store.insert(item1.id().to_string(), item1);
store.insert(item2.id().to_string(), item2);

View File

@@ -1,4 +1,4 @@
use domain::models::LibraryItem;
use domain::models::{LibraryItem, LibraryItemRow};
use domain::value_objects::ContentType;
use crate::library::list_seasons;
@@ -11,25 +11,25 @@ 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(
format!("test::e{i}"),
"test".into(),
format!("e{i}"),
format!("BB S{season:02}E{:02}", i + 1),
ContentType::Episode,
2700,
Some("Breaking Bad".into()),
Some(*season),
Some(i as u32 + 1),
None,
vec![],
vec![],
None,
None,
None,
None,
"2026-01-01".into(),
);
let item = LibraryItem::from_persistence(LibraryItemRow {
id: 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,
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: "2026-01-01".into(),
});
store.insert(item.id().to_string(), item);
}
}

View File

@@ -1,4 +1,4 @@
use domain::models::LibraryItem;
use domain::models::{LibraryItem, LibraryItemRow};
use domain::value_objects::ContentType;
use crate::library::list_shows;
@@ -20,25 +20,25 @@ fn seed_episodes(repo: &std::sync::Arc<domain::testing::InMemoryLibraryRepositor
.iter()
.enumerate()
{
let item = LibraryItem::from_persistence(
format!("test::e{i}"),
"test".into(),
format!("e{i}"),
format!("{series} S{season:02}E{i:02}"),
ContentType::Episode,
2700,
Some(series.to_string()),
Some(*season),
Some(i as u32 + 1),
None,
vec![],
vec![],
None,
None,
None,
None,
"2026-01-01".into(),
);
let item = LibraryItem::from_persistence(LibraryItemRow {
id: 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,
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: "2026-01-01".into(),
});
store.insert(item.id().to_string(), item);
}
}

View File

@@ -1,4 +1,4 @@
use domain::models::LibraryItem;
use domain::models::{LibraryItem, LibraryItemRow};
use domain::value_objects::ContentType;
use crate::library::queries::SearchItemsQuery;
@@ -22,63 +22,63 @@ fn seed_items(repo: &std::sync::Arc<domain::testing::InMemoryLibraryRepository>)
fn seed_items_with_genres(repo: &std::sync::Arc<domain::testing::InMemoryLibraryRepository>) {
let mut store = repo.items.lock().unwrap();
let action = LibraryItem::from_persistence(
"test::m1".into(),
"test".into(),
"m1".into(),
"Die Hard".into(),
ContentType::Movie,
7800,
None,
None,
None,
Some(1988),
vec!["Action".into(), "Thriller".into()],
vec![],
None,
None,
None,
None,
"2026-01-01".into(),
);
let scifi = LibraryItem::from_persistence(
"test::m2".into(),
"test".into(),
"m2".into(),
"Alien".into(),
ContentType::Movie,
7020,
None,
None,
None,
Some(1979),
vec!["Sci-Fi".into(), "Horror".into()],
vec![],
None,
None,
None,
None,
"2026-01-01".into(),
);
let comedy = LibraryItem::from_persistence(
"test::m3".into(),
"test".into(),
"m3".into(),
"Airplane!".into(),
ContentType::Movie,
5280,
None,
None,
None,
Some(1980),
vec!["Comedy".into()],
vec![],
None,
None,
None,
None,
"2026-01-01".into(),
);
let action = LibraryItem::from_persistence(LibraryItemRow {
id: "test::m1".into(),
provider_id: "test".into(),
external_id: "m1".into(),
title: "Die Hard".into(),
content_type: ContentType::Movie,
duration_secs: 7800,
series_name: None,
season_number: None,
episode_number: None,
year: Some(1988),
genres: vec!["Action".into(), "Thriller".into()],
tags: vec![],
collection_id: None,
collection_name: None,
collection_type: None,
thumbnail_url: None,
synced_at: "2026-01-01".into(),
});
let scifi = LibraryItem::from_persistence(LibraryItemRow {
id: "test::m2".into(),
provider_id: "test".into(),
external_id: "m2".into(),
title: "Alien".into(),
content_type: ContentType::Movie,
duration_secs: 7020,
series_name: None,
season_number: None,
episode_number: None,
year: Some(1979),
genres: vec!["Sci-Fi".into(), "Horror".into()],
tags: vec![],
collection_id: None,
collection_name: None,
collection_type: None,
thumbnail_url: None,
synced_at: "2026-01-01".into(),
});
let comedy = LibraryItem::from_persistence(LibraryItemRow {
id: "test::m3".into(),
provider_id: "test".into(),
external_id: "m3".into(),
title: "Airplane!".into(),
content_type: ContentType::Movie,
duration_secs: 5280,
series_name: None,
season_number: None,
episode_number: None,
year: Some(1980),
genres: vec!["Comedy".into()],
tags: vec![],
collection_id: None,
collection_name: None,
collection_type: None,
thumbnail_url: None,
synced_at: "2026-01-01".into(),
});
store.insert(action.id().to_string(), action);
store.insert(scifi.id().to_string(), scifi);

View File

@@ -20,7 +20,7 @@ async fn delete_after_removes_later_generations() {
.channels
.lock()
.unwrap()
.insert(channel_id.value(), channel);
.insert(channel_id, channel);
// Manually insert schedules with different generations.
let now = chrono::Utc::now();
@@ -36,7 +36,7 @@ async fn delete_after_removes_later_generations() {
.schedules
.lock()
.unwrap()
.insert(sched.id().value(), sched);
.insert(sched.id(), sched);
}
// Delete generations > 1.

View File

@@ -18,7 +18,7 @@ async fn generate_produces_empty_schedule_for_channel_with_no_blocks() {
.channels
.lock()
.unwrap()
.insert(channel.id().value(), channel.clone());
.insert(channel.id(), channel.clone());
let schedule = generate::execute(
&deps,
@@ -58,7 +58,7 @@ async fn generate_increments_generation() {
.channels
.lock()
.unwrap()
.insert(channel.id().value(), channel.clone());
.insert(channel.id(), channel.clone());
let first = generate::execute(
&deps,