diff --git a/crates/adapters/adapter-common/src/lib.rs b/crates/adapters/adapter-common/src/lib.rs index d2ec3d9..41b2513 100644 --- a/crates/adapters/adapter-common/src/lib.rs +++ b/crates/adapters/adapter-common/src/lib.rs @@ -1,5 +1,5 @@ use chrono::{DateTime, Utc}; -use domain::{DomainError, RecyclePolicy, ScheduleConfig, ScheduleConfigCompat}; +use domain::{DomainError, RotationPolicy, ScheduleConfig, ScheduleConfigCompat}; use serde::de::DeserializeOwned; use uuid::Uuid; @@ -32,8 +32,8 @@ pub fn parse_schedule_config(json: &str) -> Result Ok(ScheduleConfig::from(compat)) } -pub fn parse_recycle_policy(json: &str) -> Result { - parse_json(json, "recycle_policy") +pub fn parse_rotation_policy(json: &str) -> Result { + parse_json(json, "rotation_policy") } pub fn parse_enum_or_default(value: String) -> T { @@ -151,9 +151,9 @@ mod tests { } #[test] - fn parse_recycle_policy_valid() { + fn parse_rotation_policy_valid() { let json = r#"{"cooldown_days":7,"cooldown_generations":3,"min_available_ratio":0.3}"#; - let policy = parse_recycle_policy(json).unwrap(); + let policy = parse_rotation_policy(json).unwrap(); assert_eq!(policy.cooldown_days, Some(7)); } diff --git a/crates/adapters/sqlite/src/channel.rs b/crates/adapters/sqlite/src/channel.rs index 0103ae7..0599a7f 100644 --- a/crates/adapters/sqlite/src/channel.rs +++ b/crates/adapters/sqlite/src/channel.rs @@ -4,7 +4,7 @@ use sqlx::{Row, SqlitePool}; use uuid::Uuid; use adapter_common::{ - map_sqlx_error, parse_dt, parse_enum_or_default, parse_recycle_policy, parse_schedule_config, + map_sqlx_error, parse_dt, parse_enum_or_default, parse_rotation_policy, parse_schedule_config, parse_uuid, serialize_enum_as_string, }; use domain::{ @@ -23,7 +23,7 @@ impl SqliteChannelRepository { } } -const SELECT_COLS: &str = "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"; +const SELECT_COLS: &str = "id, owner_id, name, description, timezone, schedule_config, recycle_policy AS rotation_policy, auto_schedule, access_mode, logo, logo_position, logo_opacity, webhook_url, webhook_poll_interval_secs, webhook_body_template, webhook_headers, created_at, updated_at"; #[derive(Debug, sqlx::FromRow)] struct ChannelRow { @@ -33,10 +33,9 @@ struct ChannelRow { description: Option, timezone: String, schedule_config: String, - recycle_policy: String, + rotation_policy: String, auto_schedule: i64, access_mode: String, - access_password_hash: Option, logo: Option, logo_position: String, logo_opacity: f32, @@ -53,7 +52,7 @@ impl ChannelRow { let id = ChannelId::from_uuid(parse_uuid(&self.id, "channel id")?); let owner_id = UserId::from_uuid(parse_uuid(&self.owner_id, "owner id")?); let schedule_config = parse_schedule_config(&self.schedule_config)?; - let recycle_policy = parse_recycle_policy(&self.recycle_policy)?; + let rotation_policy = parse_rotation_policy(&self.rotation_policy)?; let access_mode: AccessMode = parse_enum_or_default(self.access_mode); let logo_position: LogoPosition = parse_enum_or_default(self.logo_position); @@ -64,10 +63,9 @@ impl ChannelRow { description: self.description, timezone: self.timezone, schedule_config, - recycle_policy, + rotation_policy, auto_schedule: self.auto_schedule != 0, access_mode, - access_password_hash: self.access_password_hash, logo: self.logo, logo_position, logo_opacity: self.logo_opacity, @@ -109,8 +107,8 @@ impl ChannelCommand for SqliteChannelRepository { async fn save(&self, channel: &Channel) -> DomainResult<()> { let schedule_config = serde_json::to_string(channel.schedule_config()) .map_err(|e| DomainError::RepositoryError(format!("serialize schedule_config: {e}")))?; - let recycle_policy = serde_json::to_string(channel.recycle_policy()) - .map_err(|e| DomainError::RepositoryError(format!("serialize recycle_policy: {e}")))?; + let rotation_policy = serde_json::to_string(channel.rotation_policy()) + .map_err(|e| DomainError::RepositoryError(format!("serialize rotation_policy: {e}")))?; let access_mode = serialize_enum_as_string(channel.access_mode(), "public"); let logo_position = serialize_enum_as_string(channel.logo_position(), "top_right"); @@ -118,10 +116,10 @@ impl ChannelCommand for SqliteChannelRepository { r#" INSERT INTO channels (id, owner_id, name, description, timezone, schedule_config, recycle_policy, - auto_schedule, access_mode, access_password_hash, logo, logo_position, + auto_schedule, access_mode, logo, logo_position, logo_opacity, webhook_url, webhook_poll_interval_secs, webhook_body_template, webhook_headers, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET name = excluded.name, description = excluded.description, @@ -130,7 +128,6 @@ impl ChannelCommand for SqliteChannelRepository { recycle_policy = excluded.recycle_policy, auto_schedule = excluded.auto_schedule, access_mode = excluded.access_mode, - access_password_hash = excluded.access_password_hash, logo = excluded.logo, logo_position = excluded.logo_position, logo_opacity = excluded.logo_opacity, @@ -147,10 +144,9 @@ impl ChannelCommand for SqliteChannelRepository { .bind(channel.description()) .bind(channel.timezone()) .bind(&schedule_config) - .bind(&recycle_policy) + .bind(&rotation_policy) .bind(channel.auto_schedule() as i64) .bind(&access_mode) - .bind(channel.access_password_hash()) .bind(channel.logo()) .bind(&logo_position) .bind(channel.logo_opacity()) diff --git a/crates/api-types/src/channels.rs b/crates/api-types/src/channels.rs index 63be443..28c27a1 100644 --- a/crates/api-types/src/channels.rs +++ b/crates/api-types/src/channels.rs @@ -11,7 +11,6 @@ pub struct CreateChannelRequest { pub description: Option, pub timezone: String, pub access_mode: Option, - pub access_password: Option, pub webhook_url: Option, pub webhook_poll_interval_secs: Option, pub webhook_body_template: Option, @@ -26,10 +25,9 @@ pub struct UpdateChannelRequest { #[schema(value_type = Option)] pub schedule_config: Option, #[schema(value_type = Option)] - pub recycle_policy: Option, + pub rotation_policy: Option, pub auto_schedule: Option, pub access_mode: Option, - pub access_password: Option, pub logo: Option>, pub logo_position: Option, pub logo_opacity: Option, @@ -47,7 +45,7 @@ pub struct ChannelResponse { pub description: Option, pub timezone: String, pub schedule_config: serde_json::Value, - pub recycle_policy: serde_json::Value, + pub rotation_policy: serde_json::Value, pub auto_schedule: bool, pub access_mode: String, pub logo: Option, @@ -70,7 +68,7 @@ impl From for ChannelResponse { description: c.description().map(|s| s.to_string()), timezone: c.timezone().to_string(), schedule_config: serde_json::to_value(c.schedule_config()).unwrap_or_default(), - recycle_policy: serde_json::to_value(c.recycle_policy()).unwrap_or_default(), + rotation_policy: serde_json::to_value(c.rotation_policy()).unwrap_or_default(), auto_schedule: c.auto_schedule(), access_mode: enum_to_string(c.access_mode()), logo: c.logo().map(|s| s.to_string()), diff --git a/crates/api-types/src/schedule.rs b/crates/api-types/src/schedule.rs index 5f03628..8d130f8 100644 --- a/crates/api-types/src/schedule.rs +++ b/crates/api-types/src/schedule.rs @@ -5,8 +5,6 @@ use uuid::Uuid; use crate::common::enum_to_string; -const DEFAULT_ACCESS_MODE: &str = "public"; - #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct MediaItemResponse { pub id: String, @@ -47,8 +45,6 @@ pub struct SlotResponse { pub end_at: DateTime, pub item: MediaItemResponse, pub source_block_id: Uuid, - #[serde(default)] - pub block_access_mode: String, } impl From for SlotResponse { @@ -59,26 +55,6 @@ impl From for SlotResponse { end_at: s.end_at(), item: s.item().clone().into(), source_block_id: s.source_block_id().value(), - block_access_mode: String::from(DEFAULT_ACCESS_MODE), - } - } -} - -impl SlotResponse { - pub fn with_block_access(slot: domain::ScheduledSlot, channel: &domain::Channel) -> Self { - let block_access_mode = channel - .schedule_config() - .all_blocks() - .find(|b| b.id() == slot.source_block_id()) - .map(|b| enum_to_string(b.access_mode())) - .unwrap_or_else(|| String::from(DEFAULT_ACCESS_MODE)); - Self { - id: slot.id().value(), - start_at: slot.start_at(), - end_at: slot.end_at(), - item: slot.item().clone().into(), - source_block_id: slot.source_block_id().value(), - block_access_mode, } } } @@ -87,7 +63,6 @@ impl SlotResponse { pub struct CurrentBroadcastResponse { pub slot: SlotResponse, pub offset_secs: u32, - pub block_access_mode: String, } #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] diff --git a/crates/application/src/auth/tests/login.rs b/crates/application/src/auth/tests/login.rs index 5800411..d344bf8 100644 --- a/crates/application/src/auth/tests/login.rs +++ b/crates/application/src/auth/tests/login.rs @@ -121,10 +121,10 @@ async fn login_fails_for_unknown_email() { } #[tokio::test] -async fn login_fails_for_oidc_only_user() { +async fn login_fails_for_user_without_password() { let repo = Arc::new(InMemoryUserRepository::new()); - let email = Email::new("oidc@example.com").unwrap(); - let user = domain::models::User::new("oidc|subject", email); + let email = Email::new("external@example.com").unwrap(); + let user = domain::models::User::new("external|subject", email); repo.store.lock().unwrap().insert(user.id(), user); let deps = AuthDeps { @@ -138,7 +138,7 @@ async fn login_fails_for_oidc_only_user() { let result = login::execute( &deps, LoginCommand { - email: "oidc@example.com".into(), + email: "external@example.com".into(), password: "password123".into(), remember_me: false, }, diff --git a/crates/application/src/channels/commands.rs b/crates/application/src/channels/commands.rs index 56f4624..9adcd3a 100644 --- a/crates/application/src/channels/commands.rs +++ b/crates/application/src/channels/commands.rs @@ -1,5 +1,5 @@ use domain::models::ScheduleConfig; -use domain::value_objects::{ChannelId, RecyclePolicy, UserId}; +use domain::value_objects::{ChannelId, RotationPolicy, UserId}; pub struct CreateChannelCommand { pub owner_id: UserId, @@ -14,7 +14,7 @@ pub struct UpdateChannelCommand { pub description: Option>, pub timezone: Option, pub schedule_config: Option, - pub recycle_policy: Option, + pub rotation_policy: Option, pub auto_schedule: Option, } diff --git a/crates/application/src/channels/tests/update.rs b/crates/application/src/channels/tests/update.rs index 5866823..9d9041a 100644 --- a/crates/application/src/channels/tests/update.rs +++ b/crates/application/src/channels/tests/update.rs @@ -43,7 +43,7 @@ async fn updates_channel_name() { description: None, timezone: None, schedule_config: None, - recycle_policy: None, + rotation_policy: None, auto_schedule: None, }, ) @@ -80,7 +80,7 @@ async fn update_fails_if_not_owner() { description: None, timezone: None, schedule_config: None, - recycle_policy: None, + rotation_policy: None, auto_schedule: None, }, ) @@ -106,7 +106,7 @@ async fn update_nonexistent_channel_returns_not_found() { description: None, timezone: None, schedule_config: None, - recycle_policy: None, + rotation_policy: None, auto_schedule: None, }, ) @@ -146,7 +146,7 @@ async fn update_config_creates_snapshot() { description: None, timezone: None, schedule_config: Some(new_config), - recycle_policy: None, + rotation_policy: None, auto_schedule: None, }, ) @@ -185,7 +185,7 @@ async fn update_without_config_skips_snapshot() { description: None, timezone: None, schedule_config: None, - recycle_policy: None, + rotation_policy: None, auto_schedule: None, }, ) @@ -223,7 +223,7 @@ async fn update_description_clear() { description: Some(Some("A description".into())), timezone: None, schedule_config: None, - recycle_policy: None, + rotation_policy: None, auto_schedule: None, }, ) @@ -241,7 +241,7 @@ async fn update_description_clear() { description: Some(None), timezone: None, schedule_config: None, - recycle_policy: None, + rotation_policy: None, auto_schedule: None, }, ) diff --git a/crates/application/src/channels/update.rs b/crates/application/src/channels/update.rs index 7b1acf9..12b70d9 100644 --- a/crates/application/src/channels/update.rs +++ b/crates/application/src/channels/update.rs @@ -29,8 +29,8 @@ pub async fn execute(deps: &ChannelCommandDeps, cmd: UpdateChannelCommand) -> Do if let Some(config) = cmd.schedule_config { channel.set_schedule_config(config); } - if let Some(policy) = cmd.recycle_policy { - channel.set_recycle_policy(policy); + if let Some(policy) = cmd.rotation_policy { + channel.set_rotation_policy(policy); } if let Some(auto) = cmd.auto_schedule { channel.set_auto_schedule(auto); diff --git a/crates/domain/src/models/channel.rs b/crates/domain/src/models/channel.rs index b0bbb76..4cd2297 100644 --- a/crates/domain/src/models/channel.rs +++ b/crates/domain/src/models/channel.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use crate::value_objects::{ AccessMode, BlockId, ChannelId, FillStrategy, LogoPosition, MediaFilter, MediaItemId, - RecyclePolicy, UserId, Weekday, + RotationPolicy, UserId, Weekday, }; const SECONDS_IN_DAY: u32 = 86_400; @@ -19,10 +19,9 @@ pub struct Channel { description: Option, timezone: String, schedule_config: ScheduleConfig, - recycle_policy: RecyclePolicy, + rotation_policy: RotationPolicy, auto_schedule: bool, access_mode: AccessMode, - access_password_hash: Option, logo: Option, logo_position: LogoPosition, logo_opacity: f32, @@ -41,10 +40,9 @@ pub struct ChannelRow { pub description: Option, pub timezone: String, pub schedule_config: ScheduleConfig, - pub recycle_policy: RecyclePolicy, + pub rotation_policy: RotationPolicy, pub auto_schedule: bool, pub access_mode: AccessMode, - pub access_password_hash: Option, pub logo: Option, pub logo_position: LogoPosition, pub logo_opacity: f32, @@ -70,10 +68,9 @@ impl Channel { description: None, timezone: timezone.into(), schedule_config: ScheduleConfig::default(), - recycle_policy: RecyclePolicy::default(), + rotation_policy: RotationPolicy::default(), auto_schedule: false, access_mode: AccessMode::default(), - access_password_hash: None, logo: None, logo_position: LogoPosition::default(), logo_opacity: DEFAULT_LOGO_OPACITY, @@ -94,10 +91,9 @@ impl Channel { description: row.description, timezone: row.timezone, schedule_config: row.schedule_config, - recycle_policy: row.recycle_policy, + rotation_policy: row.rotation_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, @@ -134,8 +130,8 @@ impl Channel { &self.schedule_config } - pub fn recycle_policy(&self) -> &RecyclePolicy { - &self.recycle_policy + pub fn rotation_policy(&self) -> &RotationPolicy { + &self.rotation_policy } pub fn auto_schedule(&self) -> bool { @@ -146,10 +142,6 @@ impl Channel { &self.access_mode } - pub fn access_password_hash(&self) -> Option<&str> { - self.access_password_hash.as_deref() - } - pub fn logo(&self) -> Option<&str> { self.logo.as_deref() } @@ -206,8 +198,8 @@ impl Channel { self.updated_at = Utc::now(); } - pub fn set_recycle_policy(&mut self, policy: RecyclePolicy) { - self.recycle_policy = policy; + pub fn set_rotation_policy(&mut self, policy: RotationPolicy) { + self.rotation_policy = policy; self.updated_at = Utc::now(); } @@ -331,13 +323,7 @@ pub struct ProgrammingBlock { loop_on_finish: bool, #[serde(default)] - ignore_recycle_policy: bool, - - #[serde(default)] - access_mode: AccessMode, - - #[serde(default, skip_serializing_if = "Option::is_none")] - access_password_hash: Option, + ignore_rotation_policy: bool, } impl ProgrammingBlock { @@ -359,9 +345,7 @@ impl ProgrammingBlock { provider_id: String::new(), }, loop_on_finish: true, - ignore_recycle_policy: false, - access_mode: AccessMode::default(), - access_password_hash: None, + ignore_rotation_policy: false, } } @@ -381,9 +365,7 @@ impl ProgrammingBlock { provider_id: String::new(), }, loop_on_finish: true, - ignore_recycle_policy: false, - access_mode: AccessMode::default(), - access_password_hash: None, + ignore_rotation_policy: false, } } @@ -411,16 +393,8 @@ impl ProgrammingBlock { self.loop_on_finish } - pub fn ignore_recycle_policy(&self) -> bool { - self.ignore_recycle_policy - } - - pub fn access_mode(&self) -> &AccessMode { - &self.access_mode - } - - pub fn access_password_hash(&self) -> Option<&str> { - self.access_password_hash.as_deref() + pub fn ignore_rotation_policy(&self) -> bool { + self.ignore_rotation_policy } } diff --git a/crates/domain/src/models/tests/channel.rs b/crates/domain/src/models/tests/channel.rs index cb2182d..2752bd8 100644 --- a/crates/domain/src/models/tests/channel.rs +++ b/crates/domain/src/models/tests/channel.rs @@ -94,7 +94,7 @@ fn programming_block_getters() { assert_eq!(block.start_time(), t(8, 0)); assert_eq!(block.duration_mins(), 120); assert!(block.loop_on_finish()); - assert!(!block.ignore_recycle_policy()); + assert!(!block.ignore_rotation_policy()); } #[test] diff --git a/crates/domain/src/models/tests/user.rs b/crates/domain/src/models/tests/user.rs index 14971f4..525f098 100644 --- a/crates/domain/src/models/tests/user.rs +++ b/crates/domain/src/models/tests/user.rs @@ -3,10 +3,10 @@ use super::*; #[test] fn new_generates_id_and_timestamp() { let email = Email::new("test@example.com").unwrap(); - let user = User::new("oidc|123", email); + let user = User::new("external|123", email); assert!(!user.is_admin()); assert!(user.password_hash().is_none()); - assert_eq!(user.subject(), "oidc|123"); + assert_eq!(user.subject(), "external|123"); } #[test] diff --git a/crates/domain/src/services/schedule/mod.rs b/crates/domain/src/services/schedule/mod.rs index 21c3324..4fac1de 100644 --- a/crates/domain/src/services/schedule/mod.rs +++ b/crates/domain/src/services/schedule/mod.rs @@ -9,10 +9,10 @@ use crate::models::{ ScheduledSlot, }; use crate::ports::{ChannelQuery, IProviderRegistry, ScheduleCommand, ScheduleQuery, StreamQuality}; -use crate::value_objects::{BlockId, ChannelId, FillStrategy, MediaFilter, MediaItemId, RecyclePolicy, Weekday}; +use crate::value_objects::{BlockId, ChannelId, FillStrategy, MediaFilter, MediaItemId, RotationPolicy, Weekday}; mod fill; -mod recycle; +mod rotation; const SCHEDULE_DURATION_DAYS: i64 = 7; @@ -27,12 +27,12 @@ struct AlgorithmicParams<'a> { strategy: &'a FillStrategy, block_id: BlockId, loop_on_finish: bool, - ignore_recycle_policy: bool, + ignore_rotation_policy: bool, } -struct RecycleContext<'a> { +struct RotationContext<'a> { history: &'a [PlaybackRecord], - policy: &'a RecyclePolicy, + policy: &'a RotationPolicy, generation: u32, last_item_id: Option<&'a MediaItemId>, } @@ -131,9 +131,9 @@ impl ScheduleEngineService { start: slot_start, end: slot_end, }, - RecycleContext { + RotationContext { history: &history, - policy: channel.recycle_policy(), + policy: channel.rotation_policy(), generation, last_item_id, }, @@ -255,7 +255,7 @@ impl ScheduleEngineService { &self, block: &ProgrammingBlock, window: BlockTimeWindow, - recycle: RecycleContext<'_>, + rotation: RotationContext<'_>, ) -> DomainResult> { match block.content() { BlockContent::Manual { items, .. } => { @@ -274,10 +274,10 @@ impl ScheduleEngineService { strategy, block_id: block.id(), loop_on_finish: block.loop_on_finish(), - ignore_recycle_policy: block.ignore_recycle_policy(), + ignore_rotation_policy: block.ignore_rotation_policy(), }, window, - recycle, + rotation, ) .await } @@ -313,7 +313,7 @@ impl ScheduleEngineService { &self, params: AlgorithmicParams<'_>, window: BlockTimeWindow, - recycle: RecycleContext<'_>, + rotation: RotationContext<'_>, ) -> DomainResult> { let candidates = self .provider_registry @@ -324,10 +324,10 @@ impl ScheduleEngineService { return Ok(vec![]); } - let pool = if params.ignore_recycle_policy { + let pool = if params.ignore_rotation_policy { candidates.clone() } else { - recycle::apply_recycle_policy(&candidates, recycle.history, recycle.policy, recycle.generation) + rotation::apply_rotation_policy(&candidates, rotation.history, rotation.policy, rotation.generation) }; let target_secs = (window.end - window.start).num_seconds() as u32; let selected = fill::fill_block( @@ -335,7 +335,7 @@ impl ScheduleEngineService { &pool, target_secs, params.strategy, - recycle.last_item_id, + rotation.last_item_id, params.loop_on_finish, ); diff --git a/crates/domain/src/services/schedule/recycle.rs b/crates/domain/src/services/schedule/rotation.rs similarity index 84% rename from crates/domain/src/services/schedule/recycle.rs rename to crates/domain/src/services/schedule/rotation.rs index 94aff00..3c3d540 100644 --- a/crates/domain/src/services/schedule/recycle.rs +++ b/crates/domain/src/services/schedule/rotation.rs @@ -3,12 +3,12 @@ use std::collections::HashSet; use chrono::Utc; use crate::models::{MediaItem, PlaybackRecord}; -use crate::value_objects::{MediaItemId, RecyclePolicy}; +use crate::value_objects::{MediaItemId, RotationPolicy}; -pub(super) fn apply_recycle_policy( +pub(super) fn apply_rotation_policy( candidates: &[MediaItem], history: &[PlaybackRecord], - policy: &RecyclePolicy, + policy: &RotationPolicy, current_generation: u32, ) -> Vec { let now = Utc::now(); @@ -41,7 +41,7 @@ pub(super) fn apply_recycle_policy( (candidates.len() as f32 * policy.min_available_ratio).ceil() as usize; if available.len() < min_count { - // Pool too small after cooldowns — recycle everything + // Pool too small after cooldowns — rotate everything back in candidates.to_vec() } else { available @@ -49,5 +49,5 @@ pub(super) fn apply_recycle_policy( } #[cfg(test)] -#[path = "tests/recycle.rs"] +#[path = "tests/rotation.rs"] mod tests; diff --git a/crates/domain/src/services/schedule/tests/recycle.rs b/crates/domain/src/services/schedule/tests/rotation.rs similarity index 79% rename from crates/domain/src/services/schedule/tests/recycle.rs rename to crates/domain/src/services/schedule/tests/rotation.rs index 8665cfe..92f0be8 100644 --- a/crates/domain/src/services/schedule/tests/recycle.rs +++ b/crates/domain/src/services/schedule/tests/rotation.rs @@ -13,12 +13,12 @@ fn record(item_id: &str, generation: u32) -> PlaybackRecord { #[test] fn no_history_returns_all() { let pool = vec![item("a"), item("b"), item("c")]; - let policy = RecyclePolicy { + let policy = RotationPolicy { cooldown_days: Some(7), cooldown_generations: None, min_available_ratio: 0.2, }; - let result = apply_recycle_policy(&pool, &[], &policy, 1); + let result = apply_rotation_policy(&pool, &[], &policy, 1); assert_eq!(result.len(), 3); } @@ -26,12 +26,12 @@ fn no_history_returns_all() { fn generation_cooldown_excludes() { let pool = vec![item("a"), item("b"), item("c")]; let history = vec![record("a", 1)]; - let policy = RecyclePolicy { + let policy = RotationPolicy { cooldown_days: None, cooldown_generations: Some(2), min_available_ratio: 0.0, }; - let result = apply_recycle_policy(&pool, &history, &policy, 2); + let result = apply_rotation_policy(&pool, &history, &policy, 2); assert_eq!(result.len(), 2); assert!(result.iter().all(|i| i.id().value() != "a")); } @@ -40,11 +40,11 @@ fn generation_cooldown_excludes() { fn min_available_ratio_waives_cooldown() { let pool = vec![item("a"), item("b")]; let history = vec![record("a", 1), record("b", 1)]; - let policy = RecyclePolicy { + let policy = RotationPolicy { cooldown_days: None, cooldown_generations: Some(5), min_available_ratio: 0.5, }; - let result = apply_recycle_policy(&pool, &history, &policy, 2); + let result = apply_rotation_policy(&pool, &history, &policy, 2); assert_eq!(result.len(), 2); } diff --git a/crates/domain/src/value_objects/channel.rs b/crates/domain/src/value_objects/channel.rs index bfef4c6..a307435 100644 --- a/crates/domain/src/value_objects/channel.rs +++ b/crates/domain/src/value_objects/channel.rs @@ -5,9 +5,7 @@ use serde::{Deserialize, Serialize}; pub enum AccessMode { #[default] Public, - PasswordProtected, - AccountRequired, - OwnerOnly, + Private, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] diff --git a/crates/domain/src/value_objects/scheduling.rs b/crates/domain/src/value_objects/scheduling.rs index 3cf1068..0e9189e 100644 --- a/crates/domain/src/value_objects/scheduling.rs +++ b/crates/domain/src/value_objects/scheduling.rs @@ -34,13 +34,13 @@ const DEFAULT_COOLDOWN_DAYS: u32 = 30; const DEFAULT_MIN_AVAILABLE_RATIO: f32 = 0.2; #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RecyclePolicy { +pub struct RotationPolicy { pub cooldown_days: Option, pub cooldown_generations: Option, pub min_available_ratio: f32, } -impl Default for RecyclePolicy { +impl Default for RotationPolicy { fn default() -> Self { Self { cooldown_days: Some(DEFAULT_COOLDOWN_DAYS), diff --git a/crates/mcp/src/tools/channels.rs b/crates/mcp/src/tools/channels.rs index c8cd654..17e8be2 100644 --- a/crates/mcp/src/tools/channels.rs +++ b/crates/mcp/src/tools/channels.rs @@ -65,7 +65,7 @@ pub async fn update_channel( description: description.map(Some), timezone, schedule_config, - recycle_policy: None, + rotation_policy: None, auto_schedule: None, }; match application::channels::update::execute(cmd_deps, cmd).await { diff --git a/crates/presentation/src/handlers/channels.rs b/crates/presentation/src/handlers/channels.rs index d439138..d6d2c17 100644 --- a/crates/presentation/src/handlers/channels.rs +++ b/crates/presentation/src/handlers/channels.rs @@ -81,7 +81,7 @@ pub async fn update_channel( description: req.description.map(Some), timezone: req.timezone, schedule_config: req.schedule_config.map(Into::into), - recycle_policy: req.recycle_policy, + rotation_policy: req.rotation_policy, auto_schedule: req.auto_schedule, }; let channel = application::channels::update::execute(&state.channel_command_deps, cmd).await?; diff --git a/crates/presentation/src/handlers/schedule.rs b/crates/presentation/src/handlers/schedule.rs index a0ad19e..2abb0e1 100644 --- a/crates/presentation/src/handlers/schedule.rs +++ b/crates/presentation/src/handlers/schedule.rs @@ -45,15 +45,10 @@ pub async fn get_current_broadcast( match application::schedule::get_current_broadcast::execute(&state.schedule_deps, query).await? { Some(result) => { - let slot_response = match &result.channel { - Some(ch) => SlotResponse::with_block_access(result.broadcast.slot().clone(), ch), - None => SlotResponse::from(result.broadcast.slot().clone()), - }; - let block_access_mode = slot_response.block_access_mode.clone(); + let slot_response = SlotResponse::from(result.broadcast.slot().clone()); Ok(Json(CurrentBroadcastResponse { slot: slot_response, offset_secs: result.broadcast.offset_secs(), - block_access_mode, }) .into_response()) }