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:
@@ -1,20 +1,21 @@
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::value_objects::{ChannelId, UserId};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum DomainError {
|
||||
#[error("User not found: {0}")]
|
||||
UserNotFound(Uuid),
|
||||
UserNotFound(UserId),
|
||||
|
||||
#[error("User already exists: {0}")]
|
||||
UserAlreadyExists(String),
|
||||
|
||||
#[error("Channel not found: {0}")]
|
||||
ChannelNotFound(Uuid),
|
||||
ChannelNotFound(ChannelId),
|
||||
|
||||
#[error("No active schedule for channel: {0}")]
|
||||
NoActiveSchedule(Uuid),
|
||||
NoActiveSchedule(ChannelId),
|
||||
|
||||
#[error("Validation error: {0}")]
|
||||
ValidationError(String),
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::value_objects::{ActivityEventId, ChannelId};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ActivityEvent {
|
||||
id: Uuid,
|
||||
id: ActivityEventId,
|
||||
timestamp: DateTime<Utc>,
|
||||
event_type: String,
|
||||
detail: String,
|
||||
channel_id: Option<Uuid>,
|
||||
channel_id: Option<ChannelId>,
|
||||
}
|
||||
|
||||
impl ActivityEvent {
|
||||
pub fn new(
|
||||
event_type: impl Into<String>,
|
||||
detail: impl Into<String>,
|
||||
channel_id: Option<Uuid>,
|
||||
channel_id: Option<ChannelId>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
id: ActivityEventId::generate(),
|
||||
timestamp: Utc::now(),
|
||||
event_type: event_type.into(),
|
||||
detail: detail.into(),
|
||||
@@ -26,11 +27,11 @@ impl ActivityEvent {
|
||||
}
|
||||
|
||||
pub fn from_persistence(
|
||||
id: Uuid,
|
||||
id: ActivityEventId,
|
||||
timestamp: DateTime<Utc>,
|
||||
event_type: String,
|
||||
detail: String,
|
||||
channel_id: Option<Uuid>,
|
||||
channel_id: Option<ChannelId>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
@@ -41,7 +42,7 @@ impl ActivityEvent {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Uuid {
|
||||
pub fn id(&self) -> ActivityEventId {
|
||||
self.id
|
||||
}
|
||||
|
||||
@@ -57,7 +58,7 @@ impl ActivityEvent {
|
||||
&self.detail
|
||||
}
|
||||
|
||||
pub fn channel_id(&self) -> Option<Uuid> {
|
||||
pub fn channel_id(&self) -> Option<ChannelId> {
|
||||
self.channel_id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,28 @@ pub struct Channel {
|
||||
updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
pub struct ChannelRow {
|
||||
pub id: ChannelId,
|
||||
pub owner_id: UserId,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub timezone: String,
|
||||
pub schedule_config: ScheduleConfig,
|
||||
pub recycle_policy: RecyclePolicy,
|
||||
pub auto_schedule: bool,
|
||||
pub access_mode: AccessMode,
|
||||
pub access_password_hash: Option<String>,
|
||||
pub logo: Option<String>,
|
||||
pub logo_position: LogoPosition,
|
||||
pub logo_opacity: f32,
|
||||
pub webhook_url: Option<String>,
|
||||
pub webhook_poll_interval_secs: u32,
|
||||
pub webhook_body_template: Option<String>,
|
||||
pub webhook_headers: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
pub fn new(
|
||||
owner_id: UserId,
|
||||
@@ -64,47 +86,27 @@ impl Channel {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_persistence(
|
||||
id: ChannelId,
|
||||
owner_id: UserId,
|
||||
name: String,
|
||||
description: Option<String>,
|
||||
timezone: String,
|
||||
schedule_config: ScheduleConfig,
|
||||
recycle_policy: RecyclePolicy,
|
||||
auto_schedule: bool,
|
||||
access_mode: AccessMode,
|
||||
access_password_hash: Option<String>,
|
||||
logo: Option<String>,
|
||||
logo_position: LogoPosition,
|
||||
logo_opacity: f32,
|
||||
webhook_url: Option<String>,
|
||||
webhook_poll_interval_secs: u32,
|
||||
webhook_body_template: Option<String>,
|
||||
webhook_headers: Option<String>,
|
||||
created_at: DateTime<Utc>,
|
||||
updated_at: DateTime<Utc>,
|
||||
) -> Self {
|
||||
pub fn from_persistence(row: ChannelRow) -> Self {
|
||||
Self {
|
||||
id,
|
||||
owner_id,
|
||||
name,
|
||||
description,
|
||||
timezone,
|
||||
schedule_config,
|
||||
recycle_policy,
|
||||
auto_schedule,
|
||||
access_mode,
|
||||
access_password_hash,
|
||||
logo,
|
||||
logo_position,
|
||||
logo_opacity,
|
||||
webhook_url,
|
||||
webhook_poll_interval_secs,
|
||||
webhook_body_template,
|
||||
webhook_headers,
|
||||
created_at,
|
||||
updated_at,
|
||||
id: row.id,
|
||||
owner_id: row.owner_id,
|
||||
name: row.name,
|
||||
description: row.description,
|
||||
timezone: row.timezone,
|
||||
schedule_config: row.schedule_config,
|
||||
recycle_policy: row.recycle_policy,
|
||||
auto_schedule: row.auto_schedule,
|
||||
access_mode: row.access_mode,
|
||||
access_password_hash: row.access_password_hash,
|
||||
logo: row.logo,
|
||||
logo_position: row.logo_position,
|
||||
logo_opacity: row.logo_opacity,
|
||||
webhook_url: row.webhook_url,
|
||||
webhook_poll_interval_secs: row.webhook_poll_interval_secs,
|
||||
webhook_body_template: row.webhook_body_template,
|
||||
webhook_headers: row.webhook_headers,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::value_objects::ChannelId;
|
||||
use crate::value_objects::{ChannelId, SnapshotId};
|
||||
|
||||
use super::ScheduleConfig;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ChannelConfigSnapshot {
|
||||
id: Uuid,
|
||||
id: SnapshotId,
|
||||
channel_id: ChannelId,
|
||||
config: ScheduleConfig,
|
||||
version_num: i64,
|
||||
@@ -23,7 +22,7 @@ impl ChannelConfigSnapshot {
|
||||
version_num: i64,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
id: SnapshotId::generate(),
|
||||
channel_id,
|
||||
config,
|
||||
version_num,
|
||||
@@ -33,7 +32,7 @@ impl ChannelConfigSnapshot {
|
||||
}
|
||||
|
||||
pub fn from_persistence(
|
||||
id: Uuid,
|
||||
id: SnapshotId,
|
||||
channel_id: ChannelId,
|
||||
config: ScheduleConfig,
|
||||
version_num: i64,
|
||||
@@ -50,7 +49,7 @@ impl ChannelConfigSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Uuid {
|
||||
pub fn id(&self) -> SnapshotId {
|
||||
self.id
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,26 @@ pub struct LibraryItem {
|
||||
synced_at: String,
|
||||
}
|
||||
|
||||
pub struct LibraryItemRow {
|
||||
pub id: String,
|
||||
pub provider_id: String,
|
||||
pub external_id: String,
|
||||
pub title: String,
|
||||
pub content_type: ContentType,
|
||||
pub duration_secs: u32,
|
||||
pub series_name: Option<String>,
|
||||
pub season_number: Option<u32>,
|
||||
pub episode_number: Option<u32>,
|
||||
pub year: Option<u16>,
|
||||
pub genres: Vec<String>,
|
||||
pub tags: Vec<String>,
|
||||
pub collection_id: Option<String>,
|
||||
pub collection_name: Option<String>,
|
||||
pub collection_type: Option<String>,
|
||||
pub thumbnail_url: Option<String>,
|
||||
pub synced_at: String,
|
||||
}
|
||||
|
||||
impl LibraryItem {
|
||||
pub fn new(
|
||||
provider_id: impl Into<String>,
|
||||
@@ -56,43 +76,25 @@ impl LibraryItem {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_persistence(
|
||||
id: String,
|
||||
provider_id: String,
|
||||
external_id: String,
|
||||
title: String,
|
||||
content_type: ContentType,
|
||||
duration_secs: u32,
|
||||
series_name: Option<String>,
|
||||
season_number: Option<u32>,
|
||||
episode_number: Option<u32>,
|
||||
year: Option<u16>,
|
||||
genres: Vec<String>,
|
||||
tags: Vec<String>,
|
||||
collection_id: Option<String>,
|
||||
collection_name: Option<String>,
|
||||
collection_type: Option<String>,
|
||||
thumbnail_url: Option<String>,
|
||||
synced_at: String,
|
||||
) -> Self {
|
||||
pub fn from_persistence(row: LibraryItemRow) -> Self {
|
||||
Self {
|
||||
id,
|
||||
provider_id,
|
||||
external_id,
|
||||
title,
|
||||
content_type,
|
||||
duration_secs,
|
||||
series_name,
|
||||
season_number,
|
||||
episode_number,
|
||||
year,
|
||||
genres,
|
||||
tags,
|
||||
collection_id,
|
||||
collection_name,
|
||||
collection_type,
|
||||
thumbnail_url,
|
||||
synced_at,
|
||||
id: row.id,
|
||||
provider_id: row.provider_id,
|
||||
external_id: row.external_id,
|
||||
title: row.title,
|
||||
content_type: row.content_type,
|
||||
duration_secs: row.duration_secs,
|
||||
series_name: row.series_name,
|
||||
season_number: row.season_number,
|
||||
episode_number: row.episode_number,
|
||||
year: row.year,
|
||||
genres: row.genres,
|
||||
tags: row.tags,
|
||||
collection_id: row.collection_id,
|
||||
collection_name: row.collection_name,
|
||||
collection_type: row.collection_type,
|
||||
thumbnail_url: row.thumbnail_url,
|
||||
synced_at: row.synced_at,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::value_objects::{ChannelId, ContentType, MediaItemId};
|
||||
use crate::value_objects::{ChannelId, ContentType, MediaItemId, PlaybackRecordId};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MediaItem {
|
||||
@@ -21,6 +20,22 @@ pub struct MediaItem {
|
||||
collection_id: Option<String>,
|
||||
}
|
||||
|
||||
pub struct MediaItemRow {
|
||||
pub id: MediaItemId,
|
||||
pub title: String,
|
||||
pub content_type: ContentType,
|
||||
pub duration_secs: u32,
|
||||
pub description: Option<String>,
|
||||
pub genres: Vec<String>,
|
||||
pub year: Option<u16>,
|
||||
pub tags: Vec<String>,
|
||||
pub series_name: Option<String>,
|
||||
pub season_number: Option<u32>,
|
||||
pub episode_number: Option<u32>,
|
||||
pub thumbnail_url: Option<String>,
|
||||
pub collection_id: Option<String>,
|
||||
}
|
||||
|
||||
impl MediaItem {
|
||||
pub fn new(
|
||||
id: MediaItemId,
|
||||
@@ -45,35 +60,21 @@ impl MediaItem {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_persistence(
|
||||
id: MediaItemId,
|
||||
title: String,
|
||||
content_type: ContentType,
|
||||
duration_secs: u32,
|
||||
description: Option<String>,
|
||||
genres: Vec<String>,
|
||||
year: Option<u16>,
|
||||
tags: Vec<String>,
|
||||
series_name: Option<String>,
|
||||
season_number: Option<u32>,
|
||||
episode_number: Option<u32>,
|
||||
thumbnail_url: Option<String>,
|
||||
collection_id: Option<String>,
|
||||
) -> Self {
|
||||
pub fn from_persistence(row: MediaItemRow) -> Self {
|
||||
Self {
|
||||
id,
|
||||
title,
|
||||
content_type,
|
||||
duration_secs,
|
||||
description,
|
||||
genres,
|
||||
year,
|
||||
tags,
|
||||
series_name,
|
||||
season_number,
|
||||
episode_number,
|
||||
thumbnail_url,
|
||||
collection_id,
|
||||
id: row.id,
|
||||
title: row.title,
|
||||
content_type: row.content_type,
|
||||
duration_secs: row.duration_secs,
|
||||
description: row.description,
|
||||
genres: row.genres,
|
||||
year: row.year,
|
||||
tags: row.tags,
|
||||
series_name: row.series_name,
|
||||
season_number: row.season_number,
|
||||
episode_number: row.episode_number,
|
||||
thumbnail_url: row.thumbnail_url,
|
||||
collection_id: row.collection_id,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +133,7 @@ impl MediaItem {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PlaybackRecord {
|
||||
id: Uuid,
|
||||
id: PlaybackRecordId,
|
||||
channel_id: ChannelId,
|
||||
item_id: MediaItemId,
|
||||
played_at: DateTime<Utc>,
|
||||
@@ -142,7 +143,7 @@ pub struct PlaybackRecord {
|
||||
impl PlaybackRecord {
|
||||
pub fn new(channel_id: ChannelId, item_id: MediaItemId, generation: u32) -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
id: PlaybackRecordId::generate(),
|
||||
channel_id,
|
||||
item_id,
|
||||
played_at: Utc::now(),
|
||||
@@ -151,7 +152,7 @@ impl PlaybackRecord {
|
||||
}
|
||||
|
||||
pub fn from_persistence(
|
||||
id: Uuid,
|
||||
id: PlaybackRecordId,
|
||||
channel_id: ChannelId,
|
||||
item_id: MediaItemId,
|
||||
played_at: DateTime<Utc>,
|
||||
@@ -166,7 +167,7 @@ impl PlaybackRecord {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Uuid {
|
||||
pub fn id(&self) -> PlaybackRecordId {
|
||||
self.id
|
||||
}
|
||||
|
||||
|
||||
@@ -10,16 +10,16 @@ mod user;
|
||||
|
||||
pub use activity::ActivityEvent;
|
||||
pub use channel::{
|
||||
BlockContent, Channel, OldScheduleConfig, ProgrammingBlock, ScheduleConfig,
|
||||
BlockContent, Channel, ChannelRow, OldScheduleConfig, ProgrammingBlock, ScheduleConfig,
|
||||
ScheduleConfigCompat,
|
||||
};
|
||||
pub use collections::{PageParams, Paginated};
|
||||
pub use config_snapshot::ChannelConfigSnapshot;
|
||||
pub use library::{
|
||||
LibraryCollection, LibraryItem, LibrarySyncLogEntry, LibrarySyncResult, SeasonSummary,
|
||||
ShowSummary,
|
||||
LibraryCollection, LibraryItem, LibraryItemRow, LibrarySyncLogEntry, LibrarySyncResult,
|
||||
SeasonSummary, ShowSummary,
|
||||
};
|
||||
pub use media::{MediaItem, PlaybackRecord};
|
||||
pub use media::{MediaItem, MediaItemRow, PlaybackRecord};
|
||||
pub use provider_config::ProviderConfigRow;
|
||||
pub use schedule::{CurrentBroadcast, GeneratedSchedule, ScheduledSlot};
|
||||
pub use user::User;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::value_objects::ActivityEventId;
|
||||
|
||||
#[test]
|
||||
fn new_generates_id_and_timestamp() {
|
||||
@@ -10,15 +11,15 @@ fn new_generates_id_and_timestamp() {
|
||||
|
||||
#[test]
|
||||
fn new_with_channel_id() {
|
||||
let ch_id = Uuid::new_v4();
|
||||
let ch_id = ChannelId::generate();
|
||||
let event = ActivityEvent::new("schedule_generated", "Gen #5", Some(ch_id));
|
||||
assert_eq!(event.channel_id(), Some(ch_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_persistence_round_trip() {
|
||||
let id = Uuid::new_v4();
|
||||
let ch_id = Uuid::new_v4();
|
||||
let id = ActivityEventId::generate();
|
||||
let ch_id = ChannelId::generate();
|
||||
let now = Utc::now();
|
||||
let event = ActivityEvent::from_persistence(
|
||||
id,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::value_objects::SnapshotId;
|
||||
|
||||
#[test]
|
||||
fn new_generates_id_and_timestamp() {
|
||||
@@ -11,7 +12,7 @@ fn new_generates_id_and_timestamp() {
|
||||
|
||||
#[test]
|
||||
fn from_persistence_round_trip() {
|
||||
let id = Uuid::new_v4();
|
||||
let id = SnapshotId::generate();
|
||||
let ch_id = ChannelId::generate();
|
||||
let now = Utc::now();
|
||||
let snap = ChannelConfigSnapshot::from_persistence(
|
||||
|
||||
@@ -21,25 +21,25 @@ fn library_item_new_defaults_optional_fields() {
|
||||
|
||||
#[test]
|
||||
fn library_item_from_persistence_all_fields() {
|
||||
let item = LibraryItem::from_persistence(
|
||||
"jf::abc".into(),
|
||||
"jf".into(),
|
||||
"abc".into(),
|
||||
"Breaking Bad S01E01".into(),
|
||||
ContentType::Episode,
|
||||
2700,
|
||||
Some("Breaking Bad".into()),
|
||||
Some(1),
|
||||
Some(1),
|
||||
Some(2008),
|
||||
vec!["Drama".into()],
|
||||
vec!["tv".into()],
|
||||
Some("col-1".into()),
|
||||
Some("TV Shows".into()),
|
||||
Some("tvshows".into()),
|
||||
Some("http://thumb.jpg".into()),
|
||||
"2026-03-19T00:00:00Z".into(),
|
||||
);
|
||||
let item = LibraryItem::from_persistence(LibraryItemRow {
|
||||
id: "jf::abc".into(),
|
||||
provider_id: "jf".into(),
|
||||
external_id: "abc".into(),
|
||||
title: "Breaking Bad S01E01".into(),
|
||||
content_type: ContentType::Episode,
|
||||
duration_secs: 2700,
|
||||
series_name: Some("Breaking Bad".into()),
|
||||
season_number: Some(1),
|
||||
episode_number: Some(1),
|
||||
year: Some(2008),
|
||||
genres: vec!["Drama".into()],
|
||||
tags: vec!["tv".into()],
|
||||
collection_id: Some("col-1".into()),
|
||||
collection_name: Some("TV Shows".into()),
|
||||
collection_type: Some("tvshows".into()),
|
||||
thumbnail_url: Some("http://thumb.jpg".into()),
|
||||
synced_at: "2026-03-19T00:00:00Z".into(),
|
||||
});
|
||||
assert_eq!(item.series_name(), Some("Breaking Bad"));
|
||||
assert_eq!(item.season_number(), Some(1));
|
||||
assert_eq!(item.year(), Some(2008));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::value_objects::PlaybackRecordId;
|
||||
|
||||
#[test]
|
||||
fn media_item_new_defaults() {
|
||||
@@ -18,21 +19,21 @@ fn media_item_new_defaults() {
|
||||
|
||||
#[test]
|
||||
fn media_item_from_persistence_round_trip() {
|
||||
let item = MediaItem::from_persistence(
|
||||
MediaItemId::new("jf::abc"),
|
||||
"Breaking Bad S01E01".into(),
|
||||
ContentType::Episode,
|
||||
2700,
|
||||
Some("Pilot episode".into()),
|
||||
vec!["Drama".into()],
|
||||
Some(2008),
|
||||
vec!["tv".into()],
|
||||
Some("Breaking Bad".into()),
|
||||
Some(1),
|
||||
Some(1),
|
||||
Some("http://thumb.jpg".into()),
|
||||
Some("col-1".into()),
|
||||
);
|
||||
let item = MediaItem::from_persistence(MediaItemRow {
|
||||
id: MediaItemId::new("jf::abc"),
|
||||
title: "Breaking Bad S01E01".into(),
|
||||
content_type: ContentType::Episode,
|
||||
duration_secs: 2700,
|
||||
description: Some("Pilot episode".into()),
|
||||
genres: vec!["Drama".into()],
|
||||
year: Some(2008),
|
||||
tags: vec!["tv".into()],
|
||||
series_name: Some("Breaking Bad".into()),
|
||||
season_number: Some(1),
|
||||
episode_number: Some(1),
|
||||
thumbnail_url: Some("http://thumb.jpg".into()),
|
||||
collection_id: Some("col-1".into()),
|
||||
});
|
||||
assert_eq!(item.title(), "Breaking Bad S01E01");
|
||||
assert_eq!(item.series_name(), Some("Breaking Bad"));
|
||||
assert_eq!(item.season_number(), Some(1));
|
||||
@@ -53,7 +54,7 @@ fn playback_record_new() {
|
||||
|
||||
#[test]
|
||||
fn playback_record_from_persistence() {
|
||||
let id = Uuid::new_v4();
|
||||
let id = PlaybackRecordId::generate();
|
||||
let ch_id = ChannelId::generate();
|
||||
let item_id = MediaItemId::new("test::2");
|
||||
let now = Utc::now();
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use async_trait::async_trait;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::errors::DomainResult;
|
||||
use crate::models::{Channel, ChannelConfigSnapshot, ScheduleConfig};
|
||||
use crate::value_objects::{ChannelId, UserId};
|
||||
use crate::value_objects::{ChannelId, SnapshotId, UserId};
|
||||
|
||||
#[async_trait]
|
||||
pub trait ChannelCommand: Send + Sync {
|
||||
@@ -21,7 +20,7 @@ pub trait ChannelCommand: Send + Sync {
|
||||
async fn patch_config_snapshot_label(
|
||||
&self,
|
||||
channel_id: ChannelId,
|
||||
snapshot_id: Uuid,
|
||||
snapshot_id: SnapshotId,
|
||||
label: Option<String>,
|
||||
) -> DomainResult<Option<ChannelConfigSnapshot>>;
|
||||
}
|
||||
@@ -44,6 +43,6 @@ pub trait ChannelQuery: Send + Sync {
|
||||
async fn get_config_snapshot(
|
||||
&self,
|
||||
channel_id: ChannelId,
|
||||
snapshot_id: Uuid,
|
||||
snapshot_id: SnapshotId,
|
||||
) -> DomainResult<Option<ChannelConfigSnapshot>>;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,15 @@ struct BlockTimeWindow {
|
||||
end: DateTime<Utc>,
|
||||
}
|
||||
|
||||
struct AlgorithmicParams<'a> {
|
||||
provider_id: &'a str,
|
||||
filter: &'a MediaFilter,
|
||||
strategy: &'a FillStrategy,
|
||||
block_id: BlockId,
|
||||
loop_on_finish: bool,
|
||||
ignore_recycle_policy: bool,
|
||||
}
|
||||
|
||||
struct RecycleContext<'a> {
|
||||
history: &'a [PlaybackRecord],
|
||||
policy: &'a RecyclePolicy,
|
||||
@@ -59,7 +68,7 @@ impl ScheduleEngineService {
|
||||
.channel_query
|
||||
.find_by_id(channel_id)
|
||||
.await?
|
||||
.ok_or(DomainError::ChannelNotFound(channel_id.value()))?;
|
||||
.ok_or(DomainError::ChannelNotFound(channel_id))?;
|
||||
|
||||
let tz: Tz = channel
|
||||
.timezone()
|
||||
@@ -259,14 +268,16 @@ impl ScheduleEngineService {
|
||||
provider_id,
|
||||
} => {
|
||||
self.resolve_algorithmic(
|
||||
provider_id,
|
||||
filter,
|
||||
strategy,
|
||||
AlgorithmicParams {
|
||||
provider_id,
|
||||
filter,
|
||||
strategy,
|
||||
block_id: block.id(),
|
||||
loop_on_finish: block.loop_on_finish(),
|
||||
ignore_recycle_policy: block.ignore_recycle_policy(),
|
||||
},
|
||||
window,
|
||||
recycle,
|
||||
block.id(),
|
||||
block.loop_on_finish(),
|
||||
block.ignore_recycle_policy(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -300,25 +311,20 @@ impl ScheduleEngineService {
|
||||
|
||||
async fn resolve_algorithmic(
|
||||
&self,
|
||||
provider_id: &str,
|
||||
filter: &MediaFilter,
|
||||
strategy: &FillStrategy,
|
||||
params: AlgorithmicParams<'_>,
|
||||
window: BlockTimeWindow,
|
||||
recycle: RecycleContext<'_>,
|
||||
block_id: BlockId,
|
||||
loop_on_finish: bool,
|
||||
ignore_recycle_policy: bool,
|
||||
) -> DomainResult<Vec<ScheduledSlot>> {
|
||||
let candidates = self
|
||||
.provider_registry
|
||||
.fetch_items(provider_id, filter)
|
||||
.fetch_items(params.provider_id, params.filter)
|
||||
.await?;
|
||||
|
||||
if candidates.is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let pool = if ignore_recycle_policy {
|
||||
let pool = if params.ignore_recycle_policy {
|
||||
candidates.clone()
|
||||
} else {
|
||||
recycle::apply_recycle_policy(&candidates, recycle.history, recycle.policy, recycle.generation)
|
||||
@@ -328,9 +334,9 @@ impl ScheduleEngineService {
|
||||
&candidates,
|
||||
&pool,
|
||||
target_secs,
|
||||
strategy,
|
||||
params.strategy,
|
||||
recycle.last_item_id,
|
||||
loop_on_finish,
|
||||
params.loop_on_finish,
|
||||
);
|
||||
|
||||
let mut slots = Vec::new();
|
||||
@@ -342,7 +348,7 @@ impl ScheduleEngineService {
|
||||
}
|
||||
let item_end =
|
||||
(cursor + Duration::seconds(item.duration_secs() as i64)).min(window.end);
|
||||
slots.push(ScheduledSlot::new(cursor, item_end, item.clone(), block_id));
|
||||
slots.push(ScheduledSlot::new(cursor, item_end, item.clone(), params.block_id));
|
||||
cursor = item_end;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ use std::sync::Mutex;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use chrono::{DateTime, Utc};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::errors::DomainResult;
|
||||
use crate::models::{
|
||||
@@ -18,11 +17,12 @@ use crate::ports::{
|
||||
ScheduleQuery, TranscodeSettingsRepository, UserCommand, UserQuery,
|
||||
};
|
||||
use crate::value_objects::{
|
||||
BlockId, ChannelId, ContentType, LibrarySearchFilter, MediaItemId, ScheduleId, UserId,
|
||||
BlockId, ChannelId, ContentType, LibrarySearchFilter, MediaItemId, ScheduleId, SnapshotId,
|
||||
UserId,
|
||||
};
|
||||
|
||||
pub struct InMemoryUserRepository {
|
||||
pub store: Mutex<HashMap<Uuid, crate::models::User>>,
|
||||
pub store: Mutex<HashMap<UserId, crate::models::User>>,
|
||||
}
|
||||
|
||||
impl InMemoryUserRepository {
|
||||
@@ -45,12 +45,12 @@ impl UserCommand for InMemoryUserRepository {
|
||||
self.store
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(user.id().value(), user.clone());
|
||||
.insert(user.id(), user.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete(&self, id: UserId) -> DomainResult<()> {
|
||||
self.store.lock().unwrap().remove(&id.value());
|
||||
self.store.lock().unwrap().remove(&id);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ impl UserCommand for InMemoryUserRepository {
|
||||
#[async_trait]
|
||||
impl UserQuery for InMemoryUserRepository {
|
||||
async fn find_by_id(&self, id: UserId) -> DomainResult<Option<crate::models::User>> {
|
||||
Ok(self.store.lock().unwrap().get(&id.value()).cloned())
|
||||
Ok(self.store.lock().unwrap().get(&id).cloned())
|
||||
}
|
||||
|
||||
async fn find_by_subject(&self, subject: &str) -> DomainResult<Option<crate::models::User>> {
|
||||
@@ -80,7 +80,7 @@ impl UserQuery for InMemoryUserRepository {
|
||||
}
|
||||
|
||||
pub struct InMemoryChannelRepository {
|
||||
pub channels: Mutex<HashMap<Uuid, Channel>>,
|
||||
pub channels: Mutex<HashMap<ChannelId, Channel>>,
|
||||
pub snapshots: Mutex<Vec<ChannelConfigSnapshot>>,
|
||||
}
|
||||
|
||||
@@ -105,12 +105,12 @@ impl ChannelCommand for InMemoryChannelRepository {
|
||||
self.channels
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(channel.id().value(), channel.clone());
|
||||
.insert(channel.id(), channel.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete(&self, id: ChannelId) -> DomainResult<()> {
|
||||
self.channels.lock().unwrap().remove(&id.value());
|
||||
self.channels.lock().unwrap().remove(&id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ impl ChannelCommand for InMemoryChannelRepository {
|
||||
async fn patch_config_snapshot_label(
|
||||
&self,
|
||||
channel_id: ChannelId,
|
||||
snapshot_id: Uuid,
|
||||
snapshot_id: SnapshotId,
|
||||
label: Option<String>,
|
||||
) -> DomainResult<Option<ChannelConfigSnapshot>> {
|
||||
let mut snaps = self.snapshots.lock().unwrap();
|
||||
@@ -176,7 +176,7 @@ impl ChannelCommand for InMemoryChannelRepository {
|
||||
#[async_trait]
|
||||
impl ChannelQuery for InMemoryChannelRepository {
|
||||
async fn find_by_id(&self, id: ChannelId) -> DomainResult<Option<Channel>> {
|
||||
Ok(self.channels.lock().unwrap().get(&id.value()).cloned())
|
||||
Ok(self.channels.lock().unwrap().get(&id).cloned())
|
||||
}
|
||||
|
||||
async fn find_by_owner(&self, owner_id: UserId) -> DomainResult<Vec<Channel>> {
|
||||
@@ -218,7 +218,7 @@ impl ChannelQuery for InMemoryChannelRepository {
|
||||
async fn get_config_snapshot(
|
||||
&self,
|
||||
channel_id: ChannelId,
|
||||
snapshot_id: Uuid,
|
||||
snapshot_id: SnapshotId,
|
||||
) -> DomainResult<Option<ChannelConfigSnapshot>> {
|
||||
let snaps = self.snapshots.lock().unwrap();
|
||||
Ok(snaps
|
||||
@@ -229,7 +229,7 @@ impl ChannelQuery for InMemoryChannelRepository {
|
||||
}
|
||||
|
||||
pub struct InMemoryScheduleRepository {
|
||||
pub schedules: Mutex<HashMap<Uuid, GeneratedSchedule>>,
|
||||
pub schedules: Mutex<HashMap<ScheduleId, GeneratedSchedule>>,
|
||||
pub playback_records: Mutex<Vec<PlaybackRecord>>,
|
||||
}
|
||||
|
||||
@@ -254,7 +254,7 @@ impl ScheduleCommand for InMemoryScheduleRepository {
|
||||
self.schedules
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(schedule.id().value(), schedule.clone());
|
||||
.insert(schedule.id(), schedule.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -644,11 +644,7 @@ impl ActivityLogCommand for InMemoryActivityLog {
|
||||
detail: &str,
|
||||
channel_id: Option<ChannelId>,
|
||||
) -> DomainResult<()> {
|
||||
let event = ActivityEvent::new(
|
||||
event_type,
|
||||
detail,
|
||||
channel_id.map(|c| c.value()),
|
||||
);
|
||||
let event = ActivityEvent::new(event_type, detail, channel_id);
|
||||
self.events.lock().unwrap().push(event);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -45,6 +45,9 @@ uuid_id!(ChannelId);
|
||||
uuid_id!(SlotId);
|
||||
uuid_id!(BlockId);
|
||||
uuid_id!(ScheduleId);
|
||||
uuid_id!(SnapshotId);
|
||||
uuid_id!(ActivityEventId);
|
||||
uuid_id!(PlaybackRecordId);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||
pub struct MediaItemId(String);
|
||||
|
||||
Reference in New Issue
Block a user