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

@@ -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<ScheduleConfig, DomainError>
Ok(ScheduleConfig::from(compat))
}
pub fn parse_recycle_policy(json: &str) -> Result<RecyclePolicy, DomainError> {
parse_json(json, "recycle_policy")
pub fn parse_rotation_policy(json: &str) -> Result<RotationPolicy, DomainError> {
parse_json(json, "rotation_policy")
}
pub fn parse_enum_or_default<T: DeserializeOwned + 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));
}

View File

@@ -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<String>,
timezone: String,
schedule_config: String,
recycle_policy: String,
rotation_policy: String,
auto_schedule: i64,
access_mode: String,
access_password_hash: Option<String>,
logo: Option<String>,
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())

View File

@@ -11,7 +11,6 @@ pub struct CreateChannelRequest {
pub description: Option<String>,
pub timezone: String,
pub access_mode: Option<String>,
pub access_password: Option<String>,
pub webhook_url: Option<String>,
pub webhook_poll_interval_secs: Option<u32>,
pub webhook_body_template: Option<String>,
@@ -26,10 +25,9 @@ pub struct UpdateChannelRequest {
#[schema(value_type = Option<Object>)]
pub schedule_config: Option<domain::models::ScheduleConfigCompat>,
#[schema(value_type = Option<Object>)]
pub recycle_policy: Option<domain::RecyclePolicy>,
pub rotation_policy: Option<domain::RotationPolicy>,
pub auto_schedule: Option<bool>,
pub access_mode: Option<String>,
pub access_password: Option<String>,
pub logo: Option<Option<String>>,
pub logo_position: Option<String>,
pub logo_opacity: Option<f32>,
@@ -47,7 +45,7 @@ pub struct ChannelResponse {
pub description: Option<String>,
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<String>,
@@ -70,7 +68,7 @@ impl From<domain::Channel> 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()),

View File

@@ -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<Utc>,
pub item: MediaItemResponse,
pub source_block_id: Uuid,
#[serde(default)]
pub block_access_mode: String,
}
impl From<domain::ScheduledSlot> for SlotResponse {
@@ -59,26 +55,6 @@ impl From<domain::ScheduledSlot> 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)]

View File

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

View File

@@ -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<Option<String>>,
pub timezone: Option<String>,
pub schedule_config: Option<ScheduleConfig>,
pub recycle_policy: Option<RecyclePolicy>,
pub rotation_policy: Option<RotationPolicy>,
pub auto_schedule: Option<bool>,
}

View File

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

View File

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

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

View File

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

View File

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

View File

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