rename RecyclePolicy→RotationPolicy, collapse AccessMode, clean OIDC refs
This commit is contained in:
@@ -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<String>,
|
||||
timezone: String,
|
||||
schedule_config: ScheduleConfig,
|
||||
recycle_policy: RecyclePolicy,
|
||||
rotation_policy: RotationPolicy,
|
||||
auto_schedule: bool,
|
||||
access_mode: AccessMode,
|
||||
access_password_hash: Option<String>,
|
||||
logo: Option<String>,
|
||||
logo_position: LogoPosition,
|
||||
logo_opacity: f32,
|
||||
@@ -41,10 +40,9 @@ pub struct ChannelRow {
|
||||
pub description: Option<String>,
|
||||
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<String>,
|
||||
pub logo: Option<String>,
|
||||
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<String>,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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<Vec<ScheduledSlot>> {
|
||||
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<Vec<ScheduledSlot>> {
|
||||
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,
|
||||
);
|
||||
|
||||
|
||||
@@ -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<MediaItem> {
|
||||
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;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -5,9 +5,7 @@ use serde::{Deserialize, Serialize};
|
||||
pub enum AccessMode {
|
||||
#[default]
|
||||
Public,
|
||||
PasswordProtected,
|
||||
AccountRequired,
|
||||
OwnerOnly,
|
||||
Private,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
|
||||
@@ -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<u32>,
|
||||
pub cooldown_generations: Option<u32>,
|
||||
pub min_available_ratio: f32,
|
||||
}
|
||||
|
||||
impl Default for RecyclePolicy {
|
||||
impl Default for RotationPolicy {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
cooldown_days: Some(DEFAULT_COOLDOWN_DAYS),
|
||||
|
||||
Reference in New Issue
Block a user