rename RecyclePolicy→RotationPolicy, collapse AccessMode, clean OIDC refs

This commit is contained in:
2026-07-12 06:50:25 +02:00
parent efd15c4f53
commit 773e228e21
19 changed files with 81 additions and 145 deletions

View File

@@ -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
}
}

View File

@@ -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]

View File

@@ -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]

View File

@@ -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,
);

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -5,9 +5,7 @@ use serde::{Deserialize, Serialize};
pub enum AccessMode {
#[default]
Public,
PasswordProtected,
AccountRequired,
OwnerOnly,
Private,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]

View File

@@ -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),