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:
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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?;
|
||||
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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?;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user