feat(channel): add auto-schedule feature to channels with background scheduler

This commit is contained in:
2026-03-13 02:27:27 +01:00
parent dfd8f52a53
commit 1fc473342d
14 changed files with 161 additions and 6 deletions

View File

@@ -0,0 +1,76 @@
//! Background auto-scheduler task.
//!
//! Runs every hour, finds channels with `auto_schedule = true`, and regenerates
//! their schedule if it is within 24 hours of expiry (or already expired).
use std::sync::Arc;
use std::time::Duration;
use chrono::Utc;
use domain::{ChannelRepository, ScheduleEngineService};
pub async fn run_auto_scheduler(
schedule_engine: Arc<ScheduleEngineService>,
channel_repo: Arc<dyn ChannelRepository>,
) {
loop {
tokio::time::sleep(Duration::from_secs(3600)).await;
tick(&schedule_engine, &channel_repo).await;
}
}
async fn tick(
schedule_engine: &Arc<ScheduleEngineService>,
channel_repo: &Arc<dyn ChannelRepository>,
) {
let channels = match channel_repo.find_auto_schedule_enabled().await {
Ok(c) => c,
Err(e) => {
tracing::warn!("auto-scheduler: failed to fetch channels: {}", e);
return;
}
};
let now = Utc::now();
for channel in channels {
let from = match schedule_engine.get_latest_schedule(channel.id).await {
Ok(Some(s)) => {
let remaining = s.valid_until - now;
if remaining > chrono::Duration::hours(24) {
// Still fresh — skip until it gets close to expiry
continue;
} else if s.valid_until > now {
// Seamless handoff: new schedule starts where the old one ends
s.valid_until
} else {
// Expired: start from now to avoid scheduling in the past
now
}
}
Ok(None) => now,
Err(e) => {
tracing::warn!(
"auto-scheduler: failed to fetch latest schedule for channel {}: {}",
channel.id,
e
);
continue;
}
};
if let Err(e) = schedule_engine.generate_schedule(channel.id, from).await {
tracing::warn!(
"auto-scheduler: failed to generate schedule for channel {}: {}",
channel.id,
e
);
} else {
tracing::info!(
"auto-scheduler: generated schedule for channel {} starting at {}",
channel.id,
from
);
}
}
}